この記事について
前回の記事はこちら
基幹システム移行で“偶然”得た、DDDの本質体験 ― 業務ロジックだけを新規開発した話
前回の記事でご紹介した基幹システム移行における私の経験について、その背景と具体的なメカニズムを深掘りするものです。不慣れな私が感じた体験を共有できれば幸いです。
長文となります。お急ぎの方は要約をご覧ください。
複雑な基幹システム移行で、私はTDD(テスト駆動開発)とDDD(ドメイン駆動設計)に挑みました。
UIやDBを切り離し、純粋な業務ロジックの開発に集中できたことが、理想的な環境でした。
デグレード(機能後退)の恐怖があったため、ユニットテストを導入し、
ビジネスルールに焦点を当ててテストコードを作成。
これにより、「変更が怖くない」という安心感が生まれました。
さらに、コードの読みづらさに直面した際、責務分離や値オブジェクトの導入でコードを整理。
結果、複雑なロジックの実装が驚くほどスムーズに進み、品質と生産性を向上できたのです。
TDD/DDDに踏み出せない、あの「高い壁」
私はこれまで、TDD(テスト駆動開発)やDDD(ドメイン駆動設計)の有効性については書籍などで知識としては知っていました。しかし、いざ自分のプロジェクトで実践しようとすると、いつも目の前に高い壁が立ちはだかるように感じていました。
その壁とは、以下のような疑問や不安でした。
- UIやDBのテスト自動化は、どう考えてもコストが高い。
- DBを分離してテストしやすくするには、Repositoryパターンなど難解なデザインパターンを駆使する必要があるように見える。その実装イメージもわかないし、準備に時間がかかりそうだ。
- そもそも「ドメイン層」って何だ?具体的にどうコードに落とし込むんだ?
これらの疑問が、TDDやDDDに踏み出せない原因でした。
直面した基幹システム移行の「手詰まり」
今回の話は、長年自社システム部門で運用してきた基幹システム(レガシーシステム)の移行プロジェクトで起こりました。
移行プロジェクトを進めていく中、私たちは協力会社と連携し、一部業務を新規システムで進める形をとっていました。
私たちが担当したのは外部データ連携と現場向けの帳票などの開発です。
一方、協力会社には、その帳票発行の前処理となる複雑な業務ロジック(UIやデータベース含めて)を担ってもらう構成です。(以下、新システムと呼びます)
プロジェクトが進む中で、ある追加要望が持ち上がりました。それは、新システムに登録する前段階で、複雑な業務ロジックの判定処理を使いたいというものでした。(具体的には在庫チェックのような機能をご想像ください)
もともとはデータ連携の一環として簡易的に対応していましたが、本稼働が始まると、これが本格的な業務ロジックの判定処理でなければならないことが判明しました。
しかし、この判定処理は新システムにすでに実装されているものの、前後の処理と密結合しており、残念ながら一部を切り出して使用することは困難な状況でした。
というわけで、私は、追加要望で「何を実現すべきか」について、頭の中には高い解像度でイメージがあり、仕様も書けるのに、既存システムに「手が出せない」という、まさしく「手詰まり」な状況に陥っていたのです。
「安くあがる」がひらいた「あえて切り離す」戦術
開発したい「複雑な業務ロジック」の状況は、以下の通りでした。
- 入力はテキストファイル:上位システムからのデータファイルがインプットであり、DB連携機能の開発が不要でした
- 出力もテキストファイル:計算結果は新システムに渡すファイル形式であり、結果をDBに永続化するDB連携機能は不要でした
- UIや帳票も既存活用:UIや帳票もすでに開発済みのものがあったため、これらを新規に開発することも避けて通るのが合理的でした
つまり「DB関連機能の開発は工数がかかるだけで避けて通るほうが安くあがる状況」が広がっていたのです。
この環境は、私がTDD/DDD実践の壁だと感じていた「UIやDBの考慮」という複雑さを、意図せずして、しかし極めて合理的に回避できることを意味しました。まさしく純粋な「業務ロジック」だけに集中できるという、ある意味理想的な環境を手に入れたのです。
デグレード(機能後退)の恐怖から解放する、TDDへの必然性
しかし、もう一つ、私には大きな懸念がありました。それは「デグレード(機能後退)」です。
これまでの私はデータベース主体で開発を進めてきました。開発時のテストは、SQLファイルで保存しておいて、機能追加後に再度流して結果をチェックする、という方法が主流でした。
しかし、DBを使わないロジック開発で、今後も同じ品質のテストができるのか、本当にデグレードを防げるのか――大きな不安を抱えていました。
このデグレードの恐怖こそが、TDD/DDDの実践を決めた二つ目の、そして決定的な理由でした。「デグレードさせずに、かつ効率的に変更を加えられる方法」が必要だったのです。
「複雑な業務ロジック」の実装を劇的にスムーズにした3つの理由
開発環境として、ユニットテストとクラスライブラリのみ用意し、そこで開発を行いました。結果的に、あの「手詰まり」だった状況下での開発は、驚くほどスムーズに進んでいきました。その理由を整理すると、以下の3つに集約されます。
1. 業務知識を持つ「私」が純粋なドメインに集中できた
詳細な仕様書が存在しない中でも、これまでの経験で培った業務知識があったため、実現したいことは頭の中で「高い解像度」でイメージできていました。
そして、UIやDB、帳票といった「周辺の要素」に意識を分散させる必要がなかったことこそが、私の開発を加速させ、品質を高める直接的な要因となりました。
UIやDBは、データ整合性、レイアウトなど、考えることが非常に多く、それらを同時に扱うのは 「認知負荷」の高い作業です。しかし、今回はこれらを 「あえて切り離す」 ことで、「この業務ロジックの本当に正しい振る舞いは何か?」「エッジケースをどのように扱うべきか?」 という、ビジネスの本質的な問いに深く集中できました。この圧倒的な集中が、複雑なロジックを迷うことなく、迅速かつ高品質にコードへ落とし込むことを可能にしたのです。
2. ユニットテストがデグレードの恐怖から解放し、変更を「怖くない」ものに変えたから
慣れない開発手法で、機能追加のたびにデグレードが発生し、目視テストに時間を奪われるという「変更への不安」がありました。しかし、TDDのアプローチでテストを自動化したことで、この状況は劇的に変わりました。
書籍でよく見るような境界値チェックなどは最低限のため、あまり褒められたものではないのかもしれません。しかし「ビジネスルールを正確に実行できるか」という、業務上の期待値に集中してテストコードを作成しました。複雑な条件分岐や計算を、具体的な入力と期待される出力を含むユニットテストとして表現することで、ロジックの意図が明確になり、実装時の迷いや手戻りが激減しました。
そして最も大きな変化は、「デグレードの恐怖」からの解放です。機能追加やリファクタリングを行うたびに、ユニットテストが実行され、問題があればすぐに検出してくれます。これにより、「変更が怖くない」という感覚が芽生えました。
そのため「コードが読みづらい」と感じたら、その時点で内部設計を修正し、「コードがスリムかつ堅牢であること」だけを考えて、メソッドや変数名を変更できました。これにより、バグの少ない安定したコードが生まれ、結果として開発がよりスムーズに進むようになったのです。DBの列名などに配慮する必要もなく、業務ロジックの用語でストレートにコードを記述できたのも、大きな助けとなりました。
3. 責務分離と値オブジェクト化がコードを見通しよく保ち、可読性の維持に貢献した
開発は少しずつ機能を追加していく方式をとりました。
そのため一番最初のコードは、抽象化やファイル分割を避け、業務フローやロジックをそのまま追いやすい形で実装しました。そのため責務分離も最低限で、値オブジェクトなどとは無縁の実装でした。
クラスは使いますが、その中身は文字列や数値の基本型の羅列でしたが、不要な項目がないため、それなりに見通しの良い状態でした。
しかし、徐々に機能追加する中で、項目は増え、条件判断やフローは複雑さを増していきます。その中で、次の機能追加のためにコードを読み直すと、「読みづらい」と感じる場面が増えてきました。
そこで初めてDDD的な手法を意図的に使い始めます。
コードを整理し、フローだけが残り、複雑な条件判断がクラスや拡張メソッドなどの「隅っこ」に収まるような感覚が得られるようになってから、追加実装を行うようにしたことで、認知負荷が低減され可読性の維持に貢献しました。(具体例を上げると長くなるので別の機会に)
特に感動した場面は、値オブジェクトの導入でした。きっかけは、テストパターンを増やすタイミングでした。序盤はテストデータをコードに直書きしたので、クラスのプロパティが増えすぎた結果、「何番目の引数が何の値か」が分かりづらく、テストデータの登録ミスを頻繁に起こしてしまったのです。そのため関連する文字列や数値を一つのクラス(値オブジェクト)としてまとめるようにしました。するとコードが読みやすくなりミスもなくなりました。このミスの激減に、私は本当に感動しました。こんな基本的なことで感動できるのは初学者のみが感じることのできる特典かもしれません。
また、後半になると、SOLID原則にある単一責務の分離を意識して実施するようになりました。責務の分離の観点でAIのチェックを受けるスタイルです。「コードが読みづらい」と感じる部分の大半が、責務の分離が不十分だったためです。
これらの体験が、コードの可読性と保守性を高めることにつながりました。
まとめ
TDDやDDDは難解なイメージがあるかもしれませんが、今回の私の経験が示すように、必ずしも最初から完璧を目指す必要はありません。もしTDD/DDDへの一歩を踏み出せずにいるなら、まずは「UIやDBを切り離して純粋な業務ロジックに集中できる部分」から小さく始めてみることを強くお勧めします。
補足
自己紹介
私は倉庫会社のシステム部門で、社内で利用するシステムの設計・開発・運用を担当しています。
これまでの開発経験
これまでの開発経験は、『ユースケース実践ガイド: 効果的なユースケースの書き方 アリスター コーバーン (著)』や『業務システムのための上流工程入門 渡辺幸三 (著)』といった書籍に大きく影響を受けており、CRUD操作のみでユースケースシナリオを解決するようなデータモデルが品質向上と開発速度向上につながると考える、いわゆるデータ中心アプローチを得意としてきました。
今回の話にコードは出てきませんが、主な開発言語はC#で、データベースはSQL Serverです。
個人的な学びとして、C#読書会に参加しており、現在『なぜ依存を注入するのか DIの原理・原則とパターン』という書籍に取り組んでいます。(分厚い本なので、本当に少しずつしか進まずいつ終わるのか不明ですが)
ここで得たDIに関する知識が、コンストラクタインジェクションや単一責務の原則(SOLID原則)について深く理解する助けとなっています。具体的には、ユニットテストではDIコンテナを使っていません。それでも純粋な業務ロジックのテストを行う上で必要十分な実装ができました。
上記で修正した箇所は、全体的に句読点や助詞の抜け、重複表現の修正が主です。誤字脱字というよりは、より自然で読みやすい文章にするための調整となります。
ご自身で再度ご確認いただき、問題なければこのままで良いかと思います。