見出し画像

【エンジニアブログ】ダイニーのエンジニアリング3カ条

こんにちは、ダイニーでソフトウェアエンジニアをしている唐澤 @karszawa です。ここ数週間ふるって開発してきた新規プロダクトがつい先日リリースされ、店舗の方や来店客の方に大いに利用されているようです。恐縮しつつも頑張って作った甲斐を感じる繁盛具合で大変うれしく思っています!

本日はエンジニアリングブログの第二回として、ダイニーのエンジニアリングの特色を紹介してみたいと思います。

キーワード

JavaScript / TypeScript / GraphQL / Hasura / React / React Native / Expo / NestJS / Monolithic architecture

概要

本記事では、ダイニーのプロダクト開発チームが プロダクト数>エンジニア数 という状況下において、どのようにすれば効率よく開発を進められるかという試行錯誤を行ってたどり着いた3つのエンジニアリング方針を解説します。この記事で語られている方針は、そのすべてが実際に直面した問題を必死に解決しようとして得られた知見に基づいて考え出されています。とても涙なしには語れません。

対象読者

- ソフトウェアエンジニアの方
- 特に JavaScript で何でも解決したいと思っている JS 脳の JS 人間の方

ダイニーのサービスについて

一般に、ダイニーはモバイルオーダーのサービスとして認知されています。モバイルオーダーとは、飲食店にて店員ではなく来店客が自身のスマートフォンを使用して注文を取るような仕組みのことです。

実はダイニーが開発しているのはモバイルオーダーのシステムだけではありません。モバイルオーダーは注文の手段を提供するだけであって、飲食店の運営にはレジやハンディのようなプロダクトも必要になってきます。ダイニーはこれらの店舗運営を支える複数のプロダクトをパッケージとして提供しています。以下が実際に提供しているプロダクトの一覧です。

1. モバイルオーダー用の Web アプリ
2. 会計のためのレジアプリ
3. 店員が注文を取るためのハンディアプリ
4. メニューやテーブルに応じて選択的に伝票印刷を行うためのキッチンプリンタ制御システム
5. キッチンプリンタなどのハードウェアを中央集権的に監視するためのモニタリングシステム
6. キッチンで注文内容の提供状況を管理するためのキッチンディスプレイアプリ
7. 来店客が店頭の端末で注文を行うためのアプリ
8. メニューや在庫の管理と売上の集計をする管理システム
9. 自動釣銭機を操作するためのアプリ(提供予定)
10. 以上のすべてを束ね外部システムとの連携も行うバックエンドモノリス

画像4

どうしてこんなにたくさんのプロダクトがあるのか

標準的な飲食店が使っている注文管理のシステムは、約40年前から日本の伝統的大企業によって継続的に開発されている POS という仕組みの上に成り立っています。そういったシステムは長い年限の中で飲食店の業務を遂行するのに必要なありとあらゆる機能を実装してきました。これが何を意味するかというと、いくらモバイルオーダーが革新的であろうと 伝統的 POS のほぼすべての機能を備えていなければ既存の店舗業務は遂行不可能 だということです。もちろん、私たち自身が飲食店における最適なオペレーションを考案して導入店舗に提案するということも可能ではあり、むしろそちらの方が開発コストは低くはなるのだとは思いますが、それでは現在の日本の飲食店のオペレーションに対する差分が大きすぎて現場が対応しきれないでしょう。

そういった状況を踏まえ、私たちは伝統的 POS が40年かけて開発してきたプロダクト群を(できるだけモダンな)開発手法で再開発しているのです。

ダイニーのエンジニアリング3カ条

そういった プロダクト数がエンジニア数よりも多くなってしまっている状況下 において私たちが考え出した3つの方針を説明します。ちなみに現在のエンジニアのメンバー数はフルタイムが6人でパートタイムが4人です。

1. 使用言語を JavaScript (TypeScript) に統一する
2. バックエンドをモノリスの状態に保つ
3. クライアントは Hasura を介して GraphQL でデータを取得する

各項目について説明していきます。

1. 使用言語を JavaScript (TypeScript) に統一する

多くのプロダクトを提供しているサービスだと、プロダクトごとに専門のチームがあるというのはごく自然なことだと思います。そういった体制では、ある一つの機能を提供する場合でもプロダクトごとに実装者が変わるということになります。

ダイニーの場合はプロダクト数が多すぎるため、一人のエンジニアが複数のプロダクトにコミットできる状態にしなければどうしても計算が合いません。次の図はダイニーにおけるメンバーとプロダクトの関係性を表しています。

画像1

一人のエンジニアが担当しなければいけない領域というのは、何も iOS と Android の担当を統一化したいというレベルの話ではなく、モバイル / Web / サーバーサイドのすべてにまたがるようなものです。

そういった体制を実現するためには、すべてのプラットフォームで使用する技術の差異をできるだけ減らして、プロダクトスイッチのコストを下げることが重要になってきます。幸いにも、現代ではマルチプラットフォームに開発できる言語やフレームワークがいくつか存在します。私たちはそういった技術の中でも特に使用実績のある JavaScript と React をベースとした技術スタックを採用しています。

ダイニーは 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 エコシステムは十分に広大
    → 今後、機械学習など JavaScript のちからが強く及ばない領域のために新たな言語を採用する可能性はある

2. バックエンドをモノリスの状態に保つ

前述の通り、ダイニーというサービスは複数のプロダクトによって成り立っているサービスです。こうなると必然的にプロダクトごとに関連の薄いロジックがバックエンドに実装されていくことになります。そういったプロダクトごとのサービスは、各サービスをマイクロサービスとして分割することによって関連の薄いロジックを疎結合な状態で実装できます。

ダイニーがそれでもバックエンドアプリケーションをモノリスにしている理由は、ひとえに組織体制が理由です。マイクロサービス化の主たるメリットはあるマイクロサービスを担当するチームがそのサービスを裁量と責任を持って管理できるということですが、ダイニーはワンチームなのでサービスも一つで十分なのです(断言)。

とはいえ、ダイニーでは表は注文管理の仕組みから裏は伝統的 POS との SOAP による連携に至るまで様々な機能が必要とされています。こういった雑多な機能群をそれでもきれいにモジュール化するためにダイニーではバックエンドのフレームワークに NestJS を使用しています。NestJS は Express や Fastify といった Node.js の HTTP フレームワークの上にレイヤーを重ねるより高次のフレームワークで、主に DI とリクエストのフィルタ機能を提供してくれています。私たちは各種のロジックの依存関係を NestJS のモジュール化の機能を用いて解決しています。以下の図は「注文」という処理に登場するモジュール群をかなり簡素化したグラフで表現しています。

画像2

システム的にもモノリスは悪いことだけではありません。モノリスの利点は、基盤に対する改善のレバレッジを効かせやすいということです。例えば、テストの実行のためのセットアップを高速化する改善をしたとすると、それは正しくすべてのテストの高速化を意味します。まあ、改善のレバレッジが効きやすいというのは改悪のレバレッジも効きやすいということなんですけどね 👼

3. クライアントは Hasura を介して GraphQL でリソースを取得する

ダイニーでは ほぼすべてのプロダクトが GraphQL を介してリソースを取得 しています。一昔前はすべてのリクエストを REST でさばいていたため、クライアントで表示したいリソースが増えるたびにエンドポイントの実装を変更する必要がありました。たとえば、メニュー一覧画面に表示するべき情報は多彩で、並び替えなどのロジックも複雑ですが、そういったロジックをバックエンドとフロントエンドの両方にまたがって保証する必要があるため、バグが頻発するという状況が発生していました。

REST に対して GraphQL はクライアント側で必要なリソースを定義できるため、ページの内容が変わるたびにバックエンドを編集する必要がなくなり、開発がかなり高速化しました。

しかも、Hasura というデータベースに直接接続し GraphQL エンドポイントを提供できるミドルウェアを採用することで、導入コストも僅かなものに抑えることができました。通常の Web サーバーが GraphQL リクエストに対応するためには、それぞれのリソースに対してリソルバーを定義する必要があり、その実装にはそれなりに手間がかかります。ですが Hasura はデータベースのスキーマを勝手に解釈してリソルバーを定義してくれる上に、データベースクエリに対応する GraphQL の input も定義してくれるので、SQL で書けるクエリであればサーバーサイドで実行した上でクライアントに返却してくれます。

これだけでも Hasura は最高なのに、権限管理やページネーションの機能まで提供してくれているので、クライアントが真に必要なリソースだけを取得するように制限することも可能です。これにより バックエンドの実装工数は体感で 95% ほど削減 されました。体感です。最近までデータベースは PostgreSQL にしか対応していなかったので導入に踏み切れなかった方も多くいると思いますが、このたび MySQL に正式に対応するということが発表されたため、この機会にぜひ Hasura の便利さを体感していただきたいです。

データベースに存在しないようなリソースを取得したい場合は GraphQL エンドポイントを提供するアプリケーションサーバーを Hasura の Remote schema として登録すれば、そういったリクエストの取得も Hasura へのリクエストで一元化できます。ちなみに NestJS の GraphQL モジュールのおかげでバックエンドサービスで GraphQL エンドポイントを提供するのも簡単です!

画像3

他にも Hasura の様々な機能を使っているので、ダイニーは国内ではかなり Hasura を使い倒している会社だと思います。

まとめ

以上が、プロダクトの数がエンジニアの数を超えちゃってる状況でダイニーが如何にして開発を進めているかというお話でした。ダイニーはまだまだ小さな会社ですが、それにしてはこれまで多くの課題にぶち当たってきました。そうなっているのはそれだけ大きなことを成し遂げようとしているからだよと自分たちに言い聞かせつつ必死で考え出した対策が以上のエンジニアリング3カ条になります。面白い話だなと思っていただければそれだけで幸いですが、もし自らもそんな環境に身を投じたいと考えている方がいれば是非ご一緒に働かせていただきたいと強く思います。

採用情報はこちら

筆者の Twitter はこちら

この記事が気に入ったらサポートをしてみませんか?