GraphQLとその使用方法は、この記事ですでに説明しています 。 ここで、私が直面したタスクと、InterSystemsプラットフォーム向けのGraphQLの実装で達成された結果について説明します。
についての記事は何ですか
単純なスキームで、リクエストの送信からレスポンスの受信までのサイクル全体を見てみましょう。
クライアントは、2種類の要求をサーバーに送信できます。
- スキーマの要求。
サーバー上でスキームが生成され、クライアントに返されます。これについては後で詳しく説明します。 - 特定のデータセットを受信/変更するリクエスト。 この場合、AST生成、評価、および応答生成が発生します。
AST生成
解決する必要がある最初のタスクは、受信したGraphQLクエリの分析でした。 最初は、外部ライブラリを見つけてリクエストを送信し、ASTを取得したかったのです。 しかし、私はいくつかの理由でこのアイデアを放棄することにしました。 これは別のブラックボックスであり、長いコールバックをキャンセルした人はいません。
そこで、独自のパーサーを実装する必要があるという結論に達しましたが、その説明はどこで入手できますか? ここでは、 GraphQLはオープンソースプロジェクトであり、Facebookでかなりよく説明されており、他の言語のパーサーの例を見つけることは難しくありませんでした。
クエリとツリーの例を見てみましょう。
{ Sample_Company(id: 15) { Name } }
{ "Kind": "Document", "Location": { "Start": 1, "End": 45 }, "Definitions": [ { "Kind": "OperationDefinition", "Location": { "Start": 1, "End": 45 }, "Directives": [], "VariableDefinitions": [], "Name": null, "Operation": "Query", "SelectionSet": { "Kind": "SelectionSet", "Location": { "Start": 1, "End": 45 }, "Selections": [ { "Kind": "FieldSelection", "Location": { "Start": 5, "End": 44 }, "Name": { "Kind": "Name", "Location": { "Start": 5, "End": 20 }, "Value": "Sample_Company" }, "Alias": null, "Arguments": [ { "Kind": "Argument", "Location": { "Start": 26, "End": 27 }, "Name": { "Kind": "Name", "Location": { "Start": 20, "End": 23 }, "Value": "id" }, "Value": { "Kind": "ScalarValue", "Location": { "Start": 24, "End": 27 }, "KindField": 11, "Value": 15 } } ], "Directives": [], "SelectionSet": { "Kind": "SelectionSet", "Location": { "Start": 28, "End": 44 }, "Selections": [ { "Kind": "FieldSelection", "Location": { "Start": 34, "End": 42 }, "Name": { "Kind": "Name", "Location": { "Start": 34, "End": 42 }, "Value": "Name" }, "Alias": null, "Arguments": [], "Directives": [], "SelectionSet": null } ] } } ] } } ] }
検証
結果のツリーで、サーバー上のクラス、プロパティ、引数、およびそれらのタイプの存在を確認する必要があります。つまり、ツリーを検証する必要があります。 ツリーを再帰的に実行し、上記の内容がサーバー上にあるかどうかを確認します。 これがクラスの外観です。
回路生成
スキーマは、使用可能なクラス、プロパティ、およびこれらのクラスのプロパティタイプの説明のドキュメントです。
他の言語または技術でのGraphQLの実装では、リゾルバによってスキーマが生成されます。 リゾルバーは、サーバーで使用可能なデータの種類の説明です。
type Query { human(id: ID!): Human } type Human { name: String appearsIn: [Episode] starships: [Starship] } enum Episode { NEWHOPE EMPIRE JEDI } type Starship { name: String }
{ human(id: 1002) { name appearsIn starships { name } } }
{ "data": { "human": { "name": "Han Solo", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "starships": [ { "name": "Millenium Falcon" }, { "name": "Imperial shuttle" } ] } } }
しかし、回路を生成するには、その構造を理解し、いくつかの説明またはより良い例を見つける必要があります。 私が最初にしたことは、回路の構造を理解できるようにする例を見つけることでした。 GitHubには独自のGraphQL APIがあるため、そこからスキームを取得することは難しくありませんでした。 しかし、ここで別の問題が発生しました。サーバー部分が非常に大きいため、回線は最大64万行を占有します。 これを本当に理解したくなかったので、回路を取得する他の方法を探し始めました。
プラットフォームはDBMSに基づいているため、次のステップで、PostgreSQLとSQLite用のGraphQLを自分で構築して実行することにしました。 PostgreSQLの場合、スキーマは22,000行のみで、SQLiteは18,000行でした。 これはすでに優れていますが、それでも十分ではないので、私はさらに調べ始めました。
NodeJSの実装に立ち寄り、 組み立て 、最小限のリゾルバを作成し、1800行のみのスキームを取得しました。これはすでにはるかに優れています。
スキームを理解した後、クラスとそれらの相互関係に関するメタ情報を取得するのは非常に簡単なので、サーバー上でリゾルバを作成せずに自動的に生成することにしました。
回路を生成するには、いくつかのことを理解する必要があります。
- 最初から生成する必要はありません。NodeJSから回路を取得し、そこから不要なものをすべて削除し、必要なものをすべて追加できます。
- スキーマのルートにqueryTypeタイプがあり、その名前フィールドは何らかの値で初期化する必要があります。 残りの2つのタイプは、現時点では実装段階にあるため、私たちには関係ありません。
- 使用可能なすべてのクラスとそのプロパティは、 types配列に追加する必要があります。
{ "data": { "__schema": { "queryType": { "name": "Query" }, "mutationType": null, "subscriptionType": null, "types":[... ], "directives":[... ] } } }
- まず、 Queryのルート要素を記述し、すべてのクラス、それらの引数、およびこれらのクラスのタイプをフィールド配列に追加する必要があります。 これにより、ルート要素からアクセスできるようになります。
{ "kind": "OBJECT", "name": "Query", "description": "The query root of InterSystems GraphQL interface.", "fields": [ { "name": "Example_City", "description": null, "args": [ { "name": "id", "description": "ID of the object", "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, "defaultValue": null }, { "name": "Name", "description": "", "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "defaultValue": null } ], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "OBJECT", "name": "Example_City", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "Example_Country", "description": null, "args": [ { "name": "id", "description": "ID of the object", "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, "defaultValue": null }, { "name": "Name", "description": "", "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "defaultValue": null } ], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "OBJECT", "name": "Example_Country", "ofType": null } }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }
- 次に、1つ上のレベルに進み、 Queryオブジェクトで既に説明したクラスに、すべてのプロパティ、タイプ、および他のクラスとの関係を含むタイプを追加します 。
{ "kind": "OBJECT", "name": "Example_City", "description": "", "fields": [ { "name": "id", "description": "ID of the object", "args": [], "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "Country", "description": "", "args": [], "type": { "kind": "OBJECT", "name": "Example_Country", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "Name", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }, { "kind": "OBJECT", "name": "Example_Country", "description": "", "fields": [ { "name": "id", "description": "ID of the object", "args": [], "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { "name": "City", "description": "", "args": [], "type": { "kind": "LIST", "name": null, "ofType": { "kind": "OBJECT", "name": "Example_City", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { "name": "Name", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } ], "inputFields": null, "interfaces": [], "enumValues": null, "possibleTypes": null }
- 第三に、 型では、int、stringなど、すべての一般的なスカラー型がすでに説明されているので、そこにもスカラー型を追加します。
応答生成
それで、私たちは最も難しくて面白い部分に着きました。 要求に応じて、どういうわけか応答を生成する必要があります。 この場合、応答はjson形式であり、要求の構造と一致する必要があります。
新しいGraphQLクエリごとに、サーバー上でクラスを生成する必要があります。クラスは、要求されたデータを取得するためのロジックを記述します。 さらに、引数の値が変更された場合、リクエストは新しいと見なされません。 モスクワのある種のデータセットを取得し、ロンドンの次のクエリで新しいクラスが生成されず、新しい値だけが置換されます。 最終的に、このクラスにはSQLクエリがあり、実行後、結果のデータセットはJSON形式で保存され、その構造はGraphQLクエリに対応します。
{ Sample_Company(id: 15) { Name } }
Class gqlcq.qsmytrXzYZmD4dvgwVIIA [ Not ProcedureBlock ] { ClassMethod Execute(arg1) As %DynamicObject { set result = {"data":{}} set query1 = [] #SQLCOMPILE SELECT=ODBC &sql(DECLARE C1 CURSOR FOR SELECT Name INTO :f1 FROM Sample.Company WHERE id= :arg1 ) &sql(OPEN C1) &sql(FETCH C1) While (SQLCODE = 0) { do query1.%Push({"Name":(f1)}) &sql(FETCH C1) } &sql(CLOSE C1) set result.data."Sample_Company" = query1 quit result } ClassMethod IsUpToDate() As %Boolean { quit:$$$comClassKeyGet("Sample.Company",$$$cCLASShash)'="3B5DBWmwgoE" $$$NO quit $$$YES } }
このプロセスが図でどのように見えるか:
現時点では、次のリクエストに対してレスポンスが生成されます。
- ベーシック
- ネストされたオブジェクト
- 多対一の態度のみ
- 単純型のシート
- オブジェクトのシート
以下に、どのタイプの関係を実装する必要があるかを図で示します。
まとめると
- 答えは、現時点では、あまり複雑でないクエリに対してネストされたデータセットを取得できるということです。
- 自動生成スキーム -スキームは、事前に定義されたリゾルバーではなく、クライアントが使用できる保存されたクラスによって生成されます。
- フル機能のパーサー -パーサーは完全に実装されており、要求に応じてツリーを取得できます。
→プロジェクトリポジトリへのリンク
→デモサーバーへのリンク