『ドメイン駆動設計』の5つの基本アイデア

エヴァンス氏の『ドメイン駆動設計』の背景にある基本アイデアは何かという私の捉え方のメモ書き。

ドメイン駆動設計にはいろいろな側面がある。また書籍『ドメイン駆動設計』は体系だった設計方法論ではなく、設計の考え方とやり方を経験則として言語化してみた、と捉えている。

その経験則(100%ではないが多くの場合に役に立つ原則)の背景にあるエヴァンス氏の基本的な発想は次の5つに要約できると考えている。

ソフトウェアの複雑さは事業活動の複雑さに起因する

技術的な複雑さもあるが、ソフトウェアが複雑になるのは対象領域の複雑さが主たる理由という考え方。

業務アプリケーションであれば、事業活動の複雑さが業務アプリケーションの複雑さの原因と捉える。

ドメイン駆動設計は、この事業活動の複雑さに起因するソフトウェアの複雑さをうまく扱うための工夫、というのが私の捉え方。

ドメイン駆動設計という設計のアプローチを取り入れるなら、まず第一に対象領域の複雑さに焦点を合わせることが大前提になる。

モデル駆動設計の効果

複雑さに取り組む時の工夫の大きな柱がこれ。 モデルと実装を一致させることで複雑さが扱いやすくなる、という考え方。

エヴァンス氏が「まえがき」で紹介している三つのプロジェクトの例で「精緻なモデルを作ったが実装と乖離(かいり)してうまくいかなった」という経験からモデルと実装を強く結びつけ、モデルとコードを相互に作用させながら設計を改善していくという発想が生まれたのだろう。

ドメインモデルの活用

モデルといっても、プロセスモデルユースケースモデル、データモデル、状態遷移モデルなどいろいろなモデルがある。

ドメイン駆動設計が「モデル駆動設計」で重視しているのは「ドメインモデル」。エヴァンス氏のドメインモデルの解釈はファウラー氏と同じ「オブジェクト指向」のモデルであることが基本になっている。

ユースケースモデルやデータモデルとの一番の違いは、複雑な業務ロジックをどう記述するかを重視する点。

業務ルールに基づく複雑な計算判断ロジックをうまく整理する方法として、トランザクションスクリプトやテーブルモジュールではなくドメインモデルを選択するという考え方。

また、ドメインモデル作りは、開発者が業務知識を取得する手段の一つであり、関係者が同じ言葉(ユビキタス言語)を使って意図を伝えあう語彙の骨格になり、モデルとコードを一致させる道具である、という発想。

深い洞察に向かうリファクタリングの重要性

設計改善の手法としてかなり普及してきたリファクタリングの考え方を、事業活動を深く理解するための手段として活用する、という発想。

リファクタリングの目的として以下の二つを重視する。

  • 暗黙の概念の発見と言語化
  • ソフトウェアの発展性の改善

複雑な事業活動で価値を生み出すソフトウェアの開発は、最初からうまくいくわけではない。表面的かつ断片的な知識を元にした設計から出発して、実際に動くプログラムを記述し、そのコードをリファクタリングをしながら、事業活動の深い理解と、ソフトウェアの構造の改善に継続的に取り組みつづけるという発想。

戦略的な設計への取り組み方

規模が大きなソフトウェアを長期的に成長させていくという課題に対する『ドメイン駆動設計』の考え方とやり方。

  • コードに責任を持つ開発者が全体的・長期的な設計課題に取り組むことの重要性と効果
  • そのために開発の文脈を明確に区切って、全体的・長期的な課題を明確にして扱いやすくする
  • 文脈と文脈の境界で生まれる課題とその対応方法の選択肢の検討
  • クラス粒度ではあつかえない複雑さの扱い方の工夫(レイヤー構造や蒸留)

『ドメイン駆動設計』の解説記事を書きました

本日(1月18日)発売された、Software Design誌 2023年2月号の第一特集で「ドメイン駆動設計入門」を書きました。 執筆の意図と記事の概要を簡単にまとめておきます。

Software Design 2023年2月号|技術評論社

執筆の意図

特集のサブタイトルにある通り「設計力を磨きたい」読者が、ドメイン駆動設計の基礎を知ることで「設計の手法とアイデアの引き出し」を増やすことの役に立てればと思い執筆を引き受けました。

重視したこと
  1. 断片的な用語やパターンの解説でなく、ドメイン駆動設計の全体像と要点を伝える
  2. 全体像を伝えるための図や表を多めにした(ソースコードの例は少ない)
  3. 全体像と要点は、原典である『エリック・エヴァンスのドメイン駆動設計』(以下『ドメイン駆動設計』)の説明を中心にした
  4. ドメイン駆動設計の具体例として『ドメイン駆動設計』に出てくる国際海上貨物輸送の具体的な業務知識の解説を多めにした
  5. マイクロサービスなど分散アーキテクチャドメイン駆動設計の関係など、新しめの内容を盛り込んだ

基本的にはエヴァンスさんの『ドメイン駆動設計』の概要の説明です。

まだエヴァンス本を読んだことがない人や、読んだことはあるけどなんかよくわからんかったという人が、この記事を読むことで、全体の流れと要点をつかみ、エヴァンス本を興味深く読めるようになるとよいな、という思いで記事を書きました。

ドメイン駆動設計とは?(本特集記事の第1章)

ドメイン駆動設計の基本的な考え方を説明する情報が少ないと感じていました。 そういう問題意識から、この特集の第1章では『ドメイン駆動設計』の「はじめに」と第1部「ドメインモデルを機能させる」の3つの章をわかりやすく説明しようと考えました。

まず、ドメイン駆動設計の基本的な考え方を以下の図の流れで説明しています。

次に、ドメイン駆動設計という設計のやり方の中心となるドメインモデルとその三つの用途について書いています。(ドメインモデルの作り方は本特集記事の第2章で具体的に説明しています)

また、ドメインモデルを中心にどのようにアプリケーション全体を組み立てるかを、ヘキサゴナル(ポート &アダプタ)アーキテクチャやクリーンアーキテクチャの概要に触れながら説明しています。

ドメインモデルを理解しよう(本特集記事の第2章)

ドメイン駆動設計の中心となるなるドメインモデルの作り方についての解説です。

題材は『ドメイン駆動設計』のあちこちにでてくる国際海上貨物輸送のオーバーブッキングルールです。

図のような単純な理解からはじまり、業務知識を広げながらドメインモデルを成長させていくやり方を説明しています。

オーバーブッキングルールが、国際海上貨物輸送ビジネスにとっていかに重要であるか、そして、どういう複雑さがあるかという業務知識に触れながら、値オブジェクトなどを使ってドメインモデルを組み立てる道筋を紹介してみました。

国際海上貨物輸送の業務そのものについての説明が多めです。そこに興味が持てれば、ドメインモデルを組み立てるパターンの説明も理解しやすいと思います。

業務を理解するために次の三つの視点を組み合わせるやり方を説明しています。ドメインモデルの中心となるのは業務ルールの視点です。他の二つの視点と関連づけることで、業務の理解が立体的になります。

  • 業務プロセス
  • 業務データ
  • 業務ルール

また、業務ロジックを表現するための三つのパターンを簡単に比較しながら、ドメインモデルの特徴を説明しています。

分散アーキテクチャドメイン駆動設計(本特集記事の第3章)

最近は、分散アーキテクチャやマイクロサービスとの関係でドメイン駆動設計が取り上げられることが増えてきました。 この章では、ドメイン駆動設計の考え方とやり方をどのように分散アーキテクチャに活かしていくかを説明しています。

ドメイン駆動設計』が書かれた当時は、分散アーキテクチャはそれほど一般的ではありませんでしたが、この本の第4部で紹介されている大規模なシステムに取り組むための「境界づけられたコンテキスト」と「コンテキストマップ」の考え方が、最近では分散アーキテクチャのモデルとして利用されることが多くなってきています。

「境界づけられたコンテキスト」はどう分けたらうまくいくか、「コンテキストマップ」はどうつなげばよいかを設計するための考え方とやり方です。

また、さまざまな業務領域をそれぞれの特性ごとに適切な設計や開発のやリ方を検討するために『ドメイン駆動設計』の「コアドメイン」という考え方が役に立ちます。

ドメイン駆動設計のパターン名と用語集(本特集記事の第5章)

ドメイン駆動設計のパターン名や用語は、意味がわかりにくく、人によって捉え方にばらつきが多いようです。 この章では、主要な用語を取り上げながら、その用語の一般的な意味と『ドメイン駆動設計』という文脈に限定した場合の意味について説明しています。

また、用語の意味は他の用語との関係で理解することで、理解しやすくなります。図のように、主なパターン名と用語を関連づけながら、主要な用語を解説してみました。

設計の手法とアイデアの引き出しを増やす

ドメイン駆動設計は銀の弾丸でもないし、唯一の正しい設計技法でもありません。 しかし、ドメイン駆動設計の考え方とやり方を理解することで、設計の手法やアイデアの引き出しは確実に増えるはずです。

ここで紹介したSoftware Design 2023年2月号の特集記事が設計力を磨きたいと思っているソフトウェア開発者のお役にたてればうれしいかぎりです。

本の紹介『読みやすいコードのガイドライン』

はじめに

良い本です。コードを書く人であればだれでもが気づきと学びがあるでしょう。
特に、コードの規模が大きく長期間にわたってさまざまな関係者が読むことになるプログラムを書く人にはぜひ読んでほしい本です。

gihyo.jp

この本から学べるのは、どちらかというと「やり方」よりも「考え方」です。
書き方の具体例というよりは、良いコードを書くための考え方を、著者の知見に基づいて丁寧かつ具体的に説明しています。 別の言い方をすると、とにかくコードの具体例で「やり方」を覚えたい、という人には合わないかもしれません。
コードの具体例もたくさん登場しますが、それは「考え方」を説明するための例であって、コードの書き方のお手本やサンプルを集めた本ではありません。

サンプルコードはKotlinです。内容は、どちらかといえばユーザーインタフェースよりという印象です。しかし、Kotlinを知らなくてもほぼ問題なく読めますし、「考え方」の本なのでサーバーサイドのプログラミングでも役に立つ内容ばかりです。

この本の内容は次の三つに分けることができます。

  • 基本となる考え方の説明(はじめに、1章)
  • 自然言語を使った人と人とのコミュニケーション(2章、3章、7章)
  • プログラムの構造と複雑さの改善(4章、5章、6章)

それぞれに内容について、概略を紹介します。

基本となる考え方(はじめに、1章)

コードの読みやすさが開発の生産性に与える影響を述べています。
その視点から、コードの読みやすさの指標を4つあげています。

  • 単純なコード
  • 意図が明確なコード
  • 独立性の高いコード
  • 構造化されたコード

この本で説明している「読みやすいコード」の考え方を要約した内容です。

また、この考え方にそって、さまざまな一般的な設計原則のなから、以下を取り上げて説明しています。

この五つを代表的な原則として選んだ理由として「過剰に適用したとしても比較的悪影響が出にくい」ことをあげているところに、著者の設計原則についての考え方が表れていると感じました。

自然言語を使った人と人とのコミュニケーション(2章、3章、7章)

コードを書くとは、人と人とのコミュニケーションである、という観点で参考になるのが次の三つの章の考え方でしょう。

  • 第2章 命名
  • 第3章 コメント
  • 第7章 コードレビュー

自然言語を使ってうまく意図を伝達するには、どのようなことに気を付ければよいかを説明した内容です。
ソフトウェア開発とはコミュニケーションでありドキュメンテーションである、という考え方が一貫しています。コードの規模が大きく関係者も多い開発の現場で、ほんとうに役に立つ考え方をわかりやすく解説しています。
チーム開発に関わる人にはぜひ読んでほしい内容です。

プログラムの構造と複雑さの改善(4章、5章、6章)

個人的には、この三つの章が特に参考になりました。コードの書き方というよりは、設計がテーマの章だと思います。
コードが複雑になり読みにくくなる原因とその改善策を「状態」「関数」「依存関係」の視点から説明しています。

第4章 状態

状態を単純化する方法がいろいろ紹介されています。
状態の扱いはプログラムを複雑にします。この複雑さの扱い方について、学びの多い章です。
複数の変数を扱うときの「直交性」の大切さ、状態遷移の複雑さを緩和するための「不変性」「べき等性」「巡回と非巡回」などの考え方を具体例や図を使いながら説明してあります。

かなり高度な内容が含まれていると思いますが、それをわかりやすく説明できているのは、著者がソフトウェア開発の現場で、こういう考え方を回りに何度も伝えてきた経験が背景にあるのだろうと感じました。

第5章 関数

関数の動作を予測可能にする、という視点からのコードの書き方を説明しています。
「関数の責任の分割の考え方」と「関数の流れをわかりやすくする工夫とその効果」が説明されています。
「早期リターン」というある意味で単純なテクニックを取り上げて、5ページにわたって背景にある考え方、適用する時の注意点が丁寧に説明されています。こういうところがこの本の特徴であり、この本を読む価値だと思います。

第6章 依存関係

クラスの依存関係というやっかいな課題を「結合度」「方向」「重複」「明示性」の四点から取り上げています。
依存の強さ(結合度)について、7つの結合パターンと結合度の強弱についての説明は、とてもわかりやすく参考になります。考え方自体は従来からあるものですが、従来からの考え方について、いろいろ調べ、それを現場に取り入れてきた著者の知識と経験がにじみ出ている内容だと感じました。
「方向」「重複」「明示性」については、図とサンプルコードが丁寧に作られていて、なかなかの力作だと思いました。この本のもとになったプレゼンテーション、講義、現場での意見交換などからのフィードバックか活かされているのでしょう。

この三つの章は、コードの書き方というよりは設計の考え方とやり方として大いに参考にしてほしい内容です。

関連書籍など

この本に関連した書籍をいくつか取り上げておきます。

O'Reilly Japan - リーダブルコード

コードの書き方についての古典的な名著です。
取り扱っている内容は、本書と重なるところもありますが『リーダブルコード』のほうがコードよりで基礎的な内容だと思います。

『読みやすいコードのガイドライン』はわかりやすく書いてありますが、取り上げている内容や考え方の説明は、初級者向けというよりは中級者向けだと思います。

良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方:書籍案内|技術評論社

仙塲さん(ミノ駆動さん)のこの本も内容としては重なるところがあります。
仙塲さんの本は、現場の生々しいコード例が豊富で、コードの具体例で学びたい人には、こちらのほうが合うかもしれません。しかしこちらも単なるコードのサンプル集ではなく「設計の考え方」に触れることを強く意識している本です。
リファクタリングを通じた設計改善の指針と具体例という点で、こちらの本も合わせて読むことをお勧めします。

最後に『読みやすいコードのガイドライン』とは少し異なる視点で書かれた本を2冊紹介します。
これらの本の背景にある設計の考え方は、かなりの部分が共通しているので、参考になる点も多いと思います。

達人プログラマー(第2版) 熟達に向けたあなたの旅 | Ohmsha
現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法:書籍案内|技術評論社

(拙著です)

変数(variable)と値(value)

はじめてScalaに触れたとき、変数宣言(var)と値宣言(val)を使い分ける言語仕様に、なるほどなあ、と思った。簡単に言えば、変数(var)は再代入できて、値(val)は再代入できない。

プログラミングのスタイルとして、var宣言は命令的なプログラミング、val宣言は宣言的なプログラミングになる。どちらのプログラミングスタイルで書いているかを、varとvalで明示できるわけだ。

Javaだと言語の基本の仕組みはすべてが変数。final宣言をすることで再代入をコンパイルエラーにすることはできる。Javaは、C言語C++などの命令的なプログラミングの系譜の言語なのですべて変数(variable)というのは、とうぜんの言語仕様だった。

命令的なスタイルから宣言的なスタイルに

命令的なプログラミングでは変数(variable)を使う。宣言的なプログラミングでは値(value)を使う。

再代入可能な変数(variable)は、プログラムの実行時の状態が基本的には不定(不安定)になる。

それに対して再代入できない値(value)は、プログラムの実行時の状態が仕組みとして不変になり安定する。

宣言的なスタイルを選ぶ

プログラミングのアプローチとしては、命令的なスタイルより宣言的なスタイルのほうが良い。プログラムの実行時の挙動が安定することは、良いプログラムを書くために大きなアドバンテージになる。

変数(variable)と値(value)の使い分け

ScalaやKotlinのように変数(var)と値(val)を明示的に使い分けることができる言語だと、命令的なスタイルと、宣言的なスタイルを明示的に書き分けることができる。

Javaのfinal宣言

Javaだと、final宣言を使うことで、いちおう宣言的なスタイルを表明することはできる。ただしfinal宣言した変数が参照するオブジェクトの状態が変更可能であると、簡単に命令的なプログラミングに逆戻りしてしまう。

実際にやってみると

私自身は可能な限り宣言的なスタイルでプログラミングをしたいので、ScalaやKotlinでいろいろ書いてみた時は、変数宣言(var)はまったく使わず、値宣言(val)だけで書いた。

valだらけの気持ち悪さ

そうして書いたコードを眺めてみると、valだらけで、valという宣言がほとんど意味のないノイズに近い印象をうけた。その時思ったのは、何も宣言しなければ、デフォルトで値宣言(val)になり、どうしても変数宣言(var)にしたいところだけvarを記述する、という言語であればもっとよいのになあ、と思った。

final だらけの気持ち悪さ

Javaの場合は、もっとひどい。言語仕様としては、インスタンス変数・メソッドの引数・メソッドのローカル変数・クラス変数をすべてfinal宣言にできる。

しかし、宣言的なスタイルの表明ということで徹底的にfinal宣言を書きまくると、ScalaやKotlinのval宣言だらけよりも、もっと読みにくいひどいコードになる。わかりきった場所までfinalを書くことに「いったいなんの意味があるのか」と疑問に思うくらい、final宣言が邪魔に見えてくる。

Javaの場合、final宣言を徹底しないとfinalで宣言した参照先のオブジェクトで変数を上書きされてしまう可能性が高い。標準ライブラリで用意されているList・Set・Mapなどは、変更可能なオブジェクトなので、final Map map; というような宣言をしても、実質的には、宣言的なプログラミングではなく命令的なプログラミングのままになる。つまり実行時のmapの状態は不定(不安定)なままなのだ。

しかし、そこまでfinal宣言を書くことは単純に面倒だし、書けば書くほどコードが読みにくくなるのが現実。

じゃあどうするか

ScalaやKotlinだとvalだらけになるのは、言語仕様上しかたがない。ところが、Javaの場合は、別の選択肢がある。

finalを書かないという選択肢

それはfinalを書かないという選択肢である。

もちろん、言語仕様的にはfinal宣言をしていない以上、それは再代入可能な変数であり、見た目は命令的なプログラミングそのものである。

ただしプログラミングの方針あるいは約束事として、宣言的に書く、変数の再代入はしない、ということを大前提として共有できていれば、ノイジーで面倒くさいfinal宣言をまったく書かないことも選択できる。

ScalaやKotlinのように値(val)を取り入れるた言語では、宣言的に書くにはvalを書かざるを得ない。 しかし、値(val)の仕組みがないJavaの場合、それを逆手にとって、約束として再代入不可を共有してfinalは書かないというアプローチを選択できるわけだ。

ここらへんは、ふつうのぷろぐらまのirofさんのこの記事も参考になる。

finalを付けるのをやめてみた(日々常々)

final書かずに、宣言的なプログラミングスタイルで意思統一しよう

私は、finalを書いても命令的なプログラミングのままのコードをたくさん見てきた。

final宣言の記述を強制しても、コードを書く人間の頭は命令的なプログラミングスタイルのままだということ。

そこの発想を切り替えるには、final宣言の記述のルール化よりも、変数に再代入をしない、そうすることでプログラムの挙動が安定するし、プログラムのロジックがシンプルになる、という宣言的なプログラミングスタイルの考え方とやり方を粘り強く伝え、それを共有知にすることに時間とエネルギーを使う方が費用対効果が高いと思って、日々の開発に取り組んでいる。

実際のところ、表面的にfinal宣言だらけで書いているチームよりも、final宣言は書かないが、変数には再代入をしないプログラミングスタイルを共有できているチームのほうが、読みやすく質の高いコードを生み出せている手ごたえがある。

f:id:masuda220:20211128085518j:plain

ドメイン駆動設計というソフトウェア開発のやり方

この記事はドメイン駆動設計を取り入れたソフトウェア開発を実際にどのようなやり方で進めているかの事例紹介です。

2021年10月のモデルベースで要件定義をやってみた - connpassで発表した資料の解説です。

設計の効果

良い設計は悪い設計よりも変更が楽で安全です。

ドメイン駆動設計は、そういう良い設計を目指すための設計の考え方とやり方の一つです。

ドメイン駆動設計の特徴は次の2つです。

  • ソフトウェアで扱うさまざまなロジックとデータをドメインに焦点を合わせて整理して記述する
  • 関連するロジックとデータを一つのクラスにカプセル化する

ドメインとはソフトウェアの対象領域です。業務アプリケーションであれば、事業活動ドメインです。この記事では、ドメイン(事業活動)に焦点を合わせたクラス設計の考え方とやり方の基本的な流れを紹介します。

画面やユースケース中心のソフトウェア開発

アプリケーション開発でありがちなのは、画面やユースケースを要件として定義して、画面の入力をデータベースに記録しデータベースから取り出したデータを画面に表示するためのコードを書く、というやり方です。

ありがちなソフトウェア開発
ありがちなソフトウェア開発
動くソフトウェアを作るためには当たり前のやり方です。しかし、このやり方では画面や機能が増えソフトウェアの規模が大きく複雑になると、ソフトウェアの変更がやっかいで危険になります。 機能一覧やバックログリストを作って、ひとつひとつの機能をばらばらに作っていくと、あちこちに似たようなコードを重複して記述することになります。また、データベースにデータを書き込む機能と、そのデータを読み取って利用する機能との間の関係がどんどん見えにくくなります。

その結果、どこに何が書いてあるかのわかりにくく、ちょっとした変更が思わぬ副作用を引き起こす変更がやっかいで危険なプログラムになってしまいます。

モデルベースの要件定義

このような問題を解決するために考案された要件定義の手法がRDRA(ラドラ)です。 バリューソースの神崎さんが提唱されたモデルベースで軽量で柔軟な要件定義手法です。

ソフトウェア開発の視点を増やす

RDRA 2.0 視点を増やす
RDRA 2.0 視点を増やす
要件を定義するために、画面やユースケースだけではなく、ビジネスをとらえる視点を増やします。

  • ビジネスユースケースと業務フロー
  • ビジネスルール
  • 業務上で意識している状態の遷移
  • 業務で関心のある情報

画面やユースケースを個別に定義するだけでは見落としがちな、さまざん情報を取り入れることで、いままではっきりしなかった画面やユースケース目的や、画面間・ユースケース間の関係が見えるようになります。

関係づけて考える
RDRA 2.0 関係で考える
関係で考える

RDRA(Relationship Driven Requirement Analysis)の特徴は、関係(relationship)で考えることです。

さまざまな視点の情報を関係づけることで、情報の意味が明確になります。また関連づけて考えることで、それぞれの視点だけでは見落としがちな情報を発見できます。

要件モデルの品質をあげる

複数の視点を関連づけることで、それぞれの視点のゆがみや、視点間の不整合が見つかり、ゆがみや不整合を修正することで、要件モデルの品質が向上します。

事業活動と関係づける

さまざまな視点のモデルを総合的に関連づけるために役に立つのが事業活動の俯瞰モデルです(図の左上)。 事業活動は、さまざまな業務活動をばらばらに進めているわけではなく、お互いに関連しながら活動しています。表面的にはわかりにくいかもしれませんが、企業は収益を上げ続けるための組織的な行動をするための仕組みです。すべての活動はなんらかの形でつながっています。

事業活動まで視野を広げることで、要件定義のさまざな視点の情報を適切に関連づけ、全体を一貫したモデルとして体系だてることができます。

ドメイン駆動設計

事業活動にまで視野を広げることはドメイン駆動設計というソフトウェア開発のやり方の必要条件です。

しかし、事業活動まで意識してソフトウェア開発をすることのたいせつさは、ドメイン駆動設計に限らず、さまざなソフトウエア開発の考え方で強調されています。

ではドメイン駆動設計ならではの特徴とはなんでしょうか?

エヴァンスの『ドメイン駆動設計』に書かれている内容の中でも、ドメイン駆動設計ならではの特徴がはっきりと表れているのは次の3つの章です。

  • 第3章 モデル駆動設計(モデルと実装を結びつける)
  • 第10章 しなやかな設計(ソフトウェアの変更を楽で安全にする)
  • 第15章 蒸留(コアドメインに集中する)

この三つの章は『ドメイン駆動設計』の中では、読みにくい箇所かもしれません。エヴァンスが何を伝えたいのか私も最初はチンプンカンプンでした。しかしアプリケーション開発の現場で、変更を楽で安全にするための良い設計を模索し、この本を何度も読み返す中で、この三つの章がドメイン駆動設計の考え方とやり方の要点であることがだんだんとわかってきました。 この三つの章が理解できると、他の章を読む時に、この三つの章の準備や応用として位置付けて解釈ができるようになります。

事業活動に焦点を合わせる

ドメイン駆動設計のアプローチ:事業活動に焦点を合わせる
事業活動に焦点を合わせる
ドメイン駆動設計というソフトウェア開発のやり方の基本は、事業活動のモデルをいつも意識することです。

技術者にとっては最初は事業活動のモデルといってもぼんやりとした、おそらくはまちがったイメージしか持つことができません。しかし、ソフトウェアの開発を通じて、一貫して事業活動を意識しつづけることで、しだいに事業活動のモデルがしっかりしたイメージになってきます。

事業活動のモデルをしっかりと認識できるようになった開発チームの生み出すソフトウェアは、事業活動に適切な価値を提供することができるようになります。

事業活動の仕組みと決め事の言語化(可視化)

事業活動の仕組みと決め事の言語化と可視化
事業活動の仕組みと決め事の言語化と可視化
事業活動の知識が増えるだけでも、ソフトウェア開発には良い効果が表れます。つまらない勘違いや見落としが減ることで、よりニーズにあったソフトウェアを目指した開発ができるようになります。

しかし、事業活動の知識が増えただけでは、良い設計はできません。良い設計をするために重要なのは、事業活動の仕組みと決め事(ビジネスルール)を言語化し可視化する活動です。

アプリケーションが複雑になる大きな要因は、事業活動の仕組みと決め事が複雑だからです。事業活動の仕組みと決め事を整理して扱いやすくすることは、アプリケーションの複雑さを整理し扱いやすくすることに直結します。

ビジネスルールをソフトウェアで表現する

ドメイン駆動設計 ビジネスルールをソフトウェアで表現する
ビジネスルールをソフトウェアで表現する
ドメイン駆動設計を取り入れることで良い設計を生み出す活動の核心が、整理したビジネスルールをソフトウェアで表現するために、オブジェクト指向プログラミングの技法を活用することです。

オブジェクト指向プログラミングの技法といっても難しい話ではありません。

  • 業務的に関連するロジックとデータを一つのクラスのまとめる(カプセル化
  • クラス名やパッケージ名に業務の用語をそのまま使う
  • クラス間の関係、パッケージ間の関係を業務の構造と一致させる

の三つを地道に丁寧に実践するだけです。

この活動は難しいわけではありませんが、時間がかかります。ロジックとデータのうまいまとめ方や良いクラス名や良いパッケージ名が最初から見つかるわけではありません。

継続的なリファクタリング(設計改善)

ある時点の理解をもとに試しに作ってみて、作ってみた内容を関係者で評価しながら、ぎごちなさや分かりにくい箇所をよりよい設計に改善を試みる。こういうリファクタリングの活動が延々と続くのがドメイン駆動設計というソフトウェア開発のやり方です。

そして良い設計を目指したリファクタリング(設計改善)を地道に丁寧に続けるから、変更が楽で安全なソフトウェアに育てていくことができるのです。

収益構造と事業方針に焦点をあわせる

ドメイン駆動設計 収益構造と事業方針に焦点をあわせる
収益構造と事業方針に焦点をあわせる
事業活動を表現するありとあらゆるクラスに膨大なリファクタリングを実践することは現実的ではありません。時間と費用がかかるわりには大きな効果を生み出すことはできません。

中核の課題に焦点を合わせる

ドメイン駆動設計の費用対効果をあげるために、設計改善の対象を絞り込み、その対象とする範囲の設計改善に集中することが重要です。

もちろん、どこに集中すればよいかを特定すること自体がたいへんな活動になります。しかし、どこでもいいからせっせとリファクタリング、というアプローチと、どこに集中すべきかを模索しつづける、というアプローチとでは、生み出す成果に大きな違いが生まれます。

収益構造と事業方針を理解して設計する

どこに集中すべきかの模索の重要な手がかりが、その事業の収益構造と事業方針です。

収益構造は売上 ー 費用です。売上をどうやって生み出していて、どこに費用が生まれているか、という仕組みを理解することで、どこに集中すべきかを判断しやすくなります。 そして、事業活動には必ず独自の事業方針があります。ビジョンやミッションという抽象度の高い方針の理解も有用ですが、ソフトウェア開発に直接的に役に立つのは、もっと具体的な決め事として現れる事業方針です。

具体的な決め事に焦点を合わせる

たとえば、価格設定のルールや割引適用のルールです。 あるいは、いつ何をどのくらい発注するかの決め事や、キャンセル・返品・返金に関する決め事です。 これらの決め事は基本は単純ですが、実際の業務では、顧客のカテゴリー、商品の種類、取引先との関係、季節や時間による条件分けなど、さまざまな要素が複雑に絡み合ってきます。

これらの決め事は、安定した収益を確保し、事業を存続させ発展させるための決め事です。こういう決め事に焦点を合わせることで、設計を重点的に改善すべきクラスやパッケージを特定する手がかりが得られます。

全体への波及

重点対象としたクラスやパッケージの設計改善を続けると、結果的にはそれ以外のクラスやパッケージの設計も改善されます。さまざまな事業活動やそのための仕組みと決め事はなんらかの関係でつながっています。収益構造や事業方針に直結するクラスやパッケージを集中的に改善することで、そのクラスやパッケージに隣接したクラスやパッケージの改善が進みます。そういう隣接したクラスやパッケージの改善がさざ波のようにアプリケーション全体につながっていくのです。

事業方針とコードを直接的に関係づける

収益構造や事業方針はソフトウェアの設計や実装から遠く離れた関心事ではありません。日常の設計活動に直接的に関係します。

たとえば、このクラスとパッケージを分割すべきかいっしょにすべきか、という開発現場の日常的な迷いの答えも、事業方針を考えれば、明確な指針が見つかります。 汎用的なAPIにすべきか、ビジネスルールの違いを反映した個別のAPIに分割すべきかの答えは、事業活動の仕組みと決め事、今後、どのように事業を発展させていきたいかの方針から判断すべき設計課題なのです。

こういう設計を日々くりかえすことが、私が実践しているドメイン駆動設計というソフトウェア開発のやり方です。

ドメイン駆動設計の主活動と補完活動

f:id:masuda220:20211102071225j:plain
ドメイン駆動設計というソフトウェア開発のやり方
ドメイン駆動設計では、事業活動の仕組みや決め事に焦点をあわせて、ビジネスルールに基づく計算判断ロジックをクラスとして表現することが設計活動の中心です。そういう事業活動に焦点を合わせて設計したクラスの集まりをドメインモデルと呼びます。 しかし、アプリケーションとして完成するには、ドメインモデルだけではなく、事実を記録し参照するための画面やデータベース操作を扱うためのクラス設計も必要です。ドメイン駆動設計では、こちらのクラス設計は補完的な活動になります。

主軸の設計活動と補完する設計活動は相互に関連します。

主軸となるドメインモデルのクラス設計をするときには、画面やユースケースの視点から設計の妥当性を検証します。画面やユースケースを実現するためのクラス設計は、ドメインモデルのクラス設計の視点から検証します。

こういう双方向の設計のフィードバックがより良い設計を生み出します。

ドメイン駆動設計の集約のわかりにくさの原因と集約を理解するためのヒント

ドメイン駆動設計』のモデル要素のひとつとして「集約」があります。 アプリケーションの対象となる事業活動の仕組みや決め事をソフトウェアで表現する技法のひとつとして集約の考え方はとても役に立ちます。

集約パターンはデータベースのデータ整合性の視点での説明されることが多いようです。しかしデータ整合性の文脈で集約を理解しても、ドメイン駆動設計の中核の関心事である「ドメインの複雑さ」を理解しドメインの知識をクラスで表現するためにはあまり役に立ちません。

この記事では、集約パターンをドメインロジックを表現するモデルの構成要素として効果的に利用するためのヒントを提供したいと思います。

集約はデータ操作の道具ではありません。集約はビジネスルールにもとづくドメインロジックのモデリングと実装の手段です。ここがわかるとドメイン駆動設計の理解が一気に進むと思います。

どうして集約がデータ整合性の話になってしまうのか

集約は「第6章 ドメインオブジェクトのライフサイクル」で基本パターンのひとつとして説明されています。 この章はオブジェクトの永続化と復元をとりあつかっています。集約パターンのほかにリポジトリパターンとファクトリーパターンが登場します。

6章のオブジェクトのライフサイクルの関するパターン、特にリポジトリの考え方はそれ自体はドメイン(問題領域)に由来しません。エヴァンスが書いているとおりです。

6章はオブジェクトの永続化と復元の複雑さを「ドメインモデルの本来の関心事」から分離するためのパターンの紹介です。この章は他の章と異なり、ドメインの知識をどうモデリングしクラスとして表現するかよりも、データベース操作と密接に関係した内容を中心に書かれています。『ドメイン駆動設計』の中で、この章だけがドメインロジックよりもデータベースに関心事の焦点が当たっています。

その結果、この章で登場する集約パターンはデータベース操作やデータ整合性の視点からの説明が多く、またそう理解している人も多いようです。

データの記録と参照はアプリケーションの重要な関心事

アプリケーションの機能として、データの記録と参照は重要な関心事です。設計や実装に多くの時間を使うのは、データの記録と参照かもしれません。その観点からすれば、集約を「データの集約」として理解し、データベースのトランザクションと関係づけて考えるのが自然だし、多くの開発者にとって理解がしやすいのかもしれません。

ドメイン駆動設計の関心の焦点はデータではなくロジック

しかし、ドメイン駆動設計の関心の焦点はデータの記録と参照ではありません。ドメイン駆動設計が立ち向かおうとしているソフトウェアの複雑さはデータの扱い方の複雑さではなく、ドメインロジックの複雑さです。

ドメインロジックとはビジネスルール(事業活動の決め事)に基づく計算や判断のロジックです。もちろん、計算や判断にデータを使います。しかし、ドメインロジックの複雑さはデータの扱い方の問題ではなく、計算式や判断条件の複雑さをどのように整理して記述するかの問題です。

集約パターンがわかりにくいのは、本来はドメインロジックの複雑さを整理しクラスの組み合わせで表現する設計パターンである集約を、永続化やデータ整合性という別の関心事とからめて説明しているためです。集約を理解するには、まず、集約を永続化の関心事から切り離して考えたほうがわかりやすくなります。

ロジックの置き場所としてクラスを考える

ドメイン駆動設計を理解し実践するための基本は、オブジェクト指向プログラミングのクラス設計のアプローチです。

オブジェクト指向プログラミングでは、クラスは「データを整理」する手段ではありません。クラスは「ロジックを整理」する手段です。金額・数量・日付・区分値など、ビジネスで扱う値の種類を分類し、それぞれの値に対する計算・判断のロジックをそれぞれのクラスに集めて整理するための手段がクラスです。

クラスは内部に計算・判断に使うデータを持ちますが、それは内部に隠蔽されます。クラスを利用する側が意識するのは内部のデータではなく、操作(メソッド)だけです。

メソッドは、内部に保持したインスタンス変数を使った計算判断ロジックの置き場所です。メソッドがそのクラスに所属している理由は、そのメソッドがそのクラスのインスタンス変数を常に使う必然性があるからです。クラスは内部に保持したデータを操作するロジックを集めるための入れ物です。

ドメイン駆動設計は、このロジックの置き場所としてのクラス、というオブジェクト指向プログラミングのクラス設計の考え方を前提にしています。クラスをロジックの置き場所として設計することがドメイン駆動設計を理解し実践するための基本です。

関心の分離と集約

6章の「ドメインオブジェクトのライフサイクル」にでてくる集約も、まずオブジェクト指向プログラミングの基本である、クラスはロジックの置き場所という視点で理解することが役に立ちます。

集約は、複雑なロジックを複数のクラスの組み合わせで表現する設計パターンです。ひとつのクラスにさまざまな計算判断ロジックを詰め込んでしまうのは「関心の分離」の原則に反するアンチパターンです。

金額に関する計算判断と日付に関する計算判断は別にクラスに分けて整理します。金額も円ベースの価格計算と、円以外の支払い通貨に換算した請求金額の計算があれば、別のクラスに分けて関心事を分離したほうがコードをすっきりと整理できます。

このような役割を明確に分離したクラスを組み合わせて、より複雑なロジックを表現します。複雑なロジックを表現するためにクラスを組み合わせる。これが本来の集約パターンの使いどころです。

集約を設計する時にはデータの整合性や永続化を考えないようにします。ドメインロジックを整理するためには、データの整合性や永続化は異なる関心事です。ドメインロジックの整理にデータの整合性を持ち込むことは、クラス設計を不必要に複雑にします。集約は、ドメインロジックを表現するクラス設計を単純に保つための考え方であり工夫です。永続化の関心事をそこに持ち込んではいけません。

またオブジェクトをできるだけ不変(イミュータブル)に設計することで、そもそもデータの整合性をクラス設計の問題として考える必要性はなくなります。クラスをイミュータブルに設計するのも、クラス設計を単純で分かりやすくするための工夫です。

ドメイン駆動設計の集約とはロジックの集約であり関連の設計である

ドメイン駆動設計』の「5章 ソフトウェアで表現されたモデル」で、最初に取り上げられているのが「関連の設計」です。

5章は、エンティティ・値オブジェクト・ドメインサービスがパターンの形式で説明されていて、ドメイン駆動設計の説明でもこの3つのパターンはよくとりあげられます。一方、「関連」はパターン名として登場していないためかあまり取り上げられることはありません。

しかし集約を理解し活用するには、クラスの組み合わせ方を表現する関連の設計の理解と実践が基本です。関連を考えることが集約の設計の核心です。

5章で登場する集約の具体例

クラス設計では関連の設計はもっとも基本的な設計要素の一つです。エヴァンスが5章を関連の説明から始めているのもそういうことです。

5章では証券取引口座と他のクラスの関連の例が説明されています。

f:id:masuda220:20210507132856p:plain
証券取引口座

このようにクラスとクラスの関係をモデリングしたものが「集約」です。

この集約の例ではどのような計算判断のロジックに関心があり、その計算判断ロジックをどのクラスのメソッドとして提供するかは、まだはっきりしていません。分析と設計が進むにつれ、ビジネス的に重要な計算判断ロジックが特定され、適切な場所にメソッドとしてロジックが配置されていくはずです。

銘柄単位の計算判断ロジックは投資クラスに配置するだろうし、口座番号ごとの合算や評価のロジックは証券取引口座クラスが持つことになるでしょう。

複数の関心事の境界がはっきりしない集約の例

集約の別の例を見てみましょう。

この図は、集約に複数の関心事(複数の計算判断ロジック)が混在している例です。

f:id:masuda220:20210507132924p:plain
ロジックの置き場所の設計例

注文の合計金額と送料計算は、異なる関心事(異なる計算ロジック)です。

図として相互につながっていますが、注文金額を計算するための集約と、送料を計算するための集約とに分けて考えたほうがよさそうです。もちろん、配送先は注文の一部なので、その情報をどうやって送料計算の集約で参照可能にするかという設計課題はあります。この図のクラス設計のイメージのままでは、うまく実装できないかもしれません。

また、送料体系クラスは、送料の計算時に計算の条件によってはデータベースの料金設定テーブルへの参照が必要になるかもしれません。ここらへんになってくると「6章 ドメインオブジェクトのライフサイクル」の議論が関係してきます。

データの操作は別の関心事としてクラス設計からは分離する

しかし、送料を計算するロジックをクラスでどう表現するかを検討する時には、いったんはデータベース参照は別の関心事としてとらえ、クラス設計の一部として考えないようにします。そして、ロジックの表現としてわかりやすい集約のクラス設計ができたら、その後でデータベース参照の設計と実装を検討します。

ドメイン駆動設計あるいはオブジェクト指向プログラミングのクラス設計では、データベース操作の関心事は別の関心事として徹底的に分離するほうが良い設計ができます。 

異なる関心事を同時に考えると頭がこんがらがってしまいます。よい設計はできません。①ロジックの置き場所としてクラスを設計する、②データの記録と参照の視点でテーブルを設計する、③オブジェクトとレコードのマッピング方法の設計と実装を検討する。この3つを別々の関心事として分離して設計することでわかりやすい良い設計ができるようになります。

集約の設計は、ロジックの置き場所の設計として考える

5章の「関連の設計」はクラスの集約の考え方の基本です。そして、エヴァンスのようにオブジェクト指向プログラミングでクラス設計をすることがあたりまえの開発者にとっては、集約の設計がクラスの設計、つまりロジックの置き場所の設計であることをあらためて強調するという発想にはなりづらかったのだと思います。

集約がロジックの整理と表現の手段であることを強調しないまま「6章 ドメインオブジェクトのライフサイクル」の永続化とデータ整合性に関する設計課題として集約を説明したために、本来のクラスの集約の考え方とは異なって説明され解釈されることが多くなってしまったのでしょう。

6章も注意深く読めばドメインロジックの整理に焦点があたっています。タイヤの例は、走行距離に応じたタイヤのローテーションルールの話しです。購入注文の例は、購入注文の承認限度額ルールの話しです。

集約はロジックを整理するためのパターン

クラスの集約の設計は、永続化とは異なる関心事です。クラスの集約はデータの扱い方の設計ではなく、ロジックの記述と整理するための設計パターンです。集約の境界は「あるロジックを表現するために必要なクラスの範囲」として検討します。そういうクラスの集約の設計パターンを理解し実践できている前提で、はじめて6章のオブジェクトのライフサイクルとデータ整合性が重要な設計課題として議論されています。

ドメイン駆動設計』を理解する基礎固めの一つとして、集約をデータ整合性ではなく、ロジックの置き場所の設計として考えてみましょう。

ドメイン駆動設計』の後半の「しなやかな設計」にでてくるシェアパイ演算や「大規模な構造」にでてくるコアドメインとしての経路選択ロジックなどの説明を「集約の設計は計算判断ロジックの整理と記述」という視点で読むことで、ドメイン駆動設計についての理解が深まるはずです。

7つの設計原則とオブジェクト指向プログラミング

設計原則はよい設計をするための指針です。
では、よい設計とはなんでしょうか?

もっとも重要なソフトウェア品質は発展性

ソフトウェアのよし悪しを判断する基準は、大きく分けて三つの観点があります。

  • 合目的性(入出力や計算判断の機能要求を満たす)
  • 安定性(可用性・性能とスケーラビリティ・セキュリティの要求を満たす)
  • 発展性(変更容易性や拡張性)

合目的性と安定性はソフトウェアの利用者が判断可能な外部品質です。
発展性は、ソフトウェアの利用者からは判断しにくい内部品質です。

ソフトウェアの設計品質は、この内部品質です。

目的に適合し安定して稼働しているソフトウェアは価値があります。しかし、内部の設計はボロボロかもしれません。
設計品質の劣ったソフトウェアは変更がやっかいで危険です。修正や拡張のコストが高く、現実的には変更不可というソフトウェアもたくさんあります。

ソフトウェアの発展性がビジネス価値を生む

事業活動はたえまなく変化します。顧客のニーズが変化し、市場での競合関係が変わります。そういう顧客や市場の変化にうまく対応しながら事業を発展させていくために、事業を支えるソフトウェアも変化を続けることが必要です。
「作って終わり」「動いていればよい」ではなく、事業の持続的な成長に貢献できる発展性を持つソフトウェアがビジネス価値を生み出します。

ソフトウェアの発展性が向上すれば、合目的性や安定性も継続的に改善できます。

参考記事:ソフトウェアのもっとも重要な品質は発展性 - ソフトウェア設計を考える

発展性をうみだす7つの設計原則

発展性を向上し、ソフトウェアの変更を楽で安全にするための設計原則には、以下のものがあります。

f:id:masuda220:20200602100546j:plain
7つの設計原則

7つの設計原則は、それぞれが独立した原則ではなく、お互いに強く関連します。
7つの設計原則の中心にあるのがモジュール化の原則です。

モジュール化

複雑なソフトウェアをわかりやすく整理し、変更を楽で安全にするための基本原則がモジュール化です。

モジュールは、簡単にいえば一つのプログラムファイルです。
複雑で大きなソフトウェアを複数のプログラムファイルに分割し、それらを組み合わせて全体をつくりあげるのがモジュール化です。

モジュール化の2つのアプローチ

モジュール化には2つの異なる方向性があります。

ひとつはオブジェクト指向プログラミングの基本である型によるモジュール化です。
もう一つは、「入力-処理-出力」の単位でソフトウェアを分割する手続き的なモジュール化です。

7つの設計原則は、どちらの設計アプローチでも適用可能です。
しかし、型によるモジュール化の設計アプローチと、手続き的なモジュール化の設計アプローチとでは、それぞれの設計原則をどう適用するかの考え方とやり方が大きく異なります。

この記事では、型によるモジュール化(オブジェクト指向プログラミング)の設計アプローチに焦点を合わせて、7つの設計原則を説明します。

モジュール化の2つの設計アプローチの違いを簡単にまとめておきます。

型によるモジュール化

オブジェクト指向プログラミングのモジュール化は型によるプログラムの分割です。

型とは値の種類です。
値の種類ごとに、その値を使った操作(計算や判断)をグルーピングして一つのモジュール(クラス)にまとめるのが、オブジェクト指向プログラミングの基本的な考え方です。
文字列型や整数型を表現したStringクラスやIntegerクラスは、型(値の種類)によるモジュール化の具体例です。

型には二種類あります。
ひとつはStringやIntegerのようにプログラミング言語に組み込みまれた型です。

もう一つは、プログラムの開発者が独自に定義する型です。
アプリケーションで扱いたい値、例えば金額・数量・率・日付・期間・日数などを型として開発者が独自に定義します。

金額や日付を扱うために独自の型をモジュールとして作成し、それらのモジュールを組み合わせてアプリケーションをボトムアップに組み立てていくのが、オブジェクト指向プログラミングの基本アプローチです。

型によるモジュール化は、値の種類ごとに計算や判断の操作をグルーピングしてひとつのモジュールにまとめます。
こうすることで、計算判断のロジックがどのモジュールに書いてあるかをわかりやすく整理できます。そして計算判断ロジックの変更の影響を特定のクラス(モジュール)内部に閉じ込めやすくなります。

手続き的なモジュール化

手続き的なモジュール化は、入力を受け取って結果を出力するまでの一連の処理の流れ(手続き)を一つのモジュールにまとめます。 「入力-処理-出力」という単位のモジュール分割です。

手続き的なモジュール分割はトップダウンのアプローチです。
全体を大きな「入力-処理-出力」のプログラムとして定義し、さらにそのモジュールを「入力-処理-出力」のサブモジュールに分割します。

手続き的なモジュールは、大きなデータクラスを受け取り大きなデータクラスを返す設計が多くなります。

計算や判断のロジックは、受け取ったデータクラスのデータ項目を処理する手順の一部として記述します。 型によるモジュール分割とは対照的に、異なる値の種類に対する計算判断のロジックが一つのモジュールに混在します。

手続き的なモジュール構造では、同じようなデータクラスを受け取れば、同じような計算判断のロジックが複数のモジュールに重複します。
その結果、ソフトウェアの変更はやっかいで危険になります。重複したコードはソフトウェアの発展性を阻害します。

関心の分離

モジュールに分解して組み立てるのは、複雑さを扱いやすくするためです。
しかし、やみくもにモジュールに分解すれば複雑さを単純化できるわけではありません。

わかりやすく扱いやすいモジュールに分解するための設計原則が関心の分離です。
複雑さとはさまざまな関心事が入り組んでいる状態です。関心の分離は、この入り組んだ状態を切り分けて整理するための設計原則です。

 ひとつのモジュールに異なる関心事が混在すると、どこに何が書いてあるかがわかりにくくなります。
モジュールとモジュールの組み合わせ方も、ぎこちなく入り組んできます。
変更をするときに、同じモジュールの別の関心事を記述した個所まで誤って変更してしまうかもしれません。
関心が混在したモジュールを組み合わせると、モジュールとモジュールの関係が複雑なります。あるモジュールの変更が、他のどんなモジュールに影響するか、簡単には把握できません。

では、どのような方針でモジュールを分割すれば、複雑なソフトウェアをわかりやすく整理できるでしょうか?

関心の4象限

f:id:masuda220:20200602094810j:plain
関心の4象限

この図は、関心の分離の基本方針を考えるために、関心事を4つの象限に分けたものです。 横軸では「入出力」と「計算・判断」に関心を分離します。 縦軸では「業務(ビジネス)の関心」と「実装の詳細」に関心を分離します。

入出力と計算・判断の分離

関心の分離のよくある失敗が、ひとつのモジュールに入出力の関心事と計算判断の関心事を混在させてしまうことです。

アプリケーションプログラムは、画面・データベース・通信など外部との入出力を扱う部分と、メモリとCPUを使って行う内部の計算・判断の処理を扱う部分に分離できます。
この二種類の関心事を明確に分離すれば、それぞれのモジュールの役割が明確になり、ひとつひとつのモジュールの記述は単純で扱いやすくなります。

手続き的なモジュール構造は、この入出力と計算・判断の分離がうまくいきません。 入出力の手続きの中に、計算・判断ロジックを埋め込んでしまうためです。

型によるモジュール構造では、計算・判断に使う値の種類、つまり型に注目してモジュールを分割します。
入出力の関心から切り離すことで、計算判断のロジックだけを独立したモジュールにわかりやすく記述できます。
計算・判断の関心事を別モジュールに分離できれば、入出力を扱うモジュールは入出力だけに専念できます。計算・判断ロジックが入らないため、入出力を扱うモジュールを単純化できます。

入出力の関心事と計算判断の関心事を別のモジュールに分離するのが、もっとも基本的な関心の分離です。

業務の関心と実装の詳細の分離

アプリケーションプログラムの目的は業務(ビジネス)の関心事に対して役に立つ機能を提供することです。
そして機能を提供するための手段として実装の詳細があります。

プログラムの多くの部分は実装の詳細の記述です。関心の分離を意識しないと実装の詳細の中に業務の関心事が暗黙的にかつ断片的に記述されがちです。
その結果、業務の関心事をソースコードから読み取りにくくなります。
ソフトウェアの変更要求は、業務の関心事の変更です。変更の理由となる業務の関心事がプログラムのどこにどう記述されているかがわかりにくければ、変更の対象個所を特定したり、変更の影響を把握するのがたいへんになります。

業務の関心事と実装の詳細にかかわる関心事を別のモジュールに分離すれば、それぞれのモジュールの役割が明確になりプログラムの見通しがよくなります。

業務の関心事を表現するモジュールは、実装の詳細から解放され、業務の関心事だけをわかりやすく表現できます。
パッケージ名・クラス名・メソッド名に業務で使われる言葉を積極的に使うことで、業務の関心事とプログラムの記述内容が直接的に対応します。

実装の詳細を記述するモジュールは、プログラミング言語や標準ライブラリに用意された型を使って、どうやってプログラムを実現するかの実装の関心事を記述します。
実装の詳細のモジュール群は、業務の関心事を表現したモジュール群から使われる基礎部品です。

業務の関心事を表現するモジュール群と実装の詳細を記述したモジュール群を分離して、業務の関心事のモジュール群が実装の詳細のモジュール群を使うという関係が明確であれば、どこに何が書いてあるかがわかりやすくなります。
プログラムを変更する時に、業務の関心事と直接対応する変更か、実装の詳細だけに関係する変更かを切り分けがしやすく、変更の影響を狭い範囲に閉じ込めやすくなります。

もっとも複雑な関心事(ビジネスロジック)の分離を徹底する

アプリケーションが複雑になる一番の原因は、4象限の右上の関心事です。 この象限はビジネスルールに基づく計算・判断のロジック、つまりビジネスロジックです。

ビジネスルールとは事業活動を進めるためのさまざまな決め事や制約条件です。
決め事や制約条件が複雑になればなるほど、ビジネスルールに基づく計算・判断のロジックが複雑になり、さまざまな条件分岐や例外的な処理の記述のためにプログラムが入り組んできます。

ビジネスルールの複雑さに起因したアプリケーションのこの複雑さを下げるために効果的なのが、ビジネスロジックを記述するモジュールを、他の3つの象限のモジュールから分離することです。
ビジネスロジックを分離すれば、ビジネスロジック以外のモジュール群は単純になり扱いやすくなります。

では、ビジネスロジックの複雑さそのものは、どうやって整理すればよいでしょうか?
ビジネスロジックの複雑さの整理に威力を発揮するのが、カプセル化と抽象化の2つの設計原則です。

カプセル化と抽象化

カプセル化と抽象化は、型によるモジュール構造でアプリケーションを組み立てるための二本柱です。
特に、複雑なビジネスロジックをわかりやすく記述するためには、カプセル化と抽象化の2つの設計原則が威力を発揮します。

カプセル化

カプセルの意味は「小さなケース」です。*1
カプセル化とは、小さな入れものに何かをギュッと詰め込むことです。

オブジェクト指向プログラミングで、小さな入れ物(クラス)の中にギュッと詰め込むのは、データとそのデータに対する操作です。

例えば、整数データを扱う操作は、加減乗除の4種類の計算と、等しい/等しくない、大きい/小さいという4種類の比較演算があります。
整数データを保持する場所と、その整数データを対象にした加減乗除と比較演算の8つの操作を一つのモジュールにまとめる。これがカプセル化の考え方です。

手続き的なモジュール化では、操作対象のデータは引数として外部から受け取ります。データを保持する場所はロジックを記述してあるモジュールとは別のモジュールです。 つまり、カプセル化をせず、データを保持する場所と、データを使った操作を記述する場所を分けるのが、手続き的なモジュール化の特徴です。

それに対し、オブジェクト指向プログラミングのモジュール化はカプセル化です。データを保持するモジュールに、そのデータを使う計算や判断の操作をいっしょに記述します。

ビジネスロジックカプセル化

カプセル化が威力を発揮するのは、ビジネスルールに基づく計算・判断のビジネスロジックを記述する時です。  

ビジネスロジックは、ビジネスで扱う値(金額・数量・百分率・日付・日数・期間・区分)を対象にした計算や判断のロジックです。
ビジネスで扱う値を使った計算や判断のロジックを、扱う値の種類ごとに一つのモジュールにまとめておけば、どこに何が書いてあるかをわかりやすく整理できます。

金額の足し算・引き算、単価に数量を掛ける計算、百分率を掛けたときの端数の丸め計算、日付の比較、日数の計算、期間内かどうかの判定、... 。こういう計算や判断ロジックを、扱う値の種類ごとに小さな入れ物(カプセル)にまとめてモジュール化します。
こうすることで金額や日付を計算したり判断したりするビジネスロジックがプログラムのあちこちに散らばらなくなります。
この結果、金額に関するロジックの変更が必要な時は、金額クラスだけを考えればよくなります。あちこちのモジュールを調べまわる必要はありません。変更箇所を一つのクラスに特定でき、変更の影響を狭い範囲に閉じ込めることができます。

値の種類ごとに、関連するデータとロジックをひとつのモジュールにまとめるカプセル化は変更を楽で安全にします

手続き的なプログラミングは、カプセル化の設計原則とは正反対のアプローチを採用します。値を保持するデータクラスとロジックを記述する機能クラスを分けます。
データクラスと機能クラスを分けると、あるデータを使う計算・判断のロジックがどの機能クラスに書いてあるのかを洗い出すのがたいへんになります。
一つの機能クラスに記述した判断ロジックを修正しても、同じデータクラスを使っている別の機能クラスの判断ロジックの変更が漏れていた、という事故が起きやすくなります。

抽象化

抽象化とは、さまざまな詳細の中から本質的な特徴だけを選択して取りだすことです。

人間の思考能力には限界があります。いちどに多くのことを考えると、わけがわからなくなります。
ある時点で考えるべきことを重要な本質だけに限定すれば、理解も容易になり、判断もしやすくなります。

モジュールを設計する時も、モジュールのさまざまな側面をいっしょに考えると混乱します。
役に立つモジュールを設計するために、モジュールを抽象化して検討します。
モジュールの本質的な特徴とは何か、そして、モジュールの特徴をどう定義すれば役に立つかを考えるための設計原則が抽象化です。

データ抽象

オブジェクト指向プログラミングのモジュール化はデータの(値の種類)に注目するアプローチです。
アプリケーションで扱うさまざまなデータを、整数・文字列・日付などの値の種類ごとに分類したものが型です。 型(値の種類)ごとにプログラムを分割し、その値を扱うための操作のグループをひとつのモジュールにまとめる(カプセル化する)のが型によるモジュール化です。

データを扱う時に、そのデータを扱う操作に焦点を合わせることをデータ抽象と呼びます。 データ抽象は、モジュールの本質的な特徴は値に対する操作である、という考え方です。 値に対する操作とは、例えば次のようなことです。

  • 整数型の値は、加減乗除・大小比較・等しい/等しくないの演算ができる
  • 真偽型(boolean型)の値は、等しい/等しくないを判定できるが、加減乗除も大小比較もできない
  • 日付型の値は、前後比較・等しい/等しくない・日数の加算・減算ができるが、日付同士の加減乗除はできない

値の操作をモジュールの本質的な特徴と考えるデータ抽象は、モジュールを使う側にとって何が重要かに焦点を合わせてモジュールを設計する、という考え方です。

モジュールをプログラムとして完成させるには、さまざまな詳細の検討が必要です。
しかし次のようなモジュール内部の詳細は、モジュールを使う側にとって本質的な特徴ではありません。

  • モジュールが内部に保持するデータの構造
  • そのデータを使った、計算や判断ロジックの詳細
  • そのモジュールが利用している他のモジュール

モジュールを使う側にとって重要なのは、その型(値の種類)ではどういう操作が利用できるか/できないか、です。モジュールを利用する時の外部の視点からのモジュールの特徴です。

エディタによってはコードの記述を支援する機能として、変数の直後にドット(.)をタイプすると、その変数が参照しているオブジェクトの操作の一覧を表示する機能があります。この操作の一覧がデータ抽象の具体例です。プログラムを書くときのモジュールの本質的な特徴は、そのモジュールで利用可能な操作です。

モジュールを型(値の種類)で分割し、その型が提供する操作に焦点を合わせてモジュールの特徴を定義するのがデータ抽象です。モジュール内部の具体的なデータ構造ではなく、データを操作に抽象化して考えるという設計のアプローチです。

データ抽象は、モジュールの分割方針を、手続きではなく(値の種類)に注目して行う、というアイデアから生まれました。*2 *3
データ抽象、つまり型によるモジュール化オブジェクト指向プログラミングの根底にある考え方です。

ビジネスロジックとデータ抽象

データ抽象の考え方が威力を発揮するのは、関心の4象限の右上の関心事、つまりビジネスロジックを表現するモジュールを設計する時です。

ビジネスルールの実体は、金額・数量・率・区分・日付・日数・期間などのビジネスで扱うデータを使った計算と判断の決め事です。
この計算判断のルールをプログラミング言語で記述したものがビジネスロジックです。

ビジネスルールは、複雑な事業活動を進めるためのさまざまな決め事や制約です。 ビジネスロジックは、このビジネスルールの複雑さをそのまま反映して複雑になります。
ビジネスロジックの複雑さを整理して扱いやすくする方法が、型(値の種類)ごとにモジュールを分割し、そのモジュールの本質的な特徴を、値に対して行う計算判断の操作として定義するデータ抽象の考え方です。

ビジネスで関心のあるデータを、プログラミング言語でどのように扱うかの詳細はビジネスロジックの本質的な特徴ではありません。
ビジネスロジックの本質的な特徴は、どういう値にたいしてどういう計算・判断の操作をしたいか/すべきかです。

例えば金額を扱うモジュールを考えてみましょう。
金額に対して行う操作には以下のものが考えられます。

  • 金額同士の加算と減算
  • 金額と整数の乗算
  • 金額と率の乗算
  • 金額と整数の除算
  • 金額が等しい/等しくないの判断
  • 金額どうしの大小の比較

型(値の種類)によるモジュール化(データ抽象)は、このようにビジネスルールに基づく計算判断を表現するために必要な金額の計算や判断の操作を定義して、ひとつのモジュールにまとめます。
そして値の種類ごとの部品(モジュール)を組み合わせて、より複雑なビジネスロジックを表現するモジュールを組み立てます。

こうすることで、どんな種類のデータに関心があり、そのデータに対してどんな計算や判断を必要としているかを体系的に整理できます。
データの種類に注目して、ビジネスの関心事としての操作に焦点をあわせるデータ抽象のアプローチが、ビジネスロジックを記述するプログラムのモジュール化に効果を発揮します。

手続き的なモジュール構造では、金額データを扱うあちこちのモジュールに計算や判断のロジックが重複しがちです。コードの重複は変更をやっかいで危険にする元凶です。

高凝集と疎結合

高凝集と疎結合はモジュールの質を高めるための設計原則です。

関心の分離・カプセル化・データ抽象の三つの設計原則は、モジュールをどう分割するかの指針です。
分割したモジュールの質を判定するための指針が高凝集疎結合の二つの設計原則です。

凝集度

凝集とは切っても切れない関係の要素の集まりです。

凝集度が低いモジュールは、さまざまな関心事が混在しているモジュールです。
関心事が混在すると、モジュールの意図の理解が難しくなります。
また、ある関心事についての変更の影響が、本来は関係のない部分にも影響し、変更がやっかいで危険になります。

モジュールに格納するデータと操作を一つの関心事に凝集させれば、わかりやすく扱いやすいモジュールになります。
そのモジュールの使いどころの判断と使い方が直感的になります。

どういうモジュールが凝集度が高いかを判断するのは簡単ではありません。ある見方では凝集度が高いと言えるが、別の見方では異なる関心事が混在しているとも言える、ということが起きるからです。

それに対して、明らかに凝集度が低いモジュールは、比較的簡単に判断できます。

次のようなモジュールは、さまざまな関心事が混在した凝集度の低いモジュールだと考えられます。

  • メソッドの数が多い
  • メソッドの引数が多い
  • フィールドの数が多い

メソッドの数が二桁になれば、そのモジュールの凝集度は低いと言えます。データを抽象化した時に、10も20も操作があるのは、複数の関心事が混在している明らかな兆候です。

例えば、文字列型を扱うStringクラスはさまざまな操作が用意されていて便利です。しかし、けして凝集度の高い設計ではありません。 文字列を扱うという広い関心事のモジュールだからです。

文字列の操作は、もっと小さなグループに分けて目的特化のモジュールに分割することが可能です。
文字列を分割するという関心事と、文字列にどんな内容が含まれているかを検査する関心事と、文字列をフォーマッティングしたいという関心事は、切っても切れないほど濃密な関係とは言えません。異なる関心事として別のモジュールに分離できそうです。
高凝集という設計原則を理解し実践できるスキルを上げるには、こういうモジュール分割の可能性を検討して、実際に分割の実験をして効果を検証してみる姿勢がたいせつです。

メソッドの引数が2つを超えたら、凝集度の低下がはじまっています。

オブジェクト指向プログラミングのモジュール(クラス)では、操作の対象の主役は、オブジェクト内部に保持しているデータです。
メソッドに渡す引数が多いということは、メソッドの内部のロジックは、オブジェクトが内部に持つデータよりも外部から渡されたデータに強く関心を持っている可能性があります。 外部から渡されるデータに強く関係するロジックは、そのデータを保持するクラスに移動したほうが、それぞれのクラスの凝集度が高くなります。

フィールドの数が2つを超えたら、凝集度は下がりはじめます。

クラスの凝集度を判断する一つの目安が、フィールドとメソッドとの関係性です。
クラスのすべてのメソッドが、すべてのフィールドを必ず使っていれば、そのクラスの凝集度は高いと言えます。
逆に、メソッドによって使っているフィールドが異なれば、異なる関心事が混在しているクラスです。
特定のフィールドだけと関係するメソッドは、そのフィールドだけを持つ別のクラスとして分離すべきです。
そうすることで、それぞれのクラスの凝集度があがります。

こうやって、凝集度をあげるために、フィールドとメソッドの関係を、切っても切れない関係だけに限定することで、それぞれのモジュールの役割が明確になります。そして、モジュールに対する変更の影響を、そのモジュールの内部に閉じ込めやすくなります。

オブジェクト指向プログラミングの基本プラクティスであるリファクタリングのパターンは、ほとんどが凝集度の低いモジュールを凝集度の高いモジュールに改善するためのパターンです。

結合度

結合度はモジュール間の関係性についての指針です。 モジュール間の関係性は、モジュールのインタフェース(外部に公開された仕様)から判断できます。

判断の目安は三つあります。

  • 小さなインタフェース
  • 少ないインタフェース
  • 理解しやすいインタフェース

小さなインタフェースとは、モジュール間でやりとりされる情報が少ないことです。
凝集度が下がる兆候として説明した引数の数が多くなると、モジュール間でやりとりされる情報が多くなり、それだけ密な結合になってしまいます。

モジュール間の結合の数が少ないほうが、全体の構造はシンプルになります。
モジュール数が同じでも、モジュール間の関係が少ないほどプログラムがわかりやすくなり、また、変更の影響範囲が狭くなります。

あるモジュールを使う時に知っておくべき知識が少ないほど、モジュールの用途が理解しやすく使いやすいモジュールになります。
また、モジュールの誤った使い方が減りプログラムの挙動が安定します。

隠された結合性の問題

表面的には疎結合に見えても、あるモジュールを変更すると、他のモジュールでも同じような変更をしなければいけないケースがあります。

同じような計算式や条件判断のロジックやあちこちのモジュールに重複している場合です。
まったく独立して無関係に見えるモジュールでも、このようなコードの重複があると、あるモジュールに対する変更に関連して、他のモジュールにも同じ変更が必要になります。

コードが重複した複数モジュールへの変更作業は、調べるのも、変更を適用するのも、変更の結果を確認するにも、時間がかかります。このような暗黙の関係性(隠された結合性)はソフトウェアの変更をやっかいで危険にする元凶です。

隠された結合性をなくして、より質の高い疎結合を実現するにはどうすればよいでしょうか?

定義の一点性

暗黙の関係性を取り除くための設計原則が定義の一点性(Single Point of Definition)です。*4

同じ計算式や同じ条件判断は、ひとつのモジュールだけで定義すればコードの重複を防げます。

型(値の種類)によるモジュール化(オブジェクト指向プログラミング)は、この定義の一点性を実現するためのわかりやすいアプローチです。
モジュールを値の種類で分割し、ある値に関係する計算判断を、ひとつモジュールだけで定義するからです。

手続き的なモジュール化の問題のひとつが、この定義の一点性を実現しにくいことです。
手続き的なモジュール構造でも、共通のサブルーチンモジュールを導入すれば、定義の一点性を向上できる可能性はあります。
しかし、入力と出力に注目しトップダウンで手続きモジュールを分割していくアプローチで開発すると、共通のサブルーチンが自然に生まれることがありません。むしろ、同じ計算式や判断条件が、別のモジュールに重複しやすくなります。

型によるモジュール化は、関係する計算判断のロジックを値の種類ごとに一か所で定義することを目的としたアプローチです。
オブジェクト指向プログラミングが、変更を楽で安全にしてソフトウェアの発展性を向上できるのは、型によるモジュール化のアプローチが定義の一点性を実現しやすいからです。

見た目が同じコード

プログラミング言語の記述能力は貧弱です。ですので、異なる意図の計算や判断も、プログラミング言語で書くと同じコードになってしまうことがあります。

見た目には同じコードであっても、プログラムの意図は異なるかもしれません。

定義の一点性というのは、同じ意図のコードを単一定義しようという原則です。 コードの見た目が似ているから共通コードにしようという設計原則ではないことに注意が必要です。

この点でも、型(値の種類)によるモジュール化は重要な設計指針です。
金額と数量は、整数の演算という観点では、内部は似たコードになります。
しかし、金額と数量はビジネスの観点では意味が異なる数値です。金額計算と数量計算は異なる計算です。金額を扱う計算判断と数量を扱う計算判断は、異なる型(値の種類)の操作として別のモジュールで扱うべきです。
そうすることで、関心が分離された凝集度の高いモジュールになります。

7つの設計原則の学び方

設計原則は、言葉として覚えるだけでは役に立ちません。
実際にソフトウェアを開発しながら経験的に、原則の意味と適用のしかたを学ぶことが大切です。

この記事で説明した設計原則を現場で実践するために参考になりそうな、コードの実装例・書籍・ネット上の情報を紹介しておきます。

コードの実装例

型によるモジュール化のアプローチで設計した具体例をgithubで公開しています。

図書館システムの実装例
蔵書の貸出制限ルールや貸出予約の状態管理のルールを、どのように実装するかの具体例です。

オブジェクト指向プログラミングでアプリケーションを開発する場合、オブジェクトと外部のデータ形式(画面・テーブル・JSON)とのマッピングが必要になります。
ドメインオブジェクトをどのように外部形式とマッピングするかの具体例です。
ドメインオブジェクトのマッピング

ドメインオブジェクト設計のガイドライン

この記事の設計原則の考え方をもとに、ビジネスロジックをコードで表現するための設計ガイドとサンプルコードです。

実践ガイドとして使える本

(拙著)現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法
この記事で書いた設計原則を現場で実践する時に参考になると思います。

リファクタリング 既存のコードを安全に改善する
第2版もでていますが、型によるモジュール化という観点からは、第1版のほうが役に立ちます。

RDRA2.0 ハンドブック 軽く柔軟で精度の高い要件定義のモデリング手法
要件定義、特にビジネスロジックの元になるビジネスルールの発見・整理のための実践的な手法です。

設計の考え方を理解するための本

この記事で紹介した設計の考え方をより深く学ぶための本を紹介します。

エリックエヴァンスのドメイン駆動設計
ビジネスロジックに焦点を合わせる重要性とその効果を著者の実践経験をもとに解説した古典的な名著です。

オブジェクト指向入門 第2版 原則・コンセプト
オブジェクト指向入門 第2版 方法論・実践
型によるモジュール化というオブジェクト指向プログラミングの考え方とそのメリットの詳細な解説です。
また契約による設計(Design by Contract)の考え方も、この本で詳しく解説されています。

ドメイン駆動設計とオブジェクト指向入門の二冊は、こちらの記事でも紹介しています。
masuda220.hatenablog.com

ソフトウェアの発展性を重視する設計の考え方は、以下の本が参考になります。

ソフトウェアアーキテクチャ構築の原理 第2版
ソフトウェア設計をさまざまな視点・観点から俯瞰的に解説した名著です。
特に 28章 発展性パースペクティブはこの記事の根底にある考え方と共通点が多い内容です。

*1:caps:入れ物 + ule:小さいという意味の接尾辞

*2:バーバラ・リスコフたちの研究 A History Of CLU

*3:カプセル化はリスコフがデータ抽象の実現アプローチとして提唱した概念です。情報隠蔽するのではなく、内部のコードを読むことを許容しつつ、内部に依存したプログラムは書かないようにするという考え方です。

*4:参照の一点性(Single Point of Reference)と呼ぶこともあります