はじめに
こんにちは、fluctでエンジニアをやっているyanyanです。 この記事は、仕事でバックエンド開発をするときに考えていること の続きです。 前回のスライドでは、私がバックエンド開発をするときに大事にしている考え方や思想の話をしました。今回は、じゃあそうした思想の下で実際にどういうものを私が作ったかという話をしようと思います。なので、この記事を読む前にスライドの方を読むことをおすすめします。
何を作ったの?
すごいざっくりいうと、顧客向けWebサービスのバックエンド部分を0からつくりました。 0からと言っても、インフラ部分に関してはすでにあるECSサービスにタスクを追加するだけという形を取りましたが、アプリケーション開発というスコープでいうと技術選定からやり始めました。
どんなサービスなのか
- fluctが広告配信をしたり、運用支援をしている媒体社がユーザーです
- 媒体社が広告の配信実績を見るレポート機能や、fluctからのお知らせを見る画面というのは既存のものが存在していた
- が、そもそも使いにくかったり、技術的負債が溜まっていたので作り直すぞとなった
サービスの構成
ざっくりですがこういう構成です。2つあるDBはそれぞれ以下の用途で使い分けられています。
- Aurora: 広告の配信設定やユーザー情報などの運用上必要なデータを扱う
- Redshift: 広告の配信実績などの、レポーティングや分析で必要なデータを扱う
インフラ構成であと特筆すべきことは、pre/ブランチ環境の存在です。
- pre環境: 本番へのリリース前に新機能を試したりする環境
- ブランチ環境: PRごとに作られる環境
これらの存在は、後述しますがテストの戦略に関わってきたりします。
技術選定やAPI設計
まず技術選定の話ですが、どういうAPIスタイルでやるかによって使いたい言語も変わってくるだろうということで、APIスタイルの選定からはじめました。選択肢としてはスライドでも上げた3つで、結果的にはGraphQLを採用することになりました。採用に至った理由としては、
- スキーマがJSONチックで書きやすいし読みやすい
- スキーマ定義のファイル分割がいい感じにできる
- クエリする側がほしいデータを取捨選択できるという点が、レポート機能と相性がよさそう
- 既存の画面ではREST (OpenAPI) でAPIを作っていたが、API定義を書くyamlの肥大化がつれぇわ... とか、多様なユースケースに対してRESTだとつれぇetc...といった話があり、GraphQLを試してみたいという気持ちがあった
といったものがありました。その一方で
- 社内での知見が少ない
- 実装言語の選択肢が少ない
- GraphQL特有の気になりが発生する
という懸念もありました。まあこれらのconsに関しては楽観的ではありますが以下のような気持ちだったのであまり気にしてなかったというのがあります。
- 社内での知見が少ない -> 我々が知見を貯めれば良い。
- 実装言語の選択肢が少ない -> やるならライブラリの充実度的にTypeScript or Goだが、まあこの2つならいっか
- GraphQL特有の気になりが発生する -> ほかのAPIスタイルを選んでも同じことなので、うまく対処すればいいだけ
言語の選定
前述のとおりTypeScript or Goという選択肢で、それぞれのpros/consを考えました。
- Go
- pros
- DBへのアクセスとか、認証認可、ミドルウェアあたりが書きやすい
- fluct社内で知見がある
- 配信サーバーやバッチ基盤など、Goで書かれてるプロダクトがいくつかある
- cons
- プロダクトとしての規模がでかくなってきたときに書き味が未知
- これは自分が経験ないというだけだが
- フロントエンドはTypeScriptでやるという選択がすでになされていて、コンテキストスイッチが発生する
- プロダクトとしての規模がでかくなってきたときに書き味が未知
- pros
- TypeScript
- pros
- フロントエンドと同じ言語で書ける
- GraphQLライブラリが充実している
- cons
- TypeScriptでバックエンド書いた経験がない
- fluct社内での事例もない
- GoとくらべるとDBへのアクセスとか、認証認可、ミドルウェアあたりは扱いにくいかも
- pros
Goのconsはやってみないとわからないというのもあり、自分や社内での経験があるという点を重視しGoでやるという決断をしました。
ライブラリの選定
次に、GraphQLライブラリの選定の話です。選定する際に重視したポイントはこんな感じ。
- ドキュメントの充実具合
- GraphQLスキーマからリゾルバや構造体をある程度自動生成してくれること
- メンテされてるか
GraphQLスキーマからリゾルバや構造体をある程度自動生成してくれること
これに関しては、フロントエンド / バックエンド それぞれが、共通のスキーマを元にやりとりしようという方針でいたのでポイントとして入ってきています。
結果的にはgqlgen という、GraphQLスキーマからリゾルバや構造体を生成してくれて、かつドキュメントも整っていて機能が豊富なものを選びました。
アーキテクチャ
次にアークテクチャの話ですが、守るべきルールや大事にしたい考え方はスライドにあるとおりです。
- 関心事を適切に分ける
- 依存の流れを1方向に整理する
そういった考えのもと、現状以下のような構造になっています。
各レイヤーの関心事をざっと書いておくと、
- entity
- publisher consoleにおいて最も重要なビジネスオブジェクト
- usecase
- entityを用いて機能要件を達成する
- gateway
- repository の抽象
- repository
- DBへのアクセス
- gatewayで定義されたinterfaceの具体的な実装をここでやる
- resolver
- GraphQLクエリ、レスポンスの処理
- auth
- 認証・認可のことを色々やる
- middleware (上の図にはいないけど)
- ロギングとか認可とか、メインの処理の前後でやりたいこと
もちろん、最初からこういった構造をしていたわけではなくて、
- GraphQLのリゾルバを本格的に書くぞ -> resolver層の追加
- repository層mockに差し替えてぇ -> repositoryの抽象化
- repository interfaceをいろんなレイヤーで呼びてぇ -> gateway層に置く
といった修正が都度加えられた結果こうなっています。
テスト
テスト戦略も、重視していることはスライドに書いてあるとおりです。
- テストは不安を取り除きたいとこに書く
- 実行にかかるコストを考える
ユニットテスト
ユニットテストに関しては、基本的にスライドで出した例とほとんど同じ意思決定をしているので割愛します。 ただ、スライドでは書かないとしたrepository層のユニットテストは実は書くことにしています。 その理由としては、repository層の中には、クエリビルダーによって動的に複雑なSQLを組み立てている実装があり、その部分が意図通りになっているんだっけ?というのには不安を感じていたからです。
インテグレーションテスト
ここでいうインテグレーションテストとは、複数のモジュールを跨いだテストを指します。例えばrepository層 - DB間のテストだったり、handler層 ~ repository層 - DB といった一気通貫のテストのことです。 このプロダクトにおいては、インテグレーションテストは基本書かないという選択をしました。 その理由としては、
- pre/ブランチ環境の存在
- DBへのクエリちゃんとできてるかなーとか、GraphQLのクエリに対して意図したレスポンス返ってくるかなーといった不安ごとは、これらの環境で実際に試せばいいなとなりました
- redshiftを再現することのコスト
- ローカルやCI環境でredshiftを完全に再現することはそもそもできなかった(調べた限り)
- PostgreSQL + mysql_fdw でそれっぽいものは作れるが、redshift固有の記法を使っていたら詰む
- pre/ブランチ環境で実際にクエリしてみれば試すことはできるし、コスパ悪いな〜となった
こうした選択はあくまで「今はこうしている」というだけで、より良い方法が見つかればやっぱ書くという方針転換もあり得ると思っています。
思想の言語化
最後に思想の言語化の話で、もちろんDesign Docを書きました。 実は、ここまで書いてきた内容は全部Design Docに書いてあることなのです。まあ、Design Docを書いたのは半年以上前なので、今見返してみると「もうちょっとうまくやれなかったもんかねぇ...」と過去の自分に言いたくなりますが...w
Design Docというのは開発を始める前に書くのが一般的なのですが、自分がDesign Docの存在を知ったのが開発をはじめて少し経ってからでした。そのため、作り始める前にFBをもらえるというDesign Docのメリットの1つは享受できなかったのですが、スライドでも書いたとおり
- 技術選定や設計のWhyを言語化しておくこと
- 意思決定の背景をいつでも振り替えれるようにしておくこと
といった事柄を残すことは、作り始めてからでもやる価値があると考えました。
どこに書いてる?
最初は誰でもコメントできたり共同編集ができるという理由でGoogle Docsに書いていたのですが、
- そのうち埋もれてしまいそう
- 技術的な意思決定が多く書かれているため、コードの近くに置いておきたい
といった理由からGithubで管理することにしました。
今後の展望
今回は新しいプロダクト開発のいわば0 -> 1部分の具体例をお話しました。 当たり前ですがこれで開発が終わったわけではなく、これから先運用していく中で新たに機能を追加したりユーザーから上がってきた要望を元に既存の機能を改善したりといった不断の努力をしていく必要があります。 また、GraphQLというまだそれほどプラクティスが確立されていない技術を採用したわけなので、自分たちで試行錯誤をしたり、日頃から情報をキャッチアップしていかなければならないと思っています。
まとめ
自分が普段バックエンド開発をする際の思想を言語化したスライドをかなり多くの人に見ていただいたので、その具体例について今回はお話しました。 具体的な意思決定に関しては、もっといい選択肢や決定に至る理由が不十分じゃない?といった意見も出てくると思っているので、ぜひ教えていただけると助かります。