Learn enterprise architecture patterns: layered architecture, clean architecture, DDD principles, and cross-cutting concerns.
Enterprise architecture provides the structural blueprint for large-scale software systems. Understanding common patterns — layered architecture, clean architecture, hexagonal architecture, and CQRS — enables you to make informed design decisions that scale with your organization.
Core principles that guide enterprise system design:
1// Enterprise Architecture Principles
2
3/*
4 * SOLID Principles in Enterprise Context
5 * ======================================
6 *
7 * S - Single Responsibility Principle
8 * Each service/module handles one business capability
9 *
10 * O - Open/Closed Principle
11 * Systems open for extension, closed for modification
12 *
13 * L - Liskov Substitution Principle
14 * Services can be replaced without breaking contracts
15 *
16 * I - Interface Segregation Principle
17 * Fine-grained service interfaces
18 *
19 * D - Dependency Inversion Principle
20 * Depend on abstractions, not implementations
21 */
22
23// Key Enterprise Patterns
24public interface IOrderService
25{
26 Task<Order> GetOrderAsync(Guid orderId);
27 Task<Order> CreateOrderAsync(CreateOrderRequest request);
28 Task<bool> CancelOrderAsync(Guid orderId, string reason);
29}
30
31// Separation of Concerns
32public class OrderService : IOrderService
33{
34 private readonly IOrderRepository _repository;
35 private readonly IEventPublisher _eventPublisher;
36 private readonly IPaymentGateway _paymentGateway;
37 private readonly IInventoryService _inventoryService;
38 private readonly ILogger<OrderService> _logger;
39
40 public OrderService(
41 IOrderRepository repository,
42 IEventPublisher eventPublisher,
43 IPaymentGateway paymentGateway,
44 IInventoryService inventoryService,
45 ILogger<OrderService> logger)
46 {
47 _repository = repository;
48 _eventPublisher = eventPublisher;
49 _paymentGateway = paymentGateway;
50 _inventoryService = inventoryService;
51 _logger = logger;
52 }
53
54 public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
55 {
56 _logger.LogInformation("Creating order for customer {CustomerId}", request.CustomerId);
57
58 // 1. Validate business rules
59 await ValidateOrderAsync(request);
60
61 // 2. Reserve inventory
62 await _inventoryService.ReserveItemsAsync(request.Items);
63
64 // 3. Process payment
65 var paymentResult = await _paymentGateway.ProcessPaymentAsync(
66 request.PaymentDetails,
67 request.TotalAmount);
68
69 // 4. Create order
70 var order = Order.Create(request);
71 await _repository.AddAsync(order);
72
73 // 5. Publish domain event
74 await _eventPublisher.PublishAsync(new OrderCreatedEvent(order));
75
76 return order;
77 }
78}
79
80/*
81 * Architecture Decision Records (ADR)
82 * ===================================
83 * Document key architectural decisions:
84 *
85 * ADR-001: Use Event-Driven Architecture
86 * - Status: Accepted
87 * - Context: Need async communication between services
88 * - Decision: Use RabbitMQ for event publishing
89 * - Consequences: Eventual consistency, better scalability
90 *
91 * ADR-002: CQRS for Order Service
92 * - Status: Accepted
93 * - Context: Read-heavy workload with complex queries
94 * - Decision: Separate read/write models
95 * - Consequences: More complexity, better performance
96 */Layered architecture separates concerns into distinct horizontal layers:
1// Repository Pattern
2public interface IRepository<T> where T : class, IEntity
3{
4 Task<T?> GetByIdAsync(Guid id, CancellationToken ct = default);
5 Task<IReadOnlyList<T>> GetAllAsync(CancellationToken ct = default);
6 Task<T> AddAsync(T entity, CancellationToken ct = default);
7 Task UpdateAsync(T entity, CancellationToken ct = default);
8 Task DeleteAsync(T entity, CancellationToken ct = default);
9}
10
11public class OrderRepository : IRepository<Order>
12{
13 private readonly AppDbContext _context;
14
15 public OrderRepository(AppDbContext context)
16 {
17 _context = context;
18 }
19
20 public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
21 {
22 return await _context.Orders
23 .Include(o => o.Items)
24 .Include(o => o.Customer)
25 .FirstOrDefaultAsync(o => o.Id == id, ct);
26 }
27}
28
29// Unit of Work Pattern
30public interface IUnitOfWork : IDisposable
31{
32 IRepository<Order> Orders { get; }
33 IRepository<Customer> Customers { get; }
34 IRepository<Product> Products { get; }
35 Task<int> SaveChangesAsync(CancellationToken ct = default);
36 Task BeginTransactionAsync(CancellationToken ct = default);
37 Task CommitTransactionAsync(CancellationToken ct = default);
38 Task RollbackTransactionAsync(CancellationToken ct = default);
39}
40
41public class UnitOfWork : IUnitOfWork
42{
43 private readonly AppDbContext _context;
44 private IDbContextTransaction? _transaction;
45
46 public IRepository<Order> Orders { get; }
47 public IRepository<Customer> Customers { get; }
48 public IRepository<Product> Products { get; }
49
50 public UnitOfWork(AppDbContext context)
51 {
52 _context = context;
53 Orders = new OrderRepository(context);
54 Customers = new CustomerRepository(context);
55 Products = new ProductRepository(context);
56 }
57
58 public async Task BeginTransactionAsync(CancellationToken ct = default)
59 {
60 _transaction = await _context.Database.BeginTransactionAsync(ct);
61 }
62
63 public async Task CommitTransactionAsync(CancellationToken ct = default)
64 {
65 await SaveChangesAsync(ct);
66 await _transaction!.CommitAsync(ct);
67 }
68
69 public async Task RollbackTransactionAsync(CancellationToken ct = default)
70 {
71 await _transaction!.RollbackAsync(ct);
72 }
73}
74
75// Specification Pattern
76public abstract class Specification<T>
77{
78 public abstract Expression<Func<T, bool>> ToExpression();
79
80 public bool IsSatisfiedBy(T entity)
81 {
82 var predicate = ToExpression().Compile();
83 return predicate(entity);
84 }
85
86 public Specification<T> And(Specification<T> other)
87 {
88 return new AndSpecification<T>(this, other);
89 }
90
91 public Specification<T> Or(Specification<T> other)
92 {
93 return new OrSpecification<T>(this, other);
94 }
95}
96
97public class ActiveOrdersSpecification : Specification<Order>
98{
99 public override Expression<Func<Order, bool>> ToExpression()
100 {
101 return order => order.Status != OrderStatus.Cancelled
102 && order.Status != OrderStatus.Completed;
103 }
104}
105
106public class CustomerOrdersSpecification : Specification<Order>
107{
108 private readonly Guid _customerId;
109
110 public CustomerOrdersSpecification(Guid customerId)
111 {
112 _customerId = customerId;
113 }
114
115 public override Expression<Func<Order, bool>> ToExpression()
116 {
117 return order => order.CustomerId == _customerId;
118 }
119}
120
121// Usage
122var spec = new ActiveOrdersSpecification()
123 .And(new CustomerOrdersSpecification(customerId));
124var orders = await repository.FindAsync(spec);Clean Architecture inverts dependencies so business rules are isolated from frameworks:
1// Traditional N-Tier Architecture
2/*
3 * ┌─────────────────────────────────────────┐
4 * │ Presentation Layer │
5 * │ (Controllers, Views, API Endpoints) │
6 * └────────────────┬────────────────────────┘
7 * │
8 * ┌────────────────▼────────────────────────┐
9 * │ Business Logic Layer │
10 * │ (Services, Domain Logic, Rules) │
11 * └────────────────┬────────────────────────┘
12 * │
13 * ┌────────────────▼────────────────────────┐
14 * │ Data Access Layer │
15 * │ (Repositories, DbContext, ORM) │
16 * └────────────────┬────────────────────────┘
17 * │
18 * ┌────────────────▼────────────────────────┐
19 * │ Database │
20 * │ (SQL Server, PostgreSQL, etc.) │
21 * └─────────────────────────────────────────┘
22 */
23
24// Presentation Layer
25namespace MyApp.Api.Controllers;
26
27[ApiController]
28[Route("api/[controller]")]
29public class OrdersController : ControllerBase
30{
31 private readonly IOrderService _orderService;
32
33 public OrdersController(IOrderService orderService)
34 {
35 _orderService = orderService;
36 }
37
38 [HttpPost]
39 public async Task<ActionResult<OrderDto>> CreateOrder(
40 [FromBody] CreateOrderRequest request)
41 {
42 var order = await _orderService.CreateOrderAsync(request);
43 return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
44 }
45}
46
47// Business Logic Layer
48namespace MyApp.Services;
49
50public class OrderService : IOrderService
51{
52 private readonly IOrderRepository _orderRepository;
53 private readonly ICustomerRepository _customerRepository;
54 private readonly IProductService _productService;
55
56 public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
57 {
58 // Business validation
59 var customer = await _customerRepository.GetByIdAsync(request.CustomerId);
60 if (customer == null)
61 throw new CustomerNotFoundException(request.CustomerId);
62
63 // Check product availability
64 foreach (var item in request.Items)
65 {
66 var available = await _productService.CheckAvailabilityAsync(
67 item.ProductId, item.Quantity);
68 if (!available)
69 throw new InsufficientStockException(item.ProductId);
70 }
71
72 // Create order with business rules
73 var order = Order.Create(customer, request.Items);
74 order.ApplyDiscount(customer.LoyaltyTier);
75
76 await _orderRepository.AddAsync(order);
77 return order;
78 }
79}
80
81// Data Access Layer
82namespace MyApp.Data.Repositories;
83
84public class OrderRepository : IOrderRepository
85{
86 private readonly AppDbContext _context;
87
88 public OrderRepository(AppDbContext context)
89 {
90 _context = context;
91 }
92
93 public async Task<Order?> GetByIdAsync(Guid id)
94 {
95 return await _context.Orders
96 .Include(o => o.Items)
97 .ThenInclude(i => i.Product)
98 .Include(o => o.Customer)
99 .FirstOrDefaultAsync(o => o.Id == id);
100 }
101
102 public async Task AddAsync(Order order)
103 {
104 await _context.Orders.AddAsync(order);
105 await _context.SaveChangesAsync();
106 }
107}
108
109// Cross-Cutting Concerns
110namespace MyApp.Infrastructure;
111
112public class LoggingBehavior<TRequest, TResponse>
113 : IPipelineBehavior<TRequest, TResponse>
114 where TRequest : IRequest<TResponse>
115{
116 private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
117
118 public async Task<TResponse> Handle(
119 TRequest request,
120 RequestHandlerDelegate<TResponse> next,
121 CancellationToken cancellationToken)
122 {
123 var requestName = typeof(TRequest).Name;
124 _logger.LogInformation("Handling {RequestName}", requestName);
125
126 var stopwatch = Stopwatch.StartNew();
127 var response = await next();
128 stopwatch.Stop();
129
130 _logger.LogInformation(
131 "Handled {RequestName} in {ElapsedMs}ms",
132 requestName, stopwatch.ElapsedMilliseconds);
133
134 return response;
135 }
136}Cross-cutting concerns (logging, caching, auth, validation) span all layers:
1// Clean Architecture (Onion Architecture)
2/*
3 * ┌───────────────────────────────────────┐
4 * │ Infrastructure & Frameworks │
5 * │ (EF Core, HTTP Clients, File I/O) │
6 * │ ┌───────────────────────────────────┐ │
7 * │ │ Interface Adapters │ │
8 * │ │ (Controllers, Presenters, Repos) │ │
9 * │ │ ┌───────────────────────────────┐│ │
10 * │ │ │ Application Business Rules ││ │
11 * │ │ │ (Use Cases, Services) ││ │
12 * │ │ │ ┌───────────────────────────┐││ │
13 * │ │ │ │ Enterprise Business │││ │
14 * │ │ │ │ Rules (Entities) │││ │
15 * │ │ │ └───────────────────────────┘││ │
16 * │ │ └───────────────────────────────┘│ │
17 * │ └───────────────────────────────────┘ │
18 * └───────────────────────────────────────┘
19 *
20 * Dependency Rule: Dependencies point INWARD
21 */
22
23// Project Structure
24/*
25 * src/
26 * ├── MyApp.Domain/ # Enterprise Business Rules
27 * │ ├── Entities/
28 * │ ├── ValueObjects/
29 * │ ├── Enums/
30 * │ ├── Exceptions/
31 * │ └── Interfaces/
32 * │
33 * ├── MyApp.Application/ # Application Business Rules
34 * │ ├── Common/
35 * │ │ ├── Behaviors/
36 * │ │ ├── Interfaces/
37 * │ │ └── Mappings/
38 * │ ├── Features/
39 * │ │ ├── Orders/
40 * │ │ │ ├── Commands/
41 * │ │ │ └── Queries/
42 * │ │ └── Customers/
43 * │ └── DependencyInjection.cs
44 * │
45 * ├── MyApp.Infrastructure/ # Frameworks & Drivers
46 * │ ├── Persistence/
47 * │ │ ├── AppDbContext.cs
48 * │ │ ├── Configurations/
49 * │ │ └── Repositories/
50 * │ ├── Services/
51 * │ └── DependencyInjection.cs
52 * │
53 * └── MyApp.Api/ # Interface Adapters
54 * ├── Controllers/
55 * ├── Filters/
56 * ├── Middleware/
57 * └── Program.cs
58 */
59
60// Domain Layer (Core)
61namespace MyApp.Domain.Entities;
62
63public class Order : AggregateRoot
64{
65 private readonly List<OrderItem> _items = new();
66
67 public Guid CustomerId { get; private set; }
68 public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
69 public Money TotalAmount { get; private set; }
70 public OrderStatus Status { get; private set; }
71
72 private Order() { }
73
74 public static Order Create(Guid customerId, IEnumerable<OrderItem> items)
75 {
76 var order = new Order
77 {
78 Id = Guid.NewGuid(),
79 CustomerId = customerId,
80 Status = OrderStatus.Pending,
81 CreatedAt = DateTime.UtcNow
82 };
83
84 foreach (var item in items)
85 order.AddItem(item);
86
87 order.AddDomainEvent(new OrderCreatedDomainEvent(order));
88 return order;
89 }
90
91 public void AddItem(OrderItem item)
92 {
93 _items.Add(item);
94 RecalculateTotal();
95 }
96
97 private void RecalculateTotal()
98 {
99 TotalAmount = _items.Sum(i => i.TotalPrice);
100 }
101}
102
103// Application Layer
104namespace MyApp.Application.Features.Orders.Commands;
105
106public record CreateOrderCommand(
107 Guid CustomerId,
108 List<OrderItemDto> Items
109) : IRequest<OrderDto>;
110
111public class CreateOrderCommandHandler
112 : IRequestHandler<CreateOrderCommand, OrderDto>
113{
114 private readonly IOrderRepository _repository;
115 private readonly IUnitOfWork _unitOfWork;
116 private readonly IMapper _mapper;
117
118 public async Task<OrderDto> Handle(
119 CreateOrderCommand request,
120 CancellationToken cancellationToken)
121 {
122 var items = request.Items.Select(i =>
123 OrderItem.Create(i.ProductId, i.Quantity, i.UnitPrice));
124
125 var order = Order.Create(request.CustomerId, items);
126
127 await _repository.AddAsync(order, cancellationToken);
128 await _unitOfWork.SaveChangesAsync(cancellationToken);
129
130 return _mapper.Map<OrderDto>(order);
131 }
132}
133
134// Infrastructure Layer
135namespace MyApp.Infrastructure.Persistence;
136
137public class OrderRepository : IOrderRepository
138{
139 private readonly AppDbContext _context;
140
141 public async Task<Order?> GetByIdAsync(
142 Guid id,
143 CancellationToken ct = default)
144 {
145 return await _context.Orders
146 .Include(o => o.Items)
147 .FirstOrDefaultAsync(o => o.Id == id, ct);
148 }
149}
150
151// API Layer
152namespace MyApp.Api.Controllers;
153
154[ApiController]
155[Route("api/orders")]
156public class OrdersController : ControllerBase
157{
158 private readonly IMediator _mediator;
159
160 [HttpPost]
161 public async Task<ActionResult<OrderDto>> Create(
162 [FromBody] CreateOrderCommand command)
163 {
164 var result = await _mediator.Send(command);
165 return CreatedAtAction(nameof(Get), new { id = result.Id }, result);
166 }
167}Your progress is saved in this browser, on this device only.