BlogRELATIONS株式会社の開発ブログです。GraphQLこんにちは、RELATIONS奥宮です。今年も残りあとわずか。自分は先日のクリスマス、人生で初めて妻と娘(ゼロ歳)と一緒に過ごしました。)前回の久原さんのフロントエンド技術スタックの話に引き続き、今回は自分がバックエンドの実装に関して、マネジメント支援ツール「Wistant」の事例を引きながらお話したいと思います。「Wistant」について技術スタックに入る前に、Wistantがどういうプロダクトなのか、簡単に触れておきたいと思います。(プロダクトの特性が、技術やアーキテクチャの選定にも関わっていますので、前段として共有させてください)弊社で実際に使っているWistantの画面の一例Wistantは、従業員が数百名規模の企業をメインターゲットに、ピープルマネジメントの運用改善や仕組み化を支援するサービスです。具体的には、現在以下のような機能を提供しています(HPより抜粋)。目標管理メンバーが追っている目標を管理することができます。目標に対して振り返りをし、1on1を実施することでメンバーの達成を支援することができます。1on1の運用・改善1on1のペア作成、アジェンダ設定、分析、など1on1の運用や改善を楽にする機能が揃っています。パフォーマンス分析1on1後にメンバーは簡単な振り返りを行います。この結果から、組織のマネジメント状態を分析することができ、改善のための示唆が得られます。これらのメイン機能以外に、一般的なSaaSに標準で必要な機能、あるいはBtoBツールならではの必須機能もあわせて実装しています。主だったのものは以下のとおりです。外部連携Slack等のチャットツールと連携し、通知をポストする機能こうした一連の機能を、エンジニア総勢4名(「フロント:バックエンド」=「2:2」くらいの比率で稼働)で開発しています。使用しているライブラリなど構成については後述するとして、まずはバックエンドで使用している言語・フレームワーク・ライブラリについて、主だったところをざっとご紹介します。Node少人数の開発チームでいかに学習コストを抑えて開発効率をあげられるかを検討した結果、数年前から全社的にNodeを採用しています。その狙いどおり、フロントとバックエンドの垣根なくJavaScriptに特化して開発を進められることで、相乗効果が生まれ、チーム全体としてノウハウやスキルレベルが向上したと思います。I/O、イベントループ)によるところが大きいです。awaitで非同期処理を書きたかったからです。新規プロダクトなので下位互換などのしがらみ(笑)もなかったため、デファクトのExpressではなくモダンさを優先してKoaを選択した形です。v2に対応していたのも大きかったですね。(GraphQL)APIには、RESTではなくGraphQLを採用しています。これも2017年初頭には意思決定しているので、プロダクトでの採用事例としては早い部類ではないかと思います。Serverを立ててSchemaやResolverをゴリゴリ書いていくのみ...な感じでした。現在も、その実装をブラッシュアップしつつ引き継いでいます。etc)を柔軟に取り扱えますし、type(データ型)に対してResolverを設定することでフィールド毎のアクセス制御も容易に実装できます。この利便性は、ちょっともう手放せない感じです。Clientが正式対応した時点で導入しました。上記のメリットをソケットでもありがたく享受させていただいてます^^SequelizeORMにはSequelizeを採用しています(DBはMySQL)。当初はAssociationなどもがっつり書いていたのですが、開発を進めるうちにORMのAssociationとGraphQLのResolverとの食い合わせが悪いことがわかり、Sequelize側からはAssociationなど外し、ごく薄く使う形に仕様変更しました。現在は基本、マッピングとトランザクションとマイグレーションのみSequelizeが受け持っています。スキーマとGraphQLスキーマの両方を書かなければいけないとか、まだイケてない面が残っていたり。Prisma...)RxJS「CQRS」なアーキテクチャ(後述)でロジック層を実装する目的で、RxJSを採用しました。実装的には、GraphQLのMutationをトリガーにしてObservableを生成し、そのストリームに対して、認証認可・データ更新・各種通知(Email、Slack、WebPush、etc)などの処理をカスタムオペレータでつないでいく形で、ビジネスロジックを形成しています。メリットとして、各カスタムオペレータごとに職責が分離しているので、コードの見通しがとてもよくなりますし、ロジック変更にも柔軟に対応が可能です。コードを実際に見ていただいたほうがイメージ伝わると思いますので、下にサンプルコードを掲載します。);}その他ユーザ認証にはJWTを利用しています。Loaderを使用しています。Eventで処理しています。各種SaaSも利用させていただいています(クレジットカード決済はStripe、チャットボットはDialogflow、など)。)Wistantは企業単位/チーム単位で利用されるプロダクトという特性上、1回の処理に対して複数の通知を投げなければならないユースケースが多くあります。チームのAさんが目標の登録を行なった場合に、以下のような通知を行うケースを想定します。Aさんにメールで通知するチームリーダーにWebPushで通知するチームのSlackチャネルにフィードを流すチームの(Wistant上の)タイムラインにフィードを流すこちらに対応するビジネスロジックは、たとえば以下のようなダイアグラムで表すことができます。各タスクの中には非同期にできるものもあり、すべてのタスクが直列処理されるわけではありません。「目標を登録する」というユースケースに対するロジックとして一括りにするには、粒度が大きすぎる印象があります。実際、当初Wistantはこのようなスタイル(Resolverにゴリゴリ処理を書いていく)で実装を進めていたのですが、複雑になっていくビジネスロジックに対応しているうちにコードの見通しがどんどん悪くなり、密結合でメンテナンス性も悪くなり...。」をベースとしたアーキテクチャに全面改修しました。先ほどの例を、RxJSを利用してCQRS的に実装すると、以下のようなダイアグラムになります。改修ポイントは以下のとおり。書き込み系の処理と読み込み系の処理を明確に分ける(通知は読み込み系に分類)*2書き込みに関しても、目標の作成とフィードの作成は別のユースケースと考えストリームを分離し、イベントをdispatchすることで呼び出す各通知処理は、目標作成のストリームからイベントを受けとり、それぞれ個別に実行される(並行処理)改修前と比較すると、タスクごとの職責が明確になり、フローの見通しが格段に良くなったかと思います。(これはCQRSというよりRxJSの功績になりますが、)各タスクが疎結合になったことで、コードのメンテナビリティもぐっと向上しました。Wistantのバックエンドのビジネスロジックは現在、全般にわたって上記のようなCQRSベースのアーキテクチャを採用しています。次に、このアーキテクチャをAPIのインターフェイスとどう接続するかを見てみましょう。GraphQLGraphQL(APIインターフェイス)も含めた、バックエンド全体の構成は以下のようになります。CQRSパターンは、ズバリ、GraphQLとも相性がとても良いです。(そもそもGraphQLのMutation/Queryというフレーム自体、CQRSをベースにしているのかも。詳しい方いらっしゃったら是非教えてください...!!)例えば、Queryに関して。」ことがあげられます。その点、GraphQLは、定義されたスキーマの範囲であればクライアント側が自由にクエリを書いてデータを取得することができるので(あるエンティティの必要なフィールドだけ、とか、複数エンティティを1クエリで、とか、自由に指定できる)、CQRSのQueryの実装として完璧にマッチします。これは、書き込み後にレスポンスデータを返すケース(Mutation)も同様です。Command処理後、イベントとしてルートエンティティ情報をQueryに渡すだけで、あとはGraphQLがよろしくクエリをさばいてくれます。クエリ内容に関して、Command側が意識する必要は全くありません。このように、GraphQLに「CQRS+Reactive」なロジック層を組み合わせることで、それぞれの特性が存分に発揮され、柔軟でメンテナンス性の高いバックエンド構成が実現できたかな、と思っています。まとめ以上、駆け足になりましたが、Wistantのバックエンドの技術スタックの概要をご紹介しました。他の多くの言語だとフルスタックなフレームワークがデファクトで存在しますが、Nodeでは薄いWebフレームワークにお好みでミドルウェアを追加していくのが一般的です。そのぶん、ロジック層にプロダクトごと・実装者ごとのカラーが出やすいかなと思います。Wistantの場合、ビジネスの業務フローに沿ったサービスを提供する上で、取り扱うドメインは多岐にわたりますし、ユーザロールごとの権限管理など要件も複雑になります。必然、バックエンドのロジックも容易に肥大化する傾向にあります。したがって、CQRSのようなフレームやReactiveのような宣言的なコードスタイルを導入して、やや重厚ではありますがメンテナビリティを重視した構成を採用しています。これがもし、もっと軽量なサービスのバックエンドでしたら、GraphQLを採用するにしてもロジックはResolverに素直に書くだけで足りるかもしれません。いまならAppSyncなどのサーバレスバックエンドの利用を検討するかもしれません。ことほど左様に、最適な手段は相手や目的に依るもの。「技術やフレームワークは適材適所で」というのが、しごく当たり前ではありますが自分が技術選定する上で指針としているところです。今回はWistantの事例をご紹介しましたが、また機会がありましたら別の事例についても掘り下げてお伝えできればと思います。来年も、RELATIONSならびに本ブログを、どうぞよろしくお願いいたしますー。」あたりが参考になるかと思います。*2:通知に関しては、CQRS的なQueryとは厳密にはいえないと思いますが、この例では構造を単純化するため「読み込み」側に含めて考えます。13:35マネジメント支援のSaaSプロダクト「Wistant」のバックエンド技術スタックを紹介しますTweet関連記事2019-08-07Slackのリアクションに反応するアプリをGASで作ってみた話こんにちは。RELATIONS株式会社の大川です。SONI...2019-03-27Webエンジニアがスマホアプリをリリースするまでに学んだ32のコト(後編)こんにちは。RELATIONS株式会社の久原です。使ってジオフェンスとプッシュ通知を組み合わせてみましたこんにちは、RELATIONS株式会社の大川です。U...2019-02-05Webエンジニアがスマホアプリをリリースするまでに学んだ32のコト(前編)こんにちは。RELATIONS株式会社の久原です。はてなブログProRELATIONS株式会社のエンジニアメンバーによる開発ブログです。reserved.スターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。