ワンクリックで
clean-architecture-guide
Modular Monolith + Clean Architecture の設計ガイド。新しいモジュールの作成、画面の実装、機能追加を行う際には、必ずこのスキルを参照して従ってください。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Modular Monolith + Clean Architecture の設計ガイド。新しいモジュールの作成、画面の実装、機能追加を行う際には、必ずこのスキルを参照して従ってください。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
bUnit 2.x を使用した Blazor コンポーネントのユニットテストのベストプラクティスとガイドライン
7 つのカスタムエージェント(Orchestrator / Product Manager / Architect / Developer / Reviewer / Tester / Documentation)が協調して機能開発を自律的に進めるワークフロースキル。仕様策定から実装・テスト・マージ、およびドキュメント追従までの全ライフサイクルを定義する。
このリポジトリでの開発フロー(GitHub Flow)を定義するスキル。feature ブランチの作成から Draft PR、レビュー、Squash Merge までの一連の作業手順とルールを示します。コードを変更する作業を行う場合は、必ずこのスキルに従ってください。
feature-lifecycle で作成済みの PR に対して、レビュー指摘やユーザーの直接指示による微修正を軽量フローで反映するスキル。Orchestrator から Developer に委譲して実行する。
GitHub Cloud Agent の readonly GitHub CLI 制約下で feature-lifecycle を回すためのファイルベース運用スキル。Issue/PR への書き込みができない場合に、仕様・状態台帳・エージェント間ログ・PR 本文案をリポジトリ内ファイルとして管理する。
Playwright MCP server が提供されている場合は MCP を優先し、未提供時は playwright-cli で E2E テストを実行するスキル。ブラウザを headless モードで起動し、テスト結果を screenshots/ 配下の日付付きフォルダーに保存する。E2E テストを実行する際には、必ずこのスキルに従ってください。
| name | clean-architecture-guide |
| description | Modular Monolith + Clean Architecture の設計ガイド。新しいモジュールの作成、画面の実装、機能追加を行う際には、必ずこのスキルを参照して従ってください。 |
このスキルは、本プロジェクトにおける Modular Monolith + Clean Architecture の具体的な設計パターンとルールを定義します。
重要: このスキルに基づいてコード編集・生成を行う際には、対応する言語・プラットフォームのスキル(例:
dotnet10、aspnetcore-blazor10、aspire等)を必ず併せて参照してください。最新の言語機能や API を正しく使用するために必要です。
本プロジェクトは .NET Aspire を使用してアプリケーションの構成・実行・監視を行います。
aspire run で全リソースを起動し、Aspire Dashboard でログ・トレース・メトリクスを確認しますaspire publish でマニフェストを生成し、Azure Container Apps 等にデプロイします.WithReference())を通じて自動的に解決されますsrc/
AppHost/ # Aspire AppHost(オーケストレーション)
ServiceDefaults/ # 共通のサービス設定(テレメトリ、ヘルスチェック等)
Web/ # Blazor Server フロントエンド
Modules/<ModuleName>/ # 各モジュール(Clean Architecture 3 層)
Aspire の詳細な API やパターンについては
aspireスキルを参照してください。
新しいモジュールは以下の 3 プロジェクトで構成します。
src/Modules/<ModuleName>/
<ModuleName>.Domain/ # エンティティ、値オブジェクト(依存なし)
<ModuleName>.Application/ # UseCase、DTO、リポジトリインターフェース(Domain のみ依存)
<ModuleName>.Infrastructure/ # リポジトリ実装、外部サービス連携、DI 登録(Application に依存)
# 1. プロジェクト作成
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
# 2. ソリューションに追加
dotnet sln add src/Modules/<ModuleName>/<ModuleName>.Domain
dotnet sln add src/Modules/<ModuleName>/<ModuleName>.Application
dotnet sln add src/Modules/<ModuleName>/<ModuleName>.Infrastructure
# 3. プロジェクト参照設定
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
# 4. Infrastructure に DI パッケージ追加
dotnet add src/Modules/<ModuleName>/<ModuleName>.Infrastructure package Microsoft.Extensions.DependencyInjection.Abstractions
# 5. Infrastructure に Azure Cosmos DB SDK パッケージ追加
dotnet add src/Modules/<ModuleName>/<ModuleName>.Infrastructure package Microsoft.Azure.Cosmos
# 6. Web からの参照追加
dotnet add src/Web reference src/Modules/<ModuleName>/<ModuleName>.Application
dotnet add src/Web reference src/Modules/<ModuleName>/<ModuleName>.Infrastructure
# 7. 自動生成された Class1.cs を各プロジェクトから削除
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 層が行う |
| リポジトリ内で個別の永続化呼び出しは不要 | Cosmos DB SDK の各操作(CreateItemAsync, ReplaceItemAsync 等)は即座に永続化される。EF Core のような SaveChangesAsync パターンは不要 |
ビジネスの中核となるデータ構造を定義します。
namespace <ModuleName>.Domain;
public class <EntityName>
{
public int ID { get; set; }
public string Name { get; set; } = "";
// ビジネスルールに関わるメソッドもここに配置可能
}
Web 層に公開するデータ転送オブジェクトです。Domain エンティティの代わりにこれを使います。
namespace <ModuleName>.Application;
public record <EntityName>Dto(int ID, string Name);
データアクセスの抽象を定義します。UseCase 内部から使用され、Web 層には公開しません。
namespace <ModuleName>.Application;
public interface I<EntityName>Repository
{
Task<IReadOnlyList<Domain.<EntityName>>> GetAllAsync();
Task UpdateAsync(int id, ...);
}
同一パーティション内で複数操作をアトミックに実行する必要がある場合のみ使用します。Cosmos DB SDK の各操作(CreateItemAsync 等)は個別に即座永続化されるため、単一操作の UseCase では不要です。
namespace <ModuleName>.Application;
/// <summary>
/// 同一パーティション内の複数操作をアトミックに実行する場合に使用。
/// Cosmos DB の TransactionalBatch に対応する。
/// 単一操作の UseCase では不要。
/// </summary>
public interface IUnitOfWork
{
Task ExecuteBatchAsync(CancellationToken cancellationToken = default);
}
ポイント: Cosmos DB では個別の CRUD 操作が即座に永続化されるため、EF Core のような
SaveChangesAsyncパターンは不要です。IUnitOfWorkは同一パーティション内の TransactionalBatch が必要な場合のみ使用します。
Web 層が唯一依存する操作インターフェース です。1 つの操作 = 1 つの UseCase とし、ExecuteAsync メソッドを持ちます。
namespace <ModuleName>.Application.UseCases;
// インターフェース
public interface IGet<EntityName>sUseCase
{
Task<IReadOnlyList<<EntityName>Dto>> ExecuteAsync(string? searchText = null);
}
// 実装(Application 層内に配置)
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();
// Domain → DTO 変換
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) : IUpdate<EntityName>UseCase
{
public async Task ExecuteAsync(int id, ...)
{
await repository.UpdateAsync(id, ...);
// Cosmos DB SDK では各操作が即座に永続化されるため、SaveChangesAsync は不要
}
}
データアクセスには Azure Cosmos DB SDK(Microsoft.Azure.Cosmos) を直接使用します。モジュールが使用する Cosmos DB コンテナへのアクセスを提供するプロバイダークラスを定義します。
using Microsoft.Azure.Cosmos;
namespace <ModuleName>.Infrastructure;
/// <summary>
/// モジュールが使用する Cosmos DB コンテナへのアクセスを提供する。
/// </summary>
public class <ModuleName>CosmosContainers
{
public Container <ContainerName> { get; }
public <ModuleName>CosmosContainers(CosmosClient cosmosClient, string databaseName)
{
var database = cosmosClient.GetDatabase(databaseName);
<ContainerName> = database.GetContainer("<container-name>");
}
}
ポイント: Cosmos DB SDK の各操作(
CreateItemAsync,ReadItemAsync,ReplaceItemAsync,DeleteItemAsync)は即座に永続化されます。EF Core のような変更追跡やSaveChangesAsyncパターンは不要です。
リポジトリは <ModuleName>CosmosContainers を注入し、Cosmos DB SDK を使ってデータアクセスを行います。各操作は即座に永続化されます。
using <ModuleName>.Domain;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Linq;
namespace <ModuleName>.Infrastructure;
public class <EntityName>Repository(<ModuleName>CosmosContainers containers) : Application.I<EntityName>Repository
{
public async Task<IReadOnlyList<<EntityName>>> GetAllAsync()
{
var query = containers.<ContainerName>
.GetItemLinqQueryable<<EntityName>>()
.Where(e => e.Type == "<entity-type>")
.ToFeedIterator();
var results = new List<<EntityName>>();
while (query.HasMoreResults)
{
var response = await query.ReadNextAsync();
results.AddRange(response);
}
return results;
}
public async Task UpdateAsync(string id, string partitionKey, ...)
{
var response = await containers.<ContainerName>.ReadItemAsync<<EntityName>>(
id, new PartitionKey(partitionKey));
var item = response.Resource;
// プロパティの更新
await containers.<ContainerName>.ReplaceItemAsync(item, id, new PartitionKey(partitionKey));
// Cosmos DB では ReplaceItemAsync で即座に永続化される
}
}
モジュール単位で Add<ModuleName>Module() 拡張メソッドを提供します。
using <ModuleName>.Application;
using <ModuleName>.Application.UseCases;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.DependencyInjection;
namespace <ModuleName>.Infrastructure;
public static class <ModuleName>ServiceCollectionExtensions
{
public static IServiceCollection Add<ModuleName>Module(this IServiceCollection services, CosmosClient cosmosClient, string databaseName)
{
// Cosmos DB コンテナプロバイダー
services.AddSingleton(new <ModuleName>CosmosContainers(cosmosClient, databaseName));
// Infrastructure(リポジトリ実装)
services.AddScoped<I<EntityName>Repository, <EntityName>Repository>();
// Application(UseCase)
services.AddScoped<IGet<EntityName>sUseCase, Get<EntityName>sUseCase>();
services.AddScoped<IUpdate<EntityName>UseCase, Update<EntityName>UseCase>();
return services;
}
}
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();
}
}
Web プロジェクトでは Aspire ServiceDefaults を追加し、各モジュールの DI 登録を行います。
接続文字列は Aspire AppHost の .WithReference() によって自動注入されます。
using <ModuleName>.Infrastructure;
var builder = WebApplication.CreateBuilder(args);
// Aspire ServiceDefaults(テレメトリ、ヘルスチェック等)
builder.AddServiceDefaults();
// モジュール登録(CosmosClient は Aspire 経由で注入、DB 名は設定から取得)
var cosmosClient = new CosmosClient(builder.Configuration.GetConnectionString("cosmos")!);
builder.Services.Add<ModuleName>Module(cosmosClient, "{projectname}-db");
var app = builder.Build();
// Aspire デフォルトエンドポイント(ヘルスチェック等)
app.MapDefaultEndpoints();
AppHost の Program.cs で Web プロジェクトとインフラリソースを接続します。
var builder = DistributedApplication.CreateBuilder(args);
// インフラリソース
var cosmos = builder.AddAzureCosmosDB("cosmos")
.AddDatabase("{projectname}-db");
var cache = builder.AddRedis("cache");
// Web アプリケーション
builder.AddProject<Projects.Web>("web")
.WithReference(cosmos)
.WithReference(cache)
.WaitFor(cosmos);
builder.Build().Run();
ポイント: モジュールが使用する Cosmos DB やキャッシュなどのインフラリソースは AppHost で定義し、
.WithReference()で Web プロジェクトに渡します。Web のProgram.csではbuilder.Configuration.GetConnectionString()で自動的にその接続情報を取得できます。Cosmos DB のデータベース名・コンテナ名は各モジュールの DI 登録で指定します。
新しいモジュールや機能を実装する際は、単体テストとインテグレーションテストの両方を必ず作成してください。
MSTest の詳細なベストプラクティスは
csharp-mstestスキルを参照してください。 Aspire Testing の詳細な API はaspireスキルの Testing リファレンス を参照してください。
tests/
<ModuleName>.Application.Tests/ # 単体テスト(UseCase、ドメインロジック)
AppHost.IntegrationTests/ # インテグレーションテスト(Aspire AppHost 経由)
UseCase とドメインロジックの単体テストを モジュールごとに 作成します。
# 1. テストプロジェクト作成
dotnet new mstest -n <ModuleName>.Application.Tests -o tests/<ModuleName>.Application.Tests --framework net10.0
# 2. ソリューションに追加
dotnet sln add tests/<ModuleName>.Application.Tests
# 3. テスト対象への参照追加
dotnet add tests/<ModuleName>.Application.Tests reference src/Modules/<ModuleName>/<ModuleName>.Application
I<EntityName>Repository と IUnitOfWork をモック/スタブで差し替えMethodName_Scenario_ExpectedBehaviorusing <ModuleName>.Application.UseCases;
using <ModuleName>.Domain;
namespace <ModuleName>.Application.Tests;
[TestClass]
public sealed class Get<EntityName>sUseCaseTests
{
[TestMethod]
public async Task ExecuteAsync_WithSearchText_ReturnsFilteredResults()
{
// Arrange
var repository = new Fake<EntityName>Repository([
new <EntityName> { ID = 1, Name = "Alpha" },
new <EntityName> { ID = 2, Name = "Beta" },
]);
var useCase = new Get<EntityName>sUseCase(repository);
// Act
var result = await useCase.ExecuteAsync("Alpha");
// Assert
Assert.AreEqual(1, result.Count);
Assert.AreEqual("Alpha", result[0].Name);
}
}
Aspire Hosting.Testing を使い、AppHost から全リソースを起動して End-to-End に近いテストを行います。
# 1. テストプロジェクト作成
dotnet new mstest -n AppHost.IntegrationTests -o tests/AppHost.IntegrationTests --framework net10.0
# 2. ソリューションに追加
dotnet sln add tests/AppHost.IntegrationTests
# 3. AppHost への参照と Aspire Testing パッケージ追加
dotnet add tests/AppHost.IntegrationTests reference src/AppHost
dotnet add tests/AppHost.IntegrationTests package Aspire.Hosting.Testing
IsAspireTestProject を追加<PropertyGroup>
<IsAspireTestProject>true</IsAspireTestProject>
</PropertyGroup>
DistributedApplicationTestingBuilder で AppHost を丸ごと起動await using で確実にクリーンアップusing System.Net;
using Aspire.Hosting.Testing;
namespace AppHost.IntegrationTests;
[TestClass]
public sealed class HealthCheckTests
{
[TestMethod]
[Timeout(120_000)]
public async Task Web_HealthEndpoint_ReturnsOk()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AppHost>();
await using var app = await builder.BuildAsync();
await app.StartAsync();
await app.WaitForResourceReadyAsync("web");
// Act
var client = app.CreateHttpClient("web");
var response = await client.GetAsync("/health");
// Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
| ルール | 説明 |
|---|---|
| 単体テストはモジュール単位 | 各モジュールの Application.Tests プロジェクトに配置 |
| インテグレーションテストは AppHost 単位 | AppHost.IntegrationTests に集約 |
| テストは独立 | テスト間で状態を共有しない。各テストが自己完結すること |
| リポジトリをモックする | 単体テストでは DB に依存しない。Fake/Mock/Stub を使用 |
| 実インフラで検証する | インテグレーションテストでは Aspire が起動する実コンテナ(DB、キャッシュ等)を使用 |
| PR 前に全テスト通過 | dotnet test --solution {ProjectName}.slnx が成功することを確認してから PR を作成(github-flow 参照) |
新しいモジュールや機能を実装する際に確認してください。
Add<ModuleName>Module() を追加したか.WithReference() で Web に接続しているかbuilder.AddServiceDefaults() と app.MapDefaultEndpoints() を追加しているかdotnet build が成功するかaspire run で正常に起動し、Aspire Dashboard でサービスが確認できるか<ModuleName>.Application.Tests)を作成したかAppHost.IntegrationTests)にヘルスチェック等のテストを追加したかdotnet test --solution {ProjectName}.slnx が全件成功するか