【キーワード】
JavaScript / TypeScript / GraphQL / Hasura / React / React Native / Expo / NestJS / Monolithic architecture
【対象読者】
- ソフトウェアエンジニアの方
- 特に JavaScript で何でも解決したいと思っている JS 脳の JS 人間の方
【ダイニーのサービスについて】
1. モバイルオーダー用の Web アプリ
2. 会計のためのレジアプリ
3. 店員が注文を取るためのハンディアプリ
4. メニューやテーブルに応じて選択的に伝票印刷を行うためのキッチンプリンタ制御システム
5. キッチンプリンタなどのハードウェアを中央集権的に監視するためのモニタリングシステム
6. キッチンで注文内容の提供状況を管理するためのキッチンディスプレイアプリ
7. 来店客が店頭の端末で注文を行うためのアプリ
8. メニューや在庫の管理と売上の集計をする管理システム
9. 自動釣銭機を操作するためのアプリ(提供予定)
10. 以上のすべてを束ね外部システムとの連携も行うバックエンドモノリス
【ダイニーのエンジニアリング3カ条】
プロダクト数がエンジニア数よりも多くなってしまっている状況下 において私たちが考え出した3つの方針を説明します。現在のエンジニアのメンバー数はフルタイムが6人でパートタイムが4人です。
1. 使用言語を JavaScript (TypeScript) に統一する
2. バックエンドをモノリスの状態に保つ
3. クライアントは Hasura を介して GraphQL でデータを取得する
【1. 使用言語を JavaScript (TypeScript) に統一する】
一人のエンジニアが複数のプロダクトにコミットできる状態にしなければどうしても計算が合いません。
一人のエンジニアが担当しなければいけない領域というのは、何も iOS と Android の担当を統一化したいというレベルの話ではなく、モバイル / Web / サーバーサイドのすべてにまたがるようなものです。
この体制を実現するためには、すべてのプラットフォームで使用する技術の差異をできるだけ減らして、プロダクトスイッチのコストを下げることが重要になってきます。
ダイニーは Web / iOS / Android / Server-side といった環境でプロダクトを提供していますが、それぞれ次のような技術を採用しています。
・ Web → TypeScript / React / GraphQL
・ iOS → TypeScript / React Native / GraphQL
・ Android → TypeScript / React Native / Redux
・ Server-side → TypeScript / Node.js / NestJS
メリット
・すべてのヒト・チームが自己完結的に機能を開発できる
→ コミュニケーションコストが減る
→ 複数のプロダクトに変更が必要でも一人で実装しきることができる
→ タスクが実装者を制限しない
・ エンジニアの流動性が高まりチームサイズの変更が容易になる
→ チームの粒度をプロダクトベースではなく要望元ベースのものにできる
→ 施策ごとのリソースの奪い合いが発生せず優先順位付けが容易になる
・ナレッジの蓄積速度が早い
→ エコシステムに対する知見はすべてのプロダクトで共有されるため
→ プロダクト A で熟練レベルに達するとプロダクト B でも熟練レベルに
・採用をフルスタック JavaScript エンジニアに一本化できる
→ 加えてブランディング力も高まる
・技術選定の幅が狭まり意思決定が早くなる
→ 今後、機械学習など JavaScript のちからが強く及ばない領域のために新たな言語を採用する可能性はある
【2. バックエンドをモノリスの状態に保つ】
プロダクトごとに関連の薄いロジックがバックエンドに実装されていくことになります。それでもバックエンドアプリケーションをモノリスにしている理由は、ひとえに組織体制が理由です。ダイニーはワンチームなのでサービスも一つで十分なのです。
とはいえ、ダイニーでは表は注文管理の仕組みから裏は伝統的 POS との SOAP による連携に至るまで様々な機能が必要とされています。こういった雑多な機能群をきれいにモジュール化するためにダイニーではバックエンドのフレームワークに NestJS を使用しています。NestJS は Express や Fastify といった Node.js の HTTP フレームワークの上にレイヤーを重ねるより高次のフレームワークで、主に DI とリクエストのフィルタ機能を提供してくれています。私たちは各種のロジックの依存関係を NestJS のモジュール化の機能を用いて解決しています。以下の図は「注文」という処理に登場するモジュール群をかなり簡素化したグラフで表現しています。
【3. クライアントは Hasura を介して GraphQL でリソースを取得する】
ダイニーでは ほぼすべてのプロダクトが GraphQL を介してリソースを取得 しています。 GraphQL はクライアント側で必要なリソースを定義できるため、ページの内容が変わるたびにバックエンドを編集する必要がなくなり、開発がかなり高速化しました。
Hasura というデータベースに直接接続し GraphQL エンドポイントを提供できるミドルウェアを採用することで、導入コストも僅かなものに抑えることができました。通常の Web サーバーが GraphQL リクエストに対応するためには、それぞれのリソースに対してリソルバーを定義する必要があり、その実装にはそれなりに手間がかかります。ですが Hasura はデータベースのスキーマを勝手に解釈してリソルバーを定義してくれる上に、データベースクエリに対応する GraphQL の input も定義してくれるので、SQL で書けるクエリであればサーバーサイドで実行した上でクライアントに返却してくれます。
これだけでも Hasura は最高なのに、権限管理やページネーションの機能まで提供してくれているので、クライアントが真に必要なリソースだけを取得するように制限することも可能です。これにより バックエンドの実装工数は体感で 95% ほど削減 されました。体感です。
データベースに存在しないようなリソースを取得したい場合は GraphQL エンドポイントを提供するアプリケーションサーバーを Hasura の Remote schema として登録すれば、そういったリクエストの取得も Hasura へのリクエストで一元化できます。
以上が、プロダクトの数がエンジニアの数を超えちゃってる状況でダイニーが如何にして開発を進めているかというお話でした!!