Use when designing application structure, implementing layered architecture, applying DDD patterns, or making architectural decisions about separation of concerns
Solution architecture defines how code is organized into layers and components. This skill covers Domain-Driven Design (DDD) layered architecture and essential coding principles like DRY and SOLID.
Core principle: The domain is the heart of your application. All other layers exist to support it.
Not for: Simple CRUD apps, scripts, or prototypes where layering adds unnecessary complexity.
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PRESENTATION LAYER ā ā UI, Controllers, ViewModels
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā APPLICATION LAYER ā ā Use Cases, Orchestration, CQRS
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā DOMAIN LAYER ā ā Entities, Value Objects, Domain Services
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā INFRASTRUCTURE LAYER ā ā Database, External APIs, Messaging
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Dependency Rule: Each layer only depends on the layer directly inside it. Domain is innermost and has NO external dependencies.
Responsibility: User interaction and display
Contains:
Rules:
// ā
GOOD: Thin controller
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
var result = await _orderService.CreateOrderAsync(request.ToCommand());
return result.IsSuccess ? Ok(result.Value) : BadRequest(result.Error);
}
// ā BAD: Business logic in controller
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
if (request.Items.Sum(i => i.Price) < 10)
return BadRequest("Minimum order is $10");
// ... more business logic
}
Responsibility: Use case orchestration and conditional business rules
Contains:
Rules:
// ā
GOOD: Conditional rules in Application Service
public async Task<Result<OrderDto>> CreateOrderAsync(CreateOrderCommand command)
{
var customer = await _customerRepository.GetByIdAsync(command.CustomerId);
if (customer is null) return Result.Fail("Customer not found");
// Conditional rule: Check credit limit (depends on external data)
var pendingTotal = await _orderRepository.GetPendingTotalAsync(customer.Id);
if (pendingTotal + command.Total > customer.CreditLimit)
return Result.Fail("Order exceeds credit limit");
// Conditional rule: Discount policy (may vary by context)
var discount = customer.IsLoyal ? 0.1m : 0m;
// Conditional rule: Minimum order for online channel
if (command.IsOnlineOrder && command.Total < 25)
return Result.Fail("Minimum online order is $25");
var order = Order.Create(customer, command.Items, discount);
await _orderRepository.AddAsync(order);
await _unitOfWork.SaveChangesAsync();
return Result.Ok(order.ToDto());
}
Responsibility: Core business rules and invariants
Contains:
Rules:
| Rule Type | Layer | Characteristics | Examples |
|---|---|---|---|
| Core Rules | Domain | Always true, no exceptions, entity protects itself | "Quantity > 0", "Email must be valid format", "Order must have lines" |
| Conditional Rules | Application | Context-dependent, policy-based, may need external data | "Min $25 for online orders", "Credit limit check", "Loyalty discount" |
How to decide:
// ā
GOOD: Entity with behavior
public class Order : Entity<OrderId>
{
private readonly List<OrderLine> _lines = new();
public IReadOnlyList<OrderLine> Lines => _lines.AsReadOnly();
public Money Total => _lines.Sum(l => l.Subtotal);
public static Order Create(Customer customer, IEnumerable<OrderLineDto> items)
{
var order = new Order(OrderId.New(), customer.Id);
foreach (var item in items)
order.AddLine(item.ProductId, item.Quantity, item.Price);
return order;
}
public void AddLine(ProductId productId, int quantity, Money price)
{
if (quantity <= 0) throw new DomainException("Quantity must be positive");
_lines.Add(new OrderLine(productId, quantity, price));
AddDomainEvent(new OrderLineAddedEvent(Id, productId));
}
}
// ā BAD: Anemic entity (data only)
public class Order
{
public int Id { get; set; }
public List<OrderLine> Lines { get; set; } = new();
public decimal Total { get; set; }
}
Responsibility: Technical implementations and external systems
Contains:
Rules:
// ā
GOOD: Repository implementation
public class OrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public async Task<Order?> GetByIdAsync(OrderId id)
=> await _context.Orders
.Include(o => o.Lines)
.FirstOrDefaultAsync(o => o.Id == id);
public async Task AddAsync(Order order)
=> await _context.Orders.AddAsync(order);
}
| Building Block | Has Identity | Mutable | Purpose |
|---|---|---|---|
| Entity | Yes | Yes | Objects tracked by identity |
| Value Object | No | No | Descriptive, replaceable values |
| Aggregate | Yes | Yes | Consistency boundary with root entity |
| Domain Service | N/A | N/A | Logic not belonging to single entity |
| Domain Event | N/A | N/A | Something that happened in domain |
| Repository | N/A | N/A | Aggregate persistence abstraction |
// Entity: tracked by identity
public class Customer : Entity<CustomerId>
{
public string Name { get; private set; }
public Email Email { get; private set; }
}
// Value Object: tracked by value, immutable
public record Email
{
public string Value { get; }
public Email(string value)
{
if (!IsValid(value)) throw new DomainException("Invalid email");
Value = value;
}
private static bool IsValid(string email) => /* validation */;
}
Rules:
// Order is aggregate root
public class Order : AggregateRoot<OrderId>
{
private readonly List<OrderLine> _lines = new();
// Only root can modify children
public void AddLine(ProductId productId, int quantity, Money price)
{
ValidateCanAddLine();
_lines.Add(new OrderLine(productId, quantity, price));
}
}
// ā BAD: External code modifying aggregate internals
order.Lines.Add(new OrderLine(...)); // Bypasses invariant checks
Every piece of knowledge has a single, unambiguous representation.
// ā BAD: Duplicated validation
public void CreateUser(string email) { if (!email.Contains("@")) throw ...; }
public void UpdateEmail(string email) { if (!email.Contains("@")) throw ...; }
// ā
GOOD: Single source of truth
public record Email(string Value)
{
public Email(string value) : this(value)
{
if (!value.Contains("@")) throw new DomainException("Invalid email");
}
}
| Principle | Summary | Architectural Application |
|---|---|---|
| Single Responsibility | One reason to change | Each layer has distinct responsibility |
| Open/Closed | Open for extension, closed for modification | Use interfaces, strategy pattern |
| Liskov Substitution | Subtypes must be substitutable | Aggregates enforce invariants |
| Interface Segregation | Small, focused interfaces | Repository per aggregate |
| Dependency Inversion | Depend on abstractions | Domain defines interfaces, Infra implements |
| Mistake | Fix |
|---|---|
| Business logic in controllers | Move to Domain layer |
| Anemic domain models (data bags) | Add behavior to entities |
| Repository returning DTOs | Return domain objects |
| Domain depending on ORM | Use POCO entities, configure in Infra |
| Aggregate referencing another aggregate | Use IDs only |
| Application layer with business rules | Move rules to Domain |
| Fat services, thin entities | Entities should have behavior |
// ā Domain layer importing Infrastructure
using Microsoft.EntityFrameworkCore; // VIOLATION
// ā Domain layer importing Application
using MyApp.Application.DTOs; // VIOLATION
// ā
Domain layer - only domain concerns
using MyApp.Domain.ValueObjects;
using MyApp.Domain.Events;
src/
āāā MyApp.Presentation/ # UI, Controllers
ā āāā Controllers/
ā āāā ViewModels/
āāā MyApp.Application/ # Use Cases, CQRS
ā āāā Commands/
ā āāā Queries/
ā āāā Services/
āāā MyApp.Domain/ # Core Business Logic
ā āāā Entities/
ā āāā ValueObjects/
ā āāā Services/
ā āāā Events/
ā āāā Repositories/ # Interfaces only
āāā MyApp.Infrastructure/ # External Concerns
ā āāā Persistence/
ā āāā ExternalServices/
ā āāā Messaging/
āāā MyApp.Domain.Shared/ # Shared enums, constants
Where does this code belong?
Is it UI/display related?
ā Presentation Layer
Is it orchestrating a use case?
ā Application Layer
Is it a core business rule?
ā Domain Layer
Is it talking to external systems?
ā Infrastructure Layer