GraphQLサポート
Qmonus SDKにおけるGraphQLサポートは、 modelschemas
とclass
によって生成されたすべてのデータベースエンティティに対して有効になります。
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クエリでデータを取得してみましょう
テナントデータをid
とcreatedAt
だけにフィルターして取得してみる
>>> 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