703 lines
19 KiB
Markdown
703 lines
19 KiB
Markdown
# Modular Solution Structure
|
|
|
|
Best practices for organizing your Svrnty.CQRS application into clean, maintainable layers.
|
|
|
|
## Overview
|
|
|
|
For production applications, organize your code into separate projects with clear responsibilities and dependencies. This approach provides:
|
|
|
|
- ✅ **Separation of concerns** - Each project has a single responsibility
|
|
- ✅ **Dependency control** - Clear, one-way dependencies
|
|
- ✅ **Testability** - Easy to test each layer in isolation
|
|
- ✅ **Reusability** - Share domain logic across multiple APIs
|
|
- ✅ **Team scalability** - Different teams can own different projects
|
|
|
|
## Recommended Structure
|
|
|
|
```
|
|
YourSolution/
|
|
├── src/
|
|
│ ├── YourApp.Api/ # HTTP/gRPC endpoints (entry point)
|
|
│ ├── YourApp.CQRS/ # Commands, queries, handlers
|
|
│ ├── YourApp.Domain/ # Domain models, business logic
|
|
│ ├── YourApp.Infrastructure/ # Data access, external services
|
|
│ └── YourApp.Contracts/ # Shared DTOs (optional)
|
|
├── tests/
|
|
│ ├── YourApp.CQRS.Tests/ # Unit tests for handlers
|
|
│ ├── YourApp.Domain.Tests/ # Unit tests for domain logic
|
|
│ └── YourApp.Api.Tests/ # Integration tests
|
|
└── YourSolution.sln
|
|
```
|
|
|
|
## Project Responsibilities
|
|
|
|
### 1. YourApp.Api (Presentation Layer)
|
|
|
|
**Purpose:** HTTP/gRPC endpoints, configuration, startup logic
|
|
|
|
**Contains:**
|
|
- Program.cs
|
|
- appsettings.json
|
|
- Proto files (for gRPC)
|
|
- Middleware configuration
|
|
- Service registration
|
|
- Authentication/authorization setup
|
|
|
|
**Dependencies:**
|
|
```
|
|
YourApp.Api
|
|
→ YourApp.CQRS
|
|
→ YourApp.Infrastructure
|
|
→ Svrnty.CQRS.MinimalApi (or .Grpc)
|
|
```
|
|
|
|
**Example structure:**
|
|
```
|
|
YourApp.Api/
|
|
├── Program.cs
|
|
├── appsettings.json
|
|
├── appsettings.Development.json
|
|
├── Protos/
|
|
│ └── services.proto
|
|
└── Extensions/
|
|
├── ServiceRegistrationExtensions.cs
|
|
└── ConfigurationExtensions.cs
|
|
```
|
|
|
|
### 2. YourApp.CQRS (Application Layer)
|
|
|
|
**Purpose:** Commands, queries, handlers, validators, application logic
|
|
|
|
**Contains:**
|
|
- Command definitions
|
|
- Query definitions
|
|
- Command handlers
|
|
- Query handlers
|
|
- FluentValidation validators
|
|
- Application services
|
|
- DTOs (or reference Contracts project)
|
|
|
|
**Dependencies:**
|
|
```
|
|
YourApp.CQRS
|
|
→ YourApp.Domain
|
|
→ YourApp.Contracts (optional)
|
|
→ Svrnty.CQRS.Abstractions
|
|
→ FluentValidation
|
|
```
|
|
|
|
**Example structure:**
|
|
```
|
|
YourApp.CQRS/
|
|
├── Commands/
|
|
│ ├── Users/
|
|
│ │ ├── CreateUserCommand.cs
|
|
│ │ ├── CreateUserCommandHandler.cs
|
|
│ │ └── CreateUserCommandValidator.cs
|
|
│ └── Orders/
|
|
│ ├── PlaceOrderCommand.cs
|
|
│ ├── PlaceOrderCommandHandler.cs
|
|
│ └── PlaceOrderCommandValidator.cs
|
|
├── Queries/
|
|
│ ├── Users/
|
|
│ │ ├── GetUserQuery.cs
|
|
│ │ ├── GetUserQueryHandler.cs
|
|
│ │ └── GetUserQueryValidator.cs
|
|
│ └── Orders/
|
|
│ ├── GetOrderQuery.cs
|
|
│ └── GetOrderQueryHandler.cs
|
|
├── DTOs/
|
|
│ ├── UserDto.cs
|
|
│ └── OrderDto.cs
|
|
└── Services/
|
|
└── EmailService.cs
|
|
```
|
|
|
|
### 3. YourApp.Domain (Domain Layer)
|
|
|
|
**Purpose:** Business logic, domain entities, domain events, business rules
|
|
|
|
**Contains:**
|
|
- Domain entities (aggregates, value objects)
|
|
- Domain events
|
|
- Domain services
|
|
- Business rules
|
|
- Interfaces for repositories (abstractions)
|
|
|
|
**Dependencies:**
|
|
```
|
|
YourApp.Domain
|
|
→ (No dependencies - pure domain logic)
|
|
```
|
|
|
|
**Example structure:**
|
|
```
|
|
YourApp.Domain/
|
|
├── Entities/
|
|
│ ├── User.cs
|
|
│ ├── Order.cs
|
|
│ └── OrderLine.cs
|
|
├── ValueObjects/
|
|
│ ├── Email.cs
|
|
│ └── Address.cs
|
|
├── Events/
|
|
│ ├── UserCreatedEvent.cs
|
|
│ └── OrderPlacedEvent.cs
|
|
├── Services/
|
|
│ └── OrderPricingService.cs
|
|
└── Repositories/
|
|
├── IUserRepository.cs
|
|
└── IOrderRepository.cs
|
|
```
|
|
|
|
### 4. YourApp.Infrastructure (Infrastructure Layer)
|
|
|
|
**Purpose:** Data access, external services, cross-cutting concerns
|
|
|
|
**Contains:**
|
|
- EF Core DbContext
|
|
- Repository implementations
|
|
- External API clients
|
|
- File storage
|
|
- Email services
|
|
- Caching
|
|
- Logging configuration
|
|
|
|
**Dependencies:**
|
|
```
|
|
YourApp.Infrastructure
|
|
→ YourApp.Domain
|
|
→ Entity Framework Core
|
|
→ External SDK packages
|
|
```
|
|
|
|
**Example structure:**
|
|
```
|
|
YourApp.Infrastructure/
|
|
├── Data/
|
|
│ ├── ApplicationDbContext.cs
|
|
│ ├── Migrations/
|
|
│ └── Configurations/
|
|
│ ├── UserConfiguration.cs
|
|
│ └── OrderConfiguration.cs
|
|
├── Repositories/
|
|
│ ├── UserRepository.cs
|
|
│ └── OrderRepository.cs
|
|
├── ExternalServices/
|
|
│ ├── SendGridEmailService.cs
|
|
│ └── StripePaymentService.cs
|
|
└── Caching/
|
|
└── RedisCacheService.cs
|
|
```
|
|
|
|
### 5. YourApp.Contracts (Shared DTOs - Optional)
|
|
|
|
**Purpose:** Shared data transfer objects used across layers
|
|
|
|
**Contains:**
|
|
- Request DTOs
|
|
- Response DTOs
|
|
- Shared view models
|
|
|
|
**Dependencies:**
|
|
```
|
|
YourApp.Contracts
|
|
→ (No dependencies)
|
|
```
|
|
|
|
**Example structure:**
|
|
```
|
|
YourApp.Contracts/
|
|
├── Users/
|
|
│ ├── UserDto.cs
|
|
│ └── CreateUserRequest.cs
|
|
└── Orders/
|
|
├── OrderDto.cs
|
|
└── PlaceOrderRequest.cs
|
|
```
|
|
|
|
## Dependency Flow
|
|
|
|
```
|
|
┌─────────────┐
|
|
│ YourApp.Api│
|
|
└──────┬──────┘
|
|
│
|
|
▼
|
|
┌──────────────┐ ┌────────────────────┐
|
|
│YourApp.CQRS │─────▶│ YourApp.Contracts │
|
|
└──────┬───────┘ └────────────────────┘
|
|
│
|
|
▼
|
|
┌──────────────┐
|
|
│YourApp.Domain│◀──────┐
|
|
└──────────────┘ │
|
|
│
|
|
┌────────┴──────────┐
|
|
│YourApp.Infrastructure│
|
|
└───────────────────┘
|
|
```
|
|
|
|
**Key principle:** Dependencies flow downward and inward. Domain has no dependencies.
|
|
|
|
## Complete Example
|
|
|
|
### Create the Solution
|
|
|
|
```bash
|
|
# Create solution
|
|
dotnet new sln -n YourApp
|
|
|
|
# Create projects
|
|
dotnet new webapi -n YourApp.Api -o src/YourApp.Api
|
|
dotnet new classlib -n YourApp.CQRS -o src/YourApp.CQRS
|
|
dotnet new classlib -n YourApp.Domain -o src/YourApp.Domain
|
|
dotnet new classlib -n YourApp.Infrastructure -o src/YourApp.Infrastructure
|
|
dotnet new classlib -n YourApp.Contracts -o src/YourApp.Contracts
|
|
|
|
# Create test projects
|
|
dotnet new xunit -n YourApp.CQRS.Tests -o tests/YourApp.CQRS.Tests
|
|
dotnet new xunit -n YourApp.Domain.Tests -o tests/YourApp.Domain.Tests
|
|
dotnet new xunit -n YourApp.Api.Tests -o tests/YourApp.Api.Tests
|
|
|
|
# Add projects to solution
|
|
dotnet sln add src/YourApp.Api/YourApp.Api.csproj
|
|
dotnet sln add src/YourApp.CQRS/YourApp.CQRS.csproj
|
|
dotnet sln add src/YourApp.Domain/YourApp.Domain.csproj
|
|
dotnet sln add src/YourApp.Infrastructure/YourApp.Infrastructure.csproj
|
|
dotnet sln add src/YourApp.Contracts/YourApp.Contracts.csproj
|
|
dotnet sln add tests/YourApp.CQRS.Tests/YourApp.CQRS.Tests.csproj
|
|
dotnet sln add tests/YourApp.Domain.Tests/YourApp.Domain.Tests.csproj
|
|
dotnet sln add tests/YourApp.Api.Tests/YourApp.Api.Tests.csproj
|
|
```
|
|
|
|
### Add Project References
|
|
|
|
```bash
|
|
# Api references
|
|
cd src/YourApp.Api
|
|
dotnet add reference ../YourApp.CQRS/YourApp.CQRS.csproj
|
|
dotnet add reference ../YourApp.Infrastructure/YourApp.Infrastructure.csproj
|
|
|
|
# CQRS references
|
|
cd ../YourApp.CQRS
|
|
dotnet add reference ../YourApp.Domain/YourApp.Domain.csproj
|
|
dotnet add reference ../YourApp.Contracts/YourApp.Contracts.csproj
|
|
|
|
# Infrastructure references
|
|
cd ../YourApp.Infrastructure
|
|
dotnet add reference ../YourApp.Domain/YourApp.Domain.csproj
|
|
|
|
# Test references
|
|
cd ../../tests/YourApp.CQRS.Tests
|
|
dotnet add reference ../../src/YourApp.CQRS/YourApp.CQRS.csproj
|
|
|
|
cd ../YourApp.Domain.Tests
|
|
dotnet add reference ../../src/YourApp.Domain/YourApp.Domain.csproj
|
|
|
|
cd ../YourApp.Api.Tests
|
|
dotnet add reference ../../src/YourApp.Api/YourApp.Api.csproj
|
|
```
|
|
|
|
### Install NuGet Packages
|
|
|
|
```bash
|
|
# Api
|
|
cd ../../src/YourApp.Api
|
|
dotnet add package Svrnty.CQRS.MinimalApi
|
|
dotnet add package Svrnty.CQRS.FluentValidation
|
|
|
|
# CQRS
|
|
cd ../YourApp.CQRS
|
|
dotnet add package Svrnty.CQRS.Abstractions
|
|
dotnet add package FluentValidation
|
|
|
|
# Domain
|
|
cd ../YourApp.Domain
|
|
# No packages needed (pure domain logic)
|
|
|
|
# Infrastructure
|
|
cd ../YourApp.Infrastructure
|
|
dotnet add package Microsoft.EntityFrameworkCore
|
|
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
|
|
dotnet add package Microsoft.EntityFrameworkCore.Design
|
|
```
|
|
|
|
## Example Implementation
|
|
|
|
### Domain Layer
|
|
|
|
```csharp
|
|
// YourApp.Domain/Entities/User.cs
|
|
namespace YourApp.Domain.Entities;
|
|
|
|
public class User
|
|
{
|
|
public int Id { get; set; }
|
|
public string Name { get; set; } = string.Empty;
|
|
public string Email { get; set; } = string.Empty;
|
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
|
|
public static User Create(string name, string email)
|
|
{
|
|
// Business rules
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
throw new ArgumentException("Name is required", nameof(name));
|
|
|
|
if (string.IsNullOrWhiteSpace(email))
|
|
throw new ArgumentException("Email is required", nameof(email));
|
|
|
|
return new User { Name = name, Email = email };
|
|
}
|
|
}
|
|
|
|
// YourApp.Domain/Repositories/IUserRepository.cs
|
|
namespace YourApp.Domain.Repositories;
|
|
|
|
public interface IUserRepository
|
|
{
|
|
Task<User?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
|
Task<User?> GetByEmailAsync(string email, CancellationToken cancellationToken = default);
|
|
Task<int> AddAsync(User user, CancellationToken cancellationToken = default);
|
|
Task UpdateAsync(User user, CancellationToken cancellationToken = default);
|
|
}
|
|
```
|
|
|
|
### Contracts Layer
|
|
|
|
```csharp
|
|
// YourApp.Contracts/Users/UserDto.cs
|
|
namespace YourApp.Contracts.Users;
|
|
|
|
public record UserDto
|
|
{
|
|
public int Id { get; init; }
|
|
public string Name { get; init; } = string.Empty;
|
|
public string Email { get; init; } = string.Empty;
|
|
public DateTime CreatedAt { get; init; }
|
|
}
|
|
```
|
|
|
|
### CQRS Layer
|
|
|
|
```csharp
|
|
// YourApp.CQRS/Commands/Users/CreateUserCommand.cs
|
|
using YourApp.Domain.Entities;
|
|
using YourApp.Domain.Repositories;
|
|
using Svrnty.CQRS.Abstractions;
|
|
using FluentValidation;
|
|
|
|
namespace YourApp.CQRS.Commands.Users;
|
|
|
|
public record CreateUserCommand
|
|
{
|
|
public string Name { get; init; } = string.Empty;
|
|
public string Email { get; init; } = string.Empty;
|
|
}
|
|
|
|
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand, int>
|
|
{
|
|
private readonly IUserRepository _userRepository;
|
|
|
|
public CreateUserCommandHandler(IUserRepository userRepository)
|
|
{
|
|
_userRepository = userRepository;
|
|
}
|
|
|
|
public async Task<int> HandleAsync(CreateUserCommand command, CancellationToken cancellationToken)
|
|
{
|
|
// Use domain logic
|
|
var user = User.Create(command.Name, command.Email);
|
|
|
|
return await _userRepository.AddAsync(user, cancellationToken);
|
|
}
|
|
}
|
|
|
|
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
|
|
{
|
|
private readonly IUserRepository _userRepository;
|
|
|
|
public CreateUserCommandValidator(IUserRepository userRepository)
|
|
{
|
|
_userRepository = userRepository;
|
|
|
|
RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
|
|
RuleFor(x => x.Email)
|
|
.NotEmpty()
|
|
.EmailAddress()
|
|
.MustAsync(BeUniqueEmail).WithMessage("Email already exists");
|
|
}
|
|
|
|
private async Task<bool> BeUniqueEmail(string email, CancellationToken cancellationToken)
|
|
{
|
|
var exists = await _userRepository.GetByEmailAsync(email, cancellationToken);
|
|
return exists == null;
|
|
}
|
|
}
|
|
|
|
// YourApp.CQRS/Queries/Users/GetUserQuery.cs
|
|
using YourApp.Contracts.Users;
|
|
using YourApp.Domain.Repositories;
|
|
using Svrnty.CQRS.Abstractions;
|
|
|
|
namespace YourApp.CQRS.Queries.Users;
|
|
|
|
public record GetUserQuery
|
|
{
|
|
public int UserId { get; init; }
|
|
}
|
|
|
|
public class GetUserQueryHandler : IQueryHandler<GetUserQuery, UserDto>
|
|
{
|
|
private readonly IUserRepository _userRepository;
|
|
|
|
public GetUserQueryHandler(IUserRepository userRepository)
|
|
{
|
|
_userRepository = userRepository;
|
|
}
|
|
|
|
public async Task<UserDto> HandleAsync(GetUserQuery query, CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userRepository.GetByIdAsync(query.UserId, cancellationToken);
|
|
|
|
if (user == null)
|
|
throw new KeyNotFoundException($"User {query.UserId} not found");
|
|
|
|
return new UserDto
|
|
{
|
|
Id = user.Id,
|
|
Name = user.Name,
|
|
Email = user.Email,
|
|
CreatedAt = user.CreatedAt
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
### Infrastructure Layer
|
|
|
|
```csharp
|
|
// YourApp.Infrastructure/Data/ApplicationDbContext.cs
|
|
using Microsoft.EntityFrameworkCore;
|
|
using YourApp.Domain.Entities;
|
|
|
|
namespace YourApp.Infrastructure.Data;
|
|
|
|
public class ApplicationDbContext : DbContext
|
|
{
|
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
|
: base(options)
|
|
{
|
|
}
|
|
|
|
public DbSet<User> Users => Set<User>();
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
|
|
}
|
|
}
|
|
|
|
// YourApp.Infrastructure/Repositories/UserRepository.cs
|
|
using Microsoft.EntityFrameworkCore;
|
|
using YourApp.Domain.Entities;
|
|
using YourApp.Domain.Repositories;
|
|
using YourApp.Infrastructure.Data;
|
|
|
|
namespace YourApp.Infrastructure.Repositories;
|
|
|
|
public class UserRepository : IUserRepository
|
|
{
|
|
private readonly ApplicationDbContext _context;
|
|
|
|
public UserRepository(ApplicationDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<User?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.Users
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
|
|
}
|
|
|
|
public async Task<User?> GetByEmailAsync(string email, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.Users
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(u => u.Email == email, cancellationToken);
|
|
}
|
|
|
|
public async Task<int> AddAsync(User user, CancellationToken cancellationToken = default)
|
|
{
|
|
_context.Users.Add(user);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
return user.Id;
|
|
}
|
|
|
|
public async Task UpdateAsync(User user, CancellationToken cancellationToken = default)
|
|
{
|
|
_context.Users.Update(user);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Api Layer
|
|
|
|
```csharp
|
|
// YourApp.Api/Program.cs
|
|
using Microsoft.EntityFrameworkCore;
|
|
using YourApp.CQRS.Commands.Users;
|
|
using YourApp.CQRS.Queries.Users;
|
|
using YourApp.Domain.Repositories;
|
|
using YourApp.Infrastructure.Data;
|
|
using YourApp.Infrastructure.Repositories;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Infrastructure
|
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
|
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
|
|
|
|
builder.Services.AddScoped<IUserRepository, UserRepository>();
|
|
|
|
// CQRS
|
|
builder.Services.AddSvrntyCQRS();
|
|
builder.Services.AddDefaultCommandDiscovery();
|
|
builder.Services.AddDefaultQueryDiscovery();
|
|
|
|
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler, CreateUserCommandValidator>();
|
|
builder.Services.AddQuery<GetUserQuery, UserDto, GetUserQueryHandler>();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Map endpoints
|
|
app.UseSvrntyCqrs();
|
|
|
|
app.Run();
|
|
```
|
|
|
|
## Benefits of Modular Structure
|
|
|
|
### 1. Clear Responsibilities
|
|
|
|
Each project has one job:
|
|
- Api: Expose endpoints
|
|
- CQRS: Application logic
|
|
- Domain: Business rules
|
|
- Infrastructure: Technical concerns
|
|
|
|
### 2. Testability
|
|
|
|
Test each layer in isolation:
|
|
|
|
```csharp
|
|
// YourApp.Domain.Tests/Entities/UserTests.cs
|
|
[Fact]
|
|
public void Create_WithValidData_ReturnsUser()
|
|
{
|
|
var user = User.Create("Alice", "alice@example.com");
|
|
|
|
Assert.Equal("Alice", user.Name);
|
|
Assert.Equal("alice@example.com", user.Email);
|
|
}
|
|
|
|
// YourApp.CQRS.Tests/Commands/CreateUserCommandHandlerTests.cs
|
|
[Fact]
|
|
public async Task HandleAsync_WithValidCommand_CreatesUser()
|
|
{
|
|
var mockRepo = new Mock<IUserRepository>();
|
|
mockRepo.Setup(r => r.AddAsync(It.IsAny<User>(), It.IsAny<CancellationToken>()))
|
|
.ReturnsAsync(123);
|
|
|
|
var handler = new CreateUserCommandHandler(mockRepo.Object);
|
|
var command = new CreateUserCommand { Name = "Alice", Email = "alice@example.com" };
|
|
|
|
var result = await handler.HandleAsync(command, CancellationToken.None);
|
|
|
|
Assert.Equal(123, result);
|
|
}
|
|
```
|
|
|
|
### 3. Reusability
|
|
|
|
Share domain logic across multiple APIs:
|
|
|
|
```
|
|
YourApp.PublicApi ──┐
|
|
├──▶ YourApp.CQRS ──▶ YourApp.Domain
|
|
YourApp.AdminApi ──┘
|
|
```
|
|
|
|
### 4. Team Scalability
|
|
|
|
Different teams can own different projects:
|
|
- Team A: Domain & CQRS
|
|
- Team B: Infrastructure
|
|
- Team C: API
|
|
|
|
## Migration from Single Project
|
|
|
|
If you started with a single project, migrate gradually:
|
|
|
|
### Step 1: Extract Domain
|
|
|
|
1. Create YourApp.Domain project
|
|
2. Move domain entities
|
|
3. Move domain interfaces (IUserRepository, etc.)
|
|
4. Update references
|
|
|
|
### Step 2: Extract Infrastructure
|
|
|
|
1. Create YourApp.Infrastructure project
|
|
2. Move DbContext
|
|
3. Move repository implementations
|
|
4. Move external service clients
|
|
5. Update references
|
|
|
|
### Step 3: Extract CQRS
|
|
|
|
1. Create YourApp.CQRS project
|
|
2. Move commands, queries, handlers
|
|
3. Move validators
|
|
4. Update references
|
|
|
|
### Step 4: Keep Only Presentation in Api
|
|
|
|
1. Keep Program.cs
|
|
2. Keep configuration files
|
|
3. Keep middleware
|
|
4. Delete everything else (moved to other projects)
|
|
|
|
## Best Practices
|
|
|
|
### ✅ DO
|
|
|
|
- Keep domain layer pure (no dependencies)
|
|
- Use interfaces in domain, implementations in infrastructure
|
|
- Put DTOs in CQRS or Contracts layer
|
|
- Keep Api layer thin (configuration only)
|
|
- Use dependency injection
|
|
- Follow one-way dependencies (downward/inward)
|
|
|
|
### ❌ DON'T
|
|
|
|
- Don't reference Infrastructure from Domain
|
|
- Don't put business logic in Api layer
|
|
- Don't reference Api from other projects
|
|
- Don't create circular dependencies
|
|
- Don't mix presentation and business logic
|
|
|
|
## What's Next?
|
|
|
|
- **[Dependency Injection](dependency-injection.md)** - DI patterns for handlers
|
|
- **[Tutorials: Modular Solution](../tutorials/modular-solution/README.md)** - Step-by-step guide
|
|
|
|
## See Also
|
|
|
|
- [CQRS Pattern](cqrs-pattern.md) - Understanding CQRS
|
|
- [Best Practices: Testing](../best-practices/testing.md) - Testing strategies
|
|
- [Best Practices: Deployment](../best-practices/deployment.md) - Production deployment
|