アーキテクチャよりも設計を重視しよう – 米政府18Fチームの提案

A comical sketch of an architectural overview of a coding project
注釈:
CASH LAYER:キャッシュレイヤ
FRONT END:フロントエンド
ASSET SERVE:アセットを供給
WEB SERVER W/ROUND ROBIN FAILOVER:ラウンドロビンとフェールオーバーを実装したWebサーバ
THE CLOUD:クラウド
ALL READS! :全ての読み込み
WRITES:書く
READS:読む
MASTER:マスタ
INPORTANT POINTY THINGS:重要な鋭い情報
MULTI MASTER DB CLUSTER:複数のマスタからなるデータベースの集合体

「エンジニアはまずアーキテクチャの全体像から始めるべき」、というのが先人たちの知恵からの教訓となっています。データベースを使ったサービスが他のサービスと関係する様子を、線や矢印で表したのが上の図です。キャッシュレイヤ、ロードバランサ、その他の複雑な形も上図の情報フローに加えました。テクノロジに精通した人なら、このような図はパフォーマンスとセキュリティが考慮されているため、いくらか安心できるでしょう。
そうでない人は、この図を見て、とても賢い人がとても複雑なことをした結果だということに注目し、やはり安心できるでしょう。

アーキテクチャの計画は、皆を安心させてくれます。なぜなら、それはいかにも計画らしく見えるからです。エンジニアがサーバとインフラストラクチャを実装することにより、マップが現実のものになります。

残念なことに、このような壮大な計画は往々にして技術的負債につながり、完全な静止状態に向かって崩壊します。システム内のサービスにたった1つ複雑なものがあるだけで、プロジェクト全体がだめになることがあります。新しい計画に、初期段階で不明な問題点を容易に組み込むことはできません。なぜなら、全てのサービスはこのアーキテクチャの契約によって決まっているからです。

簡単に言えば、アーキテクチャの計画は、チームにウォーターフォール開発を強いるのです。このシステムにより、失敗したプロジェクトがどんどん増えています。

アーキテクチャの足かせをどう回避すればいいのか?

「デザイン」というのは、ソフトウェアの世界では曖昧な単語です。一方では、「デザイン」はグラフィックのユーザエクスペリエンスを表すために使われます。他方では、エンジニアリングの世界でいうソフトウェアの「デザイン」は、互いに独立した小さなモジュラコンポーネントを構築する過程を指します。どちらのタイプのデザインも、アーキテクチャ第一の計画によって発生する技術的負債をプロジェクトが負わないためには不可欠なものです。以下に、ユーザエクスペリエンスのデザインとソフトウェアのデザインに焦点を当てることで、プロジェクトが技術的負債を避けることを可能にする方法を挙げます。

ユーザストーリーとユーザエクスペリエンスから始める

A user story generating architecture organically.
ユーザストーリーがアーキテクチャを有機的にする

ユーザストーリーとは、ソフトウェアを使う人の視点から語られるシンプルなシナリオです。1つのアプリケーションには、様々なタイプのユーザがいます。例えば、あるアプリケーションにはそのアプリケーションを公共のWebページ経由で使用するユーザがいる一方、API経由で使用する開発者もいるかもしれません。さらにそのアプリケーションには、コンテンツを作ったり許可を出したりする管理者が必要かもしれません。

ユーザストーリーのフォーマットは、非常にシンプルです。

ある種のユーザとして、私は何かを見たり聞いたりしたいです。それによって利益が得られるからです。

このストーリーでは、ユーザのタイプ、目的、利益といった、太字の部分をあなたが書き込むことになります。アプリケーションは、ユーザのタイプに合わせた機能を作るこのような小さなシナリオから始めるべきです。これらのストーリーを書き、優先順位をつけるのはプロダクトマネージャ、あるいはプロジェクトマネージャの仕事です。

アーキテクチャ計画の観点から考えることに慣れているチームでは、大きすぎるストーリーを描いてしまうことがよくあります。1人のユーザに1つのシナリオを考えるべきところで、全体的に考えてしまうのです。次に挙げるのは、アーキテクチャに焦点を当てた、大きすぎるシナリオの例です。

Webサイトのユーザとして、私は公開されている情報を全て見たいです。それによって利用可能なサービスについての情報を得られるからです。

このストーリーを改善すると、次のようなものになります。

Webサイトのユーザとして、私はその会社のロゴがあるホームページを見たいです。それによって自分が適切なページを閲覧していることが分かるからです。

かつてアーキテクチャを考えていたチームにとって、これは考えられないぐらい小さなストーリーのようです。それでも、ストーリーの作成は初めてなので、仕上げるまでにはエンジニアリングチームから多大な協力を得ることになるでしょう。

  1. ホームページとロゴを含むWebアプリケーションをビルドする。
  2. ステージングのサーバを構成し、プロダクトマネージャがWebサイトを利用できるようにする。
  3. 本番環境のサーバを構成し、ステージングから本番環境へ移行するためのDevOps手順を作成する。

これに続くストーリーには、ここまで多くのセットアップ作業は必要ないでしょう。従って、会社向けに作成する新しいストーリーは、次のようになります。

Webサイトのユーザとして、私はプロジェクトのリストを参照したいです。それによって会社で起こっていることが分かるからです。

現在運用中のインフラストラクチャなら、このストーリーを以前よりもずっと迅速に展開できます。この架空プロジェクトのストーリーは、次のように展開します。

  • Webサイトのユーザとして、私はプロジェクトのリストを、プロジェクトごとのページに分割して表示してほしいです。それによってプロジェクトの詳細がよりよく確認できるからです。
  • Webサイトのユーザとして、私は各プロジェクトの詳細ページとプロジェクトのリストとの間で、行き来が自由にできるようになってほしいです。それによって重要な情報が素早く簡単に参照できるからです。

会社でのプロジェクトについての小規模な一連のストーリーを仕上げた結果、プロダクトの担当者は、従業員のプロファイルの表示を最優先で進めて、必要な人材に誰もがアクセスできるようにすることを決断します。この決断に従って、関連する目標を達成するための、新たな一連のストーリーを構築します。

設定した優先順位に従ってチームが複数のストーリーに取り組んだ結果、アーキテクチャはおのずと進化します。デザインプロセスの別の一面である、ソフトウェアデザインの視点から継続的なチェックがされないとなると、これはかなり危険なことです。

コードが無秩序な混乱に陥るのを防止する

Without good software design techniques, your code can become a mess as you fulfill more user stories.
ソフトウェアデザインの優れたテクニックを応用しなければ、ユーザストーリーを完成させるたびに、コードはぐちゃぐちゃになる。

指針となるアーキテクチャマップを作る以外に、コードを整然とした使いやすい状態に保つ方法があるでしょうか。

ここで「デザイン」の新たな概念がプロジェクトの指針となります。ソフトウェアデザインです。ソフトウェアデザインとは、コードを小さいモジュール単位に分割した状態で管理し続けるための規則であると考えてください。20行もある1つの関数があったとすると、エンジニアはそれを機能別に6つのメソッドに分割し、それぞれのメソッドに小さい役割を持たせます。全てのオブジェクトが、アプリケーション内の他のオブジェクトの働きを全て理解する必要はありません。各オブジェクトは与えられたタスクを完了するのに必要な情報だけを扱うようにします。

ここでモジュールをレンガだと考えます。レンガには、1つずつ分離している性質と自由に組み合わせることができる性質があるので、擁壁、住宅、宮殿など、あらゆる建築物を築くためによく使われます。小さいモジュールを集めて、大きなモジュールにすることができます。複数のモジュールをまとめると、サービスになります。アーキテクチャ図の中で、セキュリティとパフォーマンスに関するユーザストーリーは全て、図形と線として表されます。

ソフトウェアデザインに注力する場合、アーキテクチャの構築プロセスは避けて通れません。アーキテクチャマップからサービスやコードにドリルダウンするのではなく、私たちはコードからアーキテクチャを構築するアプローチを提唱します。このアプローチには、次に示す通り、大きな利点が2つあります。

  • コードやプロダクトから抽象化を進めたものなので、どこか高いところから押し付けられたものよりも実情を的確に表現できる。
  • コードのモジュールは分離していて、必要に応じて再編成することができる。

優れたソフトウェアデザインを実行するには

恐らくプログラミングという作業が生まれて間もない頃から、エンジニアたちはコードをモジュール化して柔軟性を持たせることに苦心してきました。そんな中から、指針となる優れた原則が編み出されました。

Using refactoring to impose design on the chaos that happens with continual development
注釈:
AUTH: 認証
CLIENT: クライアント
PARSER: パーサ
SERIALIZER: シリアライザ

*混沌に対するリファクタリングを実行し、デザインを施すことが即ち、継続的なソフトウェア開発となる。+

SOLID

SOLID(Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion)とは、オブジェクト指向設計の原則を表す語の頭文字を取った略語です。SOLIDはオブジェクトに焦点を当てた概念ですが、オブジェクト指向設計とは異なるアプローチのプログラミング方式においても有益であることが分かっています。SOLIDの核心は、最初の原則にあるという考えは、多くの人々の支持を集めています。その原則とは、単一責任の原則 です。単一責任の原則とは、モジュールは全て1つの目的だけを果たすものでなければならないとする考え方です。

この特質の具体的な例として、会社が作成したアプリケーションを考えます。会社は非政府組織が作成したデータに接続しています。この外部組織は、会社のエンジニアが必要とするデータを簡単に取得できるように、優れたAPIを作成しました。こうして数多くのユーザストーリーを開発するうちにエンジニアリングチームは、自分たちがサービスからデータを取得する方法は3通りあり、それぞれの方法は少しずつ違うことに気づきました。

そこでエンジニアリングチームは、単一責任の原則を使ってデザインを改善することにしました。チームが発見したのは、3箇所からAPIに接続していて、接続には以下の2つのパターンがあることです。

  • 認証プロセスは、APIへの要求送信の一部として、毎回必要になる。
  • 接続先から返されるデータは解析されるが、その方法は毎回ほぼ同じである。

そして会社のエンジニアは、認証を受け持つAPIクライアントオブジェクトを作成しました。また、データを継続的に分析するパーサも作成しました。

このロジックを1箇所にまとめると、大規模な変更を予防できます。非政府組織がデータ送信の方法を根本的に変えようと決断する時、従来3箇所から接続していたものを1箇所にまとめるとします。この時、会社が種々の異なる方法で非政府組織のAPIに接続し、APIでの認証フローが変更された場合でも、変更は1箇所で済みます。

SOLIDの他の原則(各自お調べ下さい)は、コードをより小さく、よりシンプルにすることによるコストの削減にも関わっています。

変化に合った最適化をする ただし事前の最適化は止める

よく練られた計画ほど失敗する-Robert Burns

ソフトウェアを構築する場合によくあることですが、私たちエンジニアは、単純なものを作成していると、どうせすぐに機能を追加しなければならなくなるのだろうな、と想像します。そして賢明にも、そういった機能を直ちに作ることが多いのです。それはセキュリティ強化の機能だったり、パフォーマンス改善だったり、より洗練されたグラフィックデザインだったりします。

こうした経緯の常として、追加された機能によって コードは複雑になり、作業効率が下がります。すると不具合が増加し、処理速度が低下します。不具合のためにパフォーマンスやセキュリティが低下することは多く、目的とは正反対の結果となります。

本当に改修が不可避であるとしても、変化に適した最適化を行えば、コードをきれいでシンプル、かつ分離した状態に維持できます。

その方向で最適化を進めたくなった時には、製品チームに、よく事情を知ったパートナーとなってもらう必要があります。彼らの役目は、優先順位を決定したり、課題を新しいユーザストーリーに落とし込んだりすることです。

リファクタリングに取り組む

成長し、生きているソフトウェアプロジェクトは、事前に適切な抽象化をすることはできません。そのような推測こそ、私たちがお勧めしない「事前の最適化」です。最適化が可能なパターンが現れるまで待ち、リファクタリングしましょう。

Martin Fowlerは 3度目の法則という有用なガイドラインを提唱しています。重複するコードは2度目まではそのままにしておきますが、3度目が現れたらリファクタリングを行うべきタイミングです。抽象化して、重複を解消します。この法則は単一責任の原則に相反しているようですが、よいデザインのためには適切な抽象化が必要です。原則に固執してばかりはいられません。同じようなコードが2つしかなくて、本当に重複しているのか判断できないこともよくあります。私が以前働いていたあるチームでは、4つの例が挙がったら適切な抽象概念を構築することにしていました。

継続的に改良をしていると短期的にも長期的にも開発作業の速度が上がっていきます。そのことをソフトウェアチームは認識しておく必要があります。継続的なリファクタリングを習慣に変えようとすると、最初のうちはスピードが鈍りますが、いったん習慣にしてしまえば全ての機能の速度と品質が向上します。

それでもアーキテクチャ計画は必要です

ユーザストーリーと、ソフトウェアの優れたデザインパターンによって、アーキテクチャは有機的に成長します。

ただし今でも、いくつかの既定事項のために、私たちはアーキテクチャを最初に考えなければなりません。言語とフレームワークは、チームにおける文化および最上の人材を補充する能力に影響してきます。モダンで、オープンソースで、十分なサポートを受けられる言語とフレームワークを選んでください。データベーステクノロジはアダプタ層と一緒に抽象化されますが、それでもあなたのニーズとあなたの選択が暗に意味するものを慎重に考慮することは重要です。

ソフトウェアを個々のサービスに分割すると、複数のチームが同じシステムの構築に同時に取り組むことができます。サービスに対するこの最適化は、ユーザのペルソナにエンドツーエンドで沿ったユーザストーリーを多数作成し、その実装に成功した上で実施するべきです。これにより、システムは全体として機能します。

アーキテクチャよりもデザインを優先することは、チームにとって、問題に対してアウトサイドインのアプローチで取り組むことを余儀なくされる、パラダイムシフトです。このアプローチの有利な点は、チームでシステムの全箇所を連携させて動かそうという時に、直前の統合作業のステップが必要ないことです。アーキテクチャよりデザインを優先させると勝てるのは、プロジェクトリスクが減るからなのです。