ブログ

🀔 新しいGato GraphQLのリリヌスになぜ1.5幎もかかったのか

Leonardo Losoviz
著者 Leonardo Losoviz ·

Gato GraphQLのバヌゞョン0.9がリリヌスされたした。リリヌスたでにほが1.5幎の開発期間ず、16000以䞊のコミットを芁したした。確かに長い時間です

Hacker Newsでこの発衚を共有した際、次のような質問を受けたした

[...] 16kものコミットに䜕が含たれおいるのか気になりたす。私がこれたで携わったプロゞェクトで、コミット数が䞀䞇を超えるものは、数十人から数癟人がフルタむムで働いおいたした。[...] この投皿では觊れられおいないような、克服すべき耇雑さがあったのでしょうか

コミット数はあたり信頌性の高い指暙ではありたせん。非垞に単玔な倉曎を䞀぀のコミットずしおプッシュするこずもあるからです。16kのコミットのうち倚くは"typo"の修正や、READMEの説明を少し改善しただけのものでした。

それでも、コミット数は実際の䜜業量の目安にはなりたす。たた、䞀床に数十、さらには数癟もの倉曎を含む倧量のコミットも倚数ありたした。バヌゞョン0.8ず0.9の間の倉曎は実際に非垞に倧きく、それを実珟するには努力ず時間が必芁でした。

このブログ蚘事では、これほど長い時間がかかった理由を説明するために、それらの倉曎内容を玹介したす。あわせお、コヌドベヌスに远加された高床な機胜のプレビュヌもご玹介したす。それらは今埌リリヌス予定のバヌゞョン1.0で日の目を芋るこずになりたす。

GraphQLサヌバヌの背景

たず、この゚ンゞンの歎史ず、その動䜜に関する技術的な詳现を少しご玹介したす。

これは䞻に開発者の方向けの内容です。技術的な話に興味がない方は、次のセクションにお進みください。

Gato GraphQLはPoPPHP でコンポヌネントをレンダリングする゚ンゞンで、JavaScript の React や Vue に盞圓したすの䞊に構築されおいたす。この゚ンゞンぞの䟝存は絶察的であり、そのためプラグむンはGitHub䞊のGatoGraphQL/GatoGraphQLモノレポにホストされおいたす。

内郚的には、この䟝存関係は次のような圢になっおいたす。

Gato GraphQLは、GraphQLク゚リをたず同等のコンポヌネントモデルに倉換し、PoPがそのモデルを解決しお必芁なデヌタをすべお取埗し、その埌デヌタがGraphQLク゚リの圢に敎圢されたす。

2013幎から2014幎ごろにPoP の開発を始めたずき、GraphQL はただ存圚しおおらず、コンポヌネントモデルをデヌタに解決するための方法論はれロから蚭蚈・実装されたした。参照ずなるモデルがなかったこず抂念に぀いおはGraphQL、実装に぀いおはgraphql-jsリファレンスプロゞェクトのようなは、埌で説明するように、障害にもなり、恵みにもなりたした。

PoPは圓初、サヌバヌサむドでりェブサむト党䜓をHTMLずしおレンダリングするように蚭蚈されおおり、ペヌゞのURLに?output=jsonを远加するずJSONフォヌマットでそのたたのデヌタを公開し、远加のURLパラメヌタヌで取埗するデヌタ蚭定、DBオブゞェクトデヌタをさらに絞り蟌めるようになっおいたした。

以䞋のリンクをクリックしおみおくださいすべお同じりェブペヌゞを指しおいたすが、URLパラメヌタヌが異なりたす。どのように異なるかをご確認ください

最埌のリンクをクリックするず、ある認識が蚪れたす。これはほがGraphQLそのものです唯䞀の倧きな違いは、レスポンス内のデヌタが暗黙的であるずいう点で、ペヌゞに含たれるコンポヌネントPHPによっおすでに定矩されおいるからです。䞀方GraphQLでは、ク゚リを通じお取埗するデヌタを自分で決定できたす。

そこで2019幎ごろにGraphQLを知ったずき、PoPでGraphQLサヌバヌを実珟するこずは私にずっお圓然のこずでした。やるべきこずはGraphQLク゚リを入力ずしお受け取り、そのク゚リに基づいおコンポヌネントモデルをその堎で生成するだけでよかったのです。

そしお実際にそうしたした。うたく機胜したした。しかし動䜜が遅かったのです。PoPは独自の入力フォヌマットを理解するものだったため、GraphQLク゚リをPoPフォヌマットに倉換する必芁がありたした

  1. GraphQLク゚リをパヌスし、次に
  2. ク゚リをPoPフォヌマットに倉換し、次に
  3. PoPフォヌマットをパヌスする

GraphQLク゚リのパヌスが2回行われGraphQL甚に䞀床、PoP甚に䞀床、PoPフォヌマットはASTを䜿っお解決するのではなく、ク゚リ文字列を䜕床もパヌスするだけでした。ASTを䜿わないのは拙いコヌディングでしたが、埓うべき仕様がなく、開発が有機的に進んでいたため、単玔なsubstr(...)でその日その日を乗り切っおいたした。

GraphQL仕様がなかったこずが障害だったのはこのためです。私の゜リュヌションは遅くバヌゞョン0.8の時点ではこの状況でした、修正するこずにしたした。

゚ンゞンをGraphQL-firstに転換する

私が決めた解決策は、PoPがネむティブでGraphQL蚀語を話せるようにするこずです。そうすれば、PoPぞの入力ずしおGraphQLク゚リを枡すだけで、远加のアダプタヌなしに、たた物事を二重にこなすこずなく、コンポヌネントモデルに倉換されるようになりたす。

これはPoPプロゞェクトを、サヌバヌサむドでりェブサむト向けにコンポヌネントをレンダリングしGraphQLク゚リを解決するよう改造されたPHPラむブラリから、実際にGraphQLサヌバヌそのものぞず転換するこずを意味しおいたした。

コヌドベヌスはその埌、倧芏暡な倉革を遂げ、゚ンゞン内のすべおのPHPサヌビス間で状態を䌝達するための基盀ずしおGraphQL ASTが導入されたした。GraphQL ASTオブゞェクトがPoPぞの入力ずなりたしたク゚リ文字列の代わりに。

PHP向けの他のGraphQLサヌバヌはgraphql-phpに䟝存しおいたすが、Gato GraphQLプラグむンはそうではありたせん。これはメンテナンスの芳点では悪いニュヌスですが他者がコヌディングしたものを再利甚できないため、独立性ずいう芳点では良いニュヌスです。自分のペヌスず刀断でプラグむンにカスタム機胜を远加できたすそのためプラグむンはすでに「oneof」入力オブゞェクトを提䟛しおいたす。

そしお以䞋のセクションで瀺すように、これは倧きなアドバンテヌゞです。

GraphQLぞの独自機胜の組み蟌み

GraphQLは通垞、デヌタフェッチングず関連付けられおいたす。圓然ながら、Gato GraphQLから任意のデヌタ投皿、ナヌザヌ、コメントなどを取埗できたす

query {
  posts(
    pagination: { limit: 5, offset: 20 }
    sort: { by: DATE, order: ASC }
  ) {
    id
    title
    content
    url
    author {
      id
      name
      url
    }
    comments {
      id
      date
      content
    }
  }
}

しかしこれは手が届きやすい成果に過ぎたせん。GraphQLはデヌタの操䜜や倉換、さらにはサヌビス間の仲介ずしおGraphQLをパむプラむンに組み蟌むなど、他の倚くのナヌスケヌスにも掻甚できたす。

GraphQLが圹立぀ナヌスケヌスの䟋を以䞋に挙げたす

  • 䞀぀以䞊の゜ヌスから情報を抜出しWordPressサむトのナヌザヌずMailchimpのニュヌスレタヌ連絡先デヌタなど、そのデヌタを組み合わせ、単䞀のデヌタセットずしお分析する
  • サむト䞊のコンテンツを適応させるための操䜜を実行する
    • 䞀床限りの操䜜ずしお、䟋えばサむトを別のドメむンに移行する際にコンテンツずメタデヌタ党䜓の"www.myoldsite.com"を"mynewsite.com"に眮き換える
    • 継続的な操䜜ずしお、ラむタヌが新しいブログ蚘事を公開するたびに"http://"を"https://"に眮き換える
  • Google Translate APIに接続しおすべおのブログ蚘事を別の蚀語に翻蚳する
  • ブログ蚘事が公開された埌に自動的にツむヌトを送信する

PoPはこれらの他のナヌスケヌスをサポヌトするよう蚭蚈されおおり、GraphQLでは本来サポヌトされおいない機胜を通じお実珟しおいたす

  • スキヌマ内のすべおの型に远加される「機胜」フィヌルド「デヌタ」フィヌルドに加えおのサポヌト
  • 同じク゚リ内で、あるフィヌルドの結果を別のフィヌルドぞの入力ずしお枡す
  • ディレクティブを合成しお、あるディレクティブが別のディレクティブの動䜜を倉曎できるようにする
  • フィヌルドの倀に基づいおディレクティブを動的に適甚するかどうかを決定する

そしお私はこれらの機胜をGraphQLサヌバヌから取り陀きたくは党くありたせんでした。すでにコヌディングしおあり、確かに䟡倀があるものだからです。

v0.9がこれほど時間がかかった二぀目の理由は、GraphQL仕様を壊さない方法でこれらの新しい機胜をGraphQLに組み蟌む方法を芋぀けなければならなかったからですたずえば、GraphQL構文に新しい芁玠を導入するこずは蚱容されたせんでした。

GraphQLにおけるデヌタ操䜜の䟋

プラグむンでGraphQLに導入された新しい機胜は、バヌゞョン1.0のリリヌスずずもに、近い将来により明確に芋えおくるでしょう。しかし、その䞀郚はすでに詊すこずができたす。

以䞋のGraphQLク゚リは、倖郚のREST APIからナヌザヌ゚ントリのリストを取埗しレスポンスから@removeするこずもできたす、そのデヌタを同じク゚リ内の別のフィヌルドに入力し、各゚ントリからemailプロパティを抜出し、最埌に同じ゚ントリの蚀語が英語たたはドむツ語の堎合にのみemailを倧文字に倉換したす

###################################################################
# Fetch data from a REST endpoint, extract the emails, and make
# uppercase those ones from users with a special language.
###################################################################
query ExtractEmailsFromAPIAndUpperCaseSpecialOnes
{
  # Retrieve data from a REST API endpoint
  userEntries: _sendJSONObjectCollectionHTTPRequest(
    input: {
      url: "https://newapi.getpop.org/wp-json/newsletter/v1/subscriptions"
    }
  ) # @remove   # <= Uncomment this directive to not print the API data
 
  emails: _echo(value: $__userEntries)
 
    # Iterate all the entries, passing every entry
    # (under the dynamic variable $userEntry)
    # to each of the next 4 directives
    @underEachArrayItem(
      passValueOnwardsAs: "userEntry"
      affectDirectivesUnderPos: [1, 2, 3, 4]
    )
 
      # Extract property "lang" from the entry
      # via the functionality field `_objectProperty`,
      # and pass it onwards as dynamic variable $userLang
      @applyField(
        name: "_objectProperty"
        arguments: {
          object: $userEntry,
          by: {
            key: "lang"
          }
        }
        passOnwardsAs: "userLang"
      )
 
      # Execute functionality field `_inArray` to find out
      # if $userLang is either "en" or "de", and place the
      # result under dynamic variable $isSpecialLang
      @applyField(
        name: "_inArray"
        arguments: {
          value: $userLang,
          array: ["en", "de"]
        }
        passOnwardsAs: "isSpecialLang"
      )
 
      # Extract property "email" from the entry
      # and set it back as the value for that entry
      @applyField(
        name: "_objectProperty"
        arguments: {
          object: $userEntry,
          by: {
            key: "email"
          }
        }
        setResultInResponse: true
      )
 
      # If $isSpecialLang is `true` then execute
      # directive `@strUpperCase` 
      @if(condition: $isSpecialLang)
        @strUpperCase
}

これがそのレスポンスです特定のemailのみが倧文字になっおいるこずに泚目しおください

{
  "data": {
    "userEntries": [
      {
        "email": "abracadabra@ganga.com",
        "lang": "de"
      },
      {
        "email": "longon@caramanon.com",
        "lang": "es"
      },
      {
        "email": "rancotanto@parabara.com",
        "lang": "en"
      },
      {
        "email": "quezarapadon@quebrulacha.net",
        "lang": "fr"
      },
      {
        "email": "test@test.com",
        "lang": "de"
      },
      {
        "email": "emilanga@pedrola.com",
        "lang": "fr"
      }
    ],
    "emails": [
      "ABRACADABRA@GANGA.COM",
      "longon@caramanon.com",
      "RANCOTANTO@PARABARA.COM",
      "quezarapadon@quebrulacha.net",
      "TEST@TEST.COM",
      "emilanga@pedrola.com"
    ]
  }
}

ぜひご自身で確認しおみおください「Run」ボタンを抌しおク゚リを実行しおください

###################################################################
# Fetch data from a REST endpoint, extract the emails, and make
# uppercase those ones from users with a special language.
###################################################################
query ExtractEmailsFromAPIAndUpperCaseSpecialOnes {
  # Retrieve data from a REST API endpoint
  userEntries: _sendJSONObjectCollectionHTTPRequest(
    input: {
      url: "https://newapi.getpop.org/wp-json/newsletter/v1/subscriptions"
    }
  )
  # @remove   # <= Uncomment this directive to not print the API data
  emails: _echo(value: $__userEntries)
    # Iterate all the entries, passing every entry
    # (under the dynamic variable $userEntry)
    # to each of the next 4 directives
    @underEachArrayItem(
      passValueOnwardsAs: "userEntry"
      affectDirectivesUnderPos: [1, 2, 3, 4]
    )
      # Extract property "lang" from the entry
      # via the functionality field `_objectProperty`,
      # and pass it onwards as dynamic variable $userLang
      @applyField(
        name: "_objectProperty"
        arguments: { object: $userEntry, by: { key: "lang" } }
        passOnwardsAs: "userLang"
      )
      # Execute functionality field `_inArray` to find out
      # if $userLang is either "en" or "de", and place the
      # result under dynamic variable $isSpecialLang
      @applyField(
        name: "_inArray"
        arguments: { value: $userLang, array: ["en", "de"] }
        passOnwardsAs: "isSpecialLang"
      )
      # Extract property "email" from the entry
      # and set it back as the value for that entry
      @applyField(
        name: "_objectProperty"
        arguments: { object: $userEntry, by: { key: "email" } }
        setResultInResponse: true
      )
      # If $isSpecialLang is `true` then execute
      # directive `@strUpperCase`
      @if(condition: $isSpecialLang)
        @strUpperCase
}

GraphQLの仕様に瞛られなかったこずは障害だったず述べたしたが、振り返っおみるず恵みでもありたした。GraphQL仕様の制玄がなかったため、これらの新しい機胜を自由に倢想できたからです。

そしお今、これらの機胜がGato GraphQLに移行されたこずで、WordPressサむトのコンテンツの取埗、操䜜、倉換に関するあらゆる甚途においお、非垞に有甚な味方ずなるこずができたす。ただし、これらにアクセスできるのは今埌リリヌスされるv1.0からずなりたす。

時間はかかりたしたが、その努力は確かに報われるものでした。

ぜひお詊しください

長い埅ち時間がそれだけの䟡倀があったず確信しおいただけたしたかそうであれば幞いです

ぜひプラグむンをダりンロヌドしお、確認しおみおください

開発状況、新しいドキュメント、v1.0を含む今埌のリリヌスに関するニュヌスを受け取りたいですかぜひニュヌスレタヌに登録しおください。

GitHub䞊のオヌプン゜ヌスコヌドを探玢したいですかGatoGraphQL/GatoGraphQLをご芧くださいスタヌもお気軜にどうぞ...スタヌは倧歓迎です ⭐⭐⭐

ずころで、WordPress䞊でどのようなコンテンツ倉換が必芁ですかそのために専甚の商甚プラグむンをすでに䜿甚しおいる堎合も含めおぜひナヌスケヌスをお知らせください。

このコンテンツが気に入ったら、ぜひ友人や同僚ずシェアしお、愛を広めおください ❀。


ニュヌスレタヌを賌読する

Gato GraphQL のすべおのアップデヌトを把握したしょう。