InterSystemsプラットフォームにGraphQLを実装した方法







GraphQLとその使用方法は、この記事ですでに説明しています 。 ここで、私が直面したタスクと、InterSystemsプラットフォーム向けのGraphQLの実装で達成された結果について説明します。







についての記事は何ですか





単純なスキームで、リクエストの送信からレスポンスの受信までのサイクル全体を見てみましょう。







画像






クライアントは、2種類の要求をサーバーに送信できます。









AST生成



解決する必要がある最初のタスクは、受信したGraphQLクエリの分析でした。 最初は、外部ライブラリを見つけてリクエストを送信し、ASTを取得したかったのです。 しかし、私はいくつかの理由でこのアイデアを放棄することにしました。 これは別のブラックボックスであり、長いコールバックをキャンセルした人はいません。







そこで、独自のパーサーを実装する必要があるという結論に達しましたが、その説明はどこで入手できますか? ここでは、 GraphQLはオープンソースプロジェクトであり、Facebookでかなりよく説明されており、他の言語のパーサーの例を見つけることは難しくありませんでした。







ここにASTの説明があります







クエリとツリーの例を見てみましょう。







{ Sample_Company(id: 15) { Name } }
      
      





AST
 { "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行のみのスキームを取得しました。これはすでにはるかに優れています。







スキームを理解した後、クラスとそれらの相互関係に関するメタ情報を取得するのは非常に簡単なので、サーバー上でリゾルバを作成せずに自動的に生成することにしました。







回路を生成するには、いくつかのことを理解する必要があります。









Example_CityとExample_Countryの2つのクラスの例を考えてみましょう
 { "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 }
      
      







クラス自体の説明
 { "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 }
      
      







応答生成



それで、私たちは最も難しくて面白い部分に着きました。 要求に応じて、どういうわけか応答を生成する必要があります。 この場合、応答は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 } }
      
      





このプロセスが図でどのように見えるか:







画像






現時点では、次のリクエストに対してレスポンスが生成されます。









以下に、どのタイプの関係を実装する必要があるかを図で示します。













まとめると





→プロジェクトリポジトリへのリンク

→デモサーバーへのリンク








All Articles