はじめまして! 3月16日に入社した関口です。
HRBrain のシステムはアーキテクチャがしっかりしており、既存のコードが読みやすく、入社してすんなりと既存のシステムの機能追加の実装に入っていけました。
そこで、既存のシステムに後から参加した身として、アーキテクチャがしっかりしていると受ける組織的な恩恵について思ったことを書きます。
今回の話に登場するシステムは、マイクロサービスで構成されるサービスの内の認証システムについての話です。
「変更に強い」とは何の変更に対する強さなのか
よく設計やアーキテクチャの話になると、「変更に強くなるため」の設計をしようという話になることが多いと思います。 また、そこで出てくる変更とは「コードの変更」のことを指すことが多いように思います。
例えば、「既存のシステムに機能の追加を行いやすい」とか「DB を乗り換える時にコードの変更を限定的にできる」等です。
これは、開発をしていく上で欠かせない非常に大切な点だと思います。
一方で、HRBrain に入社して綺麗な設計で書かれている既存のコードを読んでいて、アーキテクチャがしっかりしているコードは、「コードの変更」以外の「変更」にも有効だと思いました。
それが「組織の変更」です。
例えば、あるシステムを担当しているチームに、メンバーが新規で参加したとします。(まさに最近の自分ですが...) この際に新規のメンバーに立ち上がりの速度を上げてもらいやすいかということが、「組織の変更に強いかどうか」ということの1つの指標になると思います。
詳細は次の項目で書きますが、アーキテクチャがしっかりしているとコードが理解しやすく、コードを追加する時に迷いにくくなるので、新規のメンバーが立ち上がりの速度を上げやすくなると思います。
HRBrain で立ち上がりやすかった理由
HRBrain で立ち上がりやすかったと感じたのは、以下の理由です。
- オーソドックスなレイヤードアーキテクチャで構成されており、各々のレイヤーの責務がしっかりと分かれている
- 各レイヤーの依存関係が一方向に制御されている
- Input 用と Output 用の DTO をしっかりと定義している
※ 上記は「コードの変更に強い」という意味でも非常に有効なことだと思います。
※ 立ち上がりやすかったのには色々な要因がありますが、ここではコードの話に絞って書いています。
ここからは個々の理由について詳細に説明していきます。
オーソドックスなレイヤードアーキテクチャで構成されており、各々のレイヤーの責務がしっかりと分かれている
認証システムでは、ざっくりと以下のような比較的オーソドックスなレイヤードアーキテクチャになっています。(他にもいくつかディレクトリが存在しますが、ここでは割愛します)
├── application - アプリケーション層 │ └── usecase毎にいくつかの package に分かれています ├── controller - インターフェース/プレゼンテーション層 │ ├── grpc │ └── twirp ├── domain - ドメイン層 │ ├── model - ドメインモデル │ └── service - ドメインサービス ├── driver - SendGrid や Firestore の client の生成 └── infrastructure - インフラストラクチャ層 ├── grpc - gRPC(自動生成されたファイル等) ├── mail/sendgrid - メール/SendGrid └── persistence/firestore - 永続化/Firestore
- オーソドックスなレイヤードアーキテクチャを採用しているので
- レイヤードアーキテクチャを既に学んでいるエンジニアにとっては、どこにどのような責務のコードが存在するかが直感的にわかる
- レイヤードアーキテクチャを学んだことのないエンジニアにとっては、アーキテクチャの学びになるし、市販の書籍等で学習しやすい
- レイヤー毎に責務が適切に分かれているので
- コードを読み進めやすい
- コードを追加する時にどこに書いていいのか迷いにくい
- アーキテクチャという型に従って実装できるので、新メンバーでも設計において一定の品質を確保できる
「どこに何が書かれているんだろう」、「どこに何を書けばいいのだろう」と迷う時間がなくなり、新規参加時の「ドメインの理解」や設計時の「モデリング」等の本質的なことに集中することができ、生産性が上がりそうです。
※ 今回、自分が最初に担当したシステムがレイヤードアーキテクチャであり、別のシステムは別のアーキテクチャを採用しているものもあります。
※ 今回の例では、オーソドックスなレイヤードアーキテクチャなのでわかりやすいと言った書き方にしてしまいましたが、別のアーキテクチャでもしっかりとした設計の上で成り立っているものであれば、同じようなことが言えると思います。
各レイヤーの依存関係が一方向に制御されている
認証システムでは、各レイヤ間に interface を挟んで、抽象に依存するようにして、DIP を通じてレイヤー間の依存関係を制御しています。 例えば、domain レイヤ に以下のような Repository の interface を定義し、具体的な実装は infrastructure レイヤ配下に実装しています。
type HogeRepository interface { FindByID(ctx context.Context, hogeID HogeID) (*Hoge, error) }
DIP によってレイヤ間の依存関係はざっくりと以下のようになっています。
- DIP(Dependency Inversion Principle) を実現し、常に各レイヤーの依存関係が一方向に制御されるようになっているので
- コードの流れが追いやすくなっています
- コードが複雑になりにくなっています
コードの依存関係が制御されているのでコードを追っていきやすいですし、初めて書くコードでも既存の依存関係に倣って書いていくことで、複雑化しにくくなります。
Input 用と Output 用の DTO をしっかりと定義している
CQRS のようにガッツリと Write(Command) と Read(Query) を分けている訳ではありませんが、Input 用と Output 用で DTO を分けることで、ユースケース毎に必要なデータのみを抽出できます。 その結果、どのユースケースでどのようなデータが使用されるかということがわかりやすくなります。
以下のように Input 用の DTO と Output 用の DTO を用意しています。(これは application レイヤでの例です)
type FindHogeInput struct { HogeID HogeID } type FindHogeOutput struct { Foo Foo Bar Bar }
まとめ
「既存のコードを理解しやすく、実装に入っていきやすい」ということは、新規のメンバーが「プロジェクトに参加してから立ち上がりやすい」、つまり、「生産性を上げやすい」ということだと思います。
どの組織にとっても上記のようなことは大切ですが、特にある地点で爆発的に人が増える可能性のあるスタートアップにとっては特に重要なことのように思えます。
例えば、会社が資金の調達が完了したので、エンジニアを増やして一気にアクセルを踏みたいという状況があったとして、アーキテクチャや設計方針がしっかりと決まっていない場合、新しく参加したエンジニアは既存のシステムを理解するのに時間がかかるでしょうし、コードを書く際にも毎回書く場所を迷い、既存のエンジニアに相談することになりそうです。 その結果、毎回既存のエンジニアの工数も使ってしまうことになり、新規のエンジニアの生産性も上がりにくいので、採用数の割りに生産性が上がっていないということが起こるかもしれません。
アーキテクチャがしっかりしている「組織の変更にも強いコード」というのは、エンジニアのオンボーディングにとって非常に重要な要素の1つのようにも思いました。
※ コードの相談をすること自体はいいことだと思いますし、実際に HRBrain のエンジニアは非常に話やすく、相談しやすく、よく相談させていただいています。ここでは、すぐに理解できるコードだったら、本来必要ない工数が発生してしまうかもしれないという文脈で書いています。
さいごに
HRBrainではバックエンドも、フロントエンドもエンジニアを大大大募集しています! イケているアーキテクチャでコードを書いていきたいぜって方はぜひチェックしてみてください!