導入
「DBからユーザーを探す」処理が業務ロジックの中に直書きされていると、SQLとビジネスルールが混ざって読みにくくなります。Repository パターンは、データの出し入れを「コレクションのような窓口」に隠し、業務ロジックを保存先から切り離します。
図解
flowchart LR
S["業務ロジック"] -->|Add / FindById| R["IRepository<User>"]
R -.-> DB["DB / メモリ / ファイル<br/>(差し替え可能)"]
style R fill:#e1f5fe
サンプル
Repositoryは「データの型」「窓口の抽象」「保存先ごとの実装」に分かれます。保存先を増やすときはファイルを1つ足すだけです。
1. データの型(User.cs)。
public record User(int Id, string Name);
2. 窓口の抽象(IUserRepository.cs)。業務ロジックが知るのはこの形だけ。SQLは一切出てきません。
public interface IUserRepository
{
void Add(User user);
User? FindById(int id);
}
3. 保存先ごとの実装(InMemoryUserRepository.cs)。本番用のDB実装はSqlUserRepository.csとして隣に置く、という具合に増やします。
public class InMemoryUserRepository : IUserRepository
{
private readonly List<User> _users = new();
public void Add(User user) => _users.Add(user);
public User? FindById(int id) => _users.FirstOrDefault(u => u.Id == id);
}
4. 使う側(Program.cs)。まるでコレクションを触っているように読めます。
IUserRepository repo = new InMemoryUserRepository();
repo.Add(new User(1, "Taro"));
repo.Add(new User(2, "Jiro"));
var u = repo.FindById(1);
Console.WriteLine(u?.Name); // Taro
- Repository は「データの出し入れ」を抽象化した窓口
- 業務ロジックは
IUserRepositoryにだけ依存し、保存先(DB/メモリ)を知らない - テストではメモリ実装、本番ではDB実装、と差し替えられる
- 保存先の追加=ファイルの追加。既存の業務ロジックは無傷
やってみよう
FindById に加えて All() で全件を返すメソッドを増やし、名前順に並べて出力してみましょう。呼び出し側はSQLを一切書きません。
演習
var repo = new InMemoryBookRepository();
repo.Add("C#入門");
repo.Add("設計の基本");
Console.WriteLine(repo.Count()); // 2
interface IBookRepository { void Add(string title); int Count(); }
// TODO: リストに溜めて Count() で件数を返す InMemoryBookRepository を実装してください
___
- 期待される出力:
2
ヒント1を見る
class InMemoryBookRepository : IBookRepository { private readonly List<string> _b = new(); public void Add(string t) => _b.Add(t); public int Count() => _b.Count; }
ヒント2を見る
内部で List<string> に溜め、Count はそのリストの件数を返します
まとめ
- Repository はデータアクセスを「コレクション風の窓口」に隠す
- 業務ロジックを保存先から切り離せる
- メモリ実装とDB実装を差し替えでき、テストが容易
次回: 複数の変更をまとめて確定する Unit of Work です。