ASP.NET Core 6.0 を用いたクリーンアーキテクチャの実践的設計

clean architecture は、システムの中心にあるビジネスロジックを外部環境(UIやデータベースなど)から分離し、依存関係を内向きに向けることで、変更への耐性と保守性を高める設計手法です。本稿では、ASP.NET Core 6.0 を用いて実際のプロジェクト構成とコード実装を通じて、その具体的な設計と実現方法を解説します。

カテゴリ別プロジェクト構成

クリーンアーキテクチャでは、依存関係の方向性为核心(Core)に集約されるよう設計します。これにより、ビジネスルールの変更に対して影響範囲を最小限に抑え、テスト容易性や再利用性を担保します。実装は主に以下の3つのフォルダブロックで構成されます。

Core 層(ビジネスロジック中心)

このレイヤーは、ドメイン知識・業務ルールを表すコードのみを含み、他の層への依存を一切持ちません。

  • Iot.Domain ドメインエンティティ、値オブジェクト、 búsquedaキーワード基底インターフェース(例: IMaterialRepository)、列挙型、定数などを定義するプロジェクトです。リポジトリインターフェースはこの層で定義し、実装は下層(Infrastructure)で行います。

  • Iot.Application ビジネスフローを司るサービス・DTOs・コマンド・クエリ(CQRS)・イベントハンドラ・例外クラス・AutoMapperProfile などを配置します。MediatR を活用してリクエスト・レスポンス パイプラインを統一的に管理します。 依存先: Iot.Domain

# Core 層プロジェクト作成例
cd src
mkdir 1.core && cd 1.core

dotnet new classlib -n Iot.Domain --framework net6.0
dotnet new classlib -n Iot.Application --framework net6.0

cd Iot.Application
dotnet add reference ../Iot.Domain/Iot.Domain.csproj

Infrastructure 層(外部インフラ実装)

抽象レイヤーで定義されたコンポーネントの concrete 実装を提供します。これには永続化処理、メール/SMS 送信、外部 Web API 連携なども含まれます。

  • Iot.Data Entity Framework Core によるデータベースアクセス、マイグレーション設定、農業機器デバイス等のリポジトリ実装を担います。依存は上記 Iot.Application および Iot.Domain

  • Iot.Shared 複数サービス間で共有したいヘルパークラスや構成(例: EmailConfig, ShipmentDateConverter, RetryPolicy)などまとめます。他のサービスに NuGet パッケージとして配布可能な形態も想定。

# Infrastructure 層
cd ../../
mkdir 2.infrastructure && cd 2.infrastructure

dotnet new classlib -n Iot.Data --framework net6.0
dotnet new classlib -n Iot.Shared --framework net6.0

cd Iot.Data
dotnet add reference ../../1.core/Iot.Domain/Iot.Domain.csproj
dotnet add reference ../../1.core/Iot.Application/Iot.Application.csproj

cd ../Iot.Shared
dotnet add reference ../../1.core/Iot.Application/Iot.Application.csproj

Presentation 層(エントリーポイント)

HTTP リクエストを処理し、Application 層のサービスをオーケストレーションします。API コントローラー、フィルター、JWT 認証 설정、OpenAPI サポートなどを含みます。

  • Iot.WebApi Program.cs, Startup.cs(必要時), API コントローラー群(例: MaterialsController)を配置。依存は Iot.ApplicationIot.Data(DI 注入用に)。

また、フロント側(Vue.js 3 アプリ)と通信するための正当なAPIベースを提供し、CORS やレスポンス整形などの要件に対応します。

# Presentation 層 - WebAPI プロジェクト
cd ../../
mkdir 3.presentation && cd 3.presentation

dotnet new webapi -n Iot.WebApi --framework net6.0
cd Iot.WebApi

dotnet add reference ../../1.core/Iot.Application/Iot.Application.csproj
dotnet add reference ../../2.infrastructure/Iot.Data/Iot.Data.csproj
dotnet add reference ../../2.infrastructure/Iot.Shared/Iot.Shared.csproj

解決策全容の登録と依存管理

すべてのプロジェクトが作成されたら、Unified Solution として一つにまとめます。

cd ../../
dotnet new sln -n IoTPlatform
dotnet sln add ./src/1.core/Iot.Domain/Iot.Domain.csproj
dotnet sln add ./src/1.core/Iot.Application/Iot.Application.csproj
dotnet sln add ./src/2.infrastructure/Iot.Data/Iot.Data.csproj
dotnet sln add ./src/2.infrastructure/Iot.Shared/Iot.Shared.csproj
dotnet sln add ./src/3.presentation/Iot.WebApi/Iot.WebApi.csproj

依存のチェックや build 時のエラー監視には以下のコマンドが有用です:

dotnet build src/1.core/Iot.Application/Iot.Application.csproj
dotnet build src/2.infrastructure/Iot.Data/Iot.Data.csproj
dotnet build src/3.presentation/Iot.WebApi/Iot.WebApi.csproj

これにより、別のポリシーで独立テスト可能な状態を維持しつつ、CI/CD パイプラインへの統合が容易になります。

テスト戦略の設計

クリーンアーキテクチャでは、テストの独立性とカバレッジを重視します。

  • 単体テスト(Unit Test) アプリケーション层面のロジックのみを対象にし、外部依存はモック(Moq、NSubstitute)や InMemory DB を使って代替します。XUnit/NUnit/MSTest いずれもサポート可。

  • 統合テスト(Integration Test) DI コンテナを介した実環境に近いライフサイクルを再現。WebApplicationFactory を活用し、HTTP レベルでの振る舞いを検証。データベースはrequested単位でトランザクション Rollback を活用し、テスト結果の整合性を保ちます。

  • E2E / 機能テスト Playwright や Selenium を使った UI 自動検証も可能。ただし、クリーンアーキテクチャ下では、UIテストは必要最低限に抑え、ロジックテスト主導(strategy-first)のアプローチを推奨します。

// テストプロジェクト例(Iot.Application.Test)

public class MaterialServiceTests
{
    [Fact]
    public async Task CreateMaterial_ShouldReturnCreatedResult_WhenValidInput()
    {
        // Arrange
        var mockRepo = new Mock<IMaterialRepository>();
        mockRepo.Setup(r => r.AddAsync(It.IsAny<Material>())).ReturnsAsync(new Material { Id = Guid.NewGuid() });

        var service = new MaterialService(mockRepo.Object);

        var dto = new CreateMaterialDto { Name = "TemperatureSensor", Unit = "℃" };

        // Act
        var result = await service.CreateAsync(dto);

        // Assert
        Assert.NotNull(result);
        Assert.Equal("TemperatureSensor", result.Name);
        mockRepo.Verify(r => r.AddAsync(It.IsAny<Material>()), Times.Once);
    }
}

依存関係の強制遵守とレイヤー分離の利点

この構成によって得られる主な恩恵は、以下のような点です。

  1. 可視性 × 可逆性 ビジネスルールが単一の場所(Core)に集約されるため、変更要因を明確にbounds化できます。

  2. テスト容易性 外部依存(DB, API, メールサーバー)がモック可能なことにより、CI 環境でも迅速にテスト実施が可能になります。

  3. ポート・アダプタ形式の柔軟性 Presentation 層で Web/API/Swagger/Vue.js のいずれを採用しても、Application/Domain 層への変更は最小に抑えられます。

  4. **将来的なマイクロサービス移行ス"" Shared レイヤーに独立させる対象(例: 個人認証モデル、住所正規化処理)を適切に抽出しておくことで、将来的なDDDに基づくサービス境界分割がスムーズになります。

このように、クリーンアーキテクチャは単なる「レイヤー分割」ではなく、エンタープライズアプリケーションの長期的な「進化可能性(Evolutionability)」を支える設計哲学そのものです。今後は、 Persistence Layer の実装や CQS パイプラインの構築、MediatR を使ったコントローラー統合までを解説していきます。

タグ: CleanArchitecture DDD MediatR DependencyInversion EntityFrameworkCore

7月28日 18:20 投稿