GraphQLサポート

Qmonus SDKにおけるGraphQLサポートは、 modelschemasclassによって生成されたすべてのデータベースエンティティに対して有効になります。 GraphQLのサービスエンドポイントを提供するコンポーネントはAPI Gatewayであり、アクセスパスは /graphqlです。 GraphQLサービスを有効化するには、追加のPythonモジュールのインストールと起動パラメータを設定する必要があります。 GraphQLは、APIGWから直接データベースにクエリを実行するため、通信オーバーヘッドが低くなります。 また、クエリのバリエーションが多い場合や頻繁に変更される場合は、シナリオAPIを変更する必要がなく、クライアント主導での開発が可能です。

追加インストールする必要があるモジュール

お使いの環境で読み込むプラグイン開発リポジトリのrequirements.txtに以下のライブラリを指定して起動してください。
graphene==2.1.8, graphene-sqlalchemy==2.2.2

起動パラメータ

API Gatewayがデータベースに直接接続できるよう以下の起動パラメータを適切に設定してください。

parameter value description
--db_product mysql データベースの種別を指定してください(mysql、postgresql、sqliteのいづれか)
--db_host {address} データベースサーバのアドレスを指定します
--db_port {port} データベースサーバのポート番号を指定します
--db_user {username} データベースサーバのユーザー名を指定します
--db_pass {password} データベースサーバのパスワードを指定します


Note

API Gatewayがアクセスするデータベースは、従属シナリオサーバの管理データベースである必要はないことに注意してください。接続されたデータベースからの自動スキーマ抽出によりORMクラスが生成され、GraphQLにマップされます。

API

GraphQLサービスは、スキーマの取得とクエリの送信のためのAPIを提供します。

  • スキーマの取得

    GET /graphql

  • クエリの送信

    POST /graphql

    {
        "type": "object",
        "required": [
            "query"
        ],
        "properties": {
            "query": {
                "type": "string",
                "description": "GraphQL query string"
            }
        }
    }
    

チュートリアル

Step1. 事前準備

GraphQLの使用方法を学びます。Frontalからチュートリアルで使用する次のATOM定義yamlをアップロードしてください。 ATOMをアップロードすることでGraphQLでアクセスするデータベーステーブルを生成します。

category: Tutorial
name: Tenant
persistence: true
api_generation: false
abstract: false
attributes:
  identifier:
    field_name: id
    field_type: string
    field_persistence: true
    field_immutable: true
  local_fields:
    - field_name: createdAt
      field_type: DateTime
      field_persistence: true
      field_nullable: true
      field_immutable: true
      field_unique: false
      field_default: datetime.datetime.utcnow()
    - field_name: deletedAt
      field_type: DateTime
      field_persistence: true
      field_nullable: true
      field_immutable: true
      field_unique: false
  ref_fields: []
methods:
  class_methods: []
  instance_methods: []
category: Tutorial
name: Port
persistence: true
api_generation: false
abstract: false
attributes:
  identifier:
    field_name: id
    field_type: string
    field_persistence: true
    field_immutable: true
  local_fields:
    - field_name: name
      field_type: string
      field_persistence: true
      field_nullable: false
      field_immutable: false
      field_unique: false
    - field_name: isActivated
      field_type: boolean
      field_persistence: true
      field_nullable: false
      field_immutable: false
      field_unique: false
    - field_name: vlanRanges
      field_type: array
      field_persistence: true
      field_nullable: false
      field_immutable: false
      field_unique: false
    - field_name: portType
      field_type: string
      field_persistence: true
      field_nullable: false
      field_immutable: true
      field_unique: false
    - field_name: operationStatus
      field_type: string
      field_persistence: true
      field_nullable: true
      field_immutable: false
      field_unique: false
      field_enum:
        - Processing
        - Completed
        - Cancelled
  ref_fields:
    - field_name: tenantId
      field_type: string
      field_persistence: true
      field_unique: false
      ref_class: Tenant
      ref_class_field: id
methods:
  class_methods: []
  instance_methods: []

Step2. GraphQLスキーマを確認しましょう

GraphQLスキーマは、ATOMのアップロードが完了すると自動的に生成されています。API GatewayのInteractive Shellで次のコマンドを実行することでGraphQLスキーマを確認することができます。

>>> r = await callout(path="/graphql")↵
... print(json.loads(r.body)["schema"])↵
... ↵
schema {
  query: Query
}

interface CustomNode {
  GlobalId: ID!
}

scalar DateTime

scalar GenericScalar

interface Node {
  id: ID!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

type Port_ implements CustomNode {
  instance: String!
  xid: String
  xname: String
  id: String
  name: String!
  isActivated: Int!
  vlanRanges: String!
  portType: String!
  operationStatus: String
  tenantId: String
  tenant: Tenant_
  GlobalId: ID!
}

type Port_Connection {
  pageInfo: PageInfo!
  edges: [Port_Edge]!
}

type Port_Edge {
  node: Port_
  cursor: String!
}

type Query {
  node(GlobalId: ID!): Node
  Port(instance: GenericScalar, xid: GenericScalar, xname: GenericScalar, id: GenericScalar, name: GenericScalar, isActivated: GenericScalar, vlanRanges: GenericScalar, portType: GenericScalar, operationStatus: GenericScalar, tenantId: GenericScalar, GlobalId: GenericScalar, offset: Int, limit: Int, asc: [String], desc: [String], filters: GenericScalar, before: String, after: String, first: Int, last: Int): Port_Connection
  Tenant(instance: GenericScalar, xid: GenericScalar, xname: GenericScalar, id: GenericScalar, createdAt: GenericScalar, deletedAt: GenericScalar, GlobalId: GenericScalar, offset: Int, limit: Int, asc: [String], desc: [String], filters: GenericScalar, before: String, after: String, first: Int, last: Int): Tenant_Connection
}

type Tenant_ implements CustomNode {
  instance: String!
  xid: String
  xname: String
  id: String
  createdAt: DateTime
  deletedAt: DateTime
  portCollection(before: String, after: String, first: Int, last: Int): Port_Connection
  GlobalId: ID!
}

type Tenant_Connection {
  pageInfo: PageInfo!
  edges: [Tenant_Edge]!
}

type Tenant_Edge {
  node: Tenant_
  cursor: String!
}↵
↵
>>>

Step3. GraphQLでデータ検索するための仕込みデータを準備する

3つのテナントデータを作成する

>>> for i in range(3):↵
...     t = atom.Tenant(id=uuid.uuid1().hex)↵
...     await t.save()↵
...     print(t.id)↵
... ↵
6380e0ee834c11e98360000c29dd8250↵
↵
638348f2834c11e98360000c29dd8250↵
↵
6384bdd6834c11e98360000c29dd8250↵
↵

各テナントに2つのポートを作成する

>>> p1 = atom.Port(id=uuid.uuid1().hex, name="p1", isActivated=False, vlanRanges=[100, 110], portType="small", operationStatus="Processing", tenantId="6380e0ee834c11e98360000c29dd8250")↵
... p2 = atom.Port(id=uuid.uuid1().hex, name="p2", isActivated=False, vlanRanges=[111, 120], portType="small", operationStatus="Processing", tenantId="6380e0ee834c11e98360000c29dd8250")↵
... for i in [p1, p2]:↵
...     await i.save()↵
... ↵
... p3 = atom.Port(id=uuid.uuid1().hex, name="p3", isActivated=False, vlanRanges=[200, 210], portType="small", operationStatus="Processing", tenantId="638348f2834c11e98360000c29dd8250")↵
... p4 = atom.Port(id=uuid.uuid1().hex, name="p4", isActivated=False, vlanRanges=[211, 220], portType="small", operationStatus="Processing", tenantId="638348f2834c11e98360000c29dd8250")↵
... for i in [p3, p4]:↵
...     await i.save()↵
... ↵
... p5 = atom.Port(id=uuid.uuid1().hex, name="p5", isActivated=False, vlanRanges=[300, 310], portType="small", operationStatus="Processing", tenantId="6384bdd6834c11e98360000c29dd8250")↵
... p6 = atom.Port(id=uuid.uuid1().hex, name="p6", isActivated=False, vlanRanges=[311, 320], portType="small", operationStatus="Processing", tenantId="6384bdd6834c11e98360000c29dd8250")↵
... for i in [p5, p6]:↵
...     await i.save()↵
... ↵
... ports = await atom.Port.retrieve()↵
... for i in ports:↵
...     print(i.api_format)↵
... ↵
{'Port': {'instance': 'UG9ydDoyODJlMDM3YTgzNGYxMWU5ODM2MDAwMGMyOWRkODI1MA==', 'id': '282dea34834f11e98360000c29dd8250', 'name': 'p5', 'isActivated': False, 'vlanRanges': [300, 310], 'portType': 'small', 'operationStatus': 'Processing', 'tenantId': '6384bdd6834c11e98360000c29dd8250'}}↵
↵
{'Port': {'instance': 'UG9ydDoyODJlMjE4NDgzNGYxMWU5ODM2MDAwMGMyOWRkODI1MA==', 'id': '282e0c58834f11e98360000c29dd8250', 'name': 'p6', 'isActivated': False, 'vlanRanges': [311, 320], 'portType': 'small', 'operationStatus': 'Processing', 'tenantId': '6384bdd6834c11e98360000c29dd8250'}}↵
↵
{'Port': {'instance': 'UG9ydDpjZWZiZDFkODgzNGUxMWU5ODM2MDAwMGMyOWRkODI1MA==', 'id': 'cefbb6bc834e11e98360000c29dd8250', 'name': 'p3', 'isActivated': False, 'vlanRanges': [200, 210], 'portType': 'small', 'operationStatus': 'Processing', 'tenantId': '638348f2834c11e98360000c29dd8250'}}↵
↵
{'Port': {'instance': 'UG9ydDpjZWZiZjQ3ZTgzNGUxMWU5ODM2MDAwMGMyOWRkODI1MA==', 'id': 'cefbde58834e11e98360000c29dd8250', 'name': 'p4', 'isActivated': False, 'vlanRanges': [211, 220], 'portType': 'small', 'operationStatus': 'Processing', 'tenantId': '638348f2834c11e98360000c29dd8250'}}↵
↵
{'Port': {'instance': 'UG9ydDpkMjY1MmZiYTgzNGMxMWU5ODM2MDAwMGMyOWRkODI1MA==', 'id': 'd26514ee834c11e98360000c29dd8250', 'name': 'p1', 'isActivated': False, 'vlanRanges': [100, 110], 'portType': 'small', 'operationStatus': 'Processing', 'tenantId': '6380e0ee834c11e98360000c29dd8250'}}↵
↵
{'Port': {'instance': 'UG9ydDpkMjY1NTIwNjgzNGMxMWU5ODM2MDAwMGMyOWRkODI1MA==', 'id': 'd2653bae834c11e98360000c29dd8250', 'name': 'p2', 'isActivated': False, 'vlanRanges': [111, 120], 'portType': 'small', 'operationStatus': 'Processing', 'tenantId': '6380e0ee834c11e98360000c29dd8250'}}↵
↵

Step4. GraphQLクエリでデータを取得してみましょう

テナントデータをidcreatedAtだけにフィルターして取得してみる

>>> q = """↵
... {↵
...     Tenant {↵
...       edges {↵
...         node {↵
...           id↵
...           createdAt↵
...         }↵
...       }↵
...     }↵
... }"""↵
... r = await callout(path="/graphql", method="POST", body=dict(query=q))↵
... print(json.dumps(json.loads(r.body), indent=4))↵
... ↵
{
    "data": {
        "Tenant": {
            "edges": [
                {
                    "node": {
                        "id": "6380e0ee834c11e98360000c29dd8250",
                        "createdAt": "2019-05-31T02:32:52"
                    }
                },
                {
                    "node": {
                        "id": "638348f2834c11e98360000c29dd8250",
                        "createdAt": "2019-05-31T02:32:52"
                    }
                },
                {
                    "node": {
                        "id": "6384bdd6834c11e98360000c29dd8250",
                        "createdAt": "2019-05-31T02:32:52"
                    }
                }
            ]
        }
    }
}↵
↵

テナントと配下のポートデータをJoinして取得してみる

>>> q = """↵
... {↵
...     Tenant {↵
...       edges {↵
...         node {↵
...           id↵
...           createdAt↵
...           portCollection {↵
...             edges {↵
...               node {↵
...                 id↵
...                 name↵
...                 portType↵
...               }↵
...             }↵
...           }↵
...         }↵
...       }↵
...     }↵
... }"""↵
... r = await callout(path="/graphql", method="POST", body=dict(query=q))↵
... print(json.dumps(json.loads(r.body), indent=4))↵
... ↵
{
    "data": {
        "Tenant": {
            "edges": [
                {
                    "node": {
                        "id": "6380e0ee834c11e98360000c29dd8250",
                        "createdAt": "2019-05-31T02:32:52",
                        "portCollection": {
                            "edges": [
                                {
                                    "node": {
                                        "id": "d26514ee834c11e98360000c29dd8250",
                                        "name": "p1",
                                        "portType": "small"
                                    }
                                },
                                {
                                    "node": {
                                        "id": "d2653bae834c11e98360000c29dd8250",
                                        "name": "p2",
                                        "portType": "small"
                                    }
                                }
                            ]
                        }
                    }
                },
                {
                    "node": {
                        "id": "638348f2834c11e98360000c29dd8250",
                        "createdAt": "2019-05-31T02:32:52",
                        "portCollection": {
                            "edges": [
                                {
                                    "node": {
                                        "id": "cefbb6bc834e11e98360000c29dd8250",
                                        "name": "p3",
                                        "portType": "small"
                                    }
                                },
                                {
                                    "node": {
                                        "id": "cefbde58834e11e98360000c29dd8250",
                                        "name": "p4",
                                        "portType": "small"
                                    }
                                }
                            ]
                        }
                    }
                },
                {
                    "node": {
                        "id": "6384bdd6834c11e98360000c29dd8250",
                        "createdAt": "2019-05-31T02:32:52",
                        "portCollection": {
                            "edges": [
                                {
                                    "node": {
                                        "id": "282dea34834f11e98360000c29dd8250",
                                        "name": "p5",
                                        "portType": "small"
                                    }
                                },
                                {
                                    "node": {
                                        "id": "282e0c58834f11e98360000c29dd8250",
                                        "name": "p6",
                                        "portType": "small"
                                    }
                                }
                            ]
                        }
                    }
                }
            ]
        }
    }
}↵
↵

特定のテナントのデータとポートをJoinして取得してみる

>>> q = """↵
... {↵
...     Tenant (id: "638348f2834c11e98360000c29dd8250") {↵
...       edges {↵
...         node {↵
...           id↵
...           createdAt↵
...           portCollection {↵
...             edges {↵
...               node {↵
...                 id↵
...                 name↵
...                 portType↵
...               }↵
...             }↵
...           }↵
...         }↵
...       }↵
...     }↵
... }"""↵
... r = await callout(path="/graphql", method="POST", body=dict(query=q))↵
... print(json.dumps(json.loads(r.body), indent=4))↵
... ↵
{
    "data": {
        "Tenant": {
            "edges": [
                {
                    "node": {
                        "id": "638348f2834c11e98360000c29dd8250",
                        "createdAt": "2019-05-31T02:32:52",
                        "portCollection": {
                            "edges": [
                                {
                                    "node": {
                                        "id": "cefbb6bc834e11e98360000c29dd8250",
                                        "name": "p3",
                                        "portType": "small"
                                    }
                                },
                                {
                                    "node": {
                                        "id": "cefbde58834e11e98360000c29dd8250",
                                        "name": "p4",
                                        "portType": "small"
                                    }
                                }
                            ]
                        }
                    }
                }
            ]
        }
    }
}↵
↵

ページネーションを実現するためのoffset/limit/sortを使ってポートデータを取得してみる

>>> q = """↵
... {↵
...     Port (offset:0 , limit:2, desc: ["name"]) {↵
...       edges {↵
...         node {↵
...           id↵
...           name↵
...         }↵
...       }↵
...     }↵
... }"""↵
... r = await callout(path="/graphql", method="POST", body=dict(query=q))↵
... print(json.dumps(json.loads(r.body), indent=4))↵
... ↵
{
    "data": {
        "Port": {
            "edges": [
                {
                    "node": {
                        "id": "282e0c58834f11e98360000c29dd8250",
                        "name": "p6"
                    }
                },
                {
                    "node": {
                        "id": "282dea34834f11e98360000c29dd8250",
                        "name": "p5"
                    }
                }
            ]
        }
    }
}↵
↵

ポート名のOR検索で取得してみる

>>> q = """↵
... {↵
...     Port (name: ["p3", "p5"], asc: ["name"]) {↵
...       edges {↵
...         node {↵
...           id↵
...           name↵
...         }↵
...       }↵
...     }↵
... }"""↵
... r = await callout(path="/graphql", method="POST", body=dict(query=q))↵
... print(json.dumps(json.loads(r.body), indent=4))↵
... ↵
{
    "data": {
        "Port": {
            "edges": [
                {
                    "node": {
                        "id": "cefbb6bc834e11e98360000c29dd8250",
                        "name": "p3"
                    }
                },
                {
                    "node": {
                        "id": "282dea34834f11e98360000c29dd8250",
                        "name": "p5"
                    }
                }
            ]
        }
    }
}↵
↵

複合フィルタを用いてAND検索でポートデータを取得してみる

>>> q = """↵
... {↵
...     Port (filters: [{key: "name", op: "==", val: "p1"}, {key: "operationStatus", op: "starts", val: "Proce"}]) {↵
...       edges {↵
...         node {↵
...           id↵
...           name↵
...         }↵
...       }↵
...     }↵
... }"""↵
... r = await callout(path="/graphql", method="POST", body=dict(query=q))↵
... print(json.dumps(json.loads(r.body), indent=4))↵
... ↵
{
    "data": {
        "Port": {
            "edges": [
                {
                    "node": {
                        "id": "d26514ee834c11e98360000c29dd8250",
                        "name": "p1"
                    }
                }
            ]
        }
    }
}↵
↵

複合フィルタを用いてOR検索でポートデータを取得してみる

>>> q = """↵
... {↵
...     Port(filters: [[{key: "name", op: "==", val: "p1"}, {key: "name", op: "==", val: "p4"}]]) {↵
...       edges {↵
...         node {↵
...           id↵
...           name↵
...         }↵
...        }↵
...     }↵
... }"""↵
... r = await callout(path="/graphql", method="POST", body=dict(query=q))↵
... print(json.dumps(json.loads(r.body), indent=4))↵
... ↵
{
    "data": {
        "Port": {
            "edges": [
                {
                    "node": {
                        "id": "cefbde58834e11e98360000c29dd8250",
                        "name": "p4"
                    }
                },
                {
                    "node": {
                        "id": "d26514ee834c11e98360000c29dd8250",
                        "name": "p1"
                    }
                }
            ]
        }
    }
}↵
↵`

Note

複合フィルタで利用できる演算子は以下の通りです。 == != <= >= > < starts ends contains in not in