shiodaifuku.io

アーキテクチャを設計する仕事とは

メリークリスマス!

この記事はしおだいふく Advent Calendar 2020 24日目の記事です。

本日の記事は、Webアプリケーションのアーキテクチャに関する個人のスタンスを示すポエムです。

アーキテクチャ設計をしてくれと言われたときにこういうことを考えています、という内容をなぐり書きしたものです。 プロダクトの要件にあまり関わらない部分を抜粋しているのでちょっとふんわりしていると思いますし、考えていることをすべて文章にできているわけではないと思います。

また、この記事に書かれている思考は、自社でWebサービスを開発・運用しているエンジニアの立場から述べるものです。 受託案件とかWeb以外のシステム開発の場合は全然話が違うところがあると思われますので、その点はご承知おきください。

ついでなので予防線を張っておきますが、ここに書かれている内容は2020年12月現在の個人の意見に過ぎません。 明日になったら違うことを言っているかもしれませんが、そういう性質のものです。 将来自分の成長度合いを確認するために現状を整理したものですので、過度な期待はしないでください。

アーキテクチャとはなにか

アーキテクチャとは、プロダクト開発における法であると考えています。 では法とは何かというと、大事なことはキングダムがだいたい教えてくれるので引用させていただくことにします。

法とは願い

"法"とは願い!国家がその国民に望む人間の在り方の理想を形にしたものだ! 1

...だそうなので、僕の思うところによるとアーキテクチャとは「アーキテクチャを設計した人がプロダクト開発に関わるエンジニアに望むエンジニアの在り方の理想を形にしたもの」ということになりそうです。 これはなかなか良さそうではないでしょうか。 個人的にはアーキテクチャがプロダクトの根幹に関わるものでありながら、プロダクトではなくチームメンバーを主眼においているところが好きです。

要求・要件がしっかりしていて、システムがそれを満たしているならば、アーキテクチャの良し悪しに関わらずユーザの視点からは同一のプロダクトができるはずです。 プロダクトの仕様とはアーキテクチャの良し悪しとは直接関係のない概念であって、どのようなアーキテクチャを選択したかに関わらず達成されるべき項目であると考えます。 品質の善し悪しを決めるのはプロダクトの仕様2なので、アーキテクチャでどうにかして品質を向上しようと思うのはなんかずれてるなとちょうど思っていたところです。

一方で、プロダクトが完成に至るまでの道のり(≒開発にかかるコスト)や、リリース後の保守運用の難易度(≒ランニングコスト)はアーキテクチャに大きく左右されます。 この意味で、アーキテクチャ設計というのは内向きな仕事と言えます。

エンジニアにとっての良い制約

優れたアーキテクチャは、プロダクト開発に関わるエンジニアにとって良い制約となります。

何の制約もない自由な開発には、無数の選択肢が常につきまといます。 チームの選択の総体であるプロダクトが難の工夫もなしに理想の姿にたどり着くことはまずありません。

その前提のもとで、アーキテクチャと呼ばれるものが果たすべき役割のひとつが、プロダクトやチームが目指すあるべき姿を達成するにあたって意思決定の指針となることです。 各エンジニアは日々の開発において細かな判断を繰り返すことになりますが、その途中で発生するであろう悩みポイントを予測し、 それに対する回答をあらかじめ用意しておく(あるいは選択肢を狭める)ことができれば、効率よく開発をすすめることができるはずです。 エンジニアが行き詰まってチームの誰かに相談したときに、誰に相談しても同じ答えが得られる状態を実現できれば理想的と言えます。

すなわち、アーキテクチャはそれなりにメッセージ性の強いものになると思われます。

逆に悪い制約とは、過剰にエンジニアの創造性を制限するものです。 アーキテクチャがあるべき姿を最短で目指す上での阻害要因になってしまうような制約であってはいけません。

アプリケーションの拡張性を損なわない

通常、作ったWebサービスは作って終わりとなることはなく、ある程度の期間に渡って保守・運用がなされるものになります。 一度完成を迎えた後も、機能追加などの変更が発生することは避けられません。

強い制約をかけることは最初の開発効率をあげる手段としては割と有効であり、それ故に多用しがちになってしまいます。 しかし、強い制約は概して変更に脆いです。 運用が始まってから開発時に守れていたルールが邪魔になってしょうがないと感じることはよくあります。

これを踏まえると、良い制約の要素としては、可能な限り弱い制約であることが求められます。 直前の議論とは対立しますので、このあたりのさじ加減がアーキテクトの腕の見せ所ではないでしょうか。

アーキテクチャ設計とは

アーキテクチャ設計をするという作業は粘土でなにか作るのと似ているかもしれません。 最初に大雑把な形をとって、徐々に細部を作っていくようなイメージです。 ただし、作るものはアーキテクチャです。先述の通り、法という概念なのであまり詳細まで作り込んでしまわないように注意が必要です。

僕の場合は以下にあげる工程をぐるぐるめぐりながら、納得の行くところで切り上げる感じになります。 一発でスパッと決まることは(よほどシンプルなプロダクトでない限り)ありません。

データの処理方式を決める

Webアプリケーションを抽象化すると、そのほぼ全てが「どこかからデータを受け取って、何らかの処理をする」ことに帰着させることができます。 なので、とっかかりとしてはどんなデータがどのように流れるかを整理するところから始めることが多いです。

業務要件を参照し、データがどのようなタイミングで、どこから入力され、誰がどのように処理をし、どのような形式で出力するかを大まかに決定します。 ここではデータベースに保存するとか、APIを経由して外部システムに送信するとか、ファイルに出力するとか、画面に表示するとかログに吐くとかその粒度まで決めます。 それ以上詳細なフォーマットなどには踏み込みません。詳細なフォーマットまで決めるのはアプリケーションの詳細設計フェーズに任せます。

複雑なアプリケーションでは、いろいろな経路でいろいろなデータが入力されて、途中で加工されたり突き合わせられたりします。 複数のデータの経路をいい感じに束ねたり整理したりして、なるべくシンプルな構成を目指します。

この工程により、どんな特性を備えたデータストアや処理能力が必要かがわかります。 説明が難しいですが、データストアというのは総体として一時的にデータを保持しておく能力があるなにかを指します。 データベースはもちろんですが、メモリで用が足りることもあるし、ストレージのこともあるし、外部サービスになることもあるかもしれません。

技術選定

ここでいう技術とは、インフラ構成の大方針、ミドルウェア、プログラミング言語、フレームワーク、ライブラリ、クライアントAPI3などを指します。 これらの技術要素からアプリケーションの要請(≒機能要件・非機能要件)や開発チームの特性、運用時の要求に合わせて、どんな技術を採用するかを決めます。

このステップでは、少なくとも利用するプログラミング言語は決定します。 通常はフレームワークも決定してしまいます。 理屈の上では1つのアプリケーションに複数のフレームワークを共存させることもできますが、そうする意味があったことはいままでないです。 これらは不可分な単一のアプリケーションの構成単位の内部では複数共存させることができない4ため、選択の余地を残すことにメリットがないと思います。

一方で、例えばどのマネージドサービスを利用するかといったインフラ・ミドルウェアレイヤーの詳細まで決めきってしまうことはしないことが多いです。 作るものの姿が明らかになっていくにつれて、「これしか選択肢はないよね」ってなるように大枠の方針づくりをすることが中心です。

後述する境界を引く作業を行った後は、境界の外側をまるごと外部のサービスに置き換えることができるようになるかもしれません。 そのような検討もこのステップで行います。

境界を引く

アプリケーションの規模が大きくなればなるほど、部分ごとに技術的な特性や信頼性・スケーラビリティ・パフォーマンスに代表される非機能要件がバラエティに富むようになります。 結果として、1つの統一されたルールやポリシーを全体に適用することは困難になります。

そこで、アプリケーション内に論理的な境界を設けて分割統治を行います。 アプリケーションをいくつかのコンポーネントに分割することで、それぞれに異なる技術やルールを適用することができるようになります。 境界には様々な種類があり、必ずしもMECEに分割できるわけではないと思いますが、それはそれで構わないと思います。

わかりやすい例で言えばフロントエンドとバックエンドの間には境界を引くことになるでしょう。 最近のはやりでいうとバックエンドを機能ごとに API化して細かくわけたりするかもしれません。いわゆるマイクロサービスというやつです。

境界の引き方は少なからず組織構成に影響したりされたりします。 チームは境界に沿って分業することになるでしょうから、どのような職能をもったチームがどれくらい編成できるかを考慮に入れる必要があります。 ここでチーム構成を踏まえておかないと技術選定にまで歪みが波及するので注意してください。

後半になるとコンポーネント内部にも境界を作っていきます。 ドメイン駆動設計などのコンポーネント内に適用するアーキテクチャはこのへんで決まることが多いです。

境界を引いた後は、以下のようなことを考える必要があります。

  • 各コンポーネントにどういうルールを適用するか、ならびにどういう技術を採用するか
  • 分割したコンポーネント・モジュール同士をどのように結合するか
  • 界面をどのように保護するか(セキュリティ)

このようなことを考え始めると、だいたい技術要素が足りなくなって選定ステップに戻ることになります。 また、データ処理形式にもほぼ間違いなく修正が必要になります。

アーキテクチャを決めるときに気をつけていること

僕がアーキテクチャを決めるときには次に上げるようなポイントを心がけています。

有効期限を決める

アプリケーションが使い捨てでない場合は、設計したアーキテクチャに有効期限を設定します。

理想的にはプロダクトが生まれてから死ぬまでにどのような変遷が起こるかを想定して、あらゆるイベントに対応できるようなルールを作ることが望ましいのですが、 将来を予測するのは言うまでもなく非常に難しいことです。上手い人ならできるのかもしれませんが、僕のような凡人には無理です。

特にWeb業界ではトレンドの移り変わりも早いですし、技術の流行り廃りもかなり短いサイクルでやってきます。 そこでアーキテクチャが有効に機能するタイムスロットを区切って、その期間において有効な策を考えるようにしています。

未来永劫にわたってよく機能し続けるルール作りは困難ですが、いつまで耐えればOKということが前もって明らかになっていれば現実的な難易度に落とせます。 運用が始まってからもなにか問題が起こったときに「あと5年運用するんだからちゃんと直しておこう」とか「あと半年くらい持てばいいから応急処置で済ませよう」みたいな判断がスピーディにできるようになります。

技術スタック面でも、Webの業界ではどんなに長生きさせようとしてもいつか耐えられなくなります。 トレンドから外れた技術を使い続ければエンジニアが採用できなくなるなど、間接的なリスクも発生してしまいます。

アプリケーションの規模にもよりますが、僕はどんなに長くても7年くらいで再設計するように決めています。 技術選定においても、「7年耐えられればいい」という目安があることがある技術を採用したり採用しなかったりする判断を大きく支えています。

できれば設計段階で、この期限がきたら作り直しが必要ですということでビジネスオーナーに合意をとっておきたいと思っています5

ルールを変えるためのルールを作る

どんなルールでもこれがないと欠陥品なので、ルールを変えるためのルールを作ります。

いくら有効期限を定めたとはいえ、最初に決めたルールを変えずに期限まで耐え続けるというのは現実的ではありません。 アプリケーションの仕様が変わったり、技術トレンドが移り変わったり、サービス運用を始めてみたら想定外の事件が起こったり、 いろいろな理由でルールがうまく機能しなくなることがあります。

とはいえ、誰かが決めたルールを変えるのはすごく難しいことです。 どのようなコンテキストで、何を律するために作られたルールなのかを分からないまま ルールを作った本人であっても、一度運用され始めたルールを変えるのは(それが与える影響を考えると)勇気のいる判断です。

アーキテクチャ設計にあたってなんらかの制約を導入する際は、その制約を採用した理由・背景を明らかにした上でその制約を取り払ってもいい条件をセットで定めます。 状況が条件を満たした場合は、ルールを改定するなり削除するなりします。

大事なことはこの条件を後出ししないことと、開発に関わるメンバーなら誰でもすぐに参照可能な状態にしておくことです。 あとこれは現実世界にこの条項を適用するための小技ですが、変更を認める条件に「想定外の事象が発生し、変更の必要性が認められたとき」って書いておくと未来の自分を助けると思います。

曖昧さを残さない

ルールの変更を認める代わりに、曖昧さや解釈の幅を残さないようにします。 ダメなものはダメ、ダメじゃないものはダメじゃない、ときっちり二分します。

「なるべく~する」とか「極力~しない」みたいな書き方をすると、だいたい3日くらいでそのルールが機能しなくなります。 ルールが実態にそぐわないようであれば、ルールを変更してください。 一度例外を認めてしまうと、「これもいいのでは」「あれもいいのでは」とグレーゾーンへの侵略を試みる輩が後を絶たなくなり、収拾がつかなくなります。

曖昧さを排除したら制約として強すぎると感じたら、その感覚はだいたい正解です。 ルールを適用するスコープを少し絞るなどして、制約の強度を調整します。

遅延可能な意思決定はできる限り遅延させる

アーキテクチャ設計の段階では、必要が生じるまで実装方式の詳細に言及することを避けます。 上の方に書いたとおり、実質的に選択肢がひとつしかないという状況を作ることはOKですが、特定の製品・サービスを使いなさいというような制約はだいたい現実世界の変化の速さについていけなくて変更を余儀なくされます。

アーキテクチャ設計をする側が特定のサービスを使ってほしいと思った時は、だいたいそのサービスの何らかの特性を利用してほしいわけであって、 必ずしもそのサービスでなければだめな理由はないと考えられますので、「〇〇な特性を持った技術要素を使う」くらいにとどめておくほうが便利です。 こうすることで拡張可能性、変更可能性、あるいはエンジニアが工夫する余地を残しておくことができます。

余談ですが、法律が詳細を規定せずに判例をもって運用されるのと近いような気がします。

安易に流行りに乗らない

Webエンジニア界にはなんとかアーキテクチャとかなんとか設計とか新しいツールとかライブラリとかが毎日のように発明されます。 たまにはこういう流行りに乗るのも悪くはありませんが、過度な期待は持たないほうがいいです。

新しく発明される何かはそれまでの人類が苦しんでいた問題を解決する素晴らしいものに見えます。 ただし、そういう新しいモノが登場するとき、それが抱える問題点を同時に公表されることはほとんどありません。

一見問題がなさそうに思える技術(やツールや手法)は、どんな問題があるかがまだ明らかになっていないだけです。 いつか必ず何かしらの苦しみを味わうことになります。

あらゆる技術の選択にはトレードオフ、つまり何らかのリスクが伴います。 リスクが見えていない場合は避けることも妥当な選択となりえるでしょうし、リスクを抱えることにした場合も適切に境界を設けるなどそれが発言したときの影響を局所的に抑える工夫をセットで行うべきです。

(とはいえ、大抵の問題は時間さえ十分にあれば金と工数で殴ることで解決できるというのも事実です。時間さえあれば、ね。)

チャレンジ要素を盛る

そしてすぐに手のひらを返しますが、一定のリスクを覚悟した上で新しいことにも挑戦しましょう。

チャレンジ要素がないと開発しててもつまらないですし、アーキテクチャを決める人ってのはたいてい技術的には組織をリードする立場の人でしょうから、 保守的な選択ばかりしているとそういう姿勢や心持ちが必ずチームにも伝わってしまいます。

アーキテクチャ設計がチームの方を向いた仕事であることは先に説明したとおりです。チームのモチベーションに配慮することも大事です。 また、外から見ても楽しいことをやっている感を出しておくことは重要です。 遠回しに採用とかで良い方向に影響を与えます。

無意識に守れるレベルの制約に留める

こうして一生懸命考えて作った制約も、だいたいちゃんと守ってもらえません。 特に、アーキテクトが心を砕いた細かなこだわりの部分ほど見落とされる傾向にあります。 人生なんてそういうものです。

最初に言ったとおり、アーキテクチャとは法です。

具体例をあげると物騒になるのでやめておきますが、誰の目にも明らかな犯罪がダメなことはみんな知っています。 一方で、ほとんど目にする機会もないような法令までちゃんと把握して守ろうと意識している人はあまりいないと思います。 だいたいは良識に従って生活していたらよく知らんけどルールを守ってた、みたいな感じだと思います。

アーキテクチャもだいたい同じで、たとえば使用するプログラミング言語とかフレームワークとか目立つ部分に抗おうとするヤツはあまりいませんが、 実装方式あたりに細かい制約を設けるとそんなルールが存在することを知らずにルールを無視していたりします。

経験上このへんに人力で真面目に向き合うのは無理なので、エンジニアは自動化というツールを利用すべきでしょう。 よくわかってない人が人がミスで問題を起こせない(あるいは簡単に復旧可能な)ように制度設計することが大事だと思います。

アーキテクチャを決める人が頑張ること

正解かどうかはわかりませんが、僕は以下にあげるようなことに気をつけて生きています。

プロダクトを理解する

敢えて言うまでもありませんが、作ろうとしてるプロダクトがどういう特性を持っているのかを理解することは大事です。

これは開発時点で明らかになっている機能要件・非機能要件を満たすということはもちろんですが、 そのプロダクトが何をウリにしているのか(他との差別化要因はなにか)とか、将来どういう姿を目指しているのかとかはちゃんと理解しておくべきです。

プロダクトがウリにしているポイントの周辺には何度も変更が発生することになるであろうことを念頭に置き、 意識的に制約を緩めておくなどの対処が必要になると思います。

知っていることを増やす

これも当たり前ではありますが、主要な技術の特性をなんとなくでもいいので把握しておくことは大事です。 ある程度引き出しがないと、アーキテクチャ設計初期の技術選定の幅が狭くなってしまいます。 採用を検討している技術に関しては、その時点で細部まで詳しく理解している必要は必ずしもないとは思いますが、 少なくとも特徴は抑えていて、やりたいことがあったらすぐに試してみることができるくらいの状態にはなっていたいです。

技術トレンドの大きな流れも捉えておくべきです。 例をあげるならば、ブラウザのプライバシー向上の取り組みの流れとか、 各プログラミング言語やフレームワークの目指す世界観・向かっている方向性、 流行りの設計手法の根底にある考え方のような未来に向けたメタ情報を知っておくと間違いが少なくなります。

また、先述のチャレンジ要素はアーキテクチャ設計者が理解している技術からしか選べません。 なにか新しく取り入れたいことがあるなら前もって勉強しておく必要があるでしょう。 アーキテクトがよくわかってないものをとりあえずみんなでやってみよう、というのはやや無責任かと思います。

信頼貯金はしておく

そもそも信頼がなかったらアーキテクチャ設計を任せてもらえないというニワトリ卵問題ではあるような気がしなくもないですが、 アーキテクチャ設計の成果物に説得力をもたせる意味で、普段の仕事とか仕事外でもアウトプットをそれなりにやるとかして技術的な信頼は積んでおくほうがいいと思います。

チームメンバーに期待すること

最後に、これまでに述べてきたようなアーキテクチャ設計論を実践するにあたり、僕がチームメンバーに対して期待することをいくつか書いておきます。

現職では非常に優秀なメンバーに囲まれていることもあり、普段こういったことを意識することはあまりないのですが、 おそらく以下にあげるような性質を備えていないチームでここに書いたことをやろうとすると失敗するのではないかと思います。

合理的かつ怠惰である

「自分のメリットになることには自発的に取り組むが、できる限り楽をしようとする」というような考え方で行動することを暗に期待しています。 放っておいても勝手に物事をいい方に進めてくれることはあまり期待していません。

  • お給料はもらっているからやらなければいけない仕事はこなす
  • 成果を出すための方法は自分のやりたいように決める
  • 自分の時間が貴重なものであると認識している

みたいなタイプを想定しています。

「学習コストが高い」という理由で何かを諦めることがない

作成したアーキテクチャを隅々まで理解するのはそれはそれは大変なことでしょうし、技術的に新しいことに取り組もうとすると覚えなくてはならないこともたくさんあります。

そのときに「学習コストが高いからやめよう」というのはすごく簡単です。 たぶん仕事としては新しいチャレンジもなく(少なくとも僕にとって)面白いものにはならないと予想されますが、 アーキテクチャ設計の作業としては従来採用してきたものをそのまま出せばいいだけなので簡単です。 うまくいかなかった場合に言い訳するのも簡単です。

僕はやっぱり「学習コストは高いかもしれないが、価値のある投資になるだろう」と前向きに挑戦を受け入れてくれる人と働くのが好きです。 こちらとしてはもちろんチームにとってベストだと思える案を用意しますので、 メンバーからは「こいつが必要だと言ってるんだからそうなんだろう」 「非合理にコストが高いものを出しているわけではないだろう」と思ってもらえたら嬉しいです。

アーキテクチャがダメなときはダメと言ってくれる

大変申し訳無いのですが、たぶん僕が知恵をひねっても完璧パーフェクトな設計をすることはできません。 できることはせいぜい変更の余地を残すくらいなもんです。

なので、上手くいってないなとかなんかストレスだなと思ったら遠慮せずに言ってほしいです。 なんにも言ってもらえないと上手く運用できているものと誤解しがちなので、ダメなところは指摘してもらって改善につなげていきたいです。

後記

これだけ書いておきながら、全然言いたいことを書ききれた感がまったくなくてこの文章を世に出すのが若干怖いです。 そもそもアーキテクチャという言葉が指す広範な事物のほんの一部にしか触れていないので心配してもしょうがないような気もしますが。

短期間に長文を連発したので疲れました。 明日はライトなネタで済ませたいと思います。 ここまで読んでいただきありがとうございました。


  1. 原泰久,『キングダム 46巻』(集英社、2017年) 39頁
  2. 余談ですが、仕様によって定められた品質を担保するのがテストです。テストをいくら頑張っても品質は(仕様で定められたラインまでしか)向上しません。
  3. 例えばWebブラウザのWeb Storage APIなど、クライアントが備えている機能全般を指す。
  4. 異なるプログラミング言語やフレームワークを用いた場合、それらの間が境界になるため分割できる。
  5. だいたいこの期限が来るまでにそんな約束は忘れられてしまうのですが、それは言わない約束。

最新記事

  1. Cloud RunでgRPC streamingができるようになったので動かしてみたりした
  2. 2020年の執筆業まとめ
  3. アーキテクチャを設計する仕事とは
  4. 決済システムを設計するときに忘れてはならないたった1つの大切なこと
  5. third-party cookieと2020年