Polymer + Apolloのクライアントを使用してYii2のGraphQL APIサーバーを作成しています。 パート3.突然変異

パート1.サーバー

パート2.クライアント

パート3.突然変異

パート4.検証。 結論







APIを開発するときに、データを受信するだけでなく、特定の変更を加える必要がある場合があります。 この目的のために、GraphQLに存在するものは、奇妙な単語「突然変異」と呼ばれます。







サーバー



クライアント部分で十分に遊んだので、サーバーに戻っていくつかの突然変異を追加しましょう。 突然変異の場合、クエリとは別のエントリポイント(MutationType)が必要であり、機能自体はargsおよび解決フィールドのパラメーターによって実装されます。







質問:クエリセクションのフィールドを通じて突然変異を実装できますか? いい質問です。 事実、これは可能ですが、アーキテクチャ的に間違っています。 そして、Apolloライブラリはルートリクエスト、つまり 構造全体を持ち、可能なすべてを要求します。 彼女がこれを行う理由はわかりませんが、おそらく、delete()などのメソッドをクエリに含めると、誤って貴重なものを失う可能性があります。







ステップ1.必要なタイプを作成します。



/schema/mutations/UserMutationType.php:







<?php namespace app\schema\mutations; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use app\models\User; class UserMutationType extends ObjectType { public function __construct() { $config = [ 'fields' => function() { return [ //     //      //  User 'update' => [ //      //  2  -  //  -  /  //      User. //      //    ,   //     'type' => Type::boolean(), 'description' => 'Update user data.', 'args' => [ //    ,  //    User. //       //    ,   'firstname' => Type::string(), 'lastname' => Type::string(), 'status' => Type::int(), ], 'resolve' => function(User $user, $args) { //      , // ..      : //     ,     //     ,   $user->setAttributes($args); return $user->save(); } ], ]; } ]; parent::__construct($config); } }
      
      





ヒント。 resolve()の機能を可能な限り軽くするようにしてください。 ご覧のとおり、GraphQLを使用すると、これを可能な限り実行できます。 モデル内のすべてのロジックを可能な限り実行します。 スキーマとAPIは、クライアントとサーバー間の単なるリンクです。 この原則は、GraphQLだけでなく、サーバーアーキテクチャにも適用されます。







/schema/mutations/AddressMutationType.phpと同様:







 <?php namespace app\schema\mutations; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use app\models\Address; use app\schema\Types; class AddressMutationType extends ObjectType { public function __construct() { $config = [ 'fields' => function() { return [ 'update' => [ 'type' => Type::boolean(), 'description' => 'Update address.', 'args' => [ 'street' => Type::string(), 'zip' => Type::string(), 'status' => Type::int(), ], 'resolve' => function(Address $address, $args) { $address->setAttributes($args); return $address->save(); }, ], //        // user,     //    //  ,     'user' => [ 'type' => Types::userMutation(), 'description' => 'Edit user directly from his address', //    relove   // ,  ? 'resolve' => function(Address $address) { // ! //      // (,     - //  , GraphQL,    // ,       //   User,   ,   // -   ) return $address->user; } ], ]; } ]; parent::__construct($config); } }
      
      





サーバーの開発中は、サンプリングに突然変異を使用したり、データを変更するためにクエリを使用したりしないように注意してください。厳密なバインディングはありません。







さて、ルートタイプ:/schema/MutationType.php:







 <?php namespace app\schema; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use app\models\User; use app\models\Address; class MutationType extends ObjectType { public function __construct() { $config = [ 'fields' => function() { return [ 'user' => [ 'type' => Types::userMutation(), 'args' => [ 'id' => Type::nonNull(Type::int()), ], 'resolve' => function($root, $args) { return User::find()->where($args)->one(); }, ], 'address' => [ 'type' => Types::addressMutation(), 'args' => [ 'id' => Type::nonNull(Type::int()), ], 'resolve' => function($root, $args) { return Address::find()->where($args)->one(); }, ], ]; } ]; parent::__construct($config); } }
      
      





ステップ2.作成されたTypes.phpタイプを追加する



お気付きの方は、最後のステップで、Typesのカスタムタイプを使用しましたが、まだ作成していません。 これは実際に私たちがやることです。







 ... // ..      //    use app\schema\mutations\UserMutationType; use app\schema\mutations\AddressMutationType; ... private static $userMutation; private static $addressMutation; ... public static function mutation() { return self::$mutation ?: (self::$mutation = new MutationType()); } public static function userMutation() { return self::$userMutation ?: (self::$userMutation = new UserMutationType()); } public static function addressMutation() { return self::$addressMutation ?: (self::$addressMutation = new AddressMutationType()); } ...
      
      





手順3.ルートタイプをGraphqlController.phpのエントリポイントに追加する



 ... $schema = new Schema([ 'query' => Types::query(), 'mutation' => Types::mutation(), ]); ...
      
      





ステップ4.テスト



GraphiQLを開き(次のタブで新しく作成したクライアントでデータが変更されていることを確認して)、結果を見てみましょう。







リクエスト:







 mutation { user(id:1) { update(firstname:"Stan") } }
      
      





画像







画像







ここで、1つのリクエストでアドレスとそれにバインドされているユーザーを変更してみましょう。







リクエスト:







 mutation { address(id:0) { update(zip: "56844") user { update(firstname:"Michael") } } }
      
      





画像







アドレスの変更を確認するために、テンプレートをわずかに変更します。







画像







すぐに想像して、RESTfulアーキテクチャでそのようなものを起動するために洗練する必要がある方法と比較してみてください。 一般に、そのようなことは、私が知る限り、RESTの概念を越えており、GraphQLでは、もともとアーキテクチャ的に組み込まれていました。







変数



クライアントに行くまで、GraphQLに含まれる変数を把握します。 クライアントの突然変異で使用する場合、実際のアプリケーションに慣れますが、今のところは気にしません。 最初は、その利点はそれほど顕著ではありません。







変数を使用して突然変異を少し変更しましょう。







リクエスト:







 mutation ($id:Int, $zip: String, $firstname: String) { address(id: $id) { update(zip: $zip) user { update(firstname: $firstname) } } }
      
      





変数:







 { "id": 1, "zip": "87444", "firstname": "Steve" }
      
      





ご注意 技術的には、変数には個別の変数POSTパラメーターが付属しています。







GraphiQLウィンドウ(変数を入力するフィールドを下から拡張する必要があります。はい、あります):







画像







同じ値が複数の場所で使用される場合、変数は便利に思えるかもしれませんが、実際には、実際にはこれはまれな状況であり、いいえ、これは主な目的ではありません。







より便利なのは、フィールドをすぐに検証できることです。 間違った型を変数に渡そうとした場合、および/またはまったく渡さなかった場合、フィールドが必須であれば、サーバーへのリクエストは消えません。







しかし、あなたがクライアントで感じる主な利便性(私は必要とさえ言うでしょう)。







お客様



ステップ1.ミューテーションをモデル/ user.jsに追加する



ご存知のように、すべてのGraphQLクエリをモデル(ほとんどすべてではなく、すべてすべて)に格納することに同意したため、新しいミューテーションを追加します。







モデル/ user.js:







 ... //     //     export const updateAddressAndUserMutation = gql` mutation updateAddressAndUser( $id: Int!, $zip: String, $street: String, $firstname: String, $lastname: String ) { address(id: $id) { update(zip: $zip, street: $street) user { update( firstname: $firstname, lastname: $lastname ) } } } `;
      
      





ステップ2.コンポーネント



さらに面白くするために、新しいコンポーネントを作成すると同時に、コンポーネント間の通信のイベントメカニズムがどのように機能するかを確認します(GraphQLとは関係なく、したがって、熱意はありません)。







ディレクトリ/ src / update-user-addressを作成し、伝統的にそこに2つのファイルupdate-user-address.htmlおよびupdate-user-address.jsを配置します。







ご注意 他の方法でコンポーネントに名前を付けたい場合は、明白でない命名要件があることに留意してください。 実際には、名前のカスタムコンポーネントには必ず「-」を含める必要があります。 行くぞ







/src/update-user-address/update-user-address.js:







 import { PolymerApolloMixin } from 'polymer-apollo'; import { apolloClient } from '../client'; //       import { getUserInfoQuery, updateAddressAndUserMutation } from '../models/user'; class UpdateAddressUser extends PolymerApolloMixin({ apolloClient }, Polymer.Element) { static get is() { return 'update-address-user'; } static get properties() { return { user: { type: Object, value: {}, // observer   //      //  //      observer: "_updateProperties", }, //      //      // , ..    //  ,    //   zip: { type: String, value: "" }, street: { type: String, value: "" }, firstname: { type: String, value: "" }, lastname: { type: String, value: "" }, }; } get apollo() { return { getUserInfo: { query: getUserInfoQuery } }; } _updateProperties() { //       //      //     // . //      //     //    // (user = {...})  if (this.user.firstname != undefined) { //     //    this.zip = this.user.addresses[0].zip; this.street = this.user.addresses[0].street; this.firstname = this.user.firstname; this.lastname = this.user.lastname; } } //       // (  ,    //     polymer-apollo // (https://github.com/aruntk/polymer-apollo#mutations) _sendAddressUserMutation() { this.$apollo.mutate({ mutation: updateAddressAndUserMutation, // ,     // ,   variables: { id: 1, zip: this.zip, street: this.street, firstname: this.firstname, lastname: this.lastname, }, }).then((data) => { //        //    ,  , //   //    //     document.getElementById('view-block').dispatchEvent(new CustomEvent('refetch')); }) } } window.customElements.define(UpdateAddressUser.is, UpdateAddressUser);
      
      





/src/update-user-address/update-user-address.html:







 <dom-module id="update-address-user"> <template> <!--      (   ) --> ZIP Code: <input value="{{zip::input}}"><br> Street: <input value="{{street::input}}"><br> First Name: <input value="{{firstname::input}}"><br> Last Name: <input value="{{lastname::input}}"><br> <!--         --> <button on-click="_sendAddressUserMutation">Send</button> </template> </dom-module>
      
      





ステップ3.イベントリスナーをメインコンポーネントに追加する



変更後すぐに隣接コンポーネントのデータを更新して表示できるように、イベントリスナーと、GraphQLクエリを更新するメソッドを追加します。







src / graphql-client-demo-app / graphql-client-demo-app.js:







 ... //  eventListener  //   ready() { super.ready(); this.addEventListener('refetch', e => this._refetch(e)); } ... //      _refetch() { this.$apollo.refetch('getUserInfo'); } ...
      
      





ステップ4.新しく作成されたコンポーネントを接続する



index.html:







 ... <link rel="import" href="/src/graphql-client-demo-app/graphql-client-demo-app.html"> <link rel="import" href="/src/update-address-user/update-address-user.html"> <script src="bundle.js"></script> </head> <body> <graphql-client-demo-app id="view-block"></graphql-client-demo-app> <update-address-user></update-address-user> </body> </html>
      
      





entry.js:







 import './src/client.js'; import './src/graphql-client-demo-app/graphql-client-demo-app.js'; import './src/update-address-user/update-address-user.js';
      
      





ステップ5.テスト



まず、Webpackをビルドしましょう(まだ削除していない場合):







 $> webpack
      
      





ブラウザを開き、次のようなものを取得します。







画像







もちろん、この写真では、[送信]ボタンをクリックした直後に上部のデータが変更されたことを証明することはできませんが、自分で試してはいけません。 ちなみに、すべての変更はgithub: client and serverに慎重にアップロードされます







率直に言って、このアーキテクチャはまったく最適ではありません。 1つの要求が実行され、データがインターフェイスのすべての場所にプルされるように、それを改良する必要があります。 しかし、これはGraphQLの問題ではありません。







記事の次の(最終)部分では、突然変異に検証を実装する方法を見て、最後に経験に基づいてGraphQLに切り替えることの利点と欠点について結論を出します。








All Articles