# Component Design Standards for Domain Driven Design
This document outlines the coding standards for component design within a Domain-Driven Design (DDD) context. It aims to provide clear guidance for developers building robust, maintainable, and scalable software systems aligned with DDD principles. These standards will focus on creating loosely coupled, highly cohesive components that accurately reflect the business domain.
## 1. Introduction to Component Design in DDD
Component design in DDD involves breaking down a large system into smaller, manageable, and independently deployable units. Each component ideally represents a bounded context or a well-defined part of it. This approach promotes modularity, reusability, and easier maintenance. Good component design isolates domain logic and reduces the complexity of the overall system.
**Key Principles:**
* **High Cohesion:** Each component should have a single, well-defined responsibility. All elements within the component should be closely related and contribute to that responsibility.
* **Loose Coupling:** Components should minimize dependencies on each other. Changes in one component should ideally have minimal impact on other components.
* **Explicit Interfaces:** Components should communicate through well-defined interfaces that clearly outline their functionality. These interfaces should be stable and versioned.
* **Bounded Context Alignment:** Components should closely mirror the bounded contexts identified during domain analysis. Each component should be an autonomous unit that encapsulates a specific part of the domain.
## 2. Defining Components and Their Boundaries
### 2.1. Bounded Context Mapping
**Standard:** Components must align directly with bounded contexts. Each bounded context should ideally be represented by one or more components.
* **Do This:** Explicitly map components to bounded contexts during the design phase. Document the rationale behind each component's boundaries.
* **Don't Do This:** Allow components to span multiple bounded contexts, blurring the lines of responsibility.
**Why:** Ensures clear separation of concerns and prevents domain model contamination.
**Example:**
Let's say we have an e-commerce system with two bounded contexts: "Catalog" and "OrderManagement". Each context would be represented by separate components.
"""csharp
// Catalog Component
namespace ECommerce.Catalog {
public class Product {
public Guid ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public interface IProductRepository {
Product GetProduct(Guid productId);
void SaveProduct(Product product);
}
}
// OrderManagement Component
namespace ECommerce.OrderManagement {
public class Order {
public Guid OrderId { get; set; }
public Guid CustomerId { get; set; }
public List Items { get; set; } = new List();
}
public class OrderItem {
public Guid ProductId { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
public interface IOrderRepository {
Order GetOrder(Guid orderId);
void SaveOrder(Order order);
}
}
"""
### 2.2. Component Granularity
**Standard:** Components should be sized appropriately, balancing cohesion and reusability. Avoid overly large or overly granular components.
* **Do This:** Aim for components that are small enough to understand and maintain easily but large enough to represent a cohesive unit of functionality. Consider Conway's Law: your software architecture will likely mirror your team structure.
* **Don't Do This:** Create monolithic components that contain too much unrelated functionality or excessively small components that lead to unnecessary complexity.
**Why:** Improves maintainability and reduces the risk of ripple effects from changes.
**Example:** In an Identity and Access Management (IAM) system, it might be beneficial to split the component into "Authentication" and "Authorization" components. Overly granular architecture might have components like "UserRegistrationComponent", "PasswordResetComponent", which makes collaboration and data access very difficult.
### 2.3. Explicit Dependencies
**Standard:** All dependencies between components must be declared explicitly. Use dependency injection or other mechanisms to manage dependencies.
* **Do This:** Favor constructor injection to clearly define a component's dependencies.
* **Don't Do This:** Rely on implicit or hidden dependencies that are not easily discoverable.
**Why:** Enhances testability and allows for better control over component interactions.
**Example:**
"""csharp
// Good: Constructor injection clearly defines dependencies
public class OrderService {
private readonly IOrderRepository _orderRepository;
private readonly IProductRepository _productRepository;
public OrderService(IOrderRepository orderRepository, IProductRepository productRepository) {
_orderRepository = orderRepository;
_productRepository = productRepository;
}
public void PlaceOrder(Guid orderId, Guid productId, int quantity) {
var order = _orderRepository.GetOrder(orderId);
var product = _productRepository.GetProduct(productId);
// Process the order and save to repository ...
}
}
// Bad: Relying on service location or static access
public class BadOrderService {
public void PlaceOrder(Guid orderId) {
// Hard to test and understand dependencies
IOrderRepository orderRepository = ServiceLocator.GetService();
Order order = orderRepository.GetOrder(orderId);
//...
}
}
"""
## 3. Component Communication and Integration
### 3.1. Anti-Corruption Layer (ACL)
**Standard:** Utilize an Anti-Corruption Layer when integrating with external systems or components that have incompatible models.
* **Do This:** Create a translation layer that adapts the external system's data model to your domain model. This layer should isolate your domain from the external system's quirks and changes.
* **Don't Do This:** Directly expose your domain model to external systems, leading to domain model corruption.
**Why:** Protects the integrity of your domain model and prevents external dependencies from leaking into your core logic.
**Example:**
"""csharp
// External System Data Model
public class ExternalCustomer {
public string CustomerID { get; set; }
public string FullName { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
}
// Domain Model
namespace ECommerce.Customer {
public class Customer {
public Guid CustomerId { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address {
public string Street { get; set; }
public string City { get; set; }
}
}
// Anti-Corruption Layer
public interface ICustomerAdapter {
Customer GetCustomer(string externalCustomerId);
}
public class CustomerAdapter : ICustomerAdapter {
private readonly IExternalCustomerService _externalCustomerService;
public CustomerAdapter(IExternalCustomerService externalCustomerService) {
_externalCustomerService = externalCustomerService;
}
public Customer GetCustomer(string externalCustomerId) {
ExternalCustomer externalCustomer = _externalCustomerService.GetExternalCustomer(externalCustomerId);
// Translate external data to domain model
return new Customer {
CustomerId = Guid.Parse(externalCustomer.CustomerID),
Name = externalCustomer.FullName,
Address = new Address {
Street = externalCustomer.AddressLine1,
City = externalCustomer.City
}
};
}
}
"""
### 3.2. Domain Events
**Standard:** Use Domain Events to communicate state changes between components within a bounded context and across bounded contexts.
* **Do This:** Publish domain events when significant state changes occur within a component. Subscribe to domain events to react to changes in other components. Implement eventual consistency where applicable.
* **Don't Do This:** Directly access and modify the state of other components, creating tight coupling. Avoid relying on synchronous calls between components for critical operations.
**Why:** Decouples components, promotes eventual consistency, and improves system resilience.
**Example:**
"""csharp
// Domain Event
public class OrderCreatedEvent {
public Guid OrderId { get; set; }
public Guid CustomerId { get; set; }
}
// Component A (Order Management)
public class OrderService {
private readonly IOrderRepository _orderRepository;
private readonly IDomainEventPublisher _domainEventPublisher;
public OrderService(IOrderRepository orderRepository, IDomainEventPublisher domainEventPublisher) {
_orderRepository = orderRepository;
_domainEventPublisher = domainEventPublisher;
}
public void CreateOrder(Guid orderId, Guid customerId) {
var order = new Order { OrderId = orderId, CustomerId = customerId };
_orderRepository.SaveOrder(order);
var orderCreatedEvent = new OrderCreatedEvent { OrderId = orderId, CustomerId = customerId };
_domainEventPublisher.Publish(orderCreatedEvent);
}
}
// Component B (Customer Service)
public class CustomerService {
public void Handle(OrderCreatedEvent orderCreatedEvent) {
// Update customer information based on the new order
Console.WriteLine($"New Order Created for Customer: {orderCreatedEvent.CustomerId}");
}
}
//Simplified event publisher
public interface IDomainEventPublisher {
void Publish(T domainEvent);
}
"""
### 3.3. Asynchronous Communication
**Standard:** Prefer asynchronous communication between components, especially across bounded contexts, to improve performance and availability.
* **Do This:** Utilize message queues (e.g., RabbitMQ, Kafka) or other asynchronous mechanisms to decouple components.
* **Don't Do This:** Rely heavily on synchronous REST API calls between components, leading to performance bottlenecks and increased risk of failure.
**Why:** Improves system responsiveness and prevents failures in one component from cascading to others.
## 4. Component Design Patterns
### 4.1. Aggregate Root
**Standard:** Each component should expose Aggregate Roots as its primary entry points. Treat Aggregates as consistency boundaries.
* **Do This:** Design aggregates to encapsulate related entities and value objects. Expose methods on the aggregate root to perform operations that maintain the aggregate's consistency.
* **Don't Do This:** Allow direct access to entities within an aggregate from outside the aggregate.
**Why:** Enforces data integrity and consistency within the component.
**Example:**
"""csharp
// Aggregate Root: Order
namespace ECommerce.OrderManagement {
public class Order {
public Guid OrderId { get; private set; }
public Guid CustomerId { get; private set; }
private readonly List _items = new List();
public IReadOnlyCollection Items => _items.AsReadOnly();
public OrderStatus Status { get; private set; }
// Private constructor for object creation within the Aggregate
private Order(Guid orderId, Guid customerId) {
OrderId = orderId;
CustomerId = customerId;
Status = OrderStatus.Created;
}
public static Order Create(Guid orderId, Guid customerId)
{
return new Order(orderId, customerId);
}
public void AddItem(Guid productId, int quantity, decimal price) {
// Business rules and validations
if(quantity <= 0)
{
throw new InvalidOperationException("Quantity must be positive.");
}
_items.Add(new OrderItem(productId, quantity, price));
}
public void Submit()
{
//Business rules - only allow to submit orders once, only allow to submit orders with items
Status = OrderStatus.Submitted;
}
}
public enum OrderStatus
{
Created,
Submitted,
Cancelled,
Shipped
}
public class OrderItem {
public Guid ProductId { get; private set; }
public int Quantity { get; private set; }
public decimal Price { get; private set; }
// Private constructor and no setters to enforce immutability after creation.
internal OrderItem(Guid productId, int quantity, decimal price) {
ProductId = productId;
Quantity = quantity;
Price = price;
}
}
}
"""
### 4.2. Factory Pattern
**Standard:** Use factories to encapsulate the complex instantiation logic of aggregates and entities.
* **Do This:** Create factory classes or methods to manage the creation of objects with intricate dependencies or initialization requirements.
* **Don't Do This:** Directly instantiate complex objects throughout the codebase, scattering initialization logic.
**Why:** Simplifies object creation, improves testability, and promotes code reuse.
**Example:**
"""csharp
// Factory for creating orders.
namespace ECommerce.OrderManagement {
public interface IOrderFactory {
Order CreateOrder(Guid orderId, Guid customerId);
}
public class OrderFactory : IOrderFactory {
private readonly IProductRepository _productRepository;
public OrderFactory(IProductRepository productRepository) {
_productRepository = productRepository;
}
public Order CreateOrder(Guid orderId, Guid customerId) {
// Perform any necessary initialization or validation logic.
// Retrieve default products or apply discounts.
var order = Order.Create(orderId, customerId);
// Add default items
// order.AddItem(defaultProductId, 1, defaultProductPrice);
return order;
}
}
}
"""
### 4.3 Repository Pattern
**Standard:** Use the Repository pattern to abstract data access logic from the domain model.
* **Do This:** Define interfaces for repositories that specify the data access operations required by the domain. Implement repositories using specific data access technologies (e.g., Entity Framework, NoSQL databases).
* **Don't Do This:** Directly access data access technologies from the domain model, creating tight coupling and hindering testability.
**Why:** Decouples the domain model from data access concerns, making it easier to change data access technologies without impacting the core domain logic.
**Example:**
"""csharp
// Repository Interface
namespace ECommerce.OrderManagement {
public interface IOrderRepository {
Order GetOrder(Guid orderId);
void SaveOrder(Order order);
void DeleteOrder(Guid orderId);
}
// Entity Framework Implementation
public class OrderRepository : IOrderRepository {
private readonly OrderContext _context;
public OrderRepository(OrderContext context) {
_context = context;
}
public Order GetOrder(Guid orderId) {
return _context.Orders.FirstOrDefault(o => o.OrderId == orderId);
}
public void SaveOrder(Order order) {
_context.Orders.Update(order);
_context.SaveChanges();
}
public void DeleteOrder(Guid orderId) {
var order = _context.Orders.FirstOrDefault(o => o.OrderId == orderId);
if (order != null) {
_context.Orders.Remove(order);
_context.SaveChanges();
}
}
}
}
"""
## 5. Technology-Specific Considerations (C# Example)
### 5.1. Dependency Injection Container
**Standard:** Utilize a Dependency Injection (DI) container (e.g., Microsoft.Extensions.DependencyInjection, Autofac) to manage component dependencies.
* **Do This:** Register component dependencies with the DI container and use constructor injection to inject dependencies into components.
* **Don't Do This:** Manually manage component dependencies or use service locator patterns, leading to tight coupling and reduced testability.
**Why:** Simplifies dependency management, improves testability, and promotes loose coupling.
**Example:**
"""csharp
// Configure DI container
using Microsoft.Extensions.DependencyInjection;
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddScoped();
services.AddScoped();
services.AddScoped();
//... configure other services
}
}
// Usage in a controller:
public class OrderController : ControllerBase {
private readonly OrderService _orderService;
public OrderController(OrderService orderService) {
_orderService = orderService;
}
[HttpPost]
public IActionResult CreateOrder(Guid orderId, Guid customerId){
_orderService.CreateOrder(orderId, customerId);
return Ok();
}
}
"""
### 5.2. Asynchronous Programming (async/await)
**Standard:** Use the "async" and "await" keywords for asynchronous operations to improve performance and responsiveness.
* **Do This:** Mark asynchronous methods with the "async" keyword and use the "await" keyword to asynchronously wait for the completion of tasks. Avoid blocking the thread. Configure "ConfigureAwait(false)" to avoid deadlocks especially when writing reusable libraries.
* **Don't Do This:** Use synchronous blocking operations in asynchronous methods, leading to performance bottlenecks.
**Why:** Improves the scalability and responsiveness of the system.
**Example:**
"""csharp
public class OrderService {
private readonly IOrderRepository _orderRepository;
public OrderService(IOrderRepository orderRepository) {
_orderRepository = orderRepository;
}
public async Task GetOrderAsync(Guid orderId) {
// Asynchronously retrieve order from the repository
return await _orderRepository.GetOrderAsync(orderId).ConfigureAwait(false);
}
}
public interface IOrderRepository {
Task GetOrderAsync(Guid orderId);
}
public class OrderRepository : IOrderRepository {
private readonly OrderContext _context;
public OrderRepository(OrderContext context) {
_context = context;
}
public async Task GetOrderAsync(Guid orderId) {
//Asynchronously fetch data from the database
return await _context.Orders.FirstOrDefaultAsync(o => o.OrderId == orderId).ConfigureAwait(false);
}
}
"""
### 5.3. Nullable Reference Types
**Standard:** Enable and correctly utilize nullable reference types to prevent null reference exceptions and increase code safety.
* **Do This**: Enable nullable reference types in your project ("enable" in the ".csproj" file). Annotate reference types with "?" if they can be null.
* **Don't Do This**: Ignore nullable warnings. Suppress the warnings without proper null checks.
**Why**: Prevents null reference exceptions, improves code clarity, and enhances overall code safety.
**Example:**
"""csharp
#nullable enable
public class Customer
{
public string? Name { get; set; } // Name can be null
public Address Address { get; set; } // Address cannot be null by default, ensure that address can always be provided during creation
}
#nullable disable
"""
## 6. Common Anti-Patterns
* **God Component:** A component with too many responsibilities and dependencies.
* **Shared Database:** Components directly accessing the same database tables without clear ownership or synchronization.
* **Tight Coupling:** Components depending heavily on each other's internal implementation details.
* **Ignoring Domain Language:** Not using the ubiquitous language of the domain in component names and code.
* **Premature Optimization:** Optimizing component performance before identifying actual bottlenecks.
* **Lack of Testing:** Insufficient unit and integration tests for components.
* **Violating Single Responsibility Principle:** Class or module doing more than it should.
## 7. Conclusion
Adhering to these component design standards will result in a more maintainable, scalable, and resilient software system. By aligning components with bounded contexts, promoting loose coupling, and utilizing appropriate design patterns, developers can create a system that accurately reflects the business domain and adapts easily to changing requirements. Remember that DDD is an iterative process, and these standards should be refined and adapted as understanding of the domain evolves. Continuous communication with domain experts is essential for ensuring that the software accurately reflects the business needs.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Core Architecture Standards for Domain Driven Design This document outlines the coding standards for the core architecture of applications built using Domain-Driven Design (DDD) principles. It focuses on establishing a solid foundation for maintainability, scalability, and alignment with business goals. This document reflects current DDD best practices and addresses modern technological approaches. ## 1. Fundamental Architectural Patterns ### 1.1 Layered Architecture **Standard:** Implement a layered architecture to separate concerns and improve maintainability. * **Do This:** Distinguish between the User Interface, Application, Domain, and Infrastructure layers. Ensure each layer only depends on layers directly beneath it. * **Don't Do This:** Create circular dependencies between layers or allow the UI layer to directly access the database. **Why:** Layered architecture promotes separation of concerns, making the application easier to understand, test, and modify. **Code Example (C#):** """csharp // User Interface Layer (Controllers) public class OrderController : ControllerBase { private readonly IOrderService _orderService; public OrderController(IOrderService orderService) { _orderService = orderService; } [HttpPost] public IActionResult CreateOrder(CreateOrderRequest request) { var orderId = _orderService.CreateOrder(request.CustomerId, request.Items); return Ok(orderId); } } // Application Layer (Services) public interface IOrderService { Guid CreateOrder(Guid customerId, List<OrderItemDto> items); } public class OrderService : IOrderService { private readonly IOrderRepository _orderRepository; private readonly ICustomerRepository _customerRepository; public OrderService(IOrderRepository orderRepository, ICustomerRepository customerRepository) { _orderRepository = orderRepository; _customerRepository = customerRepository; } public Guid CreateOrder(Guid customerId, List<OrderItemDto> items) { var customer = _customerRepository.GetById(customerId); if (customer == null) { throw new CustomerNotFoundException(customerId); } var order = Order.Create(customer, items.Select(i => new OrderItem(i.ProductId, i.Quantity)).ToList()); _orderRepository.Add(order); return order.Id; } } // Domain Layer (Entities and Repositories) public class Order { public Guid Id { get; private set; } public Customer Customer { get; private set; } public List<OrderItem> Items { get; private set; } private Order() { } // Required for ORM private Order(Customer customer, List<OrderItem> items) { Id = Guid.NewGuid(); Customer = customer ?? throw new ArgumentNullException(nameof(customer)); Items = items ?? throw new ArgumentNullException(nameof(items)); } public static Order Create(Customer customer, List<OrderItem> items) { return new Order(customer, items); } } public interface IOrderRepository { void Add(Order order); Order? GetById(Guid id); } // Infrastructure Layer (Data Access) public class OrderRepository : IOrderRepository { private readonly AppDbContext _dbContext; public OrderRepository(AppDbContext dbContext) { _dbContext = dbContext; } public void Add(Order order) { _dbContext.Orders.Add(order); _dbContext.SaveChanges(); } public Order? GetById(Guid id) { return _dbContext.Orders.Find(id); } } """ **Anti-Pattern:** The UI layer (e.g., "OrderController") directly interacting with the "AppDbContext" to save orders. ### 1.2 Hexagonal Architecture (Ports and Adapters) **Standard:** Use Hexagonal Architecture to decouple the domain logic from external dependencies such as databases, UI, and messaging systems. * **Do This:** Define ports (interfaces) that represent the interaction points with the domain. Implement adapters that translate between the port interface and the specific technology. * **Don't Do This:** Directly reference concrete infrastructure implementations within the domain. **Why:** Hexagonal architecture makes the application more testable, flexible, and adaptable to changing technologies. It allows you to switch infrastructure concerns without modifying the core domain. **Code Example (Java):** """java // Domain Layer (Core Logic) // Order Management Interface package com.example.domain; public interface OrderManagement { Order createOrder(OrderRequest request); Order getOrder(String orderId); } // Implementation package com.example.domain; import com.example.domain.model.Order; import com.example.domain.model.OrderRequest; import com.example.ports.outbound.OrderPersistence; public class OrderServiceImpl implements OrderManagement { private final OrderPersistence orderPersistence; public OrderServiceImpl(OrderPersistence orderPersistence) { this.orderPersistence = orderPersistence; } @Override public Order createOrder(OrderRequest request) { Order order = new Order(request.getCustomerId(), request.getItems()); orderPersistence.save(order); return order; } @Override public Order getOrder(String orderId) { return orderPersistence.getOrder(orderId); } } // Define Ports (Outbound) package com.example.ports.outbound; import com.example.domain.model.Order; public interface OrderPersistence { Order save(Order order); Order getOrder(String orderId); } // Infrastructure Layer (Adapters) // Adapter for Database package com.example.adapters.outbound; import com.example.domain.model.Order; import com.example.ports.outbound.OrderPersistence; public class OrderDatabaseAdapter implements OrderPersistence { private final OrderRepository orderRepository; public OrderDatabaseAdapter(OrderRepository orderRepository) { this.orderRepository = orderRepository; } @Override public Order save(Order order) { return orderRepository.save(order); } @Override public Order getOrder(String orderId) { return orderRepository.findById(orderId).orElse(null); } } //Spring Data Repository (Example) package com.example.adapters.outbound; import com.example.domain.model.Order; import org.springframework.data.jpa.repository.JpaRepository; public interface OrderRepository extends JpaRepository<Order, String> { } // Configuration package com.example.config; import com.example.adapters.outbound.OrderDatabaseAdapter; import com.example.adapters.outbound.OrderRepository; import com.example.domain.OrderManagement; import com.example.domain.OrderServiceImpl; import com.example.ports.outbound.OrderPersistence; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public OrderPersistence orderPersistence(OrderRepository orderRepository) { return new OrderDatabaseAdapter(orderRepository); } @Bean public OrderManagement orderManagement(OrderPersistence orderPersistence) { return new OrderServiceImpl(orderPersistence); } } """ **Anti-Pattern:** The "OrderServiceImpl" directly using "JdbcTemplate" to interact with the database. This tightly couples the domain with the database and makes testing difficult. ### 1.3 Clean Architecture **Standard:** Adhere to Clean Architecture principles, ensuring that the domain logic remains independent of frameworks, UI, and external agents. * **Do This:** Structure the application with concentric layers, where the innermost layer represents the domain entities and use cases, and outer layers are responsible for infrastructure concerns. Dependencies should point inwards. * **Don't Do This:** Allow external frameworks or libraries to dictate the structure of the domain logic. **Why:** Clean Architecture maximizes the flexibility, maintainability, and testability of the application by isolating the domain from external forces. **Code Example (TypeScript/Node.js):** """typescript // Domain (Entities and Use Cases) // Entity class Order { constructor(public id: string, public customerId: string, public items: OrderItem[]) {} } class OrderItem { constructor(public productId: string, public quantity: number) {} } // Use Case (Interface) interface CreateOrderUseCase { execute(customerId: string, items: { productId: string; quantity: number }[]): Promise<Order>; } // Use Case (Implementation) class CreateOrder implements CreateOrderUseCase { constructor(private orderRepository: OrderRepository, private customerRepository: CustomerRepository) {} async execute(customerId: string, items: { productId: string; quantity: number }[]): Promise<Order> { const customer = await this.customerRepository.getById(customerId); if (!customer) { throw new Error('Customer not found'); } const order = new Order( uuidv4(), customerId, items.map((item) => new OrderItem(item.productId, item.quantity)) ); await this.orderRepository.save(order); return order; } } // Interface Adapters (Controllers and Gateways) //OrderController import { Request, Response } from 'express'; import { CreateOrderUseCase } from '../../domain/use-cases/create-order'; class OrderController { constructor(private createOrderUseCase: CreateOrderUseCase) {} async createOrder(req: Request, res: Response): Promise<void> { try { const { customerId, items } = req.body; const order = await this.createOrderUseCase.execute(customerId, items); res.status(201).json(order); } catch (error: any) { res.status(400).json({ error: error.message }); } } } // Infrastructure (Repositories) interface OrderRepository { save(order: Order): Promise<void>; getById(id: string): Promise<Order | null>; } // Implementation Example (using MongoDB) class MongoOrderRepository implements OrderRepository { constructor(private db: Db, private collectionName: string = 'orders') {} async save(order: Order): Promise<void> { await this.db.collection(this.collectionName).insertOne(order); } async getById(id: string): Promise<Order | null> { const result = await this.db.collection(this.collectionName).findOne({ id: id }); return result as Order | null; } } """ **Anti-Pattern:** Import Express.js-specific objects (like "Request", "Response") directly into the use case layer. ## 2. Project Structure and Organization ### 2.1 Package/Namespace Organization **Standard:** Organize the project into packages/namespaces that reflect the layered architecture and the domain's bounded contexts. * **Do This:** Create separate packages/namespaces for each layer (e.g., "Application", "Domain", "Infrastructure"). Within the domain layer, further organize based on bounded contexts (e.g., "Sales", "Inventory", "Shipping"). * **Don't Do This:** Put all classes in a single package or namespace, or mix classes from different layers in the same package. **Why:** Clear package organization improves code discoverability, reduces naming conflicts, and reinforces the architectural boundaries. **Example (Maven/Java):** """ src/main/java/ com/example/ application/ OrderService.java ... domain/ model/ Order.java OrderItem.java ... service/ OrderManagement.java ... repository/ OrderRepository.java //Interface definition only ... infrastructure/ persistence/ JpaOrderRepository.java //Implementation using JPA ... interfaces/ rest/ OrderController.java ... """ **Anti-Pattern:** Mixing domain entities ("Order.java") directly with persistence implementations ("JpaOrderRepository.java") in the same package. ### 2.2 Module Boundaries **Standard:** Enforce module boundaries to encapsulate implementation details and control dependencies between different parts of the application. * **Do This:** Use language-specific features like Java modules (Jigsaw) or C# internal access modifiers to restrict access to internal classes and methods. * **Don't Do This:** Expose internal implementation details through public APIs. **Why:** Module boundaries promote encapsulation, reduce coupling, and allow for independent evolution of different parts of the application. **Example (.NET/C#):** """csharp // Domain assembly namespace MyCompany.MyApplication.Domain { public class Order // Public, part of the public API { internal OrderValidationService Validator {get; set;} // Internal, only visible within the Domain assembly } internal class OrderValidationService { // Implementation details for order validation } } // Application assembly (references Domain assembly) namespace MyCompany.MyApplication.Application { public class OrderService { public void CreateOrder(Order order) { // Can access public Order class // Cannot directly access internal OrderValidationService } } } """ **Anti-Pattern:** Making the "OrderValidationService" public, thus exposing internal domain logic to the application layer, breaking encapsulation. ### 2.3 Anti-Corruption Layer **Standard:** When integrating with external systems, use an Anti-Corruption Layer (ACL) to prevent external data models or APIs from polluting the domain model. * **Do This:** Create a translation layer that converts data from the external system into the domain model, and vice versa. * **Don't Do This:** Directly use external data models or APIs within the domain logic. **Why:** An ACL protects the domain from changes in external systems, ensuring the domain remains stable and focused on the business logic. **Code Example (Python):** """python # External System Model class ExternalCustomer: def __init__(self, ext_id, full_name, address_line_1): self.ext_id = ext_id self.full_name = full_name self.address_line_1 = address_line_1 # Domain Model class Customer: def __init__(self, customer_id, name, address): self.customer_id = customer_id self.name = name self.address = address class Address: def __init__(self, street): self.street = street # Anti-Corruption Layer class CustomerTranslator: @staticmethod def to_domain(external_customer: ExternalCustomer) -> Customer: customer_id = external_customer.ext_id #Remap external ID name = external_customer.full_name address = Address(external_customer.address_line_1) return Customer(customer_id, name, address) # Usage external_customer_data = ExternalCustomer("EXT123", "John Doe", "123 Main St") customer = CustomerTranslator.to_domain(external_customer_data) print(customer.name) # John Doe """ **Anti-Pattern:** Directly using the "ExternalCustomer" object within the domain logic, tightly coupling the domain to the external system. The use of static methods can also be debated here; using a "CustomerTranslator" object injected as a dependency might be better depending on complexity. ## 3. Modern Approaches and Patterns ### 3.1 Microservices Architecture (Context Boundaries) **Standard:** When using a microservices architecture, align service boundaries with the bounded contexts identified during domain analysis. * **Do This:** Design each microservice to be responsible for a single bounded context. Ensure each service has its own database and domain model. * **Don't Do This:** Create large, monolithic services that span multiple bounded contexts. **Why:** Microservices aligned with bounded contexts promote autonomy, independent deployment, and scalability. ### 3.2 Event-Driven Architecture (Domain Events) **Standard:** Leverage domain events to decouple services and facilitate communication within and between bounded contexts. * **Do This:** Raise domain events when significant state changes occur within the domain. Use a message broker (e.g., RabbitMQ, Kafka) to distribute events asynchronously to interested subscribers. * **Don't Do This:** Tightly couple services by directly invoking methods on other services. **Why:** Event-driven architecture improves scalability, resilience, and flexibility by decoupling services and enabling asynchronous communication. **Code Example (Python with Celery/RabbitMQ):** """python # Domain Event class OrderCreatedEvent: def __init__(self, order_id, customer_id): self.order_id = order_id self.customer_id = customer_id # Application Layer (Raise Event) from celery import Celery celery_app = Celery('tasks', broker='pyamqp://guest@localhost//') def create_order(customer_id, items): order = Order(customer_id, items) # ... persist order ... event = OrderCreatedEvent(order.id, customer_id) publish_order_created_event.delay(event.__dict__) # serialize the event as a dictionary return order @celery_app.task def publish_order_created_event(event_data): # Simulate publishing to a message queue print(f"Published OrderCreatedEvent: {event_data}") # In a real implementation, you'd use a library to publish to RabbitMQ # e.g., pika, kombu """ **Anti-Pattern:** Directly calling a method on the "ShippingService" from the "OrderService" to initiate shipment after order creation. ### 3.3 CQRS (Command Query Responsibility Segregation) **Standard:** Implement CQRS to separate read and write operations, allowing for optimized data models and scaling strategies for each. * **Do This:** Create separate models and data stores for commands (write operations) and queries (read operations). Use DTOs to transfer data between layers. * **Don't Do This:** Use the same domain model for both reads and writes, leading to performance bottlenecks and complexity. **Why:** CQRS allows for independent scaling and optimization of read and write operations. **Code Example (Go):** """go // Command Model package command import "fmt" type CreateOrderCommand struct { CustomerID string Items []OrderItem } type OrderItem struct { ProductID string Quantity int } type OrderCommandHandler struct { OrderRepository OrderCommandRepository } func (h *OrderCommandHandler) Handle(cmd CreateOrderCommand) error { fmt.Printf("Handling create order command: %v\n", cmd) err := h.OrderRepository.Save(cmd) if err != nil { return fmt.Errorf("failed to save order: %w", err) } return nil } type OrderCommandRepository interface { Save(cmd CreateOrderCommand) error } // Query Model package query type Order struct { OrderID string CustomerID string Items []OrderItem } type OrderItem struct { ProductID string Quantity int } type OrderQuery struct { OrderID string } type OrderQueryHandler struct { OrderRepository OrderQueryRepository } func (h *OrderQueryHandler) Handle(query OrderQuery) (Order, error) { fmt.Printf("Handling order query: %v\n", query) order, err := h.OrderRepository.Get(query.OrderID) if err != nil { return Order{}, fmt.Errorf("failed to retrieve order: %w", err) } return order, nil } type OrderQueryRepository interface { Get(orderID string) (Order, error) } """ **Anti-Pattern:** Using the same "Order" data model for both writing (creating/updating) and reading order data. ### 3.4 Bounded Contexts **Standard**: Explicitly define bounded contexts to manage complexity by creating explicit semantic boundaries within the domain. * **Do This**: Identify ubiquitous language within each context, and model elements accordingly. Ensure each context has its own, isolated data. * **Don't Do This**: Allow models to bleed across context boundaries, leading to ambiguity and tight coupling. **Why**: Bounded contexts mitigate complexity, improve team autonomy, and encourage better alignment with business needs. ## 4. Technology-Specific Details ### Example: Spring Boot (Java) * Use Spring Data JPA for repository implementation with proper use of interfaces. * Apply Spring's "@Transactional" annotation to methods in the Application Layer to manage transactions. * Ensure proper exception handling using "@ControllerAdvice" for centralized error handling and conversion to appropriate HTTP responses. ### Example: .NET (C#) * Utilize Entity Framework Core for database interactions, ensuring proper configuration of DbContext and migrations. * Implement MediatR for handling commands and queries, providing a clean separation of concerns. * Use FluentValidation for data validation to ensure data integrity. ## 5. Conclusion These coding standards for Core Architecture in Domain-Driven Design provide a solid foundation for building maintainable, scalable, and business-aligned applications. By adhering to these guidelines, development teams can ensure a consistent and high-quality codebase that supports the long-term evolution of the system. Remember that the key is to understand *why* these standards exist and apply them thoughtfully in context.
# State Management Standards for Domain Driven Design This document outlines coding standards for state management within Domain-Driven Design (DDD) applications. It aims to provide clear guidance on how to manage application state, data flow, and reactivity while adhering to DDD principles. This ensures maintainability, performance, and security. ## 1. Introduction to State Management in DDD State management in DDD contexts refers to how the application persists, retrieves, and modifies the state of domain entities and aggregates over time. Effective state management is critical for ensuring data consistency, supporting complex business logic, and enabling scalability and resilience. It's not just about CRUD operations; it's about reflecting the evolving state of the domain accurately. ## 2. Core Principles * **Ubiquitous Language:** State representations should align with the Ubiquitous Language defined by the domain experts. * **Data Integrity:** Ensure that the state of aggregates and entities remains consistent and valid according to domain rules. * **Persistence Ignorance:** Domain models shouldn't be tightly coupled to persistence mechanisms. Repositories handle persistence concerns. * **Eventual Consistency:** In distributed systems, embrace eventual consistency, using Domain Events to propagate state changes. * **Immutability:** Favor immutable data structures where appropriate to simplify reasoning about state changes. * **Reactive Principles:** Use reactive approaches where real-time updates and responsiveness are required. ## 3. Repository Pattern The Repository pattern mediates between the domain and data mapping layers, providing a collection-like interface for accessing domain objects. ### 3.1. Standard: Repository Interface Definition * **Do This:** Define repository interfaces within the domain layer. * **Don't Do This:** Expose persistence-specific details (e.g., database queries) in the domain layer. **Why:** Keeps the domain model independent of any specific persistence technology and promotes testability. **Code Example (C#):** """csharp // Domain Layer public interface IOrderRepository { Order GetById(Guid id); IEnumerable<Order> GetAll(); void Add(Order order); void Update(Order order); void Delete(Order order); } // Infrastructure Layer (Implementation) public class OrderRepository : IOrderRepository { private readonly AppDbContext _dbContext; public OrderRepository(AppDbContext dbContext) { _dbContext = dbContext; } public Order GetById(Guid id) { return _dbContext.Orders.Find(id); } public IEnumerable<Order> GetAll() { return _dbContext.Orders.ToList(); } public void Add(Order order) { _dbContext.Orders.Add(order); _dbContext.SaveChanges(); } public void Update(Order order) { _dbContext.Orders.Update(order); _dbContext.SaveChanges(); } public void Delete(Order order) { _dbContext.Orders.Remove(order); _dbContext.SaveChanges(); } } """ ### 3.2. Standard: Repository Methods * **Do This:** Expose methods that align with domain operations (e.g., "GetOpenOrders", "FindOrdersByCustomer"). * **Don't Do This:** Create generic CRUD repositories. Specialized repositories are preferred. **Why:** Generic CRUD repositories often leak domain knowledge into the infrastructure layer. **Code Example (Java):** """java // Domain Layer public interface OrderRepository { Order findById(UUID id); List<Order> findByCustomerId(UUID customerId); List<Order> findOpenOrders(); void save(Order order); void delete(Order order); } // Infrastructure Layer (Implementation with Spring Data JPA) @Repository public interface JpaOrderRepository extends OrderRepository, JpaRepository<Order, UUID> { List<Order> findByCustomerId(UUID customerId); @Query("SELECT o FROM Order o WHERE o.status = 'OPEN'") List<Order> findOpenOrders(); } """ ### 3.3. Anti-Pattern: Active Record in Domain Entities * **Avoid:** Directly embedding persistence logic within domain entities (Active Record pattern). * **Why:** Violates the principle of persistence ignorance and makes testing difficult. Domain entities become tightly coupled to the database. ### 3.4 Technology-Specific Details (Entity Framework Core): * Use "AsNoTracking()" when querying for read-only operations to improve performance. * Configure relationships using Fluent API instead of data annotations within domain entities. Keep entities clean. """csharp //Example using AsNoTracking() public Order GetReadOnlyOrder(Guid id) { return _dbContext.Orders.AsNoTracking().FirstOrDefault(o => o.Id == id); } """ ## 4. Domain Events Domain Events represent significant occurrences within the domain. They facilitate communication between aggregates and allow for decoupling of business logic. ### 4.1. Standard: Event Definition * **Do This:** Define domain events as immutable classes representing a past event. * **Don't Do This:** Include mutable state within domain events. **Why:** Immutability ensures that events accurately reflect the state at the time of the event and prevents unintended side effects. Events represent *what* happened, not *what is happening now*. **Code Example (C#):** """csharp // Domain Layer public class OrderCreatedEvent : INotification { public Guid OrderId { get; } public Guid CustomerId { get; } public DateTime CreatedDate { get; } public OrderCreatedEvent(Guid orderId, Guid customerId, DateTime createdDate) { OrderId = orderId; CustomerId = customerId; CreatedDate = createdDate; } } """ ### 4.2. Standard: Publishing Events * **Do This:** Publish domain events after a state change occurs within an aggregate. * **Don't Do This:** Directly call event handlers within the aggregate. Use a mediator pattern or similar mechanism for decoupling. **Why:** Decoupling allows event handlers to be added or removed without modifying the core domain logic. **Code Example (C# with MediatR):** """csharp // Domain Layer (Aggregate Root) public class Order { public Guid Id { get; private set; } public Guid CustomerId { get; private set; } public OrderStatus Status { get; private set; } private readonly List<INotification> _domainEvents = new List<INotification>(); public IReadOnlyCollection<INotification> DomainEvents => _domainEvents.AsReadOnly(); public Order(Guid customerId) { Id = Guid.NewGuid(); CustomerId = customerId; Status = OrderStatus.Created; _domainEvents.Add(new OrderCreatedEvent(Id, CustomerId, DateTime.UtcNow)); } public void MarkAsShipped() { if (Status != OrderStatus.Created) { throw new InvalidOperationException("Order must be in Created status."); } Status = OrderStatus.Shipped; _domainEvents.Add(new OrderShippedEvent(Id)); } public void ClearDomainEvents() { _domainEvents.Clear(); } } //Application Layer (Using MediatR to publish events) public class OrderService { private readonly IOrderRepository _orderRepository; private readonly IMediator _mediator; public OrderService(IOrderRepository orderRepository, IMediator mediator) { _orderRepository = orderRepository; _mediator = mediator; } public async Task CreateOrder(Guid customerId) { var order = new Order(customerId); _orderRepository.Add(order); await _mediator.Publish(new OrderCreatedEvent(order.Id, order.CustomerId, DateTime.UtcNow)); //Publish the Domain Event _orderRepository.Update(order); // Save aggregate state *after* publishing events if using outbox } } // Event Handler Example public class OrderCreatedEventHandler : INotificationHandler<OrderCreatedEvent> { private readonly ILogger<OrderCreatedEventHandler> _logger; public OrderCreatedEventHandler(ILogger<OrderCreatedEventHandler> logger) { _logger = logger; } public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken) { // Log the order creation _logger.LogInformation($"Order {notification.OrderId} created for customer {notification.CustomerId}"); await Task.CompletedTask; //Potentially perform integrations, notifications etc. } } """ ### 4.3. Standard: Outbox Pattern * **Do This:** Use the Outbox Pattern for reliable event delivery, especially when dealing with distributed transactions. * **Explanation:** The Outbox pattern stores domain events in the same transaction as the aggregate state changes. A separate process then reliably publishes these events to a message broker. **Code Example (Conceptual C#):** """csharp // 1. Add Domain Event to Outbox Table within the same transaction as Order creation using (var transaction = _dbContext.Database.BeginTransaction()) { try { var order = new Order(customerId); _orderRepository.Add(order); // Save Domain Event to Outbox var outboxMessage = new OutboxMessage { Id = Guid.NewGuid(), OccurredOn = DateTime.UtcNow, Type = typeof(OrderCreatedEvent).FullName, Data = JsonConvert.SerializeObject(new OrderCreatedEvent(order.Id, order.CustomerId, DateTime.UtcNow)), ProcessedDate = null //Not processed yet }; _dbContext.OutboxMessages.Add(outboxMessage); _dbContext.SaveChanges(); transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); //Handle Exception } } // 2. Background Worker processes outbox messages public class OutboxProcessor : BackgroundService { private readonly IServiceProvider _serviceProvider; public OutboxProcessor(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using (var scope = _serviceProvider.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var mediator = scope.ServiceProvider.GetRequiredService<IMediator>(); var messages = dbContext.OutboxMessages .Where(m => m.ProcessedDate == null) .OrderBy(m => m.OccurredOn) .Take(20) //Batch Size .ToList(); foreach (var message in messages) { //Publish Domain Event var eventType = Type.GetType(message.Type); var eventData = JsonConvert.DeserializeObject(message.Data, eventType) as INotification; await mediator.Publish(eventData, stoppingToken); //Mark Message as Processed message.ProcessedDate = DateTime.UtcNow; } await dbContext.SaveChangesAsync(stoppingToken); } await Task.Delay(5000, stoppingToken); //Polling Interval } } } //3. OutboxMessage entity public class OutboxMessage { public Guid Id { get; set; } public string Type { get; set; } public string Data { get; set; } public DateTime OccurredOn { get; set; } public DateTime? ProcessedDate { get; set; } } """ ### 4.4. Anti-Pattern: Ignoring Domain Events * **Avoid:** Performing side effects directly within aggregates without raising domain events. This creates tight coupling. ## 5. Snapshots Snapshots capture the state of an aggregate at a specific point in time. They can optimize performance by reducing the need to replay all events since the aggregate's creation when retrieving it. ### 5.1. Standard: Snapshot Frequency * **Consider:** Implementing snapshots for aggregates with a large number of events, taking them periodically (e.g., after every 100th event). * **Rationale:** Significantly speeds up aggregate reconstruction, especially when dealing with a long event history. ### 5.2. Standard: Snapshot Storage * **Store:** Snapshots in a separate store from the event stream, optimized for quick retrieval of the latest snapshot. * **Explanation:** A dedicated snapshot store allows the event store to focus on event append operations, while the snapshot store provides fast read access. """csharp //Example Snapshot Entity public class OrderSnapshot { public Guid AggregateId { get; set; } public int Version { get; set; } public string Data { get; set; } //Serialized State public DateTime CreatedAt {get; set;} } """ ## 6. CQRS (Command Query Responsibility Segregation) CQRS separates read and write operations for improved performance and scalability. ### 6.1. Standard: Separate Models * **Do This:** Maintain separate read (query) models that are optimized for specific UI or reporting requirements. * **Don't Do This:** Directly expose the domain model for querying. **Why:** Read models can be denormalized and optimized for specific queries, avoiding complex joins and improving performance. Domain models are designed for behavior and consistency; query models are designed for retrieval. ### 6.2. Standard: Materialized Views * **Use:** Materialized views to pre-compute query results and store them for fast retrieval. * **Update:** Update materialized views asynchronously based on domain events. * **Example:** Using a message queue like RabbitMQ to update read models ensures eventual consistency. ### 6.3. Standard: Asynchronous Updates * **Update:** Read models asynchronously in response to domain events to ensure that the write side is not impacted by the update process of read models. **Code example (Conceptual):** """csharp //OrderCreatedEventHandler (from Domain Event section) public class OrderCreatedEventHandler : INotificationHandler<OrderCreatedEvent> { private readonly IReadOrderRepository _readOrderRepository; //Repository for the READ model public OrderCreatedEventHandler(IReadOrderRepository readOrderRepository) { _readOrderRepository = readOrderRepository; } public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken) { //Update the Read Model asynchronously var readOrder = new ReadOrder { OrderId = notification.OrderId, CustomerId = notification.CustomerId, CreatedDate = notification.CreatedDate }; await _readOrderRepository.Add(readOrder); } } //Example Read Model public class ReadOrder { public Guid OrderId { get; set; } public Guid CustomerId { get; set; } public DateTime CreatedDate { get; set; } //Potentially other data denormalized for read purposes } """ ### 6.4. Anti-Pattern: Synchronous Read Model Updates * **Avoid:** Updating read models synchronously within command handlers. This can negatively impact the responsiveness of the application. ## 7. Immutability ### 7.1. Standard: Immutable Value Objects * **Do This:** Define value objects as immutable classes or records. * **Benefits:** Immutability simplifies reasoning, prevents unintended side effects, and improves thread safety. **Code Example (C# Record):** """csharp // Domain Layer public record Address(string Street, string City, string State, string ZipCode); """ ### 7.2. Standard: Immutable Event Payloads * **Do This:** Ensure that domain events carry immutable payloads. ## 8. Concurrency and Consistency ### 8.1. Optimistic Concurrency * **Use:** Optimistic concurrency control to handle concurrent updates to aggregates. * **Implementation:** Include a version property in the aggregate and check for changes during updates. **Code Example (C#):** """csharp // Aggregate Root public class Order { public Guid Id { get; private set; } public int Version { get; private set; } // Optimistic Concurrency public void UpdateShippingAddress(string newAddress) { //Business Logic Version++; } } //Repository public void Update(Order order) { _dbContext.Entry(order).Property("Version").OriginalValue = order.Version - 1; // Set original value for concurrency check try { _dbContext.Orders.Update(order); _dbContext.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { throw new ConcurrencyException("The order has been modified by another user."); } } """ ### 8.2. Standard: Eventual Consistency Strategies * For distributed systems that span multiple bounded contexts, embrace eventual consistency, using Domain Events to propagate state changes. ## 9. Reactivity ### 9.1. Standard: Reactive Streams * **Use:** Reactive Streams (e.g., RxJava, Reactor) for handling real-time data updates and asynchronous event processing. * **Benefits:** Improve responsiveness and scalability by processing events non-blocking. ### 9.2. Standard: Backpressure * **Implement:** Backpressure mechanisms to prevent overwhelming consumers of reactive streams. * RxJava and Reactor frameworks natively support backpressure strategies like buffering, dropping, or erroring. * **Explanation:** Ensures that consumers can handle the incoming event rate, preventing performance degradation or failure. ## 10. Testing ### 10.1. Standard: Unit Tests * **Test:** Domain entities and value objects thoroughly with unit tests to ensure they behave as expected and maintain state correctly. * **Focus:** Boundary conditions and state transitions. ### 10.2. Standard: Integration Tests * **Test:** Repositories with integration tests to verify that they correctly persist and retrieve domain objects. * **Use:** In-memory databases or test containers for isolated testing. ### 10.3. Standard: Eventual Consistency Tests * **Test:** Eventual consistency scenarios with integration tests that simulate asynchronous event processing. May require polling or retry mechanisms to confirm state updates. ## 11. Security Implications ### 11.1. Standard: Secure State Transitions * **Ensure:** That all state transitions within aggregates are protected by appropriate authorization checks. * **Prevent:** Unauthorized modifications of the state. ### 11.2. Standard: Data Encryption * **Encrypt:** Sensitive data at rest in the database and during transmission over the network. ## 12. Performance Considerations * Use lazy loading strategically in Entity Framework Core or other ORMs, particularly on aggregate roots, if their child entities are not always required. Overuse can lead to N+1 query problems. * Implement caching strategies at different levels (e.g., in-memory, distributed cache) for frequently accessed read models. * Optimize database indexes for commonly used query patterns in repositories and read models. ## 13. Conclusion Adhering to these state management standards is critical for building robust, maintainable, and scalable DDD applications. By carefully considering data consistency, decoupling the domain from persistence concerns, and leveraging patterns like Domain Events and CQRS, developers can create software that accurately reflects the complexities of the business domain and adapts effectively to change. Remember to tailor these guidelines to the specific needs of your project and domain, and continuously refine them as your understanding evolves.
# Performance Optimization Standards for Domain Driven Design This document establishes coding standards for performance optimization within a Domain-Driven Design (DDD) context. These standards aim to improve application speed, responsiveness, and resource usage, leveraging modern approaches and the latest DDD principles. This document is intended to guide developers and inform AI coding assistants. ## 1. Architectural Considerations for Performance ### 1.1. Bounded Context Decomposition **Standard:** Decompose the system into loosely coupled Bounded Contexts with well-defined interfaces. **Do This:** Identify core business capabilities and represent them as independent Bounded Contexts. **Don't Do This:** Create monolithic applications with a single, large domain model. **Why:** This reduces the scope of data access and computation within each context, improving performance and scalability. Each bounded context can be independently optimized without affecting others. **Code Example (Conceptual):** """ // Monolithic Application (Anti-pattern) class OrderService { // Handles order processing, customer management, and inventory updates } // DDD with Bounded Contexts namespace Ordering { class OrderService { // Deals solely with order-related logic } } namespace Inventory { class InventoryService { // Deals solely with inventory-related logic } } namespace CustomerManagement { class CustomerService { // Deals solely with customer-related logic } } """ ### 1.2. Strategic Event Handling **Standard:** Utilize Domain Events strategically for asynchronous processing and decoupling. **Do This:** Publish Domain Events for non-critical side effects (e.g., sending notifications, updating reports). **Don't Do This:** Perform all operations synchronously within a single transaction or service call. **Why:** Asynchronous event handling moves complex operations out of the main request thread, improving responsiveness. **Code Example (C#):** """csharp // Synchronous (Anti-pattern) public class OrderService { public void PlaceOrder(Order order) { // Process order // ... // Send email confirmation (slow and blocking) SendConfirmationEmail(order.CustomerEmail); } private void SendConfirmationEmail(string email) { /* Implementation */ } } // Asynchronous with Domain Events public class OrderPlacedEvent : INotification { public Order Order { get; set; } } public class OrderService { private readonly IPublisher _publisher; public OrderService(IPublisher publisher) { _publisher = publisher; } public async Task PlaceOrder(Order order) { // Process order // ... // Publish event for asynchronous handling await _publisher.Publish(new OrderPlacedEvent { Order = order }); } } public class OrderPlacedEventHandler : INotificationHandler<OrderPlacedEvent> { public async Task Handle(OrderPlacedEvent notification, CancellationToken cancellationToken) { // Asynchronously send email confirmation await SendConfirmationEmail(notification.Order.CustomerEmail); } private async Task SendConfirmationEmail(string email) { /* await Implementation */ } } """ ### 1.3. CQRS (Command Query Responsibility Segregation) **Standard:** Employ CQRS to separate read and write operations for high-performance querying. **Do This:** Create separate data models for read and write operations. Optimize read models for specific query needs. **Don't Do This:** Use the same domain model for both reads and writes, especially for complex read scenarios. **Why:** Tailored read models significantly improve query performance. Write models can focus on consistency and transaction management. **Code Example (Conceptual - C#):** """csharp // Simplified Example // Command (Write) Model public class Order { public Guid Id { get; set; } public List<OrderItem> Items { get; set; } // Aggregate root relationships // ... Domain Logic } //Query (Read) Model public class OrderSummary { public Guid Id { get; set; } public string CustomerName { get; set; } public decimal TotalAmount { get; set; } //Optimized for listing orders } """ ## 2. Domain Model Optimization ### 2.1. Lazy Loading & Eager Loading **Standard:** Use lazy loading for related entities, and eager loading for entities frequently accessed together. **Do This:** Configure lazy loading for optional relationships and eager loading in specific queries where related data is needed. **Don't Do This:** Eager load all relationships, as it can lead to significant performance degradation ("over-fetching"). Don't rely solely on lazy loading as the database "chatters". **Why:** Balances data retrieval needs and avoids unnecessary database round trips. **Code Example (Entity Framework Core - C#):** """csharp // Lazy Loading (Requires proxy generation) public class Customer { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Order> Orders { get; set; } // Lazy-loaded collection } // Eager Loading using (var context = new ApplicationDbContext()) { var customer = context.Customers .Include(c => c.Orders) // Eagerly load Orders .FirstOrDefault(c => c.Id == customerId); // Accessing customer.Orders will not trigger a new database query } // Projection to DTO for optimized reading using (var context = new ApplicationDbContext()) { var customerDto = context.Customers .Where(c => c.Id == customerId) .Select(c => new CustomerDto { Id = c.Id, Name = c.Name, OrderCount = c.Orders.Count() }) .FirstOrDefault(); } """ ### 2.2. Value Objects & Immutability **Standard:** Utilize Value Objects extensively for representing domain concepts and enforce immutability. **Do This:** Create Value Objects for address, monetary amount, and other descriptive attributes. Immutability: Ensure that value objects cannot be changed after creation. **Don't Do This:** Use primitive types directly or create mutable domain objects, leading to shared state and potential performance issues with concurrency. **Why:** Immutability simplifies reasoning about state, eliminates the need for defensive copying, and improves concurrency. **Code Example (C#):** """csharp public class Address { public string Street { get; } public string City { get; } public string PostalCode { get; } public Address(string street, string city, string postalCode) { Street = street; City = city; PostalCode = postalCode; } } """ ### 2.3. Aggregate Boundaries **Standard:** Design aggregates around transactional consistency, but keep them as small as possible. **Do This:** Ensure that operations within an aggregate maintain consistency. Avoid creating large aggregates that encompass unrelated entities. **Don't Do This:** Create bloated aggregates, leading to excessive locking and performance bottlenecks. **Why:** Properly scoped aggregates reduce contention and improve concurrency. Limit the scope of operations that require a transaction. ## 3. Data Access Optimization ### 3.1. Compiled Queries **Standard:** Use compiled queries (e.g., with Entity Framework Core) for frequently executed queries. **Do This:** Compile queries that are executed repeatedly to reduce parsing and compilation overhead. **Don't Do This:** Use dynamic queries where they are unnecessary or compile every query - only compile those run very often. **Why:** Compiled queries cache the query execution plan, improving performance for subsequent executions. **Code Example (Entity Framework Core - C#):** """csharp private static readonly Func<ApplicationDbContext, int, Customer> _getCustomerById = EF.CompileQuery((ApplicationDbContext context, int customerId) => context.Customers.FirstOrDefault(c => c.Id == customerId)); using (var context = new ApplicationDbContext()) { var customer = _getCustomerById(context, customerId); //Fast execution } """ ### 3.2. Connection Pooling **Standard:** Leverage connection pooling provided by the database provider. **Do This:** Ensure that connection strings are configured to enable connection pooling. Use dependency injection to share context instances rather than creating new expensive ones. **Don't Do This:** Open and close database connections frequently; let the connection pool manage connections. **Why:** Connection pooling reduces the overhead of establishing and closing database connections. ### 3.3. Appropriate Indexing **Standard:** Create appropriate indexes on database columns used in common queries. **Do This:** Analyze query execution plans to identify missing indexes and create them accordingly. Consider composite indexes for multi-column queries. **Don't Do This:** Add too many indexes, as they can slow down write operations. **Why:** Indexes improve query performance by allowing the database to quickly locate relevant data. ### 3.4. Asynchronous Data Access **Standard:** Use asynchronous methods for data access operations where possible. **Do This:** Use "async" and "await" keywords for database operations to avoid blocking threads. **Don't Do This:** Perform synchronous data access in request threads, leading to thread starvation. **Code Example (Entity Framework Core - C#):** """csharp public async Task<Customer> GetCustomerAsync(int customerId) { using (var context = new ApplicationDbContext()) { return await context.Customers.FindAsync(customerId); // Asynchronous operation } } """ ### 3.5 Data Transfer Objects (DTOs) **Standard:** Returning DTOs instead of domain entities. **Do This:** Project the domain entities to flat DTOs before returning data to client or other bounded contexts. **Don't Do This:** Serializing the entire graph of domain entities, exposing internal details or unnecessarily large payloads. **Why:** DTOs allow shaping the data for a specific purpose, decreasing the payload and potential for security issues in exposing domain internals. **Code Example (C#):** """csharp public class CustomerDto { public Guid Id {get; set;} public string Name { get; set; } public string Email { get; set; } } // Service to provide customer list as DTOs public async Task<IEnumerable<CustomerDto>> GetCustomersAsync() { using(var context = new ApplicationDbContext()) { return await context.Customers.Select(c => new CustomerDto{ Id = c.Id, Name = c.Name, Email = c.Email }).ToListAsync(); } } """ ## 4. Caching Strategies ### 4.1. Layered Caching **Standard:** Implement caching at different layers of the application architecture. **Do This:** Use in-memory caching (e.g., "MemoryCache" in .NET) for frequently accessed data. Implement distributed caching (e.g., Redis, Memcached) for shared data across multiple instances. Consider caching at the data access layer. **Don't Do This:** Cache sensitive information without proper security measures. Over-cache, leading to stale data. **Why:** Reduces database load and improves response times. **Code Example (In-Memory Caching - C#):** """csharp private readonly IMemoryCache _cache; public CustomerService(IMemoryCache cache) { _cache = cache; } public async Task<Customer> GetCustomerAsync(int customerId) { string cacheKey = $"customer-{customerId}"; if (!_cache.TryGetValue(cacheKey, out Customer customer)) { using (var context = new ApplicationDbContext()) { customer = await context.Customers.FindAsync(customerId); var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromSeconds(60)); _cache.Set(cacheKey, customer, cacheEntryOptions); } } return customer; } """ ### 4.2. Cache Invalidation **Standard:** Implement effective cache invalidation strategies. **Do This:** Use time-based expiration, event-based invalidation, or a combination of both. Be aware of cache stampedes. **Don't Do This:** Rely solely on time-based expiration, as it can lead to stale data. Neglect cache invalidation, leading to inconsistencies. **Why:** Ensures that cached data remains consistent with the underlying data source. ## 5. Code Level Optimizations ### 5.1 Avoid Boxing/Unboxing **Standard:** Reduce Boxing/Unboxing operations. **Do This:** Use Generics instead of "object". Consider "struct" for small value types where value semantics are required. **Don't Do This:** Use non-generic collections such as ArrayList causing boxing and unboxing of value types. **Why:** Boxing and unboxing introduce performance overhead. ### 5.2 String Concatenation: StringBuilder **Standard:** Use "StringBuilder" for string manipulation operations. **Do This:** Create string using "StringBuilder" when concatenating strings in loops or repeatedly modifying the same string, avoid creating temporary string instances. **Don't Do This:** Repeatedly concatenating strings with the "+" operator can lead to performance issues, especially within loops. **Why:** "String" class in .NET is immutable. Using "+" creates copies on each operation. ### 5.3 Use "HashSet" for lookups **Standard:** Use "HashSet<T>" when verifying the presence of items in a set. **Do This:** Store lookup tables in a "HashSet<T>". "HashSet" implements hash-based lookup which provides a faster "O(1)" lookup time (average case) compared to "List.Contains(item)" that takes "O(n)". **Don't Do This:** Use ".Contains()" method on a list, particularly in scenarios where performance is critical. **Why:** Using "HashSet<T>" improves performance significantly. ## 6. Monitoring and Profiling ### 6.1. Performance Monitoring **Standard:** Implement performance monitoring to track key metrics. **Do This:** Use tools like Application Insights or Prometheus to monitor response times, database query times, and resource utilization. **Don't Do This:** Operate in the dark without monitoring your application's performance. **Why:** Allows you to identify performance bottlenecks and proactively address them. ### 6.2. Profiling **Standard:** Use profiling tools to identify performance bottlenecks in the code. **Do This:** Use profilers like JetBrains dotTrace or Visual Studio Profiler to analyze code execution and identify slow methods or memory leaks. **Don't Do This:** Guess where the performance bottlenecks are; use profiling data to guide your optimizations. **Why:** Provides detailed insights into code execution and allows you to target optimizations effectively. ## 7. Technology-Specific Considerations ### 7.1. .NET Performance * **Standard:** Utilize ValueTask for async methods that might complete synchronously. * **Standard:** Use "Span<T>" / "Memory<T>" for high-performance data slicing. * **Standard:** Use System.Text.Json for serialization, providing better performance. ### 7.2. Database Performance * **Standard:** Optimize database queries using execution plans. * **Standard:** Utilize database-specific features such as stored procedures for complex operations. ## 8. Continuous Improvement **Standard:** Regularly review and refine performance optimization strategies. **Do This:** Conduct performance audits, analyze monitoring data, and update coding standards based on new findings. **Don't Do This:** Treat performance optimization as a one-time task; continuous improvement is key. **Why:** Ensures that the application remains performant as it evolves.
# Testing Methodologies Standards for Domain Driven Design This document outlines the coding standards for testing methodologies in Domain Driven Design (DDD). It provides guidelines for unit, integration, and end-to-end testing within a DDD context, emphasizing maintainability, performance, and security. These standards are designed to ensure that tests are effective, reliable, and aligned with the principles of DDD. By adhering to these practices, development teams can create robust and well-tested domain models. ## 1. General Testing Principles in DDD ### 1.1. Standard: Focus on Domain Logic **Do This:** Prioritize testing domain logic encapsulated in entities, value objects, domain services, and aggregates. **Don't Do This:** Neglect testing complex interactions and business rules within the domain layer in favor of simple CRUD operations. **Why:** Domain logic is the heart of the application in DDD. Comprehensive testing ensures that business rules are correctly implemented and that the domain model behaves as expected. This focus leads to more reliable and maintainable software. **Example:** """csharp // Correct: Testing a domain service [Fact] public void TransferFunds_SufficientBalance_FundsTransferred() { // Arrange var sourceAccount = new BankAccount("123", 1000); var destinationAccount = new BankAccount("456", 0); var transferService = new FundTransferService(); // Act transferService.TransferFunds(sourceAccount, destinationAccount, 500); // Assert Assert.Equal(500, sourceAccount.Balance); Assert.Equal(500, destinationAccount.Balance); } // Incorrect: Testing a data access method instead of domain logic [Fact] public void CanRetrieveBankAccountFromDatabase() { // This tests infrastructure, not domain logic. Avoid. var bankAccount = _bankAccountRepository.GetById("123"); Assert.NotNull(bankAccount); } """ ### 1.2. Standard: Test-Driven Development (TDD) **Do This:** Use TDD to drive the design of domain objects and interactions. Write tests before writing the corresponding code. **Don't Do This:** Write code first and then write tests as an afterthought. **Why:** TDD leads to better-designed domain models because it forces you to think about the behavior of your domain objects before implementing them. It also ensures that your domain logic is always covered by tests. **Example:** """csharp // TDD Cycle Example // 1. Write a failing test based on a business requirement. [Fact] public void OrderTotal_MultipleItems_CalculatesCorrectly() { // Arrange var order = new Order(); order.AddItem(new OrderItem("Product1", 10, 2)); order.AddItem(new OrderItem("Product2", 5, 3)); // Act decimal total = order.Total(); // Assert Assert.Equal(35, total); // Test fails initially } // 2. Write the minimum amount of code to make the test pass. public class Order { private List<OrderItem> _items = new List<OrderItem>(); public decimal Total() { return _items.Sum(item => item.Price * item.Quantity); } public void AddItem(OrderItem item) { _items.Add(item); } } public class OrderItem { public string ProductName { get; set; } public decimal Price { get; set; } public int Quantity { get; set; } public OrderItem(string productName, decimal price, int quantity) { ProductName = productName; Price = price; Quantity = quantity; } } // 3. Refactor if necessary while ensuring tests continue to pass. """ ### 1.3. Standard: Behavior-Driven Development (BDD) **Do This:** Use BDD frameworks to specify the behavior of domain objects in a more expressive and human-readable way. Tools like SpecFlow or Cucumber can be used to write scenarios that describe how the system should behave. **Don't Do This:** Rely solely on unit tests without describing the overall behavior of the system in terms of scenarios and features. **Why:** BDD helps bridge the gap between technical and non-technical stakeholders by expressing acceptance criteria as executable specifications. This ensures everyone has a shared understanding of the system's behavior. **Example (SpecFlow):** """gherkin Feature: Order Management As a customer I want to be able to place an order So that I can receive the products I want Scenario: Place a valid order Given a customer with sufficient credit And a product is available in stock When the customer places an order for the product Then the order should be created successfully And the product quantity should be reduced And the customer's credit should be updated """ ## 2. Unit Testing ### 2.1. Standard: Isolate Domain Objects **Do This:** Use mocking or stubbing to isolate domain objects during unit testing. Avoid dependencies on external systems or infrastructure components. **Don't Do This:** Directly access databases, file systems, or other external resources during unit tests. **Why:** Unit tests should be fast, reliable, and repeatable. Dependencies on external resources can make tests slow, brittle, and difficult to maintain. **Example:** """csharp // Correct: Using Moq to mock a repository [Fact] public void PlaceOrder_ValidOrder_OrderPlacedSuccessfully() { // Arrange var mockRepository = new Mock<IOrderRepository>(); var orderService = new OrderService(mockRepository.Object); var order = new Order(); // Act orderService.PlaceOrder(order); // Assert mockRepository.Verify(repo => repo.Save(order), Times.Once); } // Incorrect: Directly accessing the database [Fact] public void PlaceOrder_ValidOrder_OrderPlacedSuccessfully_DatabaseAccess() { // This is BAD, avoid interacting with real database in UT // Arrange var orderService = new OrderService(new OrderRepository()); } """ ### 2.2. Standard: Testing Entities and Value Objects **Do This:** Write comprehensive tests for entities and value objects, focusing on invariant enforcement, business logic, and behavior. **Don't Do This:** Omit testing of entities and value objects, assuming they are simple data containers. **Why:** Entities and value objects encapsulate important business rules and data constraints. Proper testing ensures that these rules are enforced correctly and that the objects behave as expected. **Example:** """csharp // Testing a value object [Fact] public void Money_NegativeAmount_ThrowsException() { // Arrange, Act, Assert Assert.Throws<ArgumentException>(() => new Money(-100, "USD")); } // Testing an entity [Fact] public void AddItem_ValidItem_IncreasesItemCount() { // Arrange var order = new Order(); var item = new OrderItem("Product1", 10, 2); // Act order.AddItem(item); // Assert Assert.Equal(1, order.Items.Count); } """ ### 2.3. Standard: Testing Domain Services **Do This:** Write tests for domain services to verify that they orchestrate interactions between domain objects and implement complex business processes correctly. **Don't Do This:** Skip testing domain services or assume they are simply wrappers around other domain objects. **Why:** Domain services encapsulate important business logic that cannot be easily placed in entities or value objects. Proper testing ensures that these services behave as expected. **Example:** """csharp // Testing a domain service [Fact] public void TransferFunds_InsufficientBalance_ThrowsException() { // Arrange var sourceAccount = new BankAccount("123", 100); var destinationAccount = new BankAccount("456", 0); var transferService = new FundTransferService(); // Act & Assert Assert.Throws<InvalidOperationException>(() => transferService.TransferFunds(sourceAccount, destinationAccount, 500)); } """ ### 2.4. Standard: Using Fakes, Mocks, and Stubs **Do This:** Use fakes, mocks, and stubs appropriately to isolate the code under test. Use mocks for verifying interactions and stubs for providing fixed return values. **Don't Do This:** Overuse mocking or stubbing, leading to tests that are tightly coupled to the implementation details. **Why:** Proper use of test doubles ensures that tests are focused on the behavior of the code under test and not on the behavior of its dependencies. **Example:** """csharp // Using a mock to verify an interaction [Fact] public void SendEmail_OrderPlaced_EmailSent() { // Arrange var mockEmailService = new Mock<IEmailService>(); var orderService = new OrderService(null, mockEmailService.Object); var order = new Order(); // Act orderService.PlaceOrder(order); // Assert mockEmailService.Verify(email => email.Send(It.IsAny<string>(), It.IsAny<string>()), Times.Once); } // Using a stub to provide a fixed return value [Fact] public void GetDiscount_CustomerEligible_ReturnsDiscount() { // Arrange var stubCustomerRepository = new StubCustomerRepository(); stubCustomerRepository.SetCustomerEligibility(true); var discountService = new DiscountService(stubCustomerRepository); var customerId = "123"; // Act decimal discount = discountService.GetDiscount(customerId); // Assert Assert.Equal(0.1m, discount); // Assuming 10% discount } public class StubCustomerRepository : ICustomerRepository { private bool _isEligible; public void SetCustomerEligibility(bool isEligible) { _isEligible = isEligible; } public Customer GetById(string id) { return new Customer { IsEligible = _isEligible }; } } """ ## 3. Integration Testing ### 3.1. Standard: Verify Interactions Between Bounded Contexts **Do This:** Write integration tests to verify interactions between bounded contexts. Use messaging or APIs to simulate real-world communication. **Don't Do This:** Skip integration testing or assume that interactions between bounded contexts will work seamlessly. **Why:** Bounded contexts often have complex interactions, especially in distributed systems. Integration tests ensure that these interactions are correctly implemented and that data is consistently shared between contexts. **Example:** """csharp // Integration test between two services using a message bus [Fact] public async Task OrderService_PlacesOrder_PublishesOrderCreatedEvent() { // Arrange var messageBus = new InMemoryMessageBus(); var orderService = new OrderService(null, null, messageBus); var order = new Order(); // Act await orderService.PlaceOrder(order); // Assert var orderCreatedEvent = messageBus.PublishedEvents.OfType<OrderCreatedEvent>().FirstOrDefault(); Assert.NotNull(orderCreatedEvent); Assert.Equal(order.Id, orderCreatedEvent.OrderId); } """ ### 3.2. Standard: Test Aggregate Consistency **Do This:** Create integration tests that focus on maintaining consistency between aggregates. These tests should simulate real-world scenarios involving multiple aggregates. **Don't Do This:** Unit test aggregates in isolation and assume that consistency will be maintained during complex operations. **Why:** Aggregates are units of consistency in DDD. Integration tests ensure that consistency is maintained across multiple aggregates. **Example:** """csharp // Integration test involving two aggregates [Fact] public void ShipOrder_ValidOrder_UpdatesInventoryAndOrderStatus() { // Arrange var order = new Order { Id = Guid.NewGuid(), Status = OrderStatus.Placed }; var product = new Product { Id = Guid.NewGuid(), Inventory = 10 }; // Simulate the order being placed order.OrderItems = new List<OrderItem> { new OrderItem(product.Id.ToString(), "Product1", 10, 1) }; var orderRepository = new InMemoryOrderRepository(); orderRepository.Save(order); var productRepository = new InMemoryProductRepository(); productRepository.Save(product); var shippingService = new ShippingService(orderRepository, productRepository); // Act shippingService.ShipOrder(order.Id); // Assert var updatedOrder = orderRepository.GetById(order.Id.ToString()); Assert.Equal(OrderStatus.Shipped, updatedOrder.Status); var updatedProduct = productRepository.GetById(product.Id.ToString()); Assert.Equal(9, updatedProduct.Inventory); } """ ### 3.3. Standard: Database Integration Testing **Do This:** Use database integration tests to verify that data is correctly persisted and retrieved from the database. Use test databases to avoid affecting production data. **Don't Do This:** Skip database integration tests or assume that database interactions will work correctly based on ORM configurations alone. **Why:** Database interactions are a common source of errors. Integration tests ensure that data mappings and database queries are correctly implemented. **Example:** """csharp // Database integration test using Entity Framework Core [Fact] public void AddCustomer_ValidCustomer_CustomerPersistedToDatabase() { // Arrange var options = new DbContextOptionsBuilder<MyDbContext>() .UseInMemoryDatabase(databaseName: "TestDatabase") .Options; using (var context = new MyDbContext(options)) { var customer = new Customer { Id = Guid.NewGuid(), Name = "John Doe" }; // Act context.Customers.Add(customer); context.SaveChanges(); } // Assert using (var context = new MyDbContext(options)) { var persistedCustomer = context.Customers.FirstOrDefault(c => c.Name == "John Doe"); Assert.NotNull(persistedCustomer); Assert.Equal("John Doe", persistedCustomer.Name); } } """ ## 4. End-to-End Testing ### 4.1. Standard: Simulate User Scenarios **Do This:** Write end-to-end tests that simulate real user scenarios, covering multiple layers of the application, including the UI, application services, and domain model.. **Don't Do This:** Focus solely on unit or integration tests without verifying that the entire system works together as expected. **Why:** End-to-end tests ensure that the system meets the overall business requirements and that all components work together correctly. **Example:** """csharp // End-to-end test using Selenium [Fact] public void PlaceOrder_ValidOrder_OrderConfirmationDisplayed() { // Arrange using (var driver = new ChromeDriver()) { driver.Navigate().GoToUrl("http://localhost:5000/Order/New"); // Act driver.FindElement(By.Id("ProductName")).SendKeys("Product1"); driver.FindElement(By.Id("Quantity")).SendKeys("2"); driver.FindElement(By.Id("PlaceOrderButton")).Click(); // Assert Assert.Contains("Order Confirmation", driver.PageSource); } } // This example assumes a web application running locally on port 5000 """ ### 4.2. Standard: Test User Interface Interactions **Do This:** Use tools like Selenium or Playwright to automate user interface interactions and verify that the UI behaves as expected. **Don't Do This:** Manually test the UI or assume that UI interactions will work correctly based on unit tests alone. **Why:** UI interactions are a common source of errors. Automated UI tests ensure that the UI behaves correctly and that user workflows are working as expected. **Example:** """csharp // End-to-end test using Playwright [Fact] public async Task PlaceOrder_ValidOrder_OrderConfirmationDisplayed_Playwright() { using var playwright = await Playwright.CreateAsync(); await using var browser = await playwright.Chromium.LaunchAsync(); var page = await browser.NewPageAsync(); await page.GotoAsync("http://localhost:5000/Order/New"); await page.FillAsync("#ProductName", "Product1"); await page.FillAsync("#Quantity", "2"); await page.ClickAsync("#PlaceOrderButton"); await page.WaitForSelectorAsync("text=Order Confirmation"); var content = await page.ContentAsync(); Assert.Contains("Order Confirmation", content); } """ ### 4.3. Standard: Test Cross-Cutting Concerns **Do This:** Include tests for cross-cutting concerns like security, performance, and logging in your end-to-end tests. **Don't Do This:** Focus solely on functional requirements without considering cross-cutting concerns. **Why:** Cross-cutting concerns can have a significant impact on the overall quality and reliability of the system. End-to-end tests ensure that these concerns are addressed correctly. **Example:** """csharp // End-to-end test for security (authorization) [Fact] public void AccessAdminPage_UnauthorizedUser_RedirectedToLoginPage() { // Arrange using (var driver = new ChromeDriver()) { // Act driver.Navigate().GoToUrl("http://localhost:5000/Admin/Dashboard"); // Assert Assert.Contains("Login", driver.PageSource); // Assuming a login page } } """ ## 5. Test Data Management ### 5.1. Standard: Use Test Data Builders **Do This:** Use test data builders to create test data in a consistent and maintainable way. **Don't Do This:** Create test data directly in the test methods or hardcode values. **Why:** Test data builders make it easier to create test data and ensure that the data is consistent across tests. **Example:** """csharp // Test data builder public class OrderBuilder { private Order _order = new Order(); public OrderBuilder WithId(Guid id) { _order.Id = id; return this; } public OrderBuilder WithItem(string productName, decimal price, int quantity) { _order.AddItem(new OrderItem(productName, price, quantity)); return this; } public Order Build() { return _order; } } // Using the test data builder in a test [Fact] public void OrderTotal_MultipleItems_CalculatesCorrectly_UsingBuilder() { // Arrange var order = new OrderBuilder() .WithItem("Product1", 10, 2) .WithItem("Product2", 5, 3) .Build(); // Act decimal total = order.Total(); // Assert Assert.Equal(35, total); } """ ### 5.2. Standard: Clean Up Test Data **Do This:** Ensure that test data is cleaned up after each test to avoid affecting subsequent tests. **Don't Do This:** Leave test data in the database or other external resources after the tests are complete. **Why:** Cleaning up test data ensures that tests are repeatable and that the test environment remains in a consistent state. **Example:** """csharp // Cleaning up test data using xUnit's IDisposable public class MyTestClass : IDisposable { private readonly MyDbContext _context; public MyTestClass() { var options = new DbContextOptionsBuilder<MyDbContext>() .UseInMemoryDatabase(databaseName: "TestDatabase") .Options; _context = new MyDbContext(options); _context.Database.EnsureCreated(); } [Fact] public void MyTest() { // Your test logic here } public void Dispose() { _context.Database.EnsureDeleted(); _context.Dispose(); } } """ ## 6. Performance Testing in DDD ### 6.1. Standard: Profile Domain Logic **Do This:** Profile domain logic to identify performance bottlenecks. Ensure that performance tests reflect real-world scenarios and data volumes. **Don't Do This:** Ignore performance testing or assume that domain logic is inherently fast. **Why:** Performance bottlenecks in domain logic can significantly impact the overall performance of the system. Profiling helps identify and address these bottlenecks. **Example:** """csharp // Performance test using BenchmarkDotNet [MemoryDiagnoser] public class OrderTotalBenchmark { private Order _order; [GlobalSetup] public void Setup() { _order = new OrderBuilder() .WithItem("Product1", 10, 2) .WithItem("Product2", 5, 3) .Build(); } [Benchmark] public decimal CalculateOrderTotal() { return _order.Total(); } } """ ### 6.2. Standard: Optimize Database Queries **Do This:** Optimize database queries to minimize the number of round trips to the database and the amount of data transferred. **Don'tDo This:** Use inefficient queries that retrieve unnecessary data or perform poorly. **Why:** Database queries are often a major source of performance bottlenecks """csharp // Optimizing database queries using Include and AsNoTracking [Fact] public void GetOrder_WithItems_RetrievesOrderAndItemsEfficiently() { // Arrange - set up some pre-existing Orders and Items // Act using (var context = new AppDbContext()) { var order = context.Orders .Include(o => o.Items) // Eagerly load the order items .AsNoTracking() // Disable change tracking .FirstOrDefault(o => o.Id == 123); // Do assertions here } } """ ### 6.3 Standard: Implement Caching Strategies **Do This:** Implement caching strategies to reduce the load on the database and improve response times. Use distributed caching for scalable applications. **Don't Do This:** Overuse caching or cache data that is frequently changing. **Why:** Caching can significantly improve the performance of read-heavy applications.. ## 7. Security Testing in DDD ### 7.1. Standard: Test Authorization and Authentication **Do This:** Write tests to verify that authorization and authentication are correctly implemented and that users can only access resources they are authorized to access. **Don't Do This:** Skip security testing or assume that authorization and authentication are correctly implemented based on framework configurations alone. **Why:** Security vulnerabilities can have a significant impact on the overall security of the system. **Example:** """csharp // Security test for authorization [Fact] public void AccessAdminPage_UnauthorizedUser_RedirectedToLoginPage() { // Arrange using (var client = new TestServer(new WebHostBuilder().UseStartup<Startup>()).CreateClient()) { // Act var response = await client.GetAsync("/Admin/Dashboard"); // Assert Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); } } """ ### 7.2. Standard: Test Input Validation **Do This:** Write tests to verify that input validation is correctly implemented and that the system is protected against malicious input. **Don't Do This:** Skip input validation testing or assume that input validation is correctly implemented based on framework configurations alone. **Why:** Input validation is a critical security measure that helps prevent attacks like SQL injection and cross-site scripting (XSS). """csharp // Security test for input validation [Fact] public void CreateUser_InvalidEmail_ReturnsBadRequest() { // Arrange, Act, Assert var ex = Assert.Throws<ArgumentException>( () => new User("", "bademail", "password" )); Assert.Contains("Email", ex.Message); } """ ### 7.3. Standard: Sensitive Data Handling **Do This:** Conduct tests focusing on the secure handling of sensitive data, including encryption techniques and secure storage practices. **Don't Do This:** Store sensitive data in plain text or use weak encryption algorithms, and avoid conducting regular security audits. **Why:** Prevent unauthorized access and maintain data integrity. ## 8. Continuous Integration and Continuous Delivery (CI/CD) ### 8.1. Standard: Automate Testing **Do This:** Automate all tests as part of the CI/CD pipeline. Ensure that tests are run automatically on every commit. **Don't Do This:** Manually run tests or skip testing in the CI/CD pipeline. **Why:** Automated testing ensures that code changes are continuously validated and that defects are detected early. ### 8.2. Standard: Monitor Test Results **Do This:** Monitor test results and track test coverage. Use test dashboards to visualize test results and identify trends. **Don't Do This:** Ignore test results or assume that tests are always passing. **Why:** Monitoring test results and test coverage helps identify areas of the code that are not well-tested and track the overall quality of the system. ## 9. Anti-Patterns in DDD Testing ### 9.1. Data Access Logic in Entities **Avoid:** Placing database access logic directly inside entities. **Why:** This couples the domain model to a specific infrastructure concern, making it harder to test and maintain. ### 9.2. Anemic Domain Model **Avoid:** Creating an anemic domain model with entities that only contain data and no behavior. **Why:** This moves business logic out of the domain model and into application services, leading to a less maintainable and less testable system. ### 9.3. God Classes/Services **Avoid:** Creating God classes or services that encapsulate too much logic. **Why:** This makes the code harder to understand, test, and maintain. ### 9.4. Over-Mocking **Avoid:** Over-mocking dependencies, leading to tests that are brittle and tightly coupled to the implementation details. **Why:** Over-mocking can make tests less effective by masking real issues and making them less resilient to code changes. By following these coding standards, development teams can create robust and well-tested domain models that are aligned with the principles of DDD. This leads to more maintainable, performant, and secure software.
# API Integration Standards for Domain Driven Design This document outlines the coding standards for integrating APIs within a Domain-Driven Design (DDD) context. It provides guidelines and best practices to ensure maintainability, performance, security, and adherence to DDD principles when interacting with external systems. These standards are designed to guide developers and inform AI coding assistants to produce high-quality, DDD-compliant code. ## 1. General Principles ### 1.1. Bounded Context Alignment **Do This:** Clearly define the boundaries of your domain and how external APIs interact with them. Map API functionalities to specific domain capabilities within your bounded contexts. **Don't Do This:** Allow external API models to pollute your domain model or introduce cross-cutting concerns unrelated to the core domain. **Why:** Ensures domain integrity and prevents external dependencies from dictating domain logic. **Example:** Imagine an "Order Management" bounded context needing to interact with a "Shipping" API. Only expose the necessary data to the "Order Management" domain after transformation to match it's model and needs. ### 1.2. Anti-Corruption Layer (ACL) **Do This:** Implement an Anti-Corruption Layer to isolate your domain model from the complexities and inconsistencies of external APIs. **Don't Do This:** Directly expose external API data structures within your domain. **Why:** Shields your domain from changes in external APIs and ensures data consistency. **Example:** """csharp // External Shipping API Response (Simplified) public class ShippingApiResponse { public string TrackingId { get; set; } public string StatusDescription { get; set; } public DateTime EstimatedDelivery { get; set; } } // Shipping Domain Entity public class Shipment { public string TrackingNumber { get; private set; } public ShipmentStatus Status{ get; private set;} public DateTime ExpectedDeliveryDate { get; private set; } public Shipment(string trackingNumber, ShipmentStatus status, DateTime expectedDeliveryDate) { TrackingNumber = trackingNumber; Status = status; ExpectedDeliveryDate = expectedDeliveryDate; } } public enum ShipmentStatus { Unknown, InTransit, Delivered, Exception } // Anti-Corruption Layer public interface IShippingService { Shipment GetShipmentDetails(string orderId); } public class ShippingService : IShippingService { private readonly IExternalShippingApi _externalShippingApi; public ShippingService(IExternalShippingApi externalShippingApi) { _externalShippingApi = externalShippingApi; } public Shipment GetShipmentDetails(string orderId) { var apiResponse = _externalShippingApi.GetShippingDetails(orderId); // Map external API response to domain entity return new Shipment( apiResponse.TrackingId, MapStatus(apiResponse.StatusDescription), apiResponse.EstimatedDelivery ); } private ShipmentStatus MapStatus(string statusDescription) { switch (statusDescription.ToLower()) { case "in transit": return ShipmentStatus.InTransit; case "delivered": return ShipmentStatus.Delivered; case "exception": return ShipmentStatus.Exception; default: return ShipmentStatus.Unknown; } } } // Abstraction of the external API to ensure it's easily swappable & testable. public interface IExternalShippingApi { ShippingApiResponse GetShippingDetails(string orderId); } //Concrete implementation (Using HttpClient) public class ExternalShippingApi : IExternalShippingApi { private readonly HttpClient _httpClient; private readonly string _apiEndpoint; public ExternalShippingApi(HttpClient httpClient, IConfiguration configuration) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _apiEndpoint = configuration["ShippingApiEndpoint"] ?? throw new ArgumentNullException("ShippingApiEndpoint", "ShippingApiEndpoint must be configured"); } public ShippingApiResponse GetShippingDetails(string orderId) { // Construct the request URL var requestUrl = $"{_apiEndpoint}/shipments/{orderId}"; // Make the API call var response = _httpClient.GetAsync(requestUrl).Result; // Consider async/await // Handle unsuccessful response response.EnsureSuccessStatusCode(); // or more specific exception handling // Deserialize the response var result = response.Content.ReadAsAsync<ShippingApiResponse>().Result; // Consider async/await return result; } } """ This example uses a "ShippingService" class to act as the ACL. It transforms the "ShippingApiResponse" from an external API to a "Shipment" domain entity. The "MapStatus" method handles mapping status descriptions. An abstraction IExternalShippingApi is used to hide the implementation details, specifically HttpClient, and to more easily mock and swap implementations for testing or future changes. Additionally the sample moves the endpoint into configuration ensuring it is environment specific without requiring code changes. ### 1.3. Asynchronous Communication **Do This:** Favor asynchronous communication patterns (e.g., message queues) for interactions with external services, especially when dealing with long-running or unreliable operations. **Don't Do This:** Rely solely on synchronous request-response interactions, which can lead to performance bottlenecks and cascading failures. **Why:** Improves system resilience, scalability, and responsiveness. **Example:** Using RabbitMQ with MassTransit, the order creation process publishes a message to a shipping queue: """csharp // .NET 8 example with MassTransit & RabbitMQ using MassTransit; public class OrderCreatedEvent { public Guid OrderId { get; set; } public string CustomerId { get; set; } public List<OrderItem> OrderItems { get; set; } } public class OrderItem { public string ProductId {get;set;} public int Quantity {get;set;} } //Order creation publishing code: public interface IOrderService { Task CreateOrder(OrderCreatedEvent order); } public class OrderService : IOrderService { private readonly IBus _bus; public OrderService(IBus bus) { _bus = bus; } public async Task CreateOrder(OrderCreatedEvent order) { //Order Logic Here await _bus.Publish(order); } } // Consumer (Shipping Service): public class OrderCreatedConsumer : IConsumer<OrderCreatedEvent> { private readonly IShippingService _shippingService; public OrderCreatedConsumer(IShippingService shippingService) { _shippingService = shippingService; } public async Task Consume(ConsumeContext<OrderCreatedEvent> context) { var order = context.Message; await _shippingService.ProcessOrderForShipping(order); } } //MassTransit Configuration public static class MassTransitConfiguration { public static IServiceCollection AddRabbitMqMassTransit(this IServiceCollection services, IConfiguration configuration) { services.AddMassTransit(x => { x.AddConsumer<OrderCreatedConsumer>(); x.UsingRabbitMq((context, cfg) => { cfg.Host(configuration["RabbitMqHost"], "/", h => { h.Username(configuration["RabbitMqUser"]); h.Password(configuration["RabbitMqPassword"]); }); cfg.ReceiveEndpoint("order-created-queue", e => { e.ConfigureConsumer<OrderCreatedConsumer>(context); }); }); }); return services; } } //Program.cs builder.Services.AddRabbitMqMassTransit(builder.Configuration); """ This example demonstrates publishing an "OrderCreatedEvent" to a RabbitMQ queue. The Shipping service consumes this message and initiates shipping processing. This decouples the order creation process from the shipping process. The host, username and password or now configured through the configuration service proving a more robust deployment strategy. ## 2. API Design and Implementation ### 2.1. API Contracts **Do This:** Define clear and versioned API contracts (e.g., OpenAPI/Swagger, gRPC proto files) to ensure compatibility and facilitate communication between services. **Don't Do This:** Rely on implicit or undocumented API behaviors. **Why:** Enables collaboration, reduces integration errors, and simplifies API evolution. **Example (OpenAPI/Swagger):** """yaml #openapi: 3.0.0 #info: # title: Shipping API # version: v1 #paths: # /shipments/{trackingId}: # get: # summary: Get shipment details by tracking ID # parameters: # - in: path # name: trackingId # required: true # schema: # type: string # responses: # '200': # description: Successful operation # content: # application/json: # schema: # type: object # properties: # trackingId: # type: string # status: # type: string # estimatedDeliveryDate: # type: string # format: date-time # '404': # description: Shipment not found """ ### 2.2. API Client Generation **Do This:** Generate API clients from API contracts using tools like OpenAPI Generator, NSwag, or gRPC code generation. **Don't Do This:** Manually write API client code, which is error-prone and difficult to maintain. **Why:** Reduces boilerplate code, ensures consistency with the API contract, and simplifies updates when the API changes. **Example:** Using NSwag to generate a C# client from a Swagger definition: """powershell nswag openapi2csclient /input:swagger.json /output:ShippingApiClient.cs /namespace:MyProject.Shipping """ This command generates a "ShippingApiClient.cs" file containing the client code, mapped to the "MyProject.Shipping" namespace. ### 2.3. Idempotency **Do This:** Design your API interactions to be idempotent, particularly for critical operations like order placement or payment processing. Use mechanisms like unique request IDs or transactional outbox patterns. **Don't Do This:** Assume that API calls will always succeed on the first attempt. **Why:** Prevents unintended side effects from retries or duplicate requests, ensuring data integrity. **Example:** """csharp //Controller handling an order creation request: [HttpPost("orders")] public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request, [FromHeader(Name = "X-Request-ID")] string requestId) { if (string.IsNullOrEmpty(requestId)) { return BadRequest("Request ID is required."); } // Check if the order has already been processed using the requestId if (await _orderRepository.ExistsOrderWithRequestId(requestId)) { return Conflict("Order already processed with this Request ID."); //Or return the original accepted result. } // Create the order (Domain Logic) var order = _orderService.CreateOrder(request.CustomerId, request.Items); // Save the order with the request ID order.RequestId = requestId; await _orderRepository.SaveOrder(order); return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order); } """ This example uses an X-Request-ID header to ensure idempotency. The "_orderRepository.ExistsOrderWithRequestId()" method checks if an order with the same request ID already exists. It can also be paired with a transactional outbox pattern to guarantee that the request only gets handled once. ### 2.4. Rate Limiting and Throttling **Do This:** Implement rate limiting and throttling mechanisms to protect your APIs from abuse and ensure fair usage. **Don't Do This:** Allow uncontrolled access to your APIs, which can lead to denial-of-service attacks or resource exhaustion. **Why:** Enhances system security, stability and prevents overuse. **Example:** Using a middleware to implement basic rate limiting: """csharp //Middleware public class RateLimitingMiddleware { private readonly RequestDelegate _next; private readonly IMemoryCache _cache; private readonly int _limit; private readonly TimeSpan _duration; public RateLimitingMiddleware(RequestDelegate next, IMemoryCache cache, int limit, TimeSpan duration) { _next = next; _cache = cache; _limit = limit; _duration = duration; } public async Task InvokeAsync(HttpContext context) { var ipAddress = context.Connection.RemoteIpAddress?.ToString(); if (string.IsNullOrEmpty(ipAddress)) { context.Response.StatusCode = 400; await context.Response.WriteAsync("Could not determine IP address."); return; } var cacheKey = $"RateLimit_{ipAddress}"; _cache.TryGetValue(cacheKey, out int requestCount); if (requestCount >= _limit) { context.Response.StatusCode = 429; // Too Many Requests context.Response.Headers["Retry-After"] = _duration.Seconds.ToString(); await context.Response.WriteAsync("Too many requests. Please try again later."); return; } requestCount++; var cacheEntryOptions = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(_duration); _cache.Set(cacheKey, requestCount, cacheEntryOptions); await _next(context); } } //Extension Method public static class RateLimitingExtensions { public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder, int limit, TimeSpan duration) { return builder.UseMiddleware<RateLimitingMiddleware>(limit, duration); } } //Program.cs app.UseRateLimiting(limit: 100, duration: TimeSpan.FromMinutes(1)); // 100 requests per minute """ This middleware limits requests based on IP address. It stores the request count in an "IMemoryCache" and returns a 429 status code if the limit is exceeded. ### 2.5. Circuit Breaker **Do This:** Implement the Circuit Breaker pattern to handle failures gracefully when interacting with unreliable external APIs. Use libraries like Polly or Resilience4j. **Don't Do This:** Allow your application to repeatedly attempt failing API calls, which can exacerbate the problem. **Why:** Prevents cascading failures, improves system resilience, and allows the external API to recover. **Example:** Using Polly to implement a circuit breaker with retry and fallback policies: """csharp using Polly; using Polly.CircuitBreaker; using Polly.Fallback; using Polly.Retry; public interface IExternalApiService { Task<string> GetData(); } public class ExternalApiService : IExternalApiService{ private readonly HttpClient _httpClient; private readonly string _apiEndpoint; public ExternalApiService(HttpClient httpClient, IConfiguration configuration) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _apiEndpoint = configuration["ExternalApiEndpoint"] ?? throw new ArgumentNullException("ExternalApiEndpoint", "ExternalApiEndpoint must be configured"); } public async Task<string> GetData() { var response = await _httpClient.GetAsync(_apiEndpoint); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } } public class ResilientApiService { private readonly IExternalApiService _externalApiService; private readonly AsyncRetryPolicy _retryPolicy; private readonly AsyncCircuitBreakerPolicy _circuitBreakerPolicy; private readonly AsyncFallbackPolicy<string> _fallbackPolicy; public ResilientApiService(IExternalApiService externalApiService) { _externalApiService = externalApiService; // Retry policy _retryPolicy = Policy .Handle<HttpRequestException>() .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); // Exponential backoff // Circuit Breaker policy _circuitBreakerPolicy = Policy .Handle<HttpRequestException>() .CircuitBreakerAsync(3, TimeSpan.FromSeconds(30)); // Breaks after 3 exceptions in 30 seconds // Fallback policy _fallbackPolicy = Policy<string> .Handle<Exception>() .FallbackAsync("Fallback data."); } public async Task<string> GetDataResiliently() { return await _fallbackPolicy.WrapAsync(_retryPolicy.WrapAsync(_circuitBreakerPolicy), (ct) => Task.FromResult("Fallback data was returned")) .ExecuteAsync(async () => { return await _externalApiService.GetData(); }); } } //Usage var resilientService = new ResilientApiService(externalApiService); var data = await resilientService.GetDataResiliently(); """ This example defines retry, circuit breaker, and fallback policies. It attempts to retry failed requests, breaks the circuit after a certain number of failures, and calls a fallback method when the circuit is open. The fallback policy ensures that the application continues to function with default/cached data, even when the external API is unavailable. ## 3. Security ### 3.1. Authentication and Authorization **Do This:** Use robust authentication and authorization mechanisms (e.g., OAuth 2.0, JWT) to secure your APIs, always validate incoming requests, and adhere to the principle of least privilege. **Don't Do This:** Rely on weak or nonexistent authentication, or grant excessive permissions to API clients. **Why:** Protects sensitive data and prevents unauthorized access. **Example (OAuth 2.0):** This requires integration with an OAuth 2.0 provider (e.g., Azure AD, Auth0). The example shows a simplified flow in .NET Core. Utilize "services.AddAuthentication" and "services.AddAuthorization" with the OAuth 2.0 configuration to secure API endpoints. Use "[Authorize]" attributes on your controller actions to require authentication. ### 3.2. Input Validation **Do This:** Thoroughly validate all input received from external APIs, including data types, formats, and ranges. **Don't Do This:** Trust that external APIs will always provide valid data. **Why:** Prevents security vulnerabilities such as injection attacks and data corruption. **Example:** """csharp public class CreateCustomerRequest { [Required] [StringLength(100)] public string Name { get; set; } [EmailAddress] public string Email { get; set; } [Range(1, 150)] public int Age { get; set; } } [HttpPost("customers")] public IActionResult CreateCustomer([FromBody] CreateCustomerRequest request) { if (!ModelState.IsValid) { return BadRequest(ModelState); } // ... } """ ### 3.3. Data Masking and Encryption **Do This:** Mask or encrypt sensitive data (e.g., personally identifiable information - PII) when transmitting it over the network or storing it in a database. **Don't Do This:** Store or transmit sensitive data in plain text. **Why:** Protects sensitive data from unauthorized access in case of interception or data breach. **Example:** Using symmetric encryption with AES: """csharp using System.Security.Cryptography; using System.Text; public static class EncryptionHelper { private static readonly string _encryptionKey = "YourSecretEncryptionKey"; // Store securely! public static string Encrypt(string plainText) { byte[] iv = new byte[16]; byte[] array; using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(_encryptionKey); aes.IV = iv; ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream()) { using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) { using (StreamWriter streamWriter = new StreamWriter(cryptoStream)) { streamWriter.Write(plainText); } array = memoryStream.ToArray(); } } } return Convert.ToBase64String(array); } public static string Decrypt(string cipherText) { byte[] iv = new byte[16]; byte[] buffer = Convert.FromBase64String(cipherText); using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(_encryptionKey); aes.IV = iv; ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream(buffer)) { using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) { using (StreamReader streamReader = new StreamReader(cryptoStream)) { return streamReader.ReadToEnd(); } } } } } } //Important Notice: _encryptionKey can be stored in a secure configuration and can also use Azure Key Vault. """ ## 4. Monitoring and Logging ### 4.1. Centralized Logging **Do This:** Implement centralized logging (e.g., using ELK Stack, Azure Monitor) to track API interactions, errors, and performance metrics. Include correlation IDs to trace requests across multiple services. **Don't Do This:** Rely on local or fragmented logging, which makes it difficult to diagnose and troubleshoot issues . **Why:** Enables effective monitoring, debugging, and performance analysis. **Example:** Using Serilog with Seq as the central logging server: """csharp // Program.cs using Serilog; Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .Enrich.FromLogContext() .WriteTo.Seq("http://localhost:5341") // Replace with your Seq server URL .CreateLogger(); try { Log.Information("Starting web application"); CreateHostBuilder(args).Build().Run(); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); } """ ### 4.2. Health Checks **Do This:** Implement health check endpoints to monitor the availability and performance of your APIs. **Don't Do This:** Neglect to monitor the health of your APIs, which can lead to undetected outages. **Why:** Enables proactive detection of issues and automated recovery through load balancers or orchestrators. **Example:** """csharp //Startup.cs services.AddHealthChecks() .AddCheck("ShippingAPI", () => HealthCheckResult.Healthy("Shipping API is running")) .AddUrlGroup(new Uri("https://external-shipping-api.com/health"), "External Shipping API Health Check"); // and... app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/health"); }); """ This defines a health check endpoint "/health" that checks the status of the Shipping API. It also adds a URL-based health check to monitor an external shipping API. Tools like Kubernetes can use this endpoint to determine the health of your application pods. ## 5. Testing ### 5.1. Integration Tests **Do This:** Write integration tests to verify the interaction between your domain and external APIs. Use mock APIs or test doubles to isolate the domain logic. **Don't Do This:** Rely solely on unit tests, which may not catch integration issues. **Why:** Ensures proper functioning of the ACL and the correctness of data transformations. ### 5.2. Contract Tests **Do This:** Implement contract tests (e.g., using Pact) to ensure compatibility between your API client and the external API provider. **Don't Do This:** Deploy API changes without verifying compatibility with the external API. **Why:** Prevents integration errors caused by API changes. ## 6. API Versioning ### 6.1. Semantic Versioning **Do This:** Use semantic versioning for all APIs (both internal and external). When modifying API's use: MAJOR version when you make incompatible API changes, MINOR version when you add functionality in a backwards compatible manner, and PATCH version when you make backwards compatible bug fixes. **Don't Do This:** Expose new functionality or make breaking changes to the API without proper versioning. **Why:** Allows consumers to safely upgrade or downgrade API versions without unexpected breaking changes ### 6.2. Versioning Strategies **Do This:** Use a versioning strategy that fits your use case (e.g., URI path versioning, header versioning). **Don't Do This:** Rely on query parameter versioning or no versioning at all. **Why:** Provides clarity and avoids conflicts. URI path versioning and acceptance header versioning are the most commonly accepted strategies. **Example:** """ //URI path versioning https://api.example.com/v1/resource //Acceptance Header versioning Accept: application/vnd.example.v1+json """ By adhering to these coding standards, development teams can build robust, maintainable, secure, and scalable systems that integrate effectively with external APIs within a Domain-Driven Design context. The examples were updated to use current best practices and frameworks. The use of asynchronous operations, circuit breakers, centralized logging, and health checks provides improved resilience and better error handling. These standards will enable developers and AI coding assistants to produce high-quality, DDD-compliant code for interacting with external systems.