新卒1年目から新規プロダクトのサーバーサイド開発を任され、様々な技術書を読んだり社内外のエンジニアに相談しながら開発を進めていった結果学ぶことが色々ありました。この勉強会では、そうした経験から私がバックエンド開発の際に考えていることや大事だと思っていることについてお話します。
仕事でバックエンド開発するときに考えていること技育祭2022 勉強会1
View Slide
名前: 鈴木 進也yanyanと呼ばれています新卒2年目趣味valorantFF14 (最近始めました)キーボードで散財自己紹介2
株式会社CARTA HOLDINGS株式会社fluct 開発本部ネット広告の配信、運用支援などをやっている会社GoでAPIサーバーを書いたり、データエンジニアリングをしています自己紹介3
今日の資料、サンプルコードはGithubに置いてありますhttps://github.com/shinya-ml/geeksai-backend-study4
今日話すこと5
ざっくりとしたWebアプリケーションの構成6
この部分を作るときに考えていることを話しますざっくりとしたWebアプリケーションの構成7
他にも考えることは色々あるが、今回は以下のことについて考える認知負荷の話バックエンドアプリケーションのアーキテクチャの話API設計についてテストの話思想を言語化するお題目8
認知負荷の話9
人が学習する際にかかる記憶領域に対する負荷開発には様々な認知負荷がかかる (コードの意図や、アーキテクチャの理解etc...)A Philosophy of Software Design では、ソフトウェアの複雑性が増大している兆候の一つとしてあげられている自分は普段の開発で認知負荷が高くなりすぎていないか?をよく気にしている認知負荷とは10
理解が不十分なままコードの修正や書き足しをすると、より複雑度が高まってしまう認知負荷の高いコードは、さらなる認知負荷の上昇をもたらす規模が大きくなるにつれて複雑性の増加は避けられない工夫して複雑になりすぎないようにすることはできるなぜ認知負荷を気にしているのか11
アーキテクチャの話12
アプリケーションの実装をレイヤーごとに分けて整理するレイヤーに分けることによって以下のことが達成できる関心事の分離依存関係の整理1から作るバックエンドアプリケーションのレイヤー構造をどうやって考えていくかここでいうアーキテクチャとは13
レイヤードアーキテクチャヘキサゴナルアーキテクチャオニオンアーキテクチャクリーンアーキテクチャetc...よく目にするアーキテクチャたち[画像の出典] Ready for changes with Hexagonal Architecture, CleanArchitecture 14
彼らは銀の弾丸ではないいかなるアプリケーションでも、このアーキテクチャを適用しとけばよいというわけではない15
良いとされるアーキテクチャは、開発が進むにつれて変わっていくものアプリケーションの規模が小さい段階から、壮大なアーキテクチャにしようとすると大体つらいほとんどなにもしてないレイヤーが生まれる意味のない抽象化 (具体的な実装が1個しかないとか)なぜそのレイヤーが存在しているのかわからない = 認知負荷が高い自分たちにとって大事な考えを守りつつ、必要に応じて層を足したり抽象化をすればよい必要なときに必要な変更をする16
handler: HTTP リクエストを受け取ってレスポンスを返すマンrepository: DBとやりとりするマンentity: サービスが扱うオブジェクトを定義するマン例えば17
単に来たリクエストに応じてCRUDするだけならこれくらい素朴でもいい開発したいことに応じてアーキテクチャも変化させていく例えば18
ビジネスロジックを書く層がほしい!あとから足せば良い扱う関心事が増えた19
repositoryに依存する層のユニットテストをしたい!repositoryの部分はフェイクに差し替えたいインターフェースに依存する形にする具象が1個だけなら抽象化する必要もない抽象化したい20
私がアーキテクチャの構造を考えるときに守りたいこと1. 関心事の分離2. 依存の流れを1方向にするこれらを守りながら、その時々でベストな設計を模索する何を大事にするのかは人とか開発するサービスの特性によって変わってくる大事な考え21
関心事とは働きかける対象e.g.) DBとのやりとり、HTTP req/resについてetc...関心事の分離22
まずは存在する関心事を言語化することが大事1レイヤーが複数の関心事を扱わないようにするe.g.) ファットコントローラーサンプルコード を見てみよう関心事の分離23
認知負荷が低い触りたい実装がどこにあるかが把握しやすいe.g.) DB周りはrepository層をみればおk変更しやすい変更するためにいじらなければならない箇所が明確になる壊れたときに直しやすい壊れた原因が特定しやすい各層が1つの関心事しか扱わないとどう嬉しい?24
レイヤー構造を成すので、レイヤー間に依存関係が生まれる依存とは依存される側の知識が依存する側に漏れ出ている状態メソッドの呼び出しに必要な引数とか依存される側に変更が入ると、する側も影響を受けるあるモジュールが依存したりされたりしまくっている (密結合) と辛い依存関係25
依存の流れを交通整理する具体的な関心事をもつレイヤー -> 抽象的な関心事を持つレイヤーという依存の流れを守る依存の流れを1方向にする26
円の外側は具体的な技術的関心事内側は抽象的なビジネスのコアを成す関心事外 -> 内という向きで依存させる具象 -> 抽象へと依存させる27
つまり、具体的な知識が内側のビジネスロジックやオブジェクトに漏れ出るe.g.) DBの知識がusecase層で必要になる具体的な関心事の知識が漏洩すると...円の外側に対する変更で内側も影響を受ける技術の差し替えが難しくなるREST -> GraphQLに移行したいとかが辛い抽象が具象に依存するとどうなる?28
アプリケーションにレイヤー構造を設けることで依存関係が整理される関心ごとを適切に分けよう守りたいルールは遵守しながら、サービスの成長に合わせてアーキテクチャは変化させていこうまとめ29
API設計について30
API設計の際に選択肢として出てくるやつらRESTリソースベースのURIJSON形式でデータをやりとりする長いこと使われてきてるgRPCProtobuf形式でデータをやりとりするマイクロサービス間の通信とかで使われているGraphQLクエリ言語+クエリに対するサーバーサイド実装最近使われ始めているAPIスタイル31
ユースケースに応じて使い分けよう銀の弾丸などないGraphQLはRESTの上位互換であるとか、そんなことはないRESTを使ったほうがいい場合もある大前提32
APIの利用者誰が使うんだっけどのくらい使われるんだっけユースケースの数多様な利用者がいてユースケースも様々なんだよねーとかサービス的になにを重要視するかAPIとしての柔軟性?パフォーマンス? etc...どういう軸で考えるのか33
RESTリソースベースでエンドポイントを記述するので、1つのAPIでいろんなユースケースに対応しようとすると辛くなりがち1APIのユースケースが単純ならわかりやすいGraphQLクエリによって利用者側が柔軟に欲しいデータを記述できるのでユースケースが多様な場合にいいクエリの形式と返ってくるデータの形式がほぼ一緒なので直感的gRPCパフォーマンス重視ならこれかなー内部向けのAPIとかなら、型もかっちり書けるしいいざっくりとした私の所感34
顧客向けWebアプリケーションの開発でGraphQLを採用したバックエンドの実装はGoでgqlgenというライブラリを利用しているスキーマ定義からリゾルバーのメソッドやモデルの構造体を生成してくれるブラウザ上でGraphQLのクエリが叩けるプレイグラウンド環境の用意もいいかんじにしてくれるスキーマ設計やロギング、ドキュメンテーションなど、これからやっていきなことはたくさんある余談: 仕事でGraphQLを使っています35
正しい使い方をするのが簡単で、間違った使い方をするのが難しいAPIを使う側のことを考えて設計する適切にドキュメンテーションをする命名の一貫性レスポンスの設計良いAPIとは?36
APIスタイルによって気をつけたいことも変わってくるREST, gRPCなら...エンドポイントのURIはわかりやすくなっているかクエリパラメータやリクエストボディの設計etc...GraphQLなら...スキーマ設計命名の一貫性やわかりやすさnullが妥当に使えているかProduction Ready GraphQLという本がおすすめ例えば37
テストの話38
ユニットテストモジュール単体のテストインテグレーションテスト複数のモジュールを跨いだテストrepository - DB間のテストのような、アプリケーションの外側とのテストも含むバックエンドにおけるテストは色々ある39
Q.どのテストを書く?40
A.全部書けばええやん41
A.全部書けばええやん42
リリース前にバグに気づく変更することに対する安全性、容易性テスト対象のコードの理解を助けるetc...つまり、開発における様々な不安を取り除くなぜテストを書きたい?43
テストを書くことによって不安を取り除きたい箇所どこにテストを書きたい?44
テストを書くことによって不安を取り除きたい箇所リリース後に壊れるとサービス的に致命的な箇所お金が絡んだりして、後から直すのが辛いとかサービス的に大事なロジックが書かれているビジネスロジックとかどこにテストを書きたい45
特段不安がないとか、テストのコスパ悪そうだな〜って思った箇所には私はテストを書かないテストコードにもメンテナンスコストはかかる自動テストにかかる時間が長くなると人々はテストしなくなる-> テストしたいところだけテストするテストを書かないという選択46
こういうレイヤー構造で以下のことを考えてみるなんのテストを書きたいかなんのテストは書かないか各レイヤーの関心事handler: HTTP req/resusecase: ビジネスロジックrepository: DBとのやりとりentity: ビジネスオブジェクト例47
あくまで例で、サービスの特徴によって変わる単純な構造なので書きたいテストはそんなに多くないusecase層のユニットテスト(ロジックがあれば) entity層のユニットテストhandler ~ repository まで一気通貫のインテグレーションテストなんのテストを書きたいか48
あくまで例で、サービスの特(ryhandlerのユニットテストrepositoryのユニットテストrepository - DB 間のインテグレーションテストなんのテストを書かないか49
HTTP request/responseが関心事それ以外の殆どの処理は他の層に委譲しているつまり、ほとんどロジックがない薄い層 -> テストしたいことがないこの層にテストしたくなるようなロジックがいたら、関心事の分離がうまくできていないかもしれないhandlerのユニットテスト50
なぜテストを書き、何をテストしたいのか意図がわからないテストは、後々辛いプロダクションコードの変更でテストがコケたとき、直しづらいそのテストがなぜ存在しているのかわからないとメンテもされない本当にテストしたいところにテストを書こうテストは意図が大事51
思想を言語化しよう52
なぜこのアーキテクチャにした?なぜこの言語を選んだ?なぜGraphQLを選んだ?こうしたWhyに対する答えは、意図的に言語化しないと残らないWhyはコードを読んでもわからない53
理解の助けになる後から反省する材料になるアーキテクチャやテストに手を入れる際、既存のものの意図を知ることは大事すでにあるものがなぜこうなっているかを知った上で、どう変化させていくかを考えるWhyを言語化しておくことはなぜ大事なのか54
システムを作り始める前に書く地図のようなものこれから作るシステムが目指すゴールどういう設計で作るのかシステムがスコープとしないこと、やらないと決めたことなどを書くシステムを作るにあたって必要な意思決定が言語化されるここに、意思決定に至ったWhyも書くDesign Docを見ればシステムの目指すゴール、意思決定のwhyが分かる状態にするDesign Doc55
特定の意思決定に関することを記述する背景なぜこの意思決定をしたのか他にどんな選択肢があったのか作り始めてから行われる変化の意思決定はADRで言語化するとわかりやすいArchitecture Decision Record (ADR)56
認知負荷を意識して開発するアーキテクチャもテストも必要だと思ったことをやればよいなぜやる (やらない) のかが大事コードでは伝わらないことは、積極的に言語化していこうおわりに57