GraphQL搭配MongoDB入門項目實戰

什麼是GraphQL

GraphQL 是一種面向 API 的查詢語言。在互聯網早期,需求都以 Web 爲主,那時候數據和業務需求都不復雜,因此用 RestAPI 的方式徹底能夠知足需求。可是隨着互聯網的發展,數據量增大,業務需求多變。還有各類客戶端須要接口適配,基於 RestAPI 的方式,顯得越來呆板,所以 GraphQL 便應運而生。它至少能夠提供如下三個方面的優點前端

  1. GraphQL 提供更方便的 API 查詢

不一樣的客戶端有時候須要返回的數據格式不一樣,以前使用 RestAPI 的方式,須要後端針對每個客戶端提供單獨的接口。隨着業務需求的增長,維護的成本隨機呈指數級躍升。而使用 GraphQL 就比較開心了,只須要寫一套接口便可node

  1. 解決先後端過於依賴

在開發的過程當中,前端須要和後端反反覆覆確認各個字段,防止到時候開發到一半,由於沒有對好字段,要大塊大塊地改代碼。如今有 GraphQL 就比較方便了,你須要什麼類型的字段,就本身寫對應的查詢語法python

  1. 節約網絡和計算機內存資源

以前經過 RestAPI 的方式寫接口,有一個很大的問題在於,對於接口的定義,須要前期作大量的工做,針對接口作各類力度的拆分,但即便這樣,也沒辦法應對需求的風雲突變。有時候須要返回的僅僅是某個用戶的某一類型的數據,但不得不把該用戶的其餘信息也一併返回來,這既浪費了網絡的資源,也消耗了計算機的性能。顯然不夠優雅,GraphQL 再一次證實了它的強大,它可以提供 DIY 獲取所須要的數據,用多少,拿多少,能夠說是至關環保了git

PS : 更多 GraphQL 的介紹能夠看文末的參考資料github

介紹

這篇文章,我將用一個具體的 Todo List 實例,和你們一塊兒,一步步手動搭建一個 GraphQL + MongoDB 的項目實例。咱們將會在其中用到如下庫,開始以前須要提早安裝好:sql

  1. graphene_mongo
  2. graphene
  3. mongoengine
  4. flask_graphql
  5. Flask

在開始以前,咱們來梳理一下咱們的核心需求,咱們要創建一個 Todo List 產品,咱們核心的表只有兩個,一個是用戶表,存儲全部的用戶信息,另一個是任務表,存儲着全部用戶的任務信息。任務表經過用戶 id 與對應的用戶關聯。表結構對應的是一對多的關係,核心的數據字段以下:數據庫

task表json

{ 
    "_id" : ObjectId("5c353fd8771502a411872712"), 
    "_in_time" : "2019-01-09 08:26:53", 
    "_utime" : "2019-01-09 09:26:39", 
    "task" : "read", 
    "start_time" : "2019-01-09 08:26:53", 
    "end_time" : "2019-01-09 08:26:53", 
    "repeat" : [
        "Wed"
    ], 
    "delete_flag" : NumberInt(0), 
    "user" : "1"
}
複製代碼

user表flask

{ 
    "_id" : "1", 
    "_in_time" : "2019-01-09 08:39:16", 
    "_utime" : "2019-01-09 09:23:25", 
    "nickname" : "xiao hong", 
    "sex" : "female", 
    "photo": "http://xh.jpg",
    "delete_flag" : NumberInt(0)
}
複製代碼

項目結構

一圖勝千言,爲更清晰的瞭解項目的總體結構,我將項目的總體目錄結構打印下來,小夥伴們能夠參照着目錄結構,看接下來的搭建步驟後端

----task_graphql\
    |----api.py
    |----database\
    |    |----__init__.py
    |    |----base.py
    |    |----model_task.py
    |    |----model_user.py
    |----requirements.txt
    |----schema.py
    |----schema_task.py
    |----schema_user.py
複製代碼

pic_1.png

  • user_model 和 task_model 定義數據模塊,直接數據庫 mongo 對接
  • 上層定義的 schema 操做 shema_user 和 schema_task 對數據 model 進行增刪改查操做
  • 最後 flask 搭建對外的 api 服務實現和外界的請求交互

建立數據模型

咱們的數據模型結構很是簡單

  • user_model 列出全部的用戶信息
  • task_model 列出全部的任務信息,經過user字段與用戶表關聯,表示該任務歸屬於哪個用戶

pic_2.png

base.py
from mongoengine import connect

connect("todo_list", host="127.0.0.1:27017")
複製代碼

只須要經過調用 mongoengine 的 connect 指定對應的數據庫連接信息和數據庫便可,後面直接引入至Flask模塊會自動識別鏈接

model_user.py
import sys
sys.path.append("..")

from mongoengine import Document
from mongoengine import (StringField, IntField)
from datetime import datetime


class ModelUser(Document):

    meta = {"collection": "user"}

    id = StringField(primary_key=True)
    _in_time = StringField(required=True, default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    _utime = StringField(required=True, default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    nickname = StringField(required=True)
    sex = StringField(default="unknown", required=True)
    delete_flag = IntField(default=0, required=True)
複製代碼

所要定義的數據文檔都經過 mongoengine 的 Document 繼承,它能夠將對應字段轉換成類屬性,方便後期對數據進行各類操做,meta 字段指定對應的你須要連接的是哪張 mongo 表

model_task.py
import sys
sys.path.append("..")

from mongoengine import Document
from mongoengine import (StringField, ListField, IntField, ReferenceField)

from .model_user import ModelUser
from datetime import datetime


class ModelTask(Document):

    meta = {"collection": "task"}
    
    _in_time = StringField(default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), required=True)
    _utime = StringField(default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), required=True)
    task = StringField(default="", required=True)
    start_time = StringField(default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), required=True)
    end_time = StringField(default=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), required=True)
    repeat = ListField(StringField(required=True))
    delete_flag = IntField(default=0, required=True)
    user = ReferenceField(ModelUser, required=True)
複製代碼

其中 required 表示這個字段是必須字段,default 能夠設置該字段的默認值。ReferenceField 能夠指定和哪一個模型相關聯,這裏指定的是 ModelUser 字段,關聯默認爲對應 mongo 表中的 _id 字段

建立GraphQL查詢

如今咱們已經將數據庫和模型部分的鏈接功能完成了,接下來建立 API 部分,在咱們的 task_graphql 目錄下,有兩個文件,schema_task.py 和 schema_user.py 分別將 model_task 和 model_user 類映射成 Graphene schema對象

schema_task.py
from database.model_task import ModelTask
from graphene_mongo import MongoengineObjectType

import graphene
import schema_user

from datetime import datetime


class TaskAttribute:
    id = graphene.ID()
    _in_time = graphene.String()
    _utime = graphene.String()
    task = graphene.String()
    start_time = graphene.String()
    end_time = graphene.String()
    repeat = graphene.List(graphene.String)
    delete_flag = graphene.Int()
    user = graphene.String()


class Task(MongoengineObjectType):

    class Meta:
        model = ModelTask


class TaskNode(MongoengineObjectType):
    class Meta:
        model = ModelTask
        interfaces = (graphene.relay.Node, )
複製代碼
schema_user.py
from database.model_task import ModelTask
from graphene_mongo import MongoengineObjectType

import graphene

from datetime import datetime

class TaskAttribute:
    id = graphene.ID()
    _in_time = graphene.String()
    _utime = graphene.String()
    task = graphene.String()
    start_time = graphene.String()
    end_time = graphene.String()
    repeat = graphene.List(graphene.String)
    delete_flag = graphene.Int()
    user = graphene.String()

class Task(MongoengineObjectType):

    class Meta:
        model = ModelTask

class TaskNode(MongoengineObjectType):
    class Meta:
        model = ModelTask
        interfaces = (graphene.relay.Node, )
複製代碼

如今咱們建立一個 schema.py 的文件,把剛纔定義好的 schema_task.py 和 schema_user.py 文件引入進來,定義兩個對外訪問的接口

  • tasks: 查詢全部任務信息,返回一個list
  • users: 查詢全部用戶信息,返回一個list
import schema_user
import schema_task
import graphene
from graphene_mongo.fields import MongoengineConnectionField


class Query(graphene.ObjectType):

    node = graphene.relay.Node.Field()

    tasks = MongoengineConnectionField(schema_task.TaskNode)

    users = MongoengineConnectionField(schema_user.UserNode)

schema = graphene.Schema(query=Query)
複製代碼

建立 Flask 應用

在主目錄下建立一個 api.py 文件,將咱們以前定義好的數據庫鏈接和 schema 引入進來,用 Flask 的 add_url_rule 方法將二者關聯起來,爲了方便訪問,咱們經過引入 flask_graphql 的 GraphQLView 方法,將接口可視化出來,方便調試

from flask import Flask
from schema import schema
from flask_graphql import GraphQLView
from database.base import connect
from logger import AppLogger

log = AppLogger("task_graphql.log").get_logger()

app = Flask(__name__)
app.debug = True

app.add_url_rule("/graphql", view_func=GraphQLView.as_view("graphql", schema=schema, graphiql=True))

if __name__ == '__main__':
    app.run()
複製代碼

到這裏,咱們就已經用 graphql 成功建立了一個可查詢的 Todo List 接口,接下來。咱們能夠用它來測試一下查詢接口吧。而後在開始查詢以前你們須要本身 mock 點數據到 mongo 裏面

pic_3.png

咱們訪問接口地址(http://127.0.0.1:5000/graphql),來查詢一下看看效果

pic_4.png

添加 GraphQL 更新方法(mutation)

GraphQL 官方將更新建立操做,所有整合在 mutation 下,它包含了插入和更新數據功能,接下來咱們就繼續上面的操做,將這部分功能完善

schema_task.py
from database.model_task import ModelTask
from graphene_mongo import MongoengineObjectType

import graphene

from datetime import datetime


class TaskAttribute:
    id = graphene.ID()
    _in_time = graphene.String()
    _utime = graphene.String()
    task = graphene.String()
    start_time = graphene.String()
    end_time = graphene.String()
    repeat = graphene.List(graphene.String)
    delete_flag = graphene.Int()
    user = graphene.String()


class Task(MongoengineObjectType):

    class Meta:
        model = ModelTask


class TaskNode(MongoengineObjectType):
    class Meta:
        model = ModelTask
        interfaces = (graphene.relay.Node, )


class CreateTaskInput(graphene.InputObjectType, TaskAttribute):
    pass


class CreateTask(graphene.Mutation):

    task = graphene.Field(lambda: TaskNode)

    class Arguments:
        input = CreateTaskInput(required=True)

    def mutate(self, info, input):
        task = ModelTask(**input)
        task.save()
        return CreateTask(task=task)


class UpdateTask(graphene.Mutation):

    task = graphene.Field(lambda: TaskNode)

    class Arguments:
        input = CreateTaskInput(required=True)

    def mutate(self, info, input):
        id = input.pop("id")
        task = ModelTask.objects.get(id=id)
        task._utime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        task.update(**input)
        task.save()
        return UpdateTask(task=task)
複製代碼
schema_user.py
from database.model_user import ModelUser
from graphene_mongo.types import MongoengineObjectType
import graphene
from datetime import datetime


class UserAttribute:
    id = graphene.String()
    _in_time = graphene.String()
    _utime = graphene.String()
    nickname = graphene.String()
    photo = graphene.String()
    sex = graphene.String()
    delete_flag = graphene.Int()


class User(MongoengineObjectType):

    class Meta:
        model = ModelUser


class UserNode(MongoengineObjectType):

    class Meta:
        model = ModelUser
        interfaces = (graphene.relay.Node, )


class CreateUserInput(graphene.InputObjectType, UserAttribute):
    pass


class CreateUser(graphene.Mutation):

    user = graphene.Field(lambda: UserNode)

    class Arguments:
        input = CreateUserInput(required=True)

    def mutate(self, info, input):
        user = ModelUser(**input)
        user.save()
        return CreateUser(user=user)


class UpdateUser(graphene.Mutation):

    user = graphene.Field(lambda: UserNode)

    class Arguments:
        input = CreateUserInput(required=True)

    def mutate(self, info, input):
        id = input.pop("id")
        user = ModelUser.objects.get(id=id)
        user._utime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        user.update(**input)
        user.save()
        return UpdateUser(user=user)
複製代碼

一看代碼便知,咱們將須要添加的信息,經過input傳入進來,而後將對應的參數進行映射便可。咱們再經過實例看下建立數據的效果

pic_5.png

咱們再來試下修改數據的操做,like this

pic_6.png

bingo!!!

至此,咱們經過 GraphQL 搭配 MongoDB 的操做就完美收關了。

完整項目請查看 github: github.com/hacksman/ta…

以上都是本身一路踩過了不少坑以後總結出的方法,若有疏漏,還望指正

參考資料

tail_qrcode.jpg
相關文章
相關標籤/搜索