はじめに
こんにちは! モジュール開発部の yamakazu (@yamarkz) です。
あけましておめでとうございます。2023年もよろしくお願いします。
本記事が新年最初のプロダクトブログになるのですが、何を書こうかとても迷いました。笑
抱負的な何かが無難だと思いつつ、1年先のことまでは見通すことができない。10Xはドラスティックに事業や組織が変わるので、中長期な目線で1つのテーマを説くのは難しいなと。そう色々と考える中で、ちょうど足元で成果が出始めている具体の取り組みを紹介したい!というモチベーションが生まれてきました。
なので今回は、半年先までの将来的な抱負の意図を交えながら、直近の取り組みで手応えを感じ始めている、アーキテクチャ改善のプラクティスを紹介しようと思います。
具体的にはタイトルにもある”適応度関数”と呼ばれるプラクティスで、巷では概念としては認識されているものの、まだあまり実践例は目にしない話題です。これを独自の解釈と10Xでの取り組みを交えて、その例を示してみようと思います。
目次
- はじめに
- 目次
- 求められるアーキテクチャの進化
- アーキテクチャの進化とは何か
- 進化を支える適応度関数
- 10Xにおける適応度関数の活用
- Stailerで導入が進む適応度関数の具体例
- 手応えと展望
- 最後に
- 参考
求められるアーキテクチャの進化
Stailerはリリースから2年半、開発着手からはちょうど3年が経過したところで、これまで多くの変更を加えて事業機会に応えてきました。
ありがたいことにプロダクトはマーケットに受け入れられ、多くのお客さまにご利用いただけています。さらに今以上の要求の声も集まってきており、まだまだプロダクトをアップデートさせていく必要に駆られているのが現状です。
プロダクトと事業開発の観点では、「2023年は2度目のPMFを目指すこと」 が社内でも話されています。これまで積み上げてきたアセットを活用しながら、未だ価値提供に至れていない領域にプロダクトを適応させていくこと、パートナーとして提供が始まっているプロダクトをさらに改善していくことを今年は目指していきます。
対してエンジニアリング観点からは、このPMFで必要となる適応と改善を下支えするソフトウェアアーキテクチャを大きく変えることが必要だと考え、昨年後半から改善の取り組みが始まりました。
アーキテクチャを改善することをアーキテクティング界隈では「進化」と呼んでいます。
その表現意図やアプローチがどういったものなのかは、本記事を読み終えた後にその意味を掴んでいただけるかと思います。
アーキテクチャの進化とは何か
「アーキテクチャの進化」とは何でしょうか。
世を見渡せば様々な定説があり、アーキテクティング界隈では比喩として用いながらも明確な意図を持ち、その表現を選択していたりもします。
これを自分なりの解釈では次のように定義しています。
アーキテクチャの進化とは、トラクションを生んでいるプロダクト、およびアプリケーションコードの構造に誘導的な力を加えて漸進的な変化を生み出し、次の価値を生み出せる構造に変化させること。
つまりは、明確な目的を持って構造を変化させること です。
端的すぎるのでもう少し説明を加えると、先の定義での「次の価値」というのは広くは売上, 事業機会, 組織拡大といったものを指しますが、観点をソフトウェアエンジニアリングに限って見た場合、それは”性質”のことを指します。代表例はアジリティ, スケーラビリティなど。
アジリティが備わっていれば売上を伸ばすための仮説検証を多く回すことができ、スケーラビリティが備わっていれば、事業機会に合わせてプロダクトをアップデートすることができます。
単に「構造を変えることで性質が得られます」という話をすると、それを現場で追求する価値や意図が理解しづらいかもしれません。しかし、これらは最終的に事業価値に転換されていきます。
構造の変化 → 性質の変化 → プロダクト価値の変化 → 事業価値への転換 (事業機会 / 事業成長) という論理展開によって、最終的に手触り感のある価値として証明されるからです。
加える変化に連鎖して生まれる変化が事業価値に繋がるまで遠いが故に、取り組む価値、その取り組み自体の理解がしづらいのですが(実感知)、アーキテクチャ改善の取り組みは事業価値への転換から逆算して考えるもので、最終的に事業価値に跳ね返るということだけは間違いなく言い切れます。
アーキテクチャの改善を ”進化” と敢えて呼称する理由は、理想像から逆算して誘導的で漸進的な変化を生むことで、ソフトウェアが新たな機会 (環境や状況) に適応していく様子が、生物の種としての進化に重ねて見えるからです。比喩的にそういったイメージで界隈では表現しています。
この進化を促すアプローチはいくつか考えられます。
- 新たなアーキテクチャスタイルへの移行 (骨格)
- ドメインを中心とした組織体制と運用への移行 (組織)
- 規約と方針の策定による成果品質の標準化 (基準)
- リファクタリングとテスト整備による成果品質の改善 (品質)
アーキテクチャを進化させることはコードを単に書き換えるだけではなく、組織構造を変えることや成果基準を設けるなど、多様なアプローチで実現されるのが特徴です。
今回は3の「規約と方針の策定による成果品質の標準化」について、それを実現するプラクティスである「適応度関数」を掘り下げていきます。
進化を支える適応度関数
「適応度関数」という言葉自体、聞いたことがない方も多いかもしれません。
適応度関数 (Fitness function) は、書籍「進化的アーキテクチャ」の中で紹介されました。
これはソフトウェアアーキテクチャの改善プラクティスで、元々はThoughtworks社の社内で生み出された考え方です。
ソフトウェアアーキテクチャの改善は、ソフトウェアエンジニアリングの中でも比較的抽象度の高いテーマで、具体的なHow toの多くが外では語られません。それは遭遇する課題が独自文脈に依存していることや、課題解決手段が特異で、ナレッジを抽象化し転用すること自体が難しいという特徴からではないかと推察しています。
そんなナレッジ共有が難しい領域であるという前提を置いた上でも、実用的で実践しやすい汎用的な改善手法として、界隈で支持されているのが適応度関数です。
適応度関数はソフトウェアの成功に不可欠な性質の獲得と保護を検証評価し、獲得と保護に向けたフィードバックを非属人的にかける仕組みです。
この仕組みを普段の開発に組み込むことで、開発者はあるべき理想に向かうための誘導的なフィードバックを受け取れるようになり、フィードバックに沿った漸進的な変更を日々の開発に加えやすくすることで、結果的に "アーキテクチャの改善が自律的に進む状態" を作ることができます。
この自律的に進むという点が特に重要で、性質の観点では改善が進むことを進化性と呼んでいたりもします。
新しく見出された概念の様にも思えますが、これは従来から存在したコードカバレッジ計測やパフォーマンス計測のことでもあり、それらをソフトウェアの成功に不可欠な性質の獲得と保護という観点から取り組み自体を捉え直した考え方でもあります。
と、ここまでが提唱されている一般論で、これを踏まえた独自の解釈を参考として述べると、自分は以下のようなイメージを持っています。
事業機会と成長を追求する場合、プロダクトには常に外向きの最適化と内向きの最適化という2つの側面に対してアップデート (新規追加 / 既存変更) がかけられていきます。
プロダクトを理想状態にまで引き上げるためにはどちらの引力も必要で、適切な範囲でアップデートをかけていくことが求められます。その際に、どこまでのラインを守って、どの位置を理想状態として目指し、アップデートをかけていくのか? を知ることができ、その範囲を守るための具体的な線引きをする存在が適応度関数です。
比喩を交えて言い換えれば、適応度関数はプロダクトを理想状態に引き上げるためのアップデートに対して、健全な牽制をかけるガードレールの様なものです。
以降の内容を理解できるように、独自の解釈を交えてどういった仕組みを設けて効果を生んでいくのか、その概要を説きました。より厳密な定義は書籍を参考にしてください。
ここまでの内容を元に、自分たちの取り組みと実例を次に紹介します。
10Xにおける適応度関数の活用
まずはじめにお伝えすると、自分たちの適応度関数の活用方法は、関数単体だけでは成り立たず、複数コンポーネントの連携によってその価値を享受しています。
単発利用可能な関数を定義して終わりではなく、それを普段の開発運用に継続利用可能な形でどう組み込むのか?という問いから逆算して考えると、自然と複数コンポーネントを組み合わせて作り上げていく方向が良いという結論に至りました。
導入に際して仕組みの方向性はCTOのishkawaさんが見出したもので、書籍やThoughtworks社のブログ内でも具体で取り上げられていない内容でもあるため、ある意味これは1つの発明とも言えます。
自分たちが活用で価値を上手く享受することを目指す中で、見出した抑えるべきポイントは2つありました。
1. 同期/非同期フィードバック
2. 非属人的仕組み
それぞれで何を考えてどう表現しているのかを見ていきましょう。
1. 同期/非同期フィードバック
適応度関数の一番の価値はフィードバックです。
その例としては、あるべき理想状態は ”こうである” という矯正を促すフィードバックや、理想状態と現在を比較すると ”ここまで進んでいる” という経過進捗を示すフィードバックなどで。
これらのフィードバックを同期/非同期で適切な場所とタイミングで受け取れる状態を作ります。
同期フィードバックは、開発中のエディターやCLIといった開発の手に近い位置で発生するものを指し。開発者がフィードバックを受け止めて即座に行動反映できる(修正に時間差を生まない) Code Lint系がその価値を発揮しやすいです。
非同期フィードバックは、GitHub Actionsなどで定時実行されるスクリプトによって得られた結果を毎日Slackに流すような状態で、フィードバック自体の受け止めは個々人に委ねられますが、アクションを検討するタイミングで参照できる状態になっているのが良いです。
Dartは開発周りのツールチェインが充実しており、同期フィードバックを実現するのが容易でした。独自のLinterを定義すれば既存のエディター上のフィードバック機能の枠組みに則って適切なタイミングで情報提示と修正アクションを選択できます。
2.非属人的仕組み
適応度関数のもう1つの良さは非属人的であることです。
ここでは、あるべき理想状態をコードレビューやエキスパートによる周囲へのティーチングといった人の力に極力依存せずに理想状態に向かえることを「非属人的である」と言います。
自分たちは適応度関数の他に、Model caseとArchitecture decision records (ADR) を組み合わせてこの属人性を下げるアプローチを取っており、適応度関数自体がフィードバックを行う主体とした場合、あるべき理想の定義がADR、理想の具体例を示すのがModel caseという棲み分けで整理しています。
”あるべき”という強い主張を表すべき表現を使っている様に、これはアーキテクチャ決定とも呼ばれる、アーキテクチャ上の規約として守るもの (not 推奨 but 規約) なので、一般的なフォーマットにもなりつつあるADRを採用し、策定背景を含めて厳密に定義しておくことで、迷いや疑問が生じたら定義の詳細に触れて理想の考えに立ち返られる or 定義を拠り所に建設的な議論が行える状態にします。
フィードバックする仕組みとその背景がわかれば、それだけでも開発者は十分に動ける場合もありますが、さらにサポートする狙いとして、先行するモデルケースを具体の実装例として定義する場合もあります。
これらを揃えることで、開発者が抽象論と具象論の2つを認識しながら、人に極力依存せずに目の前の課題解決に向かうことができ、適応度関数から受け取るフィードバックをもとに、あるべき理想状態に自律的に近づくことができます。
上記2つが、自分たちが適応度関数を取り入れる中で気づいた重要だと考えるポイントです。
この考えを踏まえて具体的にどういった関数を定義して成果を作りに行っているのか、その具体例をいくつか取り上げて紹介します。
Stailerで導入が進む適応度関数の具体例
Null Safety化の進捗
アーキテクチャ特性 | フィードバック種別 | 関数種別 | 目的 |
---|---|---|---|
可読性, 堅牢性 | 非同期 | 計測 | 特性の獲得 |
StailerのサーバーはDartで開発をしています。
Dartは2023年の中頃にDart 3のリリースが控えており、Dart 3以降はfully sound, null safety languageになることが宣言されています。それに合わせてnull safe に対応がなされていないファイルは、対応することを求められています。
Stailerのサーバーアプリは、まだ全てNull Safetyな状態に移行できておらず、絶賛移行を進めている最中です。移行作業には適応度関数を活用しており、Null Safety化の進捗を毎日追いながら移行を進めています。
これは非同期フィードバック (Slackへの定期通知) で最も効果のある例で、最終のゴール状態 (全ファイルでのNull Safetyのサポート有効化) に対して、どれくらいの進捗 (%表示)で、残りの差分はいくつなのか (具体のファイル数) がわかります。
単純な例ですが、Null Safety化というゴールが明確だが足取りが長い問題への対処は根気が必要になり、継続する上での達成感が大事になるので、非常に有効な活用事例だと言えます。
モジュール境界違反の検知
アーキテクチャ特性 | フィードバック種別 | 関数種別 | 目的 |
---|---|---|---|
可読性, 変更容易性 | 同期 | 静的解析 | 特性の保護 |
Stailerはモノリスアーキテクチャからモジュラモノリスアーキテクチャに移行を始めており、この移行を成功させるにはモジュール間の境界をコントロールすることが不可欠です。
モジュール境界とは依存して良いモジュールの公開インターフェースのみを利用するもので、依存を許していないモジュールを参照することや、非公開インターフェースを参照した場合に違反として警告することです。
この違反をオーナーシップを持つメンバーが都度コードレビューで防ぎ続けるのは非現実的であるため、適応度関数としてLinterを作成し、境界違反をしない様にするためのフィードバックが回る仕組みを作りました。
先のNull Safety化とは対照的に、これは同期フィードバック (エディター上でのアラート) で効果がある例です。
Linterによって、違反を犯したコードを記述すると即座にエディター上でフィードバックがかかる状態になります。これにより、コードの記述が誤りであることを記述時点で気づくことができ、自律的に修正できます。
初めから全て規約に沿う形で移行できるのが理想ですが、既存仕様の関係から必ずしも全て上手くいくとは限りません。そういった一時的な例外ケースにも対応できる様に、Linterの ignore
を許可し、コードコメントの記述で違反検知を一時的にスキップすることもできます。
パフォーマンス計測
アーキテクチャ特性 | フィードバック種別 | 関数種別 | 目的 |
---|---|---|---|
パフォーマンス, スケーラビリティ | 非同期 | 計測 | 特性の保護 |
パフォーマンス (スループットやレスポンスタイム) がプロダクトの性質上、厳しく求められる場合、理想水準の維持を問題が顕在化する前に把握し、対処できなければいけません。
これは毎回コードの変更を加えるたびに実施するのは過度ですが重要なものです。よって、実行する仕組みを適応度関数として定義し、性質担保に責任を持つTech Leadがタイミングを見てパフォーマンス計測を行なって、パフォーマンスが基準を満たしているのかを判断する運用を行います。
非同期フィードバックをトリガーが人に依存しているものの、評価の仕組みが適応度度関数化されている例です。
手応えと展望
まだ仕組みが動き出して1ヶ月ほどですが、最近入社したメンバーからも前向きなコメントをもらえており、手応えを感じています。
エンジニアリング本部全体に仕組みの価値転換、浸透はできていないのですが、より仕組みを作り込み、成果品質を上げることに貢献していきたいです。
今後の期待を込めてやりたいことを挙げると、以下のような取り組みを検討しており、一部では動き出してもいます。
- パブリックインターフェースにコードドキュメントを記述する文化を作り、認知負荷を下げる
- 適応度関数で計測できる数値をBigQueryに都度保存し、経過時間に伴って適応度がどのように推移していくかを分析し、フィードバックをかける
- テストコードの標準規約を浸透させ、一定水準以上の成果が容易に生み出せる状態をつくる
- 配達/拠点運用のドメインをモジュールに切り出し、モジュール移行のモデルケースを作る
- ドメインごとにチームを組成し、独立して改善の意思決定と推進が行える状態を作る
最後に
アーキテクチャを進化させるための、適応度関数というプラクティスの実践を紹介してきました。
書籍の登場で概念は知られているものの、あまり具体の実践例は見聞きしない中、自分たちなりの実践を通して、その有効性と価値を検証して導入を進めてきました。手応えも感じ始めており、この取り組みを軸にさらにアーキテクチャの改善に取り組んでいこうと思っています。
本記事が適応度関数の採用を検討する方の参考になれば幸いです。
10Xでは一般論を参考にしながら自論を組み立てて展開し、技術課題の解決に泥臭く向き合える環境があります。もし興味を持っていただけたら、ぜひカジュアルにお話ししましょう。