2019年6月3日月曜日
書評『進化的アーキテクチャ ―絶え間ない変化を支える』
6月 03, 2019
琴線に触れた部分・キーワードを中心に簡単にご紹介したいと思います。
どういう本か・総評
システムの「変更しやすさ」(進化可能性)をいかにして追求し、また経年劣化させないか、というところに主眼を置いています。経年劣化から守る仕組みとして「適応度関数」という考え方を導入します。
具体的な手法や考え方は、継続的デリバリーやドメイン駆動設計などの知見を参照しており、これらの知見を俯瞰的にまとめた内容とも言えます。
個人的には、適応度関数を始めとした本書で導入される概念より、参照されている既存の概念(CI/CD、DDDやその他の設計プラクティス)の方が参考になりました。最近の設計や開発プラクティスを俯瞰する本として読むのもいいかもしれません。
適応度関数
アーキテクチャが満たしているべき要件(本書内では次元)を保護するもの。
循環的複雑度・ユニットテスト・監視・メトリクスといったシステム的・自動的に測定可能なものだけでなく、外部組織によるシステム監査などの人手による測定も包含する概念。
守るべき次元を定め、適応度関数として定義、測定し続けることで、外形的にアーキテクチャを保護する。保護することで、壊さずに変更し続けられることを担保する。
ちょっと理想論すぎるかなという印象もありますが、そういう捉え方もあるな、という感想です。
アーキテクチャの分類・マイクロサービス
本書では下記のような各アーキテクチャを対等に並べて、それぞれの歴史的背景や特性を、主に進化可能性の観点から整理しています。
- モノリス(3層レイヤ化モノリス等を含む)
- イベント駆動アーキテクチャ
- SOA
- マイクロサービス
- サーバレス
いわゆるレガシー・エンタープライズ感の強いEDAやSOAから、マイクロサービスまで、しっかり比較の土台に載せている点は非常に良いと思いました。
まあ、最終的にマイクロサービス推しであり、そこに多くの紙幅が割かれてはいますが…
マイクロサービスのどういった特性が優れていて、他のアーキテクチャではその特性は何とトレードオフにされているのか、整理して理解することができただけでも本書を読んだ価値があったと思います。
モノリスを構築できないとき、なぜマイクロサービスがその答えだと思うのか。
マイクロサービスのメリットの多くは、ビジネスドメインを中心としてサービス単位を構成することに由来するものです。(この辺は、後述の逆コンウェイ戦略にも通じます)
従い、仮にマイクロサービスであっても、サービス粒度がビジネス的な境界線(境界づけられたコンテキスト)と乖離していてはメリットの多くは享受できません。
また逆に、モノリスアーキテクチャであっても、ビジネスドメインに基づいてしっかりとモノリス内でモジュール化(疎結合化)が成されていれば、マイクロサービスに近いメリットが得られるということでもあります。
そこのところをしっかり意識して、安易に流行りのアーキテクチャというだけで飛び付かないようにしたいですね。
マイクロサービスという名前にひっかからないようにしてほしい。各サービスは決して小さい必要はない。むしろ有効な境界づけられたコンテキストを捉えることが必要なのだ。
サービス境界分割の手法
何を軸にサービス境界を分割するべきかについては下記3つが挙げられています。
- ビジネス機能グループ
既存のビジネスコミュニケーション階層を反映する。コンウェイの法則に従う。 - トランザクション境界
トランザクションは最も分離しにくい要素である。 - デプロイメント目標
デプロイ頻度が異なる箇所を境界とする。
マイクロサービスであればサービス単位として、モノリスであればモノリス内のモジュール構成として、反映していけると良いですね。
コンウェイの法則/逆コンウェイ戦略
システムを設計するあらゆる組織は、必ずその組織のコミュニケーション構造に倣った構造を持つ設計を生み出す。
一般に人はコミュニケーションコストを避ける。また、チームを跨ぐコミュニケーションはコスト高になる。
ゆえに、チーム外の人間が所管する業務フローやコードにはできるだけ影響を及ぼさないように設計・実装しようとする(調整範囲をチーム内に閉じようとする)力学が働く。
ゆえに、チーム外の人間が所管する業務フローやコードにはできるだけ影響を及ぼさないように設計・実装しようとする(調整範囲をチーム内に閉じようとする)力学が働く。
そこから転じて、解決してほしい課題に合わせて、極力チーム内で調整が閉じるように、チーム組成をする、というのが「逆コンウェイ戦略」になります。
ただ一方で、チーム内の結束が維持できるのは精々10人程度が上限(一般的なスクラムでもそう言われますね)、とも述べられていますので、そこにはトレードオフがあります。
本書ではマイクロサービスアーキテクチャを進化可能性の面で優れていると評価していますが、マイクロサービスは「ドメインエンティティ」、「デプロイ単位」、「チーム単位」を一致させながら、かつチームを小さく保ちやすく、結果として逆コンウェイ戦略を満たしやすい、という点も強調されています。
結合よりも重複
結合よりも重複が望ましいという考え方は、本書の中で繰り返されています。
マイクロサービスは無共有アーキテクチャを形成する。その目標は、できるだけ結合を減らすことだ。一般的に、結合よりも重複の方が望ましい。
ツールがあまりにもコードの再利用を容易にしてしまったせいで、現代の開発者は、たやすく結合できてしまう環境の中で適切な結合を行うことに悪戦苦闘している。
例えば、 CatalogCheckout サービスと ShipToCustomer サービスの両方が Item という概念を持つ。両方のチームが同じ名前と同じプロパティを持つので、開発者はサービスをまたいでそれを再利用しようと試みる。それが時間や労力の節約につながると考えるからだ。
けれど、それは労力を増やす結果となる。なぜなら、コンポーネントを共有する全てのチームが変更を伝搬しなければならなくなるからだ。一方、コンポーネントを結合せずに各サービスにItem があり、必要な情報だけをCatalogCheckout から ShipToCustomer に渡す場合は、それらは独立して変更することが可能だ。
開発者が再利用可能なコードを作成する場合、開発者は最終的にコードを使用する無数の方法に対応するための機能を追加する必要がある。その将来の保証全ては、開発者がコードを単一の目的のために使用することをより難しくする
共通化をしたくなる(DRYにしたくなる)のはエンジニアの性ですが、共通化は結合でもあり、変更時の影響範囲を広げるものでもあります。不用意に共通化を行うと変更容易性を損ないます。
あえて重複させるという選択肢を忘れないようにしたいですね。
腐敗防止層を設ける
レイヤを間に挟むことで、将来的な変更可能性を残しておく、という手法です。例えばO/Rマッパであれば、DBMSの変更可能性を残しておく、という意味での腐敗防止層と捉えることができます。
最近、RailsアプリからViewを排除してAPIサーバ化し、フロントを別でSPA化する機会がありましたが、この構成におけるAPIコントローラも腐敗防止層だなー、と思いながら書いていました。
RailsのViewではどうしてもViewからModelへの直参照を止められず、結果として密結合になっていきますが、SPA<->API Controller<->Modelの分離が明確になったことで、かなりモデル層の変更がやりやすくなったと感じます。
デプロイに関するプラクティス
「継続的デプロイは開発側の都合であって、ユーザは頻繁な小規模改修を望んでいない」 という観点はもっともだと思いました。
折衷案として機能トグル(コードベースにはマージするがフラグ制御でユーザオープンしない)を用いる方法などが挙げられています。
個人的には、適応度関数はそこまで刺さらなかったですが、個別のトピックは面白い・参考になるものが多かったです。
ただ個別トピックはおそらく殆どに元文献があるので、本格的な設計本をたくさん読んでいる方には物足りないかもしれません。
取っ掛かりをつかみたい人にはおすすめできるかと思います。
取っ掛かりをつかみたい人にはおすすめできるかと思います。
よければ読んでみてください。
2019年4月16日火曜日
Vue.jsとScoped CSSとz-indexの話
4月 16, 2019

こんにちは、シタテルの茨木です。
突然ですが、scoped cssいいですよね
scoped cssは、特にVue.jsにおいては、css設計のスタンダードと言っていいのではないかと思います。BEM等の学習コストの高い手法を覚える必要がなく、「コンポーネント内でのみcssクラス名の一意性を確保すればいい *1」という、シンプルで管理しやすい設計指針を打ち立てることができ、実に理にかなった手法ですね。
プロジェクト全体でのcss命名規約の統制問題は依然としてありますが、コンポーネントベースでの設計手法自体がコンポーネントを書き捨てる運用と親和性があるため、そこまで大きな問題にはならないのかなと思います。
依然として残る考慮点
一方、scoped cssだけで、コンポーネント内にcss的な関心事を完全に閉じ込められるかというとそうではなく、当然ながら例外もあります。最も代表的な例としては、cssの仕様上継承されてしまう属性ですね。
font-sizeなど、属性自体が子孫要素に継承されるものとして定義されていますので、セレクタレベルでscopedにしたところで、子孫要素への影響を避けることはできません。
まあこの辺は書いていても直感的に理解しやすいのでそこまで問題になりにくいのですが、個人的に危ないなと思うのは「z-index」「重ね合わせコンテキスト」です。
z-indexの管理
cssをざっくり理解したつもりになっているエンジニアが犯しがちな間違いの一つが、z-indexがグローバルだと思いこんでしまうことだと思います。そう考えているエンジニアは、Vueプロジェクト全体で単純にコンポーネントごとのz-indexを管理することをを思いつきます *2。例えば、コンテキストメニューのコンポーネントはz-index1000番台、モーダルダイアログのコンポーネントはz-index500番台といった管理です。(モーダル上でコンテキストメニューを開くかもしれない。その場合はコンテキストメニューがモーダルの下に回り込んでほしくない、なんてことを考えているわけですね)
この方式は重ね合わせコンテキストがページ内で1つであるうちは上手く機能しますし、ルール自体はおそらく間違いでもないです。
ただ、重ね合わせコンテキストに関する考慮を漏らしていると、「z-indexで勝っているはずなのに、なぜか後ろに回り込んでしまう要素」が出てくる可能性があり、「なぜか動かない」という状況に見舞われます。実際には仕様理解が足りていないだけなのですが。
理解すべき仕様「重ね合わせコンテキスト」の詳細についてはMDNを読んでください。
https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
ざっくり言えば、「z-indexは同じ重ね合わせコンテキストの中での比較にしか使われない」「重なる系の属性(potision: absoluteなど)が指定された要素は自身が重ね合わせコンテキストを形成する」ということです。
これもfont-sizeなどと同じで、親要素で指定された属性が(たとえscopedであろうとも)、子孫要素に影響を与えうる、ということですね。
デザインは文脈依存?
scoped cssとコンポーネントベースでの部品化は、確かに今までのcssの不便さや複雑さの大部分を解決しているように見えますが、z-indexの件を一つの例として考えると、「デザイン・見た目・UIは、どこまで部品化しても文脈依存から逃れられない」ということを示している気がします。まさに、重ね合わせ「コンテキスト」ですしね。z-indexをpropsで渡せば…みたいな発想はもちろんあるのですが、結局その先にあるのはinput要素の再発明だったりするわけです。input要素をwrapしてたはずがinput要素を作っていた…みたいな。
どんなコンテキストでもデザイン破綻せず使うことのできるUI部品の実装は、相当な労力を要します。(まあ、実際にUI部品系のnpmパッケージの中を覗いてみると、普通にz-indexがハードコードされていて変更できなかったりすることが多く、みんなほどほどに手を抜いているんだと思いますが)
プロジェクト内でのデザイン統一・再利用を想定してコンポーネントを分割設計する際に、デザイン的な汎用性を求めるのはほどほどにしておくべきなのかな、と思った次第でした。
他コンポと疎結合に作るとか、ステートレスに作るとか、そちらに労力を割いたほうが実りが大きいんじゃないかな。UIの寿命は短いので、捨てやすいように疎結合に書くのが一番大事かな、と個人的には思います。
*1: slot内のスコープが親子共通だったり、コンポーネントroot要素に親からclassが注入できるといった例外はありますが
*2: 私です
登録:
投稿 (Atom)