ちょうぜつソフトウェア設計入門――PHPで理解するオブジェクト指向の活用
こちら表紙的に後回しになっておりましたが実に面白い。
今回はその中でも P.97 「インターフェース分離原則」から着想を得て、DDD の Repository を C# で実装してみようと思います。(過去の自分の実装を見直す意味合いの強い記事になってしましました。)
※(ボブおじさんや Microsoft に倣って)以後「インターフェイス」と表記します。
目的
C# で「小さなインターフェイス」の Repository を実現する
- 必要なものだけ提供
- 実装が面倒になるのはイヤ・・・
はじめに
DDD や SOLID の詳細については先達に任せるとして、キーワードだけおさらいしておきましょう。
ISP(インターフェイス分離の原則)
- Interface Separation Principle
- ユーザーが使うインターフェースだけを提供しましょう
Repository
- データの永続化(取り出しや保存)を担うオブジェクト
- Domain 層にインターフェイス、Infra 層に実装を置いてデータアクセスの詳細をビジネスロジックから分離する
アーキテクチャー
今回は以下のようなアーキテクチャーのアプリケーションを実装するものとします。

この中でも、Domain 層に定義する RepositoryInterface と Infra 層に定義する Repository の実装について考えていきます。また、Application 層から Repository をどのように使っていくかも見ていきます。
実装
永続化対象
シンプルストレートに話を進めるため、簡単なデータクラスにします。このデータを何らかの技術で永続化するものとお考え下さい。
namespace DemoRepositoryPattern.Domain.ProductModels;
public sealed class Product
{
public string ModelName { get; }
public string SerialNumber { get; }
public string Status { get; }
public Product(string modelName, string serialNumber, string status)
{
ModelName = modelName;
SerialNumber = serialNumber;
Status = status;
}
}
Repository インターフェイス
CRUD の処理を一通り実現できるようにインターフェイスを宣言します。ポイントは、各操作ごとにインターフェイスにして「分離」します。
namespace DemoRepositoryPattern.Domain.ProductModels;
public interface IProductCreateRepository
{
public Product Create(Product product);
}
public interface IProductReadRepository
{
public Product Read(string modelName, string serialNumber);
}
public interface IProductUpdateRepository
{
public Product Update(Product product);
}
public interface IProductDeleteRepository
{
public bool Delete(Product product);
}
インターフェイス名は自分でもあまりしっくり来ていません。いちおうクラス名の意図としては、DomainObject が大事なので先に Product を持ってきています。メソッド名は具象クラスの実装時に名前衝突を考えなくていいし断然読みやすくなるので Create.Create() のようになるのも許容しています。
Repository 具象クラス
とりあえずビルドが通るようにモックアップを作成します。このとき、ひとつのクラスで前述のインターフェイスすべてを実現します。
namespace DemoRepositoryPattern.Infra.ProductRepositories;
public sealed class MockProductRepository :
IProductCreateRepository,
IProductReadRepository,
IProductUpdateRepository,
IProductDeleteRepository
{
public MockProductRepository()
{
}
public Product Create(Product product)
{
return product;
}
public Product Read(string modelName, string serialNumber)
{
Product product = new(modelName, serialNumber, "Ready");
return product;
}
public bool Update(Product product)
{
return product;
}
public bool Delete(Product product)
{
return true;
}
}
SQL Database を使って永続化を実現するぞ、ってときは同じように SqlDbProductRepository を実装してあげます。
Application の UseCase
作成した Repository を利用する UseCase を実装してみます。新しい Product を登録するという UseCase です。
このように、Repository の操作の中でも Create だけに依存していることがわかります。Read/Update/Delete に何が起ころうが、この UseCase は安泰なわけです。
namespace DemoRepositoryPattern.Application.ProductUseCases;
public interface IRegisterProductUseCase
{
public bool Execute(string modelName, string serialNumber);
}
public sealed class RegisterProductUseCase : IRegisterProductUseCase
{
private readonly IProductCreateRepository _productCreateRepository;
public RegisterProductUseCase(
IProductCreateRepository productCreateRepository)
{
_productCreateRepository = productCreateRepository;
}
public bool Execute(string modelName, string serialNumber)
{
Product product = new(modelName, serialNumber, "new");
Product created = _productCreateRepository.Create(product);
if (product.ModelName == created.ModelName && product.SerialNumber == created.SerialNumber)
{
return true;
}
return false;
}
}
おわりに
今回の ISP を考慮した Repository の実装はこのようになりました。(UseCase の名前は適当です)

効果
- Repository のインターフェイスを分離することで利用箇所での疎結合を実現できる
- 具象クラスをひとつにすることで、実装の簡単さや保守性を保つことができる
参考
P.97 「インターフェース分離原則」
P.99「ISP: インターフェイス分離の原則」
P.146 「リポジトリ」