| name | clean-architecture-guide |
| description | Modular Monolith + Clean Architecture の設計ガイド。新しいモジュールの作成、画面の実装、機能追加を行う際には、必ずこのスキルを参照して従ってください。 |
Clean Architecture 設計ガイド
このスキルは、本プロジェクトにおける Modular Monolith + Clean Architecture の具体的な設計パターンとルールを定義します。
重要: このスキルに基づいてコード編集・生成を行う際には、対応する言語・プラットフォームのスキル(例: dotnet10、aspnetcore-blazor10 等)を必ず併せて参照してください。最新の言語機能や API を正しく使用するために必要です。
1. モジュール構成
新しいモジュールは以下の 3 プロジェクトで構成します。
src/Modules/<ModuleName>/
<ModuleName>.Domain/ # エンティティ、値オブジェクト(依存なし)
<ModuleName>.Application/ # UseCase、DTO、リポジトリインターフェース(Domain のみ依存)
<ModuleName>.Infrastructure/ # リポジトリ実装、外部サービス連携、DI 登録(Application に依存)
プロジェクト作成手順
dotnet new classlib -n <ModuleName>.Domain -o src/Modules/<ModuleName>/<ModuleName>.Domain --framework net10.0
dotnet new classlib -n <ModuleName>.Application -o src/Modules/<ModuleName>/<ModuleName>.Application --framework net10.0
dotnet new classlib -n <ModuleName>.Infrastructure -o src/Modules/<ModuleName>/<ModuleName>.Infrastructure --framework net10.0
dotnet sln add src/Modules/<ModuleName>/<ModuleName>.Domain
dotnet sln add src/Modules/<ModuleName>/<ModuleName>.Application
dotnet sln add src/Modules/<ModuleName>/<ModuleName>.Infrastructure
dotnet add src/Modules/<ModuleName>/<ModuleName>.Application reference src/Modules/<ModuleName>/<ModuleName>.Domain
dotnet add src/Modules/<ModuleName>/<ModuleName>.Infrastructure reference src/Modules/<ModuleName>/<ModuleName>.Application
dotnet add src/Modules/<ModuleName>/<ModuleName>.Infrastructure package Microsoft.Extensions.DependencyInjection.Abstractions
dotnet add src/Modules/<ModuleName>/<ModuleName>.Infrastructure package Microsoft.EntityFrameworkCore
dotnet add src/Modules/<ModuleName>/<ModuleName>.Infrastructure package Microsoft.EntityFrameworkCore.<Provider>
dotnet add src/Web reference src/Modules/<ModuleName>/<ModuleName>.Application
dotnet add src/Web reference src/Modules/<ModuleName>/<ModuleName>.Infrastructure
2. 依存関係ルール
Web ──→ Application (UseCase インターフェース、DTO のみ)
Web ──→ Infrastructure (DI 登録の拡張メソッド呼び出しのためのみ)
Infrastructure ──→ Application (リポジトリインターフェースの実装)
Application ──→ Domain (エンティティの利用)
Domain ──→ (依存なし)
禁止事項
| ルール | 説明 |
|---|
| Web から Domain エンティティを直接参照しない | Web 層は Application 層の DTO のみを使用する |
| Web から Repository を直接注入しない | Web 層は UseCase インターフェース経由でのみデータにアクセスする |
| ビジネスロジックを Web / Infrastructure に書かない | ビジネスロジックは Domain と Application のみに配置する |
| Application 層から Infrastructure を参照しない | Application 層はインターフェースを定義し、実装は Infrastructure 層が行う |
| リポジトリ内で SaveChangesAsync を呼ばない | SaveChangesAsync は UseCase が IUnitOfWork 経由で呼ぶ。1 UseCase = 1 トランザクション単位とする |
3. 各層の実装パターン
3.1 Domain 層 — エンティティ
ビジネスの中核となるデータ構造を定義します。
namespace <ModuleName>.Domain;
public class <EntityName>
{
public int ID { get; set; }
public string Name { get; set; } = "";
}
3.2 Application 層 — DTO
Web 層に公開するデータ転送オブジェクトです。Domain エンティティの代わりにこれを使います。
namespace <ModuleName>.Application;
public record <EntityName>Dto(int ID, string Name);
3.3 Application 層 — リポジトリインターフェース
データアクセスの抽象を定義します。UseCase 内部から使用され、Web 層には公開しません。
namespace <ModuleName>.Application;
public interface I<EntityName>Repository
{
Task<IReadOnlyList<Domain.<EntityName>>> GetAllAsync();
Task UpdateAsync(int id, ...);
}
3.4 Application 層 — IUnitOfWork インターフェース
Unit of Work の抽象を定義します。UseCase がトランザクションの完了(SaveChangesAsync)を制御するために使用します。
namespace <ModuleName>.Application;
public interface IUnitOfWork
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
ポイント: IUnitOfWork を Application 層に置くことで、UseCase は Infrastructure 層(DbContext)に直接依存せずにトランザクション管理を行えます。
3.5 Application 層 — UseCase
Web 層が唯一依存する操作インターフェース です。1 つの操作 = 1 つの UseCase とし、ExecuteAsync メソッドを持ちます。
namespace <ModuleName>.Application.UseCases;
public interface IGet<EntityName>sUseCase
{
Task<IReadOnlyList<<EntityName>Dto>> ExecuteAsync(string? searchText = null);
}
public class Get<EntityName>sUseCase(I<EntityName>Repository repository) : IGet<EntityName>sUseCase
{
public async Task<IReadOnlyList<<EntityName>Dto>> ExecuteAsync(string? searchText = null)
{
var entities = await repository.GetAllAsync();
var filtered = string.IsNullOrEmpty(searchText)
? entities
: entities.Where(e => e.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase)).ToList();
return filtered.Select(e => new <EntityName>Dto(e.ID, e.Name)).ToList();
}
}
namespace <ModuleName>.Application.UseCases;
public interface IUpdate<EntityName>UseCase
{
Task ExecuteAsync(int id, ...);
}
public class Update<EntityName>UseCase(I<EntityName>Repository repository, IUnitOfWork unitOfWork) : IUpdate<EntityName>UseCase
{
public async Task ExecuteAsync(int id, ...)
{
await repository.UpdateAsync(id, ...);
await unitOfWork.SaveChangesAsync();
}
}
3.6 Infrastructure 層 — DbContext(Unit of Work 実装)
データアクセスには Entity Framework Core を使用します。DbContext が Application 層の IUnitOfWork を実装し、Unit of Work パターンの役割を担います。トランザクション管理と変更追跡を一括して行います。
using <ModuleName>.Application;
using <ModuleName>.Domain;
using Microsoft.EntityFrameworkCore;
namespace <ModuleName>.Infrastructure;
public class <ModuleName>DbContext(DbContextOptions<<ModuleName>DbContext> options) : DbContext(options), IUnitOfWork
{
public DbSet<<EntityName>> <EntityName>s => Set<<EntityName>>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<<EntityName>>(entity =>
{
entity.HasKey(e => e.ID);
});
}
}
3.7 Infrastructure 層 — リポジトリ実装
リポジトリは DbContext を注入し、EF Core を使ってデータアクセスを行います。SaveChangesAsync() はリポジトリでは呼び出さず、UseCase 側で制御します。
using <ModuleName>.Domain;
using Microsoft.EntityFrameworkCore;
namespace <ModuleName>.Infrastructure;
public class <EntityName>Repository(<ModuleName>DbContext dbContext) : Application.I<EntityName>Repository
{
public async Task<IReadOnlyList<<EntityName>>> GetAllAsync()
{
return await dbContext.<EntityName>s.ToListAsync();
}
public async Task UpdateAsync(int id, ...)
{
var item = await dbContext.<EntityName>s.FindAsync(id);
if (item is not null)
{
}
}
}
3.8 Infrastructure 層 — DI 登録
モジュール単位で Add<ModuleName>Module() 拡張メソッドを提供します。
using <ModuleName>.Application;
using <ModuleName>.Application.UseCases;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace <ModuleName>.Infrastructure;
public static class <ModuleName>ServiceCollectionExtensions
{
public static IServiceCollection Add<ModuleName>Module(this IServiceCollection services, string connectionString)
{
services.AddDbContext<<ModuleName>DbContext>(options =>
options.UseSqlServer(connectionString));
services.AddScoped<IUnitOfWork>(sp => sp.GetRequiredService<<ModuleName>DbContext>());
services.AddScoped<I<EntityName>Repository, <EntityName>Repository>();
services.AddScoped<IGet<EntityName>sUseCase, Get<EntityName>sUseCase>();
services.AddScoped<IUpdate<EntityName>UseCase, Update<EntityName>UseCase>();
return services;
}
}
4. Web 層(Blazor ページ)の実装パターン
Web 層は UseCase インターフェースと DTO のみ を使用します。
@page "/<route>"
@using <ModuleName>.Application
@using <ModuleName>.Application.UseCases
@rendermode InteractiveServer
<PageTitle>ページタイトル</PageTitle>
<h1>ページタイトル</h1>
@* UI コンポーネント *@
@code {
[Inject]
private IGet<EntityName>sUseCase Get<EntityName>sUseCase { get; set; } = default!;
[Inject]
private IUpdate<EntityName>UseCase Update<EntityName>UseCase { get; set; } = default!;
private IReadOnlyList<<EntityName>Dto> items = [];
protected override async Task OnInitializedAsync()
{
items = await Get<EntityName>sUseCase.ExecuteAsync();
}
}
Program.cs での登録
using <ModuleName>.Infrastructure;
builder.Services.Add<ModuleName>Module(builder.Configuration.GetConnectionString("<ModuleName>")!);
5. チェックリスト
新しいモジュールや機能を実装する際に確認してください。