Architecture Review Skill (DDD + Clean Architecture)
Specialized architectural review for modular monoliths using Domain-Driven Design (DDD), Clean/Onion Architecture, and bITdevKit patterns. This skill verifies layer boundaries, domain purity, CQRS patterns, and proper use of Result<T> error handling.
When to Apply This Skill
Use this skill when:
- •Conducting architecture reviews of new features or modules
- •Reviewing pull requests that add/modify domain models, commands, queries, or endpoints
- •Refactoring existing code to align with DDD/Clean Architecture
- •Onboarding new developers to the architectural patterns
- •Validating that layer boundaries remain intact after changes
- •Auditing cross-module dependencies in a modular monolith
Architecture Overview
Layer Structure (Onion/Clean Architecture)
Dependencies flow inward only from outer layers to inner layers:
┌─────────────────────────────────────────┐ │ Presentation (Outermost) │ Minimal API endpoints, DTOs │ ┌───────────────────────────────────┐ │ │ │ Infrastructure │ │ EF Core, Repositories, Jobs │ │ ┌─────────────────────────────┐ │ │ │ │ │ Application │ │ │ Commands, Queries, Handlers │ │ │ ┌───────────────────────┐ │ │ │ │ │ │ │ Domain (Innermost) │ │ │ │ Aggregates, Entities, Value Objects │ │ │ │ Pure Business Logic │ │ │ │ Domain Events, Enumerations │ │ │ │ ZERO Dependencies │ │ │ │ │ │ │ └───────────────────────┘ │ │ │ │ │ └─────────────────────────────┘ │ │ │ └───────────────────────────────────┘ │ └─────────────────────────────────────────┘
Module Organization
Each module under src/Modules/<ModuleName> follows this structure:
CoreModule/
├── CoreModule.Domain/ # Pure business logic (NO external dependencies)
│ └── Model/
│ ├── CustomerAggregate/
│ │ ├── Customer.cs (Aggregate Root)
│ │ └── Events/
│ ├── EmailAddress.cs (Value Object)
│ └── CustomerStatus.cs (Enumeration)
├── CoreModule.Application/ # Use case orchestration (references Domain only)
│ ├── Commands/
│ │ ├── CustomerCreateCommand.cs
│ │ └── CustomerCreateCommandHandler.cs
│ └── Queries/
├── CoreModule.Infrastructure/ # Technical implementation (references Domain + Application)
│ ├── EntityFramework/
│ │ ├── CoreModuleDbContext.cs
│ │ └── Configurations/
│ └── Repositories/
└── CoreModule.Presentation/ # Endpoints & DTOs (references Application via IRequester)
└── Web/
└── Endpoints/
└── CustomerEndpoints.cs
Review Priorities
🔴 CRITICAL Issues (Must Fix Before Merge)
These violations break architectural boundaries or introduce serious design flaws:
- •Layer boundary violations: Dependency flows outward (e.g., Domain → Application, Application → Infrastructure)
- •Cross-module direct references: Modules directly referencing each other (breaks modular isolation)
- •Domain impurity: Domain layer has external dependencies (EF Core, Application, Infrastructure)
- •DbContext in Application/Domain: Direct DbContext usage outside Infrastructure layer
- •Public setters on aggregates/entities: Breaks encapsulation; use change methods instead
- •Exceptions for business rules: Using exceptions instead of Result<T> for expected failures
- •Test independence violations: Tests sharing mutable state or depending on execution order
- •Resource leaks: IDisposable not properly disposed (missing using statements)
- •Endpoints with business logic: Business rules implemented in Presentation layer
🟡 IMPORTANT Issues (Should Fix Soon)
These issues affect maintainability, consistency, or future extensibility:
- •CQRS naming violations: Commands/queries not following
[Entity][Action]Command/Querypattern - •Repository pattern violations: Not using IGenericRepository<T> in Application layer
- •Strongly-typed ID inconsistency: Missing
[TypedEntityId<Guid>]attributes - •Specification pattern ignored: Complex queries inline instead of using specifications
- •IRequester not used: Endpoints directly instantiating handlers instead of using IRequester.SendAsync
- •N+1 query problems: Missing .Include() causing excessive database round-trips
- •Async/await violations: Blocking on async code (.Result, .Wait())
- •CancellationToken missing: Async methods not accepting CancellationToken parameters
- •Domain event naming: Not in past tense or missing "DomainEvent" suffix
🟢 SUGGESTIONS (Nice to Have)
These improve code quality but are not blocking:
- •XML documentation: Missing on public APIs
- •Mapster configuration: Ad-hoc mapping instead of MapperRegister
- •OpenAPI metadata: Endpoints missing .WithName, .WithSummary, .Produces<T>
- •Modern C# features: Opportunities to use pattern matching, expression-bodied members, etc.
- •Value object naming: Generic names (Email) instead of descriptive (EmailAddress)
Quick Reference to Detailed Guidance
This skill includes comprehensive checklists, examples, and templates:
Checklists (6 files)
- •Layer Boundaries: Verify dependency flow, detect circular references
- •Domain Patterns: Validate aggregates, entities, value objects, domain events
- •CQRS Patterns: Check commands, queries, handlers, validators, IRequester usage
- •Repository & Data Access: Ensure proper abstraction, specification pattern, N+1 detection
- •Presentation Endpoints: Validate thin adapters, IRequester delegation, OpenAPI docs
- •Result & Error Handling: Enforce Result<T> pattern, HTTP mapping, error clarity
Examples (5 files)
- •Aggregate Patterns: WRONG vs CORRECT aggregate implementations
- •Value Object Patterns: WRONG vs CORRECT value object implementations
- •CQRS Examples: Command/query structure, handler delegation, IRequester usage
- •Result Pattern Examples: Using Result<T> instead of exceptions
- •Layer Violations: Common boundary violations and how to fix them
Documentation (2 files)
- •ADR Quick Reference: One-paragraph summaries of all 20 ADRs with quick lookup
- •bITdevKit Patterns: IRequester/INotifier, repository behaviors, module registration
Templates (2 files)
- •Architecture Review Template: Comment format for violations
- •Review Summary Template: Summary format with ADR references
Example Workflow: Conducting an Architecture Review
Step 1: Identify Scope
Determine which modules and layers are affected:
# Check which files changed in a PR git diff main...feature-branch --name-only | grep "src/Modules/" # Example output: # src/Modules/CoreModule/CoreModule.Domain/Model/CustomerAggregate/Customer.cs # src/Modules/CoreModule/CoreModule.Application/Commands/CustomerCreateCommand.cs # src/Modules/CoreModule/CoreModule.Presentation/Web/Endpoints/CustomerEndpoints.cs
Layers affected: Domain, Application, Presentation
Step 2: Check Layer Boundaries (🔴 CRITICAL)
Use: checklists/01-layer-boundaries.md
Verify dependencies flow inward only:
- •✅ Domain: No
usingstatements referencing Application, Infrastructure, or Presentation - •✅ Application: Only
usingstatements referencing Domain - •✅ Presentation: Uses
IRequester.SendAsync()to call Application layer
Reference: ADR-0001 (Clean/Onion Architecture)
Step 3: Validate Domain Patterns (🔴 CRITICAL)
Use: checklists/02-domain-patterns.md
Check aggregate Customer.cs:
- •✅ Private setters on all properties
- •✅ Factory method
Customer.Create()returnsResult<Customer> - •✅ Change methods (
ChangeName,ChangeEmail) returnResult<Customer> - •✅ Domain events registered (
CustomerCreatedDomainEvent) - •✅
[TypedEntityId<Guid>]attribute onCustomerId - •✅ Collection exposed as
IReadOnlyCollection<Address>
Reference: ADR-0012 (Domain Logic in Domain Layer), ADR-0008 (Typed Entity IDs)
Step 4: Review CQRS Implementation (🟡 IMPORTANT)
Use: checklists/03-cqrs-patterns.md
Check CustomerCreateCommand.cs:
- •✅ Named
CustomerCreateCommand(follows[Entity][Action]Commandpattern) - •✅ Nested
Validatorclass usingAbstractValidator<T> - •✅ Handler uses
IGenericRepository<Customer>, NOT DbContext - •✅ Handler delegates to
Customer.Create()(domain), not business logic in handler - •✅ Returns
Result<CustomerId>
Reference: ADR-0011 (Application Logic in Commands/Queries), ADR-0009 (FluentValidation)
Step 5: Validate Repository Usage (🔴 CRITICAL)
Use: checklists/04-repository-data-access.md
Check handler:
- •✅ Injects
IGenericRepository<Customer>(abstraction) - •❌ Injects
CoreModuleDbContextdirectly → WRONG (ADR-0004 violation)
Fix: Replace DbContext with repository abstraction.
Reference: ADR-0004 (Repository Pattern with Decorator Behaviors)
Step 6: Review Endpoints (🟡 IMPORTANT)
Use: checklists/05-presentation-endpoints.md
Check CustomerEndpoints.cs:
- •✅ Derives from
EndpointsBase - •✅ Uses
IRequester.SendAsync(command, ct)to delegate to Application - •✅ Uses
.MapHttpCreated()to mapResult<CustomerId>to HTTP 201 - •✅ Includes
CancellationToken ctparameter - •⚠️ Missing
.WithName("CreateCustomer")→ SUGGESTION (🟢)
Reference: ADR-0014 (Minimal API Endpoints), ADR-0005 (Requester/Notifier)
Step 7: Check Result<T> Error Handling (🔴 CRITICAL)
Use: checklists/06-result-error-handling.md
Check error handling:
- •✅
Customer.Create()returnsResult<Customer> - •✅ Validation failures return
Result.Failure("error message") - •❌
throw new ValidationException()in domain method → WRONG (ADR-0002 violation)
Fix: Replace exception with Result<T>.
Reference: ADR-0002 (Result Pattern for Error Handling)
Step 8: Generate Review Summary
Use: templates/review-summary-template.md
Create summary with:
- •Issues Found: 🔴 2, 🟡 1, 🟢 1
- •Top 3 Priorities: DbContext in Application (ADR-0004), Exception for business rule (ADR-0002), Missing endpoint name (🟢)
- •Architecture Compliance: ⚠️ (critical issues present)
- •ADRs Referenced: ADR-0001, ADR-0002, ADR-0004, ADR-0005, ADR-0008, ADR-0009, ADR-0011, ADR-0012, ADR-0014
Integration with Architectural Decision Records (ADRs)
This skill references 20 Architectural Decision Records (ADRs) located in docs/ADR/. Each ADR documents a key architectural decision with context, rationale, and consequences.
How to Use ADRs in Reviews
- •Identify the pattern: Determine which architectural pattern is involved (e.g., layer boundaries, CQRS, Result<T>)
- •Find the ADR: Use docs/adr-quick-reference.md to locate the relevant ADR
- •Reference in feedback: Cite the ADR number and title in review comments (e.g., "ADR-0001: Clean/Onion Architecture")
- •Explain the impact: Use the ADR's rationale to explain why the violation matters
Key ADRs by Category
Core Architecture:
- •ADR-0001: Clean/Onion Architecture with Strict Layer Boundaries
- •ADR-0003: Modular Monolith Architecture
Domain & Data:
- •ADR-0012: Domain Logic Encapsulation in Domain Layer
- •ADR-0008: Typed Entity IDs using Source Generators
- •ADR-0007: Entity Framework Core with Code-First Migrations
Application Layer:
- •ADR-0011: Application Logic in Commands & Queries
- •ADR-0009: FluentValidation Strategy
- •ADR-0010: Mapster for Object Mapping
Error Handling:
- •ADR-0002: Result Pattern for Error Handling
Presentation & API:
- •ADR-0014: Minimal API Endpoints with DTO Exposure
- •ADR-0005: Requester/Notifier (Mediator) Pattern
Infrastructure:
- •ADR-0004: Repository Pattern with Decorator Behaviors
- •ADR-0019: Specification Pattern for Repository Queries
See docs/adr-quick-reference.md for complete list with one-paragraph summaries.
Common Violation Patterns
🔴 CRITICAL: Domain → Application Dependency
Symptom: Domain layer references Application types (commands, queries, handlers)
// WRONG: Domain references Application
namespace MyApp.Domain.CustomerAggregate;
using MyApp.Application.Commands; // ❌ Domain → Application dependency
public class Customer : AggregateRoot<CustomerId>
{
public CustomerCreatedCommand ToCommand() // ❌ Domain knows about Application
{
return new CustomerCreatedCommand(this.FirstName, this.LastName);
}
}
Why Critical: Violates ADR-0001 (Clean/Onion Architecture). Domain must be pure business logic with ZERO external dependencies.
Fix: Remove Application reference. Application layer creates commands from domain entities, not vice versa.
Reference: examples/layer-violations.md
🔴 CRITICAL: Application → Infrastructure (DbContext)
Symptom: Application handlers inject DbContext directly
// WRONG: Application uses DbContext
namespace MyApp.Application.Commands;
using MyApp.Infrastructure.EntityFramework; // ❌ Application → Infrastructure dependency
public class CustomerCreateCommandHandler
{
private readonly CoreModuleDbContext context; // ❌ Direct DbContext usage
public async Task<Result<CustomerId>> Handle(CustomerCreateCommand request, CancellationToken ct)
{
var customer = Customer.Create(...);
this.context.Customers.Add(customer); // ❌ Application knows about EF Core
await this.context.SaveChangesAsync(ct);
}
}
Why Critical: Violates ADR-0001 (layer boundaries) and ADR-0004 (repository pattern). Application layer cannot reference Infrastructure.
Fix: Use IGenericRepository<Customer> abstraction.
Reference: examples/layer-violations.md, checklists/04-repository-data-access.md
🔴 CRITICAL: Exceptions for Business Rules
Symptom: Domain methods throw exceptions for validation failures
// WRONG: Exception for business rule
public static Customer Create(string name, string email)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ValidationException("Name is required"); // ❌ Exception for expected failure
}
return new Customer(name, email);
}
Why Critical: Violates ADR-0002 (Result Pattern). Exceptions should only be used for truly exceptional cases, not expected failures.
Fix: Return Result<Customer> instead.
Reference: examples/result-pattern-examples.md
🟡 IMPORTANT: CQRS Naming Violations
Symptom: Commands/queries not following naming conventions
// WRONG: Poor naming public sealed record CreateCustomerRequest(...) : IRequest<Result<CustomerId>>; // ❌ "Request" suffix public sealed record GetCustomer(...) : IRequest<Result<CustomerModel>>; // ❌ Missing "Query" suffix
Why Important: Violates ADR-0011 (CQRS patterns). Inconsistent naming makes codebase harder to navigate.
Fix: Use [Entity][Action]Command and [Entity][Action]Query patterns.
Reference: checklists/03-cqrs-patterns.md
🟡 IMPORTANT: N+1 Query Problem
Symptom: Missing .Include() causes multiple database round-trips
// WRONG: N+1 query problem
var customers = await repository.FindAllAsync(cancellationToken: ct);
foreach (var customer in customers)
{
// Each iteration triggers a separate query for addresses!
var addresses = customer.Addresses.ToList();
}
Why Important: Performance issue. Can cause significant slowdowns with large datasets.
Fix: Use eager loading with specifications.
Reference: checklists/04-repository-data-access.md
Success Criteria for Architecture Reviews
Code passes architectural review when:
- •✅ No layer boundary violations: Dependencies flow inward only
- •✅ Domain layer is pure: ZERO external dependencies (only bITdevKit domain abstractions)
- •✅ No circular module references: Modules are self-contained
- •✅ Repository abstractions used: Application uses
IGenericRepository<T>, not DbContext - •✅ Result<T> pattern enforced: Domain methods return Result for failures, not exceptions
- •✅ Aggregates properly encapsulated: Private setters, change methods, factory methods
- •✅ CQRS naming consistent: Commands/queries follow
[Entity][Action]Command/Querypattern - •✅ Endpoints are thin adapters: No business logic in Presentation layer
- •✅ IRequester pattern used: Endpoints delegate to Application via
IRequester.SendAsync() - •✅ No N+1 query problems: Proper eager loading with .Include() or specifications
- •✅ CancellationToken propagated: All async methods accept and pass CancellationToken
- •✅ Test independence: No shared mutable state between tests
Tips for Effective Architecture Reviews
Do
- •Start with layer boundaries: Verify dependencies flow inward before checking patterns
- •Reference ADRs: Always cite the relevant ADR number and title in feedback
- •Provide examples: Show WRONG vs CORRECT code, not just abstract explanations
- •Explain impact: Don't just say "this violates ADR-0001", explain why it matters
- •Use checklists systematically: Work through checklists sequentially to avoid missing issues
- •Distinguish CRITICAL vs IMPORTANT: Use emoji indicators (🔴🟡🟢) to prioritize feedback
- •Test architecture rules: Suggest NetArchTest rules to prevent future violations
Don't
- •Mix code quality and architecture: Focus on architectural concerns; use review-code skill for code quality
- •Overwhelm with minor issues: Prioritize critical and important issues over suggestions
- •Accept "it works" as justification: Architecture violations accumulate technical debt
- •Skip positive feedback: Acknowledge good patterns when you see them
- •Review without ADR context: Always consult ADRs to understand the rationale behind patterns
Related Skills
- •review-code: Use for code quality, security, testing, performance, and documentation reviews
- •domain-add-aggregate: Use to scaffold new domain aggregates following DDD patterns
- •adr-writer: Use to create new ADRs for architectural decisions
References
- •AGENTS.md: Project-specific architecture patterns and conventions
- •.github/copilot-instructions.md: Detailed coding guidelines
- •docs/ADR/: All 20 Architectural Decision Records
- •bITdevKit Documentation: Official bITdevKit patterns and features
Version: 1.0 Last Updated: 2026-01-14 Maintainer: bITdevKit Team