はじめに

こんな経験はありませんか。長い時間をかけてバグを追いかけた末に、あるオブジェクトが受け渡される途中で、別のコードによって書き換えられていたことに気づいた経験です。データを読み取るだけだと思っていたメソッドが、実際にはその状態も変更し、後続の処理にまで影響していたのです。

Web API が HTTP リクエストを受け取り、その内容を request DTO に変換する場面を考えてみましょう。データは次に、ビジネスルールを扱うドメインオブジェクトを経て、データベースの読み書きに使う永続化モデルへと渡されます。最後に、API が response DTO を生成します。この途中のどこかで、本来は保持されるはずのデータを書き換えてしまうと、後続の処理で予想外の結果が生じることがあります。

補足:DTO は data transfer object(データ転送オブジェクト)の略です。request DTO は外部からのリクエストのデータを、response DTO は API が返すデータを運びます。

次の図は、HTTP リクエストからレスポンスまでのデータの流れの一例です。どのレイヤーでもオブジェクトの状態を変更できると、データがどこから来て、どの処理が変更したのかを追いにくくなります。

補足:図の Domain Object は、中心となるビジネス上の概念やルールを表します。Persistence Entity は、データベースやデータアクセスツールに合わせた永続化モデルです。これは一般的な責任分担の一例であり、すべてのシステムで別々の型を用意したり、特定のレイヤーアーキテクチャを採用したりする必要があるという意味ではありません。

この流れでは、次のことが起こり得ます。

では、あるオブジェクトのプロパティは、今どの値なのでしょうか。誰が、どのレイヤーで変更したのでしょうか。

これらの問題には、共通の原因があります。ミュータブルな状態(mutable state)がコード内で受け渡されるため、ある時点のデータがどの値なのか、誰がどこで変更したのかを確かめにくくなるのです。

本書で取り上げ、解決を目指すのは、まさにこの問題です。

役割ごとにモデルを使い分ければ、各ステップの責任を明確にできます。しかし、一つの処理の流れの中でオブジェクトを受け渡すだけでも、後続のステップが、すでに検証したデータを書き換えたり、残しておきたかった状態を上書きしたりすることがあります。そのオブジェクトが別の処理の流れでも共有されていれば、変更の影響はさらに追いにくくなります。

本書では、別のアプローチを紹介します。値を変えずに保ちたいデータは、作成後も元の値を保ち、変更が必要なときは新しいバージョンを明示的に作ります。こうすることで、呼び出し元は元のデータを変更せずに変換結果を使うことができ、各処理のテストもしやすくなります。

銀の弾丸ではない

本書は、オブジェクト指向(OOP)を捨てるよう勧めるものではありません。オブジェクト指向は、複雑なビジネスルールのカプセル化、ライフサイクルを持つエンティティの管理、オブジェクトが常に満たすべきルールの維持といった用途に適しています。これらの場面では、今もオブジェクト指向のカプセル化が必要です。

また、関数型プログラミング(functional programming)の本でもありません。関数型プログラミングをあらかじめ知らなくても、本書を読み、その方法を活用できます。具体的なデータフローから出発し、どこにイミュータブル設計を採用する価値があり、どのようなコストが生じるのかを考えていきます。

判断力を養う

C# の record、init、with、イミュータブルコレクション、パターンマッチングを使うと、イミュータブルなデータモデルを作り、データ変換で状態の変化を表現しやすくなります。それぞれの使い方はすでに知っていても、実際の業務フローの中でどう組み合わせればよいか、まだ迷うかもしれません。本書では例を通じて、これらの機能でデータの作成、検証、変換をどう組み立てるかを説明し、構文の知識をプロジェクトで使える設計手法につなげます。

すべての型を record に変えたり、すべてのメソッドをサービスクラス(service class)へ移したりすれば、よい設計になるわけではありません。イミュータブルなモデルも振る舞いを持てます。データと振る舞いをどう配置するかは、ビジネスルール、呼び出し元での使い方、保守コストを踏まえて考える必要があります。

本書を通じて身につけてほしいのは、次のような判断力です。コードや設計上の問題を見たとき、そこにイミュータブル設計(immutable design)が適しているかを判断し、適しているなら、どう実現すればよいかを考えられること。 例を読み進めながら、どの変更がコードをわかりやすくするのか、どこは今の方法で十分なのかを、一緒に見極めていきましょう。

それでは、最も基本的な問いから始めましょう。ミュータブルなオブジェクトは、いったいどのような問題を引き起こすのでしょうか。