Flutterでモブプログラミング会を(12人で)やってみました

JX通信社のアプリエンジニアのぬまっち(@numatch2552)です。普段はAndroidエンジニアをやっています。 そして1年ほど前に技術書ビブリオバトルの記事を書いた者です。

早いものであの記事から1年が経ちました。今回も自分が担当する社内勉強会ではちょっと変わった趣向で開催してみました!

高まるFlutterの機運

昨年末に安定版がリリースされたFlutterですが、社内でも機運が高まっていました! さらにアプリエンジニアとして、業務で使う事もあって自分として知見もあったので 勉強会のメインに選定しようと思いました。

しかし新しいプラットフォームでの勉強会って、参加者の環境構築で躓くことが多いですよね。 かくいう自分も経験があります。 最初はFlutterのハッカソンを考えていましたが、Flutter未体験の参加者が多い状態なので頭を悩ませました。

モブプログラミングでやってみよう

そこで思いついたのは、モブプログラミングと掛け合わせることでした。 モブプログラミングとは下記の記事を参考にさせて頂きました。↓↓↓
まだ1人でコード書いてるの?モブプロを1日中、3週間毎日続けて得た知見 | Kurashicom Engineers' Blog

第一に勉強会なので、業務のようにスピード優先というものはありませんし、 知見の共有という点ではモブプロが最適に思えました。 モブプロも初めての実践でしたが、「やればきっと出来る!」くらいの勢いで挑みました(笑)

どこまでを目指すのか

社内勉強会は2時間なので、その範囲でどこまで作るのかを事前に考えようと思いました。 しかもモブプロ未経験者が集まって進めるので、スピードは上がらないことが予想されます。

その上で Flutterってこういう作りなんだ! という体験はしてもらいたかったので、 下記の仕様を考えました。

  • 全部で2画面
  • 最初の画面(main.dart)
    • 画面中央にテキスト・ボタンがある
    • ボタンの押下で2番目の画面に遷移する
  • 2番目の画面(second_page.dart)
    • FloatingActionButtonがある
    • FABが押下されるとリストが描画される
    • FABが押下される度にリストの項目が増えていく

当日イメージしやすいように、事前にこちらで簡単なアプリを作成しておきました。 モブプロに入る前に参加者に見て貰うことで、どこまでをゴールにするかが明確に伝わったかなと思います。

勉強会当日

12人によるモブプログラミング

当日、勉強会を開催する時間になると参加者が集まってきました。 その数12人!(事前に把握しておけよ、と)

経験者を除くと10人が未経験者でした。 これでモブプログラミングが成り立つのかと正直焦りましたが、もうやり遂げるしかありません。

キーボード問題

始めようとしてすぐに問題が発覚です。 キーボードには USキーボードとJISキーボードがあるのです。

JX通信社では好きな方のキーボードを選ぶ事ができます。(ちなみに自分の業務用MacBookはUSキーボードです。) 自分のMacBookをみんなで回していけばモブプログラミング出来るだろう、と考えていましたがJIS派のエンジニアもいるので、頭を抱えました。

ここでメンバから「複数のワイヤレスキーボードを接続すればいける」と助言を頂いて助かりました。 初っ端から躓きましたが、社内から両方のキーボードを揃えてきてスタートです。

最初の画面を作る

基本的に経験者はノータッチですが、本当に詰まった時は助言する形で進めました。

「Widgetって何だろう」 「とりあえずMaterialAppってのを使うといいみたい!」

main関数から最初を画面を呼び出しところから始まり、ツールバーを表示する方法などをみんなで調べながら進めていました。

f:id:numatch-jx:20190711212724p:plain
勉強会風景

みんなDart言語も初めてなので、コンパイルエラーの解決方法も一苦労です。

「ここでreturnが必要なのはなのは何でだろう?」 「何かが足りないってエラー出てますね。セミコロンかも。」

(言語の好みの話題も出てきて、個人的に興味深かったです。)

ドライバーを5分交代で次々と変えていきながら、ナビゲーターがサポートする形で進めていきました。

やがてStatelessWidgetScaffoldWidgetを使って最初の画面が形作られます。

ButtonWidgetの実装で詰まった時の事が印象的でした。 一口にButtonのWidgetといってもFlatButtonIconButtonなど種類がありますが、 みんなで意見を出し合って仕様を満たすRaisedButtonが選ばれました。

押下後の動作についての実装もWebメインの方だとイメージが湧きにくかったようですが、 経験者がアロー関数で実装する方法を紹介して最初の画面が完成します。

二番目の画面の実装

次の画面では FloatingActionButtonが押される度に画面に表示されるリストの内容が更新される という目標があります。 ここでは Flutterの要素でも重要な StatefulWidget を使う必要が出てきます。

だんだん参加者の方たちも、最初の画面よりは慣れてきたのか進みが早くなってきましたが、 FloatingActionButtonの実装や、表示するList内容の定義まではすんなり迷う事なく進んでいたと思います。

Button押下時の実装方法は前の画面で分かってきましたが、中々画面の表示を更新させる方法が分からずにドライバーが交代していきました。

経験者が助言しなければ厳しいのではないか、とも思っていました。 ですが、9人のナビゲーターが調べていく中で助言なしに StatefulWidgetsetState()を組み合わせることでbuildが再度コールされる事に辿り着きました。

f:id:numatch-jx:20190708213527j:plain
勉強会風景2

画面表示が更新された時、自然と歓声が湧き上がったのがとても印象的でした。 ナビゲーターが知見を共有しあって実装の解を見つけ、みんなで進捗を祝う。まさにモブプログラミングの醍醐味を味わえた瞬間だったと思います。

そして、2時間という決まった枠組みの中で目標としていた仕様を満たしたアプリが出来上がりました。

f:id:numatch-jx:20190708213044g:plain
完成したFlutterアプリ

開催してみて

正直始まるまでは上手く進行できるのか、決められた枠組みの中で目標としたアプリが出来るのか、不安でいっぱいでした。

サーバーサイドエンジニアやフロントエンジニアなど、普段ネイティブアプリ開発とは離れた業務をされてる方も沢山参加頂きましたが、 抜群のチーム開発力を発揮して、みんなでモブプログラミングを進めることが出来て嬉しかったです。

終わってみればJXエンジニアのメンバがFlutterに対して興味津々であったことに、非常に助けられたと思います。

FlutterのWidgetを元とした構成のおかげで、複数のファイルを行き来することなく一つのファイルで画面を構成することが出来ることも モブプログラミングとの相性が良いのではないか?と感じる事も出来ました。

改めてFlutterの可能性も再発見出来たので、嬉しく感じる勉強会になったと思います。

メルカリの「1on1 Workshop」で1on1の極意を学んできた

JX通信社で VPoE をしている小笠原(@yamitzky)です。今回は、メルカリ社で開催された「1on1 Workshop」の公開講座にお邪魔してきたので、そのレポートです。

JX通信社では、VPoE のお仕事として、エンジニア中心に定期的な 1on1 (一対一の面談)をしています。

僕の 1on1 のスタイルとしては、

  • 隔週30分、会社の会議室(個室)で
  • 基本的にはアジェンダなしで、「最近どう?」「何か困ってる?」みたいなことから聞く
  • 1on1後に、議事録をSlackで送る
  • それ以外は特別なツールは使わない
  • 「悩み相談」と「やっていいこと」を共有するのがメイン

といった感じです。「聞きたいことリスト」みたいな形で持ってきてくれる人や、キャリア相談をする人、技術相談をする人、恋バナをする人もいますが、基本的には同じやり方でしています。

応募した背景と 1on1 の悩み

僕の場合は 1on1 にありがちな「進捗報告の場になってしまう」みたいな問題は起きていなかったのですが、社内の他のマネージャーともやり方が異なり、「僕の1on1はこれでいいのだろうか・・・🤔」という漠然とした悩みを持っていました。

そのような課題感がある中で、メルカリの石黒さんの note で「1on1 Workshop公開講座をやります!」とのことで、さっそく応募しました!

note.mu

応募フォームで事前に送った「質問」としては、

  • やり方がマネージャーごとに異なるが、会社全体でどう均質化していけばよいか?
  • マネージャーが答えっぽいものを言うのではなく、メンバー自身が解決できるようにするためにはどうしたらいいか?
  • メンバーの悩みを聞き出せなかった失敗経験があるが、引き出すためのテクニックはあるか?

といったところです。当日、Q&Aみたいな形で「明確な回答」をいただいたわけではないのですが、Workshopとおすすめいただいた本で9割5分くらい解決できた感じがします・・・!

当日

f:id:yamitzky:20190628172039j:plain
導入は石黒さん、本編は池田さんからの講義でした!

当日はメルカリ社のイベントスペース(会議スペース)での開催で、2時間半ほどのプログラムでした。

Workshop のプログラムとしては、スライドを使った講義だけでなく、当日参加した他企業の方とのディスカッションなどもあり、相対化しながら知ることができたので面白かったです。

f:id:yamitzky:20190628172332p:plain
こんな感じのディスカッションタイムがあります

各社で違う 1on1

JX 社内でも 1on1 のやり方が違うように、当日参加した各社でも形式は異なっていました。

たとえば、

  • 開催頻度
    • 毎週1時間開催する
    • 逆に、入社後は毎週やるけど、それ以降はやらない、とか
  • アジェンダ
    • 決めてる会社が多かった
    • たとえば「YWT」形式などで、学びや振り返りを重視する
    • Google Driveにアジェンダを書いておいたり
  • 場所
    • 社外でスマホ片手に、とか
    • 「会議室とか固いところだと話せない!」という人も
  • ツール

など、すごくバラバラです。メルカリ社内でも 1on1 の形式はマネージャーに任されており(やらないマネージャーもいる)、1on1 Workshop は「守破離の守」を伝えるためのもの、ということです。

1on1と「レベル」

おすすめされていた書籍「シリコンバレー流 最強の育て方」より紹介されていたのが「1on1 のレベル」です。

f:id:yamitzky:20190628172524p:plain

自分の 1on1 を振り返ってみると、人によっては「レベル1」ができてないことも「レベル5」くらいまでできてそうなこともあり、この時点で「全員同じではダメじゃん」と気づきました。

1on1 で大切なこと

1on1 で大切なことは、メタに言えば「どういう意図でやるか」ということだと思います。

f:id:yamitzky:20190628172605p:plain
これもディスカッションタイムの議題にありました

僕がVPoEとして 1on1 で実現したいことは「メンバーが目標達成に向かっていけるように、障壁を取り除く」ということです。 これは僕自身が権限を持って解決することも1つではあるのですが、「やっていいですよ」と権限に気づかせたり、あるいはエンジニアリングの観点で必要なヒントを与えるといったことも含みます。

メンバーが 1on1 という機会を活用するには、このあたりの意図を十分に伝える必要がありそうだなと思いました。

1on1、むずい...

f:id:yamitzky:20190628172832p:plain

Workshop の中で一番「むずそう...」と思ったのが、「メンバーの人となりをどれくらい知っているか」です。趣味や休日は知ってる人も多いのですが、性格については深く知らない人も多いです。全員が全員、そこまで踏み込むのが正解ではないと思いますが、特に悩みが深い人に関してはそこから深掘りをした方が良いかな、と思って少しずつ実践したりしています。

Tryすること

今回の Workshop を通じて、自分なりに考えてみた「Try」は

  • 1on1の意図を伝える
  • 1on1のやり方を一人ひとりに最適化する

です。改善をして、1on1 を通じた「エンジニアの成果と成長」に向き合っていきたいと思える会でした。石黒さん(@takaya_i)、池田さん(@sakicchoo)、ありがとうございました!

他の方の参加レポ

他社のレポも面白かったので合わせてご覧ください。

報道を自動化するエンジニアはゲーム自動化の夢を見るか

Pythonエンジニアの @kimihiro_n です。 今回は先日行った社内勉強会の話を。

社内勉強会

弊社では月1回、社内の開発者で勉強会を行っています。 内容はLT大会だったり、もくもく会だったり、ハンズオンだったりと幹事の人が好きにテーマを設定して実施しています。 おすすめの本を持ち寄ってビブリオバトル をやった時もありました。

今回、久しぶりに自分の担当が回ってきたわけですが、「AI同士の対戦」みたいなのやってみたいなーと前から思ってました。 AIというと大げさですが、各自プログラムしたゲームのCOM(CPUともいう)を持ち寄って、誰が一番強いのかを決めるみたいな内容です。 ニュースや報道の自動化を進めている弊社のエンジニアだったらゲームの自動操縦だってお手の物のはず…!

COM VS COM で一番強いチームを決める

実際にどうやってプログラム同士を戦わせるかですが、頭に浮かんだのは昔あった「カルネージハート」というゲームです。 アルゴリズムを組み込んだロボット同士を3on3で戦わせて遊ぶゲームなのですが、これがよく出来ていて一時期PSP版にハマっていました。 コーディングはテキストではなくてGUIのパネルみたいなのを並べて作るのですが、条件分岐やループ、関数的な別モジュールの利用といったようにかなり高度なことまで実装できます。 なのでカルネージハートとPSPを人数分用意して…と言いたいところですが、無理だったので代替案を考えることにしました。

ゲームを作る

Web 上でそういったプログラム同士の対戦をするサイト、どこかで見かけた覚えがあったので当初それを掘り起こして使おうと思ってました。 しかしサイト自体が閉鎖してしまったのか検索が甘かったのか、望みのものが見つかりませんでした…。 こうなったら自作するしか…ということで1人ハッカソンを始めました。

作りたいゲーム

  • 操作が単純である
  • ルールも単純である
  • 言語に不慣れなエンジニアでも楽しめる
  • 運要素が少ない(頑張ればその分勝ちやすい)
  • 2人1チームでも協力できる

ぱっと浮かんだ要件は上記のような感じでした。 勉強会の枠は2時間なので、その中で準備・実装・試合をこなすにはそれなりにシンプルであることが重要です。 また開発者といっても「サーバーサイドエンジニア」「アプリエンジニア」、「MLエンジニア」のように専門がそれぞれ異なります。 普段その言語を書いているエンジニアが強くて、他のエンジニアは動かすので精一杯みたいなのだと面白みにかけてしまいます。 なので基本文法を抑えていれば楽しめるようなゲームが望ましいです。 あとはプログラムを改善した分だけ強くなれる性質があったらなー、とか2人で分担や相談できたらなーみたいなことを漠然と思っていました。

Pong ゲーム

f:id:nsmr_jx:20190322171713p:plain
Pong

いろいろ考えた末思い浮かんだのが Pong ゲームでした。 操作が上か下かの2択でシンプル。ルールもボールを跳ね返して相手のゴールに入れるだけで単純です。 また、複数バーを用意することでチームっぽさも出すことができます。 ボールの動きが単純なので最終的な移動地点を予測してしまえば終わってしまう欠点はあるのですが、勉強会の短い時間であればちょうどいい難易度かなというのもポイントです。

Pyxel で作る Pong ゲーム

作るものが決まったので早速実装です。 当初みんな馴染みが多いだろうと Javascript で実装する気満々だったのですが、事前アンケートを取ってみたら Python のほうが慣れてる人多くて Python で作ることにしました。

github.com

ちょうど Python で使ってみたかった Pyxel(ピクセル) というゲームエンジンがあったのでこれ幸いと利用してみることに。

サンプルにあるように

import pyxel

pyxel.init(160, 120)

def update():
    if pyxel.btnp(pyxel.KEY_Q):
        pyxel.quit()

def draw():
    pyxel.cls(0)
    pyxel.rect(10, 10, 20, 20, 11)

pyxel.run(update, draw)

Pyxel 初めて触ったゲームエンジンですが、updateで毎フレームの内部状態を更新して、drawでレンダリングしてあげれば利用できるというシンプルな仕組みでとても書きやすかったです。 ドット絵を描くエディタやサウンド編集ツールなんかもついていてかなり凝ったゲームも作れそうです。今回は時間なくてシンプルなドットを描画するのみにとどまりましたがいつかちゃんとしたゲーム作りもチャレンジしたいです。

github.com

で、なんとか出来た対戦ゲームがこちら。 当たり判定に若干怪しいところとかがありますが、大きな理不尽なく遊べるくらいにはなりました。 コマンドラインの引数でチームのプログラムへのモジュールパスを受け取って対戦が可能になっています。

    def atk_action(self, info: GameInfo, state: State) -> int:
        return random.randint(-2, 2)

    def def_action(self, info: GameInfo, state: State) -> int:
        return random.randint(-1, 1)

各自に実装してもらうものも極力シンプルにしていて、毎フレームごとに呼び出される関数が何を返すかで前衛、後衛が上下に移動する仕組みになっています。 Python 不慣れでも苦なく動かせるのではないでしょうか。

pip で配布

Python で作ったゲーム、どうやって配布しようと思っていたのですが、pip で配布してしまえばいいのではという事に気づいてしまったので pongpy という名前で登録しました。 PC ゲームつくるならこの配布方式強いですね。アプリ化とか考えると Python では鬼門ですが…。

勉強会当日

チームビルディング

事前告知してインストールに支障がないかとかを見てもらいつつ、勉強会を迎えました。 最初にペア or ソロ参戦かでチームを作ってもらい、1時間強ほど実装を進めてもらいました。

f:id:nsmr_jx:20190322174456j:plain
作業風景
前衛と後衛で分担して実装をすすめてるチームが多かったですね。 後衛のほうが幅が広くボールがくるまで時間があるので、こっちを強化すると格段に勝ちやすくなります。

また1セットごとに左右が入れ替わって戦う仕組みをいれてあるのですが、ここへの対応をちゃんと入れないと前衛が自チーム側にボールを返して自爆してしまうみたいな罠があります。 実装の後回しになってしまいやすい部分ですがちゃんと考慮してくれたチームがいくつかあって開発者冥利に尽きました。

トーナメント

f:id:nsmr_jx:20190322173941p:plain
トーナメント

実装が出揃ったらいよいよトーナメント戦です。 GitLab にレポジトリを作って、マージリクエストという形でソースコードを出してもらいました。

f:id:nsmr_jx:20190322181628p:plain
観戦の様子
AirMac で画面をミラーリングすると Pyxel がエラーを吐いてしまうなどのトラブルがありましたが、1試合1分ほどで決着がつくので始まってからはサクサクと進みました。 やはり対戦状況が画面で見れるっていうのは楽しいですね。予想以上に盛り上がってよかったです。 (非エンジニアでも見て楽しめるので全社で観戦者募っても良かったかもしれない…。)

f:id:nsmr_jx:20190320200647g:plain
白熱の決勝戦
そして決勝戦です。かなり接戦でいい勝負していました。 前衛の青に当たると上下への振れ幅を大きく返せるので決め手になりやすいです。

f:id:nsmr_jx:20190322100102j:plain
優勝チーム
見事優勝した sugibayashi チームです👏 副賞として Pong にちなんだ ポンジュースがプレゼントされましました。

f:id:nsmr_jx:20190322155112g:plain
参考実装
ちなみに参考実装として用意していたボールの予測を実装したチーム同士の戦いです。 このチームに勝ってくれるのを密かに期待してましたが流石に1時間だと厳しかったみたいで…。 1日単位でハッカソンみたいな機会があったらまたぜひやってみたいですね。 DQNとかの機械学習を持ち出してくるチームもあると思いますし。

Kotlin NativeでAlfredのWorkflowを作ろう

この記事はJX通信社Advent Calendarの23日目です。

お久しぶりです。sakebookです。本日もKotlinネタです。タイトルの通り、今回はKotlin NativeでAlfredのWorkflowを作った話です。

tech.jxpress.net

Alfred

Macアプリです。ショートカットでいろんなアプリを起動させることができます。

その中でもWorkflowという機能が強力で、このために課金しました(課金しなくても十分便利ですが)。

同僚が自作のWorkflowを作っていて便利そうにしていたので、自分でも作ってみることにしました。

Workflowを作る

全くのはじめてだったのですが、こちらの記事がわかりやすかったです。

kuronat.hatenablog.com

記事内でも書かれてますが、要はJSONを吐くことができればいいわけです。

Kotlinでjarを生成して動かそうと思ったのですが、環境に依存するのと、どうせなら早く動くものがいいと思ってKotlin Nativeでやってみることにしました。

できたもの

areaと入力してから都道府県名や市区町村コードを入力することで候補が表示されます。データはJSONにしてworkflow呼び出し時に渡すようにしてます。

具体的にはworkflowで次のように実行してます。

$ ./実行ファイル.kexe query データ.json

f:id:sakebook:20181224150234g:plain
workflowを動かしてる画面

コードはこちらです。

github.com

環境は以下の通りです

  • Gradle Wrapper:4.7
  • kotlin-native-gradle-plugin:1.3.11
  • kotlinx-serialization-runtime-native:0.9.0

詰まったこと

  • Gradleのバージョン
    • 5.0ではじめは試してたのですが、依存解決がうまく行きませんでした。
  • pluginの記述
    • applyの方で書くかpluginsで書くかによってsettings.gradleも異なってきます。
  • Kotlin NativeからKotlinx.serializationを使う
    • Gradle5.0のコマンドで生成されるプロジェクトだとkonanを使っており、解決できませんでした。

多くのハマりどころはあったのですが、最終的にKotlinx.serializationのKotlin Nativeのサンプルを使うのが一番シンプルでした。この辺りは別途まとめたいと思ってます。

github.com

まとめ

Kotlin Nativeは単純に動かすだけならシンプルなのですが、込み入ったことをやろうとするとまだまだ情報が足りなくてハマることが多いです。しかし今までなかった選択肢を増やせるので、積極的にチャレンジして行きたい領域でもあります。

参考

Guides and Tutorials / Alfred

A Basic Kotlin/Native Application / Kotlin Programming Language

Gradle for Kotlin/Native / Kotlin Programming Language

GraphQL を RESTful API と比較しながら実装して理解する

この記事は GraphQL Advent Calendar 2018JX通信社 Advent Calendar 2018の19日目です。

NewsDigest で API のバックエンドとして GraphQL を本番利用して1年ぐらい経ちました。GraphQL 自体は、「クエリ言語」という位置づけですが、実際には「API のスキーマ」という使われ方(a.k.a. Anti-REST な何か)が多いと思います。

f:id:yamitzky:20181215174940p:plain

実際、NewsDigest の API としては、もともと RESTful なものを運用してたのですが、GraphQL へ移行しました。つまり、

  • RESTful の代わりとしての利用
  • プライベートな API
  • アプリから使う API
  • サーバーサイドエンジニアがメンテするもの(BFFな文脈で、フロント側のエンジニアがメンテするケースも多いと思いますが)

といった使い方です。GitHub のようなパブリックな API でもなければ、Facebook のようにリソースが複雑に絡まりあっているようなものでもなく、ただシンプルに「アプリ用の、RESTful API の代わり」として使っている感じです。

さて、GraphQL が流行ってくるにつれて「GraphQL は何をやってくれるものなの?」「どう実装したら良いの?」という話が聞かれたりしたので、「REST と比較して実装みたらわかりやすいんじゃないか」という趣旨の記事です。

RESTful vs GraphQL

まず、RESTful な API では、次のような URL が生えてきます。

  • GET /books
  • GET /books/1
  • POST /books
  • PUT /books/1
  • DELETE /books/1

一方で GraphQL な API では、最低限必要なのは次の URL だけです。 *1

  • POST /graphql

そのかわりに、POST のペイロードとして次のようなものを送ります。REST における GET /books は、

{
    books {
        author
        name
    }
}

といった具合です。 *2

RESTful な API

今回は簡略化して、 GET /booksPOST /books だけを扱います。

例として、Node の express で実装してみます。

const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.json())

const BooksDB = {
  db: [{ author: "yamitzky", name: "Road to GraphQL" }],
  list () {
    return this.db
  },
  add (book) {
    this.db = [...this.db, book]
  }
}

app.get('/books', (req, res) => {
  const books = BooksDB.list()
  res.json({ books })
})

app.post('/books', (req, res) => {
  const book = req.body
  BooksDB.add(book)
  res.json(book)
})

app.listen(3000)

実装としては単純で、GET /booksPOST /books というエンドポイントに、それぞれ「本の一覧を取得」と「本を追加」という処理を書いたハンドラー(コールバック)を追加しているだけです。

これは express 固有の話じゃなくて、他の API 用フレームワーク等(例えば Flask や Go の net/http) でも「パスを定義して、それを処理するハンドラーを書く」というのはだいたい同じでしょう。Rails のような MVC な設計であれば、Controller の Action を生やしていくイメージです*3

GraphQL な API

次に、 apollo というライブラリを使って、GraphQL な API を実装してみます。まずは、 GET /books に該当するものから実装してみましょう。

const { ApolloServer, gql } = require('apollo-server')

const BooksDB = {
  db: [{ author: "yamitzky", name: "Road to GraphQL" }],
  list () {
    return this.db
  },
  add (book) {
    this.db = [...this.db, book]
  }
}

const typeDefs = gql`
  type Book {
    name: String
    author: String
  }

  type Query {
    books: [Book]
  }
`

const resolvers = {
  Query: {
    books () {
      return BooksDB.list()
    }
  },
}

const server = new ApolloServer({ typeDefs, resolvers });
server.listen()

先程の REST な API と比較して新しく出てきた概念は、 typeDefsresolvers を定義しているところです。前者は、GraphQL のスキーマを定義していて、後者は、スキーマ上に定義されたリソースをどのように解決するか(要はDBから取ってきてオブジェクトを返すもの)を定義したリゾルバーです。この「スキーマ定義」と「リゾルバー」に出てくる Querybooks といった変数が、ちょうど、対応しています。

これはまさに、「URL(パス)の定義」と「ハンドラー」の対応関係と一緒です。RESTful な API においては、URL とリソースは対応しているわけですから、REST と GraphQL の違いは、 「リソースをどのような URL で定義するか」と「リソースをどのようなスキーマで定義するか」という違い になります。

今度は、POST /books も追加してみましょう。GraphQL での Create/Update/Delete の操作は、各操作を関数っぽく定義していきます。*4

const typeDefs = gql`
  type Book {
    name: String
    author: String
  }

  type Query {
    books: [Book]
  }

  type Mutation {
    addBook(author: String!, name: String!): Book!
  }
`

const resolvers = {
  Query: {
    books () {
      return BooksDB.list()
    }
  },
  Mutation: {
    addBook (root, args) {
      const book = args
      BooksDB.add(book)
      return book
    }
  }
}

Mutation(Create/Update/Delete)に関しても、Query と同じように、フィールドとリゾルバーを定義します。

今回は apollo というライブラリの例ではありますが、Python の graphene や Go の gqlgen などでも全く同じで、「スキーマにフィールドを定義して、それに対応したリゾルバーを用意する」という形になります(graphene はスキーマを class で定義する、という違いはあります)。

GraphQL が提供しているもの

GraphQL 自体は冒頭にも述べたように、ただの「クエリ言語」です。なので、その 実装や、どういうデータベースをバックエンドに持つか、あるいは、HTTPで提供するかどうかさえも関心外 です。

BFF の文脈では、GraphQL な API が提供するのは「秩序ある神エンドポイント /graphql」です。「1リクエストで、必要なリソースだけをすべて取得する」という手段が提供することによって、無秩序にフロントエンド向け URL を生やすということがなくなるわけです。

また、Prisma のようなものもあります。こういうのは、「レールに乗っけると便利になる、フルスタックフレームワーク」みたいな感じで認識しておくと良いと思います(使ったことないけど)。すでにあるデータベースなどの資産を活かして移行したいような場合とかは、apollo のような薄いものを使うと良いと思います。

GraphQL を使うべきか?

GraphQL は、秩序を守りながら API を提供する手段に過ぎないです。 なので絶対に GraphQL を使うべき理由も、絶対に GraphQL を使うべきでない理由も、あんまりない ような気がしています。

けど、便利なケース/便利じゃないケースはあります。便利じゃないケースとしては、ファイルアップロード(multipart form)とかは一工夫必要です。ちなみに、 NewsDigestでの「REST API の代わりとして使う」は普通に便利なケース でした。

あとは、Prisma や AppSync*5apollo-link-state *6 のような、GraphQL のルールに則ることで便利に使えるものも多いので、活用すると DX が上がると思います*7

メリットに関する補足 ↓

yamitzky.hatenablog.com

まとめ

まとめると、 RESTful な API の「URL を定義して、対応するハンドラーを実装する」はGraphQL における「スキーマを定義して、対応するリゾルバーを実装する」に該当する ということです。

また、JX通信社では、一緒に GraphQL API を作りたい仲間を募集中です。ちなみに GraphQL API は Go 使ってます。

www.wantedly.com

*1:実際には GET でも取得できるケースも多いと思います。また、どういう URL かは、GraphQL の責務ではありません。例えば、 PUT /hogehoge というエンドポイントだけを生やして、GraphQL API を作ることもできます

*2:イメージをつきやすくするために、かなり簡略化した例です

*3:Rails 久しく触ってないので、誤りがあればすみません

*4:こちらの例では Book 型で返してますが、成功したか失敗したかだけを Bool で返しても特に問題ないです

*5:マネージドな GraphQL APIサーバーです

*6:Redux のような状態管理のライブラリで、フロントエンドの状態管理を GraphQL のスキーマに乗っけることができます

*7:NewsDigestではいずれも使ってないのですが・・・笑