giftee engineer blog

俺たちはこう ADR(Architecture Decision Record)を書いている

2023-10-02

チームの ADR 文化について

thumbnail

こんにちは、ギフティでエンジニアをやっている中屋です!

自分は割とドキュメントが好きです。 ドキュメントはなさすぎるよりはありすぎた方がマシと思っている派閥です。

はじめに

我々のチームでは開発ドキュメントとして ADR(Architecture Decision Record)というものを書き残すようにしています。

ADR とは平たく言うと、「設計上の選択を記録したもの」です。システム開発のなんらかの意思決定に対して、その判断理由などを短いテキストファイルにまとめたものを指します。 自分が初めて ADR という言葉を知ったのは『Design It!』という本でした。

最近ではいろんな企業が ADR を書いているのが、ブログ等から伺えます。 ADR そのもの自体の詳しい解説についてはネット上にたくさん情報があるのでここでは割愛します。

参考:

なぜ ADR を書くのか

意思決定の過程を残しておくことは、後から振り返って意思決定の良し悪しを再確認したり、チームへの新規参入者のキャッチアップなどに対して非常に有効です。

スタートアップのシステム開発のチーム編成は流動的なものです。とあるシステムを10年も同じ人が保守運用し続けることは稀で、5年ですら危ういです。 それぐらい人の入れ替わりが激しいと、暗黙知というものがうまく機能しません。

前出の『Design It!』にはこのように書いてあります。

オーラルヒストリー(口述記録)は、誰かがそれを語り継げる場合にしか存続しない。6人程度の小さなチームでさえ、語り継ぐことは困難を伴う。

ギフティではチームによっては業務ドメインごとにシステムを細かく割っている背景もあり、一つのシステムに関わる人数が2〜3人ということも少なくありません。

また、特に我々のチームは現在、社内の多くのシステムと接続する基盤的なアプリケーションを作っており、今後長く稼働し続けることが前提としてあります。 それに加えて、コーディングにおいてはフレームワークを使ってない(※1)ので、割と独自実装というか自分たちでコーディングアーキテクチャを決めながらコードを書いていく部分が多いのですが、そうすると rails のようなフレームワークを使っているときと比べて相対的に意思決定の数が増えます。そうしたことから意思決定ログを残しておくということが尚更大事になることが予想されました。

また、現状を説明しただけの仕様ドキュメントには、当時検討したが採用しなかった選択肢の情報が欠落しがちです。 なぜ複数ある選択肢からその選択をしたのか、どうして他の選択肢を切り捨てたのか、といった情報はとても有益です。プロダクトがスケールしていったときに過去の決定を覆したりする際にもそうした情報が役に立つ場合があります。また、情報共有の観点からも、プロの判断は若手エンジニアの教養としても良い材料になるので、意思決定を言語化し共有できる形で残しておくとそうした共有の面でもプラスになります。

※1: 開発言語に Go を採用しており、Go でバックエンド API を立てる場合はフルスタック Web フレームワークを使わないことも多い

ADR を書く時の工夫

ADR 自体は他の開発ドキュメントと同じく、単なるドキュメントです。

ドキュメントを書くときにハードルとなるのが、

  • そもそも書くのが面倒くさいので書かれない
  • 書いたはいいもののその後のメンテナンスを担保するのが難しい

ドキュメント関連で一番頭を悩ませるのが後者の、書いたドキュメントのメンテナンスだと思います。適切にメンテナンスされずにドキュメントが腐ってしまうことはよくあります。場合によってはそのような腐ったドキュメントが裏目に出て、それを元にした判断が間違った結果を生じさせることもあります。こうした点から、ドキュメントを書くのを嫌うエンジニアがいるのも正直理解できます。

ADR もドキュメントですと述べたように、同様の問題が発生します。 我々のチームでは意識として以下のようなことに気をつけています。

  • なんらかのトレードオフを選択した場合は ADR を書く

    • 迷ったら書いておく
  • 内容は簡素でも良い

    • 形式張ってたくさん書く必要はない
    • 書くことが負担になってしまい全く書かれなくなってしまうのは避けたいので、ある程度カジュアルに書いていくことも大事
    • トレードオフの選択肢がわかること、できればそれらのメリデメが残っているとなお良い
  • そのとき時点の意思決定を残すものである

    • 現状どうなっているか、を説明するものではない
    • メンテナンスすることを前提としない
    • 書く側も読む側もその前提で ADR を取り扱う

また、その決定に支配的な理由がない場合は、その旨もしっかりと書いておく、というのが意外と重要だったりします。ちょっと不可解だけどまあいいかなぐらいの気持ちで下した判断を、後から知らない人が見たときに、現状のコードや構成を変更していいのかどうかで悩むということを減らすことができます。これが残っていないと、意図して現状こうなっていて変更したら何か重大な影響を外に及ぼすのか、特に大きな理由がないのでカジュアルに変更していいのかがわからず、影響範囲調査がかなり辛いものとなる時があります。

どのように書いているのか

では、具体的にどのような ADR を書いているのか、ここで一部サンプルを紹介したいと思います。

テンプレート

ADR の形式にはオフィシャルな決定版というのは存在しませんが、いくつかデファクトスタンダードっぽいものがあり、それをカスタマイズして使っています。 具体的には以下のようなテンプレートを用意しておいて、これを埋めていきます。

# Title
<!-- Title という文字を消してこの ADR のタイトルを書いてください -->

Status: Accepted
<!-- プルリクベースで開発するので、プルリクを作る段で Accepted の状態でOK -->
<!-- 別のADRによって置き換えられた場合 Replaced by #{ADR No.} に変更 -->
<!-- 明らかに不要になった場合 Deprecated に変更 -->

Relevant PR:
<!-- reference できるプルリクがあればそのリンクを貼ってください -->

# Context

<!-- アーキテクチャ上の判断をするに至った背景や経緯を書いてください -->

## References

<!-- 判断に使った資料などがあればここにリンクなどを貼ってください -->
<!-- Context の文中に記載しても問題ないです -->

# Decision

<!-- 下した判断を簡潔に書いてください -->

## Reason

<!-- 下した判断の理由を書いてください -->
<!-- Decision の他に検討した選択肢があれば書いてください -->

# Consequences

<!-- Decisionを適用した結果、その決定がなされる前と後で何が変わったか(良くなったか/悪くなったか)を書いてください。 -->

(本当は意思決定をする前の段階で、こういう決定でいいですか、という ADR を書いてプルリク上でそれを議論する、というやり方が多いっぽいのですが、我々は対面で議論して決まった結果を ADR に書き起こすことが多いので、Status という箇所はあってないようなものになっています)

また、ファイル名は yyyymmdd_hogefugaについて のように日付を入れることで、いつ時点のものなのかをわかるようにしています。

どこで管理するか

現状では Git のリポジトリで管理しています。ADR 専用のリポジトリがあるわけではなく、各サービスのリポジトリに ADR もコミットしています。それは ADR を書く範囲が現状ではサービスごとに留まっているからです。 ADR 追加のプルリクを作成し、メンバーの承認を得てからマージされるという流れになります。

実際のサンプル

実際に書かれた ADR をいくつか紹介したいと思います。
(公開してはまずい部分は内容を多少ぼやかしたり、一部ワードを変えているところがあります。ADR そのものの良し悪しより雰囲気が伝わることをここでは主眼に置いています)

以下はプロジェクトに CQRS(Command and Query Responsibility Segregation) を導入するかどうかを決めた際の ADR になります。 現状の問題点とそれを解消する方法、その方法を採用した際のデメリットについて記載しています。

# CQRSを導入します

Status: Accepted

Relevant PR:

# Context
- DDD の集約・リポジトリという概念では GraphQL query の柔軟さに対応できないので、何らかの解決方法が必要
- GraphQL ではユーザーが必要な情報を指定してクエリを投げるので、集約単位でのオブジェクト復元では、RDB からレコードを取得する際にオーバーフェッチ・N+1問題が発生してしまう

## References
https://little-hands.hatenablog.com/entry/2019/12/02/cqrs

# Decision
- CQRS というアーキテクチャを導入し、情報の参照と更新でそれぞれ異なるモデルを利用する
- GraphQL query では参照用のクエリモデルを利用し、GraphQL mutation では集約を利用する
- CQRS には、データソース分離(クエリでは RDB を使いコマンドでは KVS を使う)をするパターンもあるが、コストが高すぎるのであくまでモデルの分割までにする

## Reason
- GraphQL には query/mutation という概念があり、戦術 DDD には CQRS というアーキテクチャがあり、互いに相性がよいこと
- この方法でオーバーフェッチや N+1 の解決が検証できていること
- 実装のコストやシステムアーキテクチャの複雑性が高くなりすぎないこと
  - クエリモデルの実装は、集約の実装ほど複雑ではないので許容できる
    - 参照だけなので、集約のようにオブジェクトの破壊的変更を考慮する必要がない
  - クエリ側とコマンド側でデータソースを分けることは、互いのデータの統合のためにイベントソーシングをしたくなり複雑すぎる

# Consequences
- CQRS を導入することで、GraphQL が抱えるクエリ最適化の問題を解決できる

また、以下はとあるロジックをモデルと永続化レイヤーのどちらに実装するかを決めた際の ADR です。 性能的な問題とあるべき状態を天秤にかけて直近はどちらの方針にするかを説明しています。また、将来的にその決定がどういう場合に変更を検討するべきかについても記載しています。 これにより、この決定に対して将来問題が発生した際に、考慮すべきポイントがなんなのかを考える材料の足しになります。

# Title
出金可能な入金一覧をどう作成するか決める

Status: Accepted

Relevant PR:

# Context
- 出金時には、出金可能額が残っている出金期限内の入金群を、入金時刻の古い順から使っていくというルールがある
- この入金群を作成する方法として、ドメインモデルにロジックを書くか、リポジトリの実装として SQL を書くかという選択肢がある
- ドメインモデルにロジックを書く場合は、出金可能な入金一覧を作成するドメインモデルの完全性が担保されるが、入金の数が増えた場合には性能的な問題が発生しうる
- SQL でロジックを書く場合は、出金可能な入金一覧というドメインモデルの完全性は担保されないが、入金の数が増えた場合にも性能的な問題が発生しづらくなる

# Decision
- ドメインモデルに出金可能額が残っているかつ出金期限内であるというロジックを書く
- ただし、入金時刻の古い順にソートするのは SQL でやる

## Reason
- 理想的にはドメインモデルに寄せたいが、性能を考慮したときに SQL にかかざるを得なくなるかもしれない、という状況である
- 仮にロジックを SQL に寄せたとしても、それによって性能がどの程度担保され、取り扱える入金数の限界値がどの程度上昇するかは計測するまで未知である
- 実際の性能限界は計測してみないとわからないが、計測の準備自体にオーバーヘッドがあるので簡単にはできない
- そのため、一旦はドメインモデルのロジックとし、パフォーマンスを計測した上で問題があれば、SQL を利用することも検討する
- ただし、並び替えに関しては SQL で行うことが自然かつ圧倒的にやりやすいので、SQL を利用する

# Consequences
出金可能な入金一覧をどう作成するか決まった

ここでは割とコーディングテクニック的な要素が強いものを紹介しましたが、他にも業務ドメイン的な仕様に関するもの(内容が内容なだけにここでは紹介できませんが)なども ADR として書かれています。 例えば、あるデータをこのアプリケーションで扱うかどうかであったり、ライブラリの選定時に複数の選択肢で迷った上で判断した場合などです。

どのような効果があるのか

実際に我々のチームに新しく参画してくれた方に仕様を説明する際に過去の ADR を参照しながら説明をしたり、オンボーディングの段階で一通り目を通してもらうことで、今までの意思決定の過程をさらえるので、キャッチアップに際して役立っています。 他のチームへの情報共有などをする際に、改めて資料を作る必要がなく、ADR をそのまま共有したりといった使い方もできます。

また、エンジニアの意識としても ADR を書いて共有することで、頭の中で整理した内容を言語化する過程でより一層理解が深まったり、認識の些細なズレみたいなものも減らせるので良い効果をもたらしていると感じています。

総じて、書くコストよりもそれによってもたらされるポジティブな面の方が大きく感じられています。

まとめ

自分はシステム開発におけるドキュメンティングは結構重要なものだと思っています。開発時の情報の欠落というのは直面すると本当に辛いものです。秩序立っていない古文書の海に潜るのは本当に生産性がないですし、時には情報が跡形もなく消えている場合もあり、その際の意思決定はギャンブルになることもあります。

もちろん、常にそうというわけではありません。ドキュメントは単なるツールであり、手段でしかないです。 人の入れ替わりが激しい環境であったり、常にベストな判断ができるわけではなく外的要因、政治的要因で判断を曲げざるを得ないことが多かったりという状況下において、ディシジョンのログを残すことは大きな意味を帯びてくると思います。

今後もその時々に合わせて適切に情報を取り扱い、暗黙知によるカオスを少しでも減らしていければと思います。