Clean Architecture Solution Template for ASP.NET Core

repository·main·Indexed 12 days ago

https://github.com/jasontaylordev/cleanarchitecture

A structured approach to enterprise application development for ASP.NET Core using .NET Aspire. The template supports Angular and React frontend frameworks, and PostgreSQL, SQL Server, or SQLite database providers. It features a decoupled architecture utilizing MediatR for domain events and IApplicationDbContext for data access, with orchestration and testing managed via .NET Aspire.

Tokens
12.2K
Snippets
51
Records
61
Agent score
91%

What's inside Clean Architecture Solution Template

  1. Understand the React client project structure

    main

    The React client follows a standard Vite/React structure:

    • src/: Contains the React source code.
    • src/main.jsx: The application entry point.
    • src/App.js: The root component.
    • src/components/: Directory for React components.
    • public/: Static assets like favicons and manifests.
    • vite.config.ts: Vite configuration, including API proxy settings.
    • index.html: The base HTML template.
  2. How data access works in the Application layer

    main

    In this architecture, the Application layer accesses data directly via an interface called IApplicationDbContext. This interface exposes DbSet<T> properties for your entities.

    Instead of using a Repository pattern to wrap the database, Command and Query handlers use IApplicationDbContext to perform operations. The concrete implementation, ApplicationDbContext, resides in the Infrastructure layer and implements the interface. This approach allows handlers to leverage the full expressiveness of EF Core (such as projections, .Include(), and raw SQL) without the indirection of a repository layer.

    Key Mental Model:

    • Dependency Direction: The Application layer defines the interface (IApplicationDbContext), and the Infrastructure layer implements it. This satisfies Dependency Inversion because the dependency arrow points inward toward the Application layer.
    • Framework Reference: The Application layer has a compile-time dependency on EF Core abstractions (DbSet<T>, IQueryable<T>), but it remains decoupled from the concrete DbContext and the specific database provider.
  3. When to use the Repository pattern instead of IApplicationDbContext

    main

    The default decision to use IApplicationDbContext directly in handlers is intended for projects that do not strictly follow Domain-Driven Design (DDD).

    Use the Repository pattern if:

    • You are implementing Domain-Driven Design (DDD). In DDD, repositories are a domain concept used to manage aggregates. They should be defined in the Domain layer (e.g., IOrderRepository) and implemented in the Infrastructure layer.
    • You need to express data access in the language of the domain (e.g., FindByCustomer) rather than the language of persistence mechanics.

    Do NOT use the Repository pattern if:

    • You are simply trying to hide EF Core from the Application layer. This ADR argues that wrapping DbContext in a repository adds unnecessary complexity and indirection without providing meaningful abstraction, as the repository methods often end up mirroring EF Core query patterns anyway.
  4. How domain events integrate with MediatR

    main

    In this architecture, domain events are designed to be first-class MediatR notifications. To achieve this, the BaseEvent class in the Domain layer implements the INotification interface from the MediatR.Contracts package.

    This approach allows domain events to be dispatched directly through the Application layer's pub/sub system using IMediator.Publish() immediately after SaveChangesAsync is called, without requiring an intermediate adapter or mapping layer to convert domain events into MediatR notifications.

  5. How orchestration and testing work with .NET Aspire

    main

    The solution uses .NET Aspire as the primary orchestration layer to manage the full application stack for both local development and automated testing. This eliminates the need for manual database installation or manual launching of backend and frontend services.

    There are two specific orchestration projects provided:

    1. AppHost: Orchestrates the entire stack (Frontend, Backend, Databases, etc.) for local development and running Web.AcceptanceTests.
    2. TestAppHost: Orchestrates only the necessary infrastructure (like the database) specifically for use by Application.FunctionalTests.

    By using Aspire, services communicate via service discovery (referencing each other by name) rather than hardcoded URLs or ports. It also automatically configures observability (OpenTelemetry), health checks, and HTTP resilience through the ServiceDefaults project.

  6. Manage the CleanArchitecture React Client with npm scripts

    main

    The React client uses Vite and provides several npm scripts for development, building, and linting. During development, the server proxies API requests to the ASP.NET Core backend.

    # Run in development mode with hot module replacement (opens at https://localhost:44447)
    npm start
    
    # Build the app for production to the 'build' folder
    npm run build
    
    # Preview the production build locally
    npm run preview
    
    # Run ESLint on the src directory
    npm run lint
  7. Use the ADR template for architectural decisions

    main

    When documenting architectural decisions within this project, use the ADR-NNN: Title template to ensure consistency. An Architectural Decision Record (ADR) should follow this structure:

    1. Status: Indicate if the decision is Accepted, Deprecated, or Superseded by [ADR-NNN](link).
    2. Date: The date of the decision in YYYY-MM-DD format.
    3. Context: A factual description of the situation, technical constraints, or team needs prompting the decision.
    4. Decision: A clear, imperative statement of the chosen path.
    5. Rationale: An explanation of why this option was chosen over alternatives, including why rejected alternatives were dismissed.
    6. Consequences: A breakdown of what the decision makes Easier (enables/simplifies) and what it makes Harder (costs/requirements).
    # ADR-NNN: Title
    
    ## Status
    <!-- Accepted | Deprecated | Superseded by [ADR-NNN](ADR-NNN-title.md) -->
    
    ## Date
    <!-- YYYY-MM-DD -->
    
    ## Context
    <!-- What is the situation that has prompted this decision? -->
    
    ## Decision
    <!-- A clear, imperative statement of what was decided. -->
    
    ## Rationale
    <!-- Why this option over the alternatives? -->
    
    ## Consequences
    
    **Easier:**
    - What this decision enables or simplifies.
    
    **Harder:**
    - What this decision costs or requires accepting.
  8. Generate Angular code scaffolding

    main

    Use the Angular CLI ng generate command to scaffold new files within the project. This ensures consistent structure for components, services, and other Angular primitives.

    # Generate a new component
    ng generate component component-name
    
    # Generate other types
    ng generate directive|pipe|service|class|guard|interface|enum|module
  9. Create a new solution with the Clean Architecture template

    main

    Use the ca-sln template to scaffold a new project. You can customize the frontend framework and the database provider using the --client-framework (-cf) and --database (-db) options.

    Available Options:

    • --client-framework (-cf): angular, react, or none (Default: angular)
    • --database (-db): postgresql, sqlite, or sqlserver (Default: sqlite)
    • --output (-o): The name of your project directory.

    Run dotnet new ca-sln --help for a full list of available options.

    dotnet new ca-sln --client-framework [angular|react|none] --database [postgresql|sqlite|sqlserver] --output YourProjectName