# 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
{
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 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 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 _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 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> 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 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" when verifying the presence of items in a set.
**Do This:** Store lookup tables in a "HashSet". "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" 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" / "Memory" 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.
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'
# Code Style and Conventions Standards for Domain Driven Design This document outlines the code style and conventions to be followed when developing applications using Domain Driven Design (DDD). Adhering to these standards will improve code readability, maintainability, and consistency across the codebase. These guidelines are designed to work in conjunction with modern development practices and tooling. ## 1. General Formatting and Style ### 1.1 Code Formatting * **Do This:** Use automated code formatters (e.g., Prettier, Black, ktlint) to enforce consistent formatting. Configure your IDE to format code on save. * **Don't Do This:** Manually format code; it's error-prone and time-consuming. * **Why:** Consistent formatting reduces cognitive load and makes it easier to understand the code's structure. * **Example (using Prettier for JavaScript/TypeScript):** """javascript // .prettierrc.js module.exports = { semi: true, trailingComma: 'all', singleQuote: true, printWidth: 120, tabWidth: 2, }; """ """bash npx prettier --write . """ ### 1.2 Naming Conventions * **Do This:** Follow language-specific naming conventions (e.g., PascalCase for classes in C# and Java, snake_case for variables in Python). * **Don't Do This:** Use abbreviations or single-letter variable names that are not universally understood. * **Why:** Clear names communicate intent and purpose. * **Example (JavaScript/TypeScript):** """typescript // Good class OrderService { private readonly orderRepository: OrderRepository; constructor(orderRepository: OrderRepository) { this.orderRepository = orderRepository; } async placeOrder(order: Order): Promise<void> { // ... implementation ... } } // Bad class OS { private readonly oR: OrderRepository; constructor(or: OrderRepository) { this.oR = or; } async pO(o: Order) { // ... implementation ... } } """ ### 1.3 Comments and Documentation * **Do This:** Write clear and concise comments explaining complex logic or design decisions. Use documentation generators (e.g., JSDoc, Sphinx) to create API documentation. * **Don't Do This:** Write obvious comments that state the obvious or comments that are outdated. * **Why:** Documentation helps others (and your future self) understand the code's purpose and usage. * **Example (JSDoc for JavaScript/TypeScript):** """typescript /** * Represents an order placed by a customer. * @class */ class Order { /** * @param {string} orderId - The unique identifier for the order. * @param {string} customerId - The ID of the customer who placed the order. * @param {Array<OrderItem>} items - The items included in the order. */ constructor( public readonly orderId: string, public readonly customerId: string, public readonly items: OrderItem[] ) {} /** * Calculates the total amount of the order. * @returns {number} The total amount. */ calculateTotal(): number { return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0); } } """ ## 2. Domain Driven Design Specific Conventions ### 2.1 Aggregate Naming * **Do This:** Name aggregates using nouns that reflect the business concept they represent. Use singular nouns in the class name. * **Don't Do This:** Use technical or ambiguous terms. * **Why:** Aggregates are core domain concepts, their names should be clear and meaningful to domain experts. * **Example:** """csharp //Good public class Customer { ... } //Bad public class CustomerData { ... } """ ### 2.2 Repository Naming * **Do This:** Name repositories after the aggregate they manage. Use "[AggregateRoot]Repository" naming style. * **Don't Do This:** Add generic prefixes/suffixes that don't express the domain. * **Why:** Repositories provide access to aggregates, their names should reflect this relationship. * **Example:** """csharp //Good public interface ICustomerRepository { ... } //Bad public interface IRepository<Customer> { ... } // Too Generic public interface ICustomerDataRepository { ... } // Avoid 'Data' in Repositories """ ### 2.3 Value Object Implementation * **Do This:** Implement value objects as immutable classes. Use equality comparisons based on their attributes. It is advisable to create tests to verify the immutable characteristics. Use dedicated immutable collections, such as "ImmutableList". * **Don't Do This:** Allow value objects to be modified after creation or compare by reference. * **Why:** Immutability ensures consistency and prevents unintended side effects. Structural equality ensures that value objects are compared based on their state, not their identity. * **Example (C#):** """csharp public class Address : IEquatable<Address> { public string Street { get; } public string City { get; } public string ZipCode { get; } public Address(string street, string city, string zipCode) { Street = street; City = city; ZipCode = zipCode; } public bool Equals(Address other) { if (other is null) return false; if (ReferenceEquals(this, other)) return true; return Street == other.Street && City == other.City && ZipCode == other.ZipCode; } public override bool Equals(object obj) { return Equals(obj as Address); } public override int GetHashCode() { return HashCode.Combine(Street, City, ZipCode); } public override string ToString() { return $"{Street}, {City}, {ZipCode}"; } } """ ### 2.4 Domain Events * **Do This:** Name domain events in the past tense to indicate something that has already happened. Create specific domain event payload classes rather than using string/int primitives. * **Don't Do This:** Use present or future tense names. * **Why:** Domain events represent significant occurrences in the domain, their names should reflect this. * **Example (C#):** """csharp // Good public class OrderPlacedEvent : INotification { public Guid OrderId { get; } public DateTime PlacedDate { get; } public OrderPlacedEvent(Guid orderId, DateTime placedDate) { OrderId = orderId; PlacedDate = placedDate; } } // Bad public class PlaceOrder : INotification { ... } public class OrderWillBePlaced : INotification { ... } """ * **Example (using MediatR in C#):** """csharp // Order Aggregate public class Order { public Guid Id { get; private set; } public string CustomerId { get; private set; } private readonly List<OrderItem> _items = new List<OrderItem>(); public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly(); private Order() { } // Required for EF Core public Order(string customerId) { Id = Guid.NewGuid(); CustomerId = customerId; } public void AddItem(string productId, int quantity, decimal price) { // Business logic to validate item addition _items.Add(new OrderItem(productId, quantity, price)); // Raise domain event AddDomainEvent(new OrderItemAddedEvent(Id, productId, quantity, price)); } private readonly List<INotification> _domainEvents = new List<INotification>(); public IReadOnlyCollection<INotification> DomainEvents => _domainEvents.AsReadOnly(); public void AddDomainEvent(INotification eventItem) { _domainEvents.Add(eventItem); } public void ClearDomainEvents() { _domainEvents.Clear(); } } // Domain Event public class OrderItemAddedEvent : INotification { public Guid OrderId { get; } public string ProductId { get; } public int Quantity { get; } public decimal Price { get; } public OrderItemAddedEvent(Guid orderId, string productId, int quantity, decimal price) { OrderId = orderId; ProductId = productId; Quantity = quantity; Price = price; } } // Domain Event Handler public class OrderItemAddedEventHandler : INotificationHandler<OrderItemAddedEvent> { private readonly ILogger<OrderItemAddedEventHandler> _logger; public OrderItemAddedEventHandler(ILogger<OrderItemAddedEventHandler> logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderItemAddedEvent notification, CancellationToken cancellationToken) { // Logic to handle the event: e.g., update inventory, send notification _logger.LogInformation($"Order Item Added Event handled for OrderId: {notification.OrderId}, ProductId: {notification.ProductId}"); // simulate a slow task to showcase async/await await Task.Delay(1000, cancellationToken); _logger.LogInformation($"Order Item Added Event finished handling for OrderId: {notification.OrderId}, ProductId: {notification.ProductId}"); } } // Usage public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand, Guid> { private readonly IOrderRepository _orderRepository; private readonly IPublisher _publisher; public PlaceOrderCommandHandler(IOrderRepository orderRepository, IPublisher publisher) { _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _publisher = publisher ?? throw new ArgumentNullException(nameof(publisher)); } public async Task<Guid> Handle(PlaceOrderCommand command, CancellationToken cancellationToken) { var order = new Order(command.CustomerId); foreach (var item in command.Items) { order.AddItem(item.ProductId, item.Quantity, item.Price); } await _orderRepository.AddAsync(order, cancellationToken); // Dispatch Domain Events Collection // Choices: // A) Right BEFORE committing data (EF SaveChanges) into the DB will make a single transaction including // side effects from the integration events that are generated and published by the integration event handlers. // B) Right AFTER committing data (EF SaveChanges) into the DB will guarantee consistency between DB and signalr. await DispatchDomainEvents(order, cancellationToken); return order.Id; } private async Task DispatchDomainEvents(Order order, CancellationToken cancellationToken = default) { var domainEvents = order.DomainEvents.ToList(); order.ClearDomainEvents(); foreach (var domainEvent in domainEvents) await _publisher.Publish(domainEvent, cancellationToken); } } """ ### 2.5 Service Layer Conventions * **Do This:** Name application services after the use case they represent. Name domain services using verbs, indicating an operation within the domain. Use dependency injection to provide dependencies to the service layer. * **Don't Do This:** Create anemic services that simply delegate to repositories or put domain logic in the application service. * **Why:** A well-defined service layer encapsulates use-case specific logic and orchestrates domain interactions. Dependency injection promotes loose coupling and testability. * **Example (C#):** """csharp // Application Service public class PlaceOrderService { private readonly IOrderRepository _orderRepository; private readonly ICustomerRepository _customerRepository; private readonly IDomainEventPublisher _domainEventPublisher; // Assume you have an interface public PlaceOrderService(IOrderRepository orderRepository, ICustomerRepository customerRepository, IDomainEventPublisher domainEventPublisher) { _orderRepository = orderRepository; _customerRepository = customerRepository; _domainEventPublisher = domainEventPublisher; } public async Task<Result> PlaceOrder(string customerId, List<OrderItemDTO> orderItems) { // 1. Retrieve Customer var customer = await _customerRepository.GetByIdAsync(customerId); if (customer == null) { return Result.Fail("Invalid Customer"); } // 2. Create Order Aggregate var order = Order.Create(customer, orderItems.ConvertAll(item => new OrderItem(item.ProductId, item.Quantity, item.Price))); // 3. Validate the Order (moved to Domain) var validationResult = order.Validate(); // Domain Rule if (validationResult.IsFailed) return validationResult; // 4. Save the Order (Repository) await _orderRepository.AddAsync(order); // 5. Publish Domain Events foreach (var domainEvent in order.DomainEvents) { await _domainEventPublisher.Publish(domainEvent); // e.g. using MediatR, RabbitMQ, etc. } // Optional: Clear Domain Events for this request (if using EF, it's already handled) // order.ClearDomainEvents(); return Result.Ok(); } } // Domain Service (e.g., Discount Calculation - might depend on external services or complex business logic) public interface IDiscountService { decimal CalculateDiscount(Order order); } public class DefaultDiscountService : IDiscountService { //In a real system, this might call a Rules Engine, //ML service, etc. public decimal CalculateDiscount(Order order) { // Hardcoded 10% discount for orders above $100. if (order.TotalAmount > 100) { return 0.10m; } return 0; } } """ ### 2.6 Module Structure * **Do This:** Organize code into modules that reflect the bounded contexts of your domain. Use clear naming conventions for module directories. * **Don't Do This:** Create monolithic modules or modules that cross bounded context boundaries. * **Why:** Modularization promotes separation of concerns and reduces coupling between different parts of the system. * **Example (Directory Structure):** """ src/ ├── CustomerManagement/ │ ├── Domain/ │ │ ├── Customer.cs │ │ ├── CustomerRepository.cs │ │ └── ... │ ├── Application/ │ │ ├── RegisterCustomerService.cs │ │ └── ... │ ├── Infrastructure/ │ │ ├── CustomerDbContext.cs │ │ └── ... ├── OrderManagement/ │ ├── Domain/ │ │ ├── Order.cs │ │ ├── OrderRepository.cs │ │ └── ... │ ├── Application/ │ │ ├── PlaceOrderService.cs │ │ └── ... │ ├── Infrastructure/ │ │ ├── OrderDbContext.cs │ │ └── ... └── SharedKernel/ ├── ValueObjects/ │ ├── Address.cs │ └── ... ├── Interfaces/ ├── IRepository.cs ├── IDomainEvent.cs └── ... """ ### 2.7 Error Handling * **Do This:** Use exceptions for unexpected errors/failures. Return "Result" objects (or similar) for expected business validation failures. Implement global exception handling mechanisms. * **Don't Do This:** Use return codes for indicating errors or ignore exceptions. * **Why:** Proper error handling prevents crashes and helps identify and resolve issues. "Result" objects allow explicit handling of expected failures without relying on exception handling for normal business logic. * **Example (C# using a "Result" type):** """csharp public class Result { public bool IsSuccess { get; } public bool IsFailed => !IsSuccess; public string Error { get; } protected Result(bool isSuccess, string error) { IsSuccess = isSuccess; Error = error; } public static Result Ok() { return new Result(true, string.Empty); } public static Result Fail(string error) { return new Result(false, error); } public static Result<T> Ok<T>(T value) { return new Result<T>(true, string.Empty, value); } public static Result<T> Fail<T>(string error) { return new Result<T>(false, error, default(T)); } } public class Result<T> : Result { public T Value { get; } protected internal Result(bool isSuccess, string error, T value) : base(isSuccess, error) { Value = value; } } // Usage public class MyService { public Result<Order> CreateOrder(string customerId, List<OrderItem> items) { if (string.IsNullOrEmpty(customerId)) { return Result<Order>.Fail("Customer ID cannot be empty."); } if (items == null || items.Count == 0) { return Result<Order>.Fail("Order must have at least one item."); } // ... create order logic ... return Result<Order>.Ok(new Order()); // return order if success } } """ ### 2.8 Testability * **Do This:** Write unit tests for domain logic, integration tests for interactions with external systems, and end-to-end tests for overall system behavior. Follow the Arrange-Act-Assert pattern. Use mocking frameworks to isolate units of code. * **Don't Do This:** Write tests that are tightly coupled to implementation details or skip testing edge cases and error scenarios. * **Why:** Automated tests provide confidence in the code's correctness and prevent regressions. * **Example (C# using xUnit and Moq):** """csharp // Unit Test for Order Aggregate public class OrderTests { [Fact] public void AddItem_ValidProduct_AddsItemToOrder() { // Arrange var order = new Order("123"); string productId = "prod1"; int quantity = 2; decimal price = 10.00m; // Act order.AddItem(productId, quantity, price); // Assert Assert.Single(order.Items); Assert.Equal(productId, order.Items.First().ProductId); Assert.Equal(quantity, order.Items.First().Quantity); Assert.Equal(price, order.Items.First().Price); } [Fact] public void AddItem_AddsDomainEvent() { // Arrange var order = new Order("123"); string productId = "prod1"; int quantity = 2; decimal price = 10.00m; // Act order.AddItem(productId, quantity, price); // Assert Assert.IsType<OrderItemAddedEvent>(order.DomainEvents.First()); } } // Unit Test using Moq for Repository public class PlaceOrderServiceTests { [Fact] public async Task PlaceOrder_ValidCustomer_CreatesOrder() { // Arrange var mockOrderRepository = new Mock<IOrderRepository>(); var mockCustomerRepository = new Mock<ICustomerRepository>(); var mockDomainEventPublisher = new Mock<IDomainEventPublisher>(); mockCustomerRepository.Setup(x => x.GetByIdAsync(It.IsAny<string>())).ReturnsAsync(new Customer("123", "John Doe")); var service = new PlaceOrderService(mockOrderRepository.Object, mockCustomerRepository.Object, mockDomainEventPublisher.Object); var orderItems = new List<OrderItemDTO> { new OrderItemDTO { ProductId = "prod1", Quantity = 1, Price = 20 } }; // Act var result = await service.PlaceOrder("123", orderItems); // Assert Assert.True(result.IsSuccess); mockOrderRepository.Verify(x => x.AddAsync(It.IsAny<Order>(), default), Times.Once); // Verify repo was called mockDomainEventPublisher.Verify(x => x.Publish(It.IsAny<INotification>(), default), Times.Once); //verify DomainEvent publish was called. } // Add more tests for edge cases, validations, and error scenarios } """ ## 3. Anti-Patterns and Mistakes to Avoid * **Anemic Domain Model:** Avoid creating domain objects with only properties and no behavior. Domain objects should encapsulate business logic and enforce invariants. * **God Classes:** Avoid creating classes that are too large and have too many responsibilities. Break down large classes into smaller, more manageable ones. * **Over-Reliance on ORM:** Don't let the ORM dictate the structure of your domain model. Focus on modeling the domain first, then map it to the database. Be aware of the N+1 problem. Tools like EF Core in .NET and Hibernate in Java have LazyLoading enabled by default. If you do not explicitly include the related entites, you may end up with several queries. * **Ignoring Ubiquitous Language:** Use the language of the domain experts in your code, documentation, and communication. Avoid technical jargon that is not understood by domain experts. * **Premature Optimization:** Don't optimize code before you have identified performance bottlenecks. Focus on writing clear and maintainable code first, then optimize as needed. * **Tight Coupling:** Avoid tight coupling between modules and components. Use interfaces and dependency injection to promote loose coupling and testability. ## 4. Technology-Specific Details ### 4.1 C# * Use C# 9+ features like record types for value objects and top-level statements for simple programs. * Use nullable reference types to improve code safety. * Use LINQ for querying collections and data sources. * Use asynchronous programming ("async"/"await") for I/O-bound operations. * MediatR for in-process messaging (domain events, commands). ### 4.2 Java * Use Java 17+ version, which leverages records for value objects. * Use Lombok to reduce boilerplate code. * Use Spring Framework for dependency injection and other enterprise features. * Use Vavr for immutable collections and functional programming. * Follow SOLID principles. ### 4.3 JavaScript/TypeScript * Use TypeScript for strong typing and improved code maintainability. * Use modern ES6+ features like arrow functions, destructuring, and async/await. * Use a state management library like Redux or Zustand for managing application state (especially in complex UIs). * Use a framework like NestJS for building scalable and maintainable backend applications. ## 5. Performance Optimization Techniques for Domain Driven Design * **Lazy Loading vs. Eager Loading:** Understand the trade-offs between lazy loading and eager loading of related data in ORMs. * **Caching:** Implement caching strategies (e.g., using Redis or Memcached) to reduce database load. Cache frequently accessed data without sacrificing consistency. For aggregate roots that change infrequently, consider caching with an expiration policy. * **Read Models:** Use read models (CQRS) to optimize queries for specific use cases. * **Database Indexing:** Use appropriate database indexes to speed up queries. Analyze query performance and add indexes as needed. * **Bulk Operations:** Use bulk operations (e.g., bulk insert, bulk update) to improve the performance of data import/export operations. * **Asynchronous Processing:** Offload long-running tasks to background queues to avoid blocking the main thread. ## 6. Security Best Practices * **Input Validation:** Always validate user input to prevent injection attacks. * **Authorization:** Implement authorization checks to ensure that users can only access the resources they are authorized to access. * **Encryption:** Encrypt sensitive data at rest and in transit. * **Authentication:** Use strong authentication mechanisms to verify user identities. * **Cross-Site Scripting (XSS) Prevention:** Sanitize user input. * **Cross-Site Request Forgery (CSRF) Protection:** Use anti-CSRF tokens to prevent CSRF attacks. * **Dependency Management:** Keep dependencies up-to-date to address known security vulnerabilities. Use tools that provide Static Analysis Security Testing (SAST) and Software Composition Analysis (SCA) * **Rate Limiting:** Implement rate limiting to prevent brute-force attacks. By adhering to these code style and conventions, development teams can create more maintainable, testable, secure, and performant Domain Driven Design applications. This guide should promote a culture of excellence.
# 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.
# 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.
# Security Best Practices Standards for Domain Driven Design This document outlines security best practices for developing applications using Domain-Driven Design (DDD). It provides actionable guidelines, code examples, and explanations to help developers build secure and maintainable systems. These standards are crucial for protecting against common vulnerabilities and implementing secure coding patterns specific to DDD applications. ## 1. Architectural Security Considerations in DDD ### 1.1. Layered Architecture and Defense in Depth **Standard:** Implement a layered architecture to isolate the domain layer and apply the principle of defense in depth at each layer. **Do This:** * Clearly separate the presentation, application, domain, and infrastructure layers. * Implement security checks at each layer (e.g., input validation at the presentation layer, authorization checks at the application layer, domain rule enforcement in the domain layer). **Don't Do This:** * Bypass layers or allow direct access from the presentation layer to the domain layer. * Rely solely on one layer for security. **Why:** Layered architecture provides isolation, making it harder for attackers to compromise the entire system. Defense in depth ensures that if one layer fails, others are still in place to protect the system. **Example (C#):** """csharp // Presentation Layer (API Controller) [ApiController] [Route("api/[controller]")] public class OrderController : ControllerBase { private readonly IOrderService _orderService; public OrderController(IOrderService orderService) { _orderService = orderService; } [HttpPost] public IActionResult CreateOrder([FromBody] CreateOrderRequest request) { // Input Validation if (!ModelState.IsValid) { return BadRequest(ModelState); } try { var orderId = _orderService.CreateOrder(request.CustomerId, request.Items); return Ok(new { OrderId = orderId }); } catch (Exception ex) { // Log the error return StatusCode(500, "Internal Server Error"); } } } // Application Layer (OrderService) public interface IOrderService { Guid CreateOrder(Guid customerId, List<OrderItemDto> items); } public class OrderService : IOrderService { private readonly ICustomerRepository _customerRepository; private readonly IOrderRepository _orderRepository; private readonly IDomainEventPublisher _domainEventPublisher; public OrderService(ICustomerRepository customerRepository, IOrderRepository orderRepository, IDomainEventPublisher domainEventPublisher) { _customerRepository = customerRepository; _orderRepository = orderRepository; _domainEventPublisher = domainEventPublisher; } public Guid CreateOrder(Guid customerId, List<OrderItemDto> items) { // Authorization Check (Example) var customer = _customerRepository.GetById(customerId); if (customer == null) { throw new ArgumentException("Invalid customer ID."); } // Domain Logic (Order Creation) var orderItems = items.Select(i => new OrderItem(i.ProductId, i.Quantity, i.Price)).ToList(); var order = Order.Create(customer, orderItems); _orderRepository.Add(order); _domainEventPublisher.Publish(new OrderCreatedEvent(order.Id)); return order.Id; } } // Domain Layer (Order Aggregate) public class Order : AggregateRoot { public Guid Id { get; private set; } public Customer Customer { get; private set; } public List<OrderItem> Items { get; private set; } = new List<OrderItem>(); public OrderStatus Status { get; private set; } private Order(Customer customer, List<OrderItem> items) { Id = Guid.NewGuid(); Customer = customer ?? throw new ArgumentNullException(nameof(customer)); Items = items ?? new List<OrderItem>(); Status = OrderStatus.Created; } public static Order Create(Customer customer, List<OrderItem> items) { if (items == null || items.Count == 0) { throw new InvalidOperationException("Order must have at least one item.") } //Domain validation if (!customer.IsActive) { throw new InvalidOperationException("Customer is not valid to create order") } return new Order(customer, items); } public void AddItem(OrderItem item) { // Domain Rule: Only add item if order is in 'Created' status. if (Status != OrderStatus.Created) { throw new InvalidOperationException("Cannot add items to an order that is not in 'Created' status."); } Items.Add(item); } } public class OrderItem { public Guid ProductId { get; private set; } public int Quantity { get; private set; } public decimal Price { get; private set; } public OrderItem(Guid productId, int quantity, decimal price) { ProductId = productId; Quantity = quantity; Price = price; } } """ ### 1.2. Bounded Contexts and Microservices Security **Standard:** Secure communication between bounded contexts, especially when implemented as microservices. **Do This:** * Use secure protocols (HTTPS) for inter-service communication. * Implement authentication and authorization mechanisms (e.g., OAuth 2.0, JWT) to verify the identity of services. * Apply the principle of least privilege: grant each service only the permissions it needs. * Utilize API gateways to centralize security policies. **Don't Do This:** * Expose internal microservices directly to the public internet without proper security measures. * Share databases or credentials between bounded contexts. **Why:** Bounded contexts encapsulate specific domains, so securing their interactions is critical. Compromising one context should not lead to the compromise of others. **Example (API Gateway with JWT - simplified):** Assume two microservices: "OrderService" and "CustomerService". The API Gateway validates the JWT before routing requests. """csharp // API Gateway (Middleware) public class JwtAuthenticationMiddleware { private readonly RequestDelegate _next; private readonly IConfiguration _configuration; public JwtAuthenticationMiddleware(RequestDelegate next, IConfiguration configuration) { _next = next; _configuration = configuration; } public async Task Invoke(HttpContext context) { var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); if (token != null) { var (isValid, userId) = ValidateJwtToken(token); if (isValid) { // Attach user to context on successful JWT validation context.Items["UserId"] = userId; await _next(context); } else { context.Response.StatusCode = 401; // Unauthorized return; } } else { context.Response.StatusCode = 401; // Unauthorized return; } } private (bool, Guid) ValidateJwtToken(string token) { //Implementation of JWT validation using Configuration, and JWT libraries //Returns userId if token is valid, Guid.Empty otherwise //Note: This is a simplified example, a complete implementation would // involve verifying the signature, expiration date, and other claims. try { var jwtSettings = _configuration.GetSection("JwtSettings").Get<JwtSettings>(); var key = Encoding.ASCII.GetBytes(jwtSettings.Secret); // Assuming the secret is stored in Configuration var tokenHandler = new JwtSecurityTokenHandler(); tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = false, ValidateAudience = false, ClockSkew = TimeSpan.Zero // Disables clock skew compensation }, out SecurityToken validatedToken); var jwtToken = (JwtSecurityToken)validatedToken; var userId = Guid.Parse(jwtToken.Claims.First(x => x.Type == "id").Value); return (true, userId); } catch(Exception ex) { // Token validation failed. Log the exception for debugging purposes. Console.WriteLine($"JWT Validation Exception: {ex}"); //Replace with proper logging return (false, Guid.Empty); } } } // Extension method to easily add the middleware public static class JwtAuthenticationMiddlewareExtensions { public static IApplicationBuilder UseJwtAuthentication(this IApplicationBuilder builder) { return builder.UseMiddleware<JwtAuthenticationMiddleware>(); } } // Startup.cs or Program.cs (configure middleware) public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { //... app.UseRouting(); app.UseJwtAuthentication(); // Add the JWT authentication middleware app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } //Order Service API [ApiController] [Route("api/[controller]")] public class OrderController : ControllerBase { [HttpGet] [Authorize] //Requires authentication public IActionResult GetOrders() { var userId = HttpContext.Items["UserId"]; //Get userId from context //Access Order data based on userId or other custom logic return Ok($"Orders for User: {userId}"); } } """ ### 1.3 Data Protection and Encryption **Standard:** Encrypt sensitive data at rest and in transit. **Do This:** * Use encryption libraries to encrypt sensitive data stored in databases or files. * Implement Transport Layer Security (TLS) for all communication channels (HTTPS). * Protect encryption keys using secure key management practices (e.g., hardware security modules (HSMs), key vaults). **Don't Do This:** * Store sensitive data in plain text. * Hardcode encryption keys in the application code. **Why:** Encryption protects sensitive data from unauthorized access, even if the system is compromised. **Example (C# with "System.Security.Cryptography"):** """csharp using System; using System.IO; using System.Security.Cryptography; using System.Text; public class DataProtection { private static readonly byte[] _salt = Encoding.ASCII.GetBytes("RandomSaltValue"); // Store this securely! public static string EncryptString(string plainText, string passPhrase) { if (string.IsNullOrEmpty(plainText)) return null; if (string.IsNullOrEmpty(passPhrase)) passPhrase = ""; byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); using (PasswordDeriveBytes password = new PasswordDeriveBytes( passPhrase, _salt)) { byte[] keyBytes = password.GetBytes(32); using (Aes aes = Aes.Create()) { aes.KeySize = 256; aes.BlockSize = 128; aes.Key = keyBytes; aes.IV = new byte[16]; // Initialization Vector (IV) - consider using a randomly generated IV aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; using (ICryptoTransform cryptoTransform = aes.CreateEncryptor()) { byte[] encryptedBytes = cryptoTransform.TransformFinalBlock(plainTextBytes, 0, plainTextBytes.Length); // Convert byte array to a Base64 string return Convert.ToBase64String(encryptedBytes); } } } } public static string DecryptString(string encryptedText, string passPhrase) { if (string.IsNullOrEmpty(encryptedText)) return null; if (string.IsNullOrEmpty(passPhrase)) passPhrase = ""; byte[] encryptedTextBytes = Convert.FromBase64String(encryptedText); using (PasswordDeriveBytes password = new PasswordDeriveBytes( passPhrase, _salt)) { byte[] keyBytes = password.GetBytes(32); using (Aes aes = Aes.Create()) { aes.KeySize = 256; aes.BlockSize = 128; aes.Key = keyBytes; aes.IV = new byte[16]; // Same IV used during encryption aes.Mode = CipherMode.CBC; //Cipher Mode aes.Padding = PaddingMode.PKCS7; //Padding using (ICryptoTransform cryptoTransform = aes.CreateDecryptor()) { byte[] decryptedBytes = cryptoTransform.TransformFinalBlock(encryptedTextBytes, 0, encryptedTextBytes.Length); return Encoding.UTF8.GetString(decryptedBytes); } } } } // Example usage public static void Main(string[] args) { string sensitiveData = "This is a secret message."; string password = "MySecretPassword"; // Ideally, this would be stored securely in a secrets manager string encryptedData = EncryptString(sensitiveData, password); Console.WriteLine($"Encrypted: {encryptedData}"); string decryptedData = DecryptString(encryptedData, password); Console.WriteLine($"Decrypted: {decryptedData}"); } } """ **Important Notes:** * This is a simplified example for illustration purposes. For real-world use, consider using: * A randomly generated Initialization Vector (IV) for each encryption operation to enhance security. Store or transmit the IV alongside the encrypted data. * Key management systems like Azure Key Vault or AWS KMS to securely manage and protect the encryption keys. * "PasswordDeriveBytes" is considered legacy. Modern applications should use "Rfc2898DeriveBytes" instead. * This example uses a symmetric encryption algorithm (AES). Asymmetric encryption (e.g., RSA) may be more suitable for certain scenarios. * Always use strong passwords and treat them as sensitive information. * Properly handle exceptions and errors during encryption/decryption operations. * Audit and monitor encryption/decryption activities for potential security breaches. ## 2. Domain Layer Security ### 2.1. Domain Rule Enforcement **Standard:** Enforce security-related domain rules within the domain layer. **Do This:** * Implement validation logic within domain entities and value objects to ensure data integrity. * Throw exceptions when domain rules are violated. * Use domain events to trigger security-related actions (e.g., logging security events, auditing changes). **Don't Do This:** * Rely solely on external systems for security validation. * Allow invalid data to enter the domain layer. **Why:** The domain layer represents the core business logic, and enforcing security rules within this layer ensures that the system operates securely regardless of external factors. **Example (C#):** """csharp // Domain Entity public class User : AggregateRoot { public Guid Id { get; private set; } public string Username { get; private set; } private string _passwordHash; public User(string username, string password) { Id = Guid.NewGuid(); Username = username; SetPassword(password); } public void SetPassword(string password) { if (string.IsNullOrWhiteSpace(password)) { throw new InvalidOperationException("Password cannot be empty."); } if (password.Length < 8) { throw new InvalidOperationException("Password must be at least 8 characters long."); } _passwordHash = HashPassword(password); AddDomainEvent(new PasswordChangedEvent(Id)); } private string HashPassword(string password) { // Use a strong hashing algorithm (e.g., Argon2, BCrypt) return BCrypt.Net.BCrypt.HashPassword(password); } public bool VerifyPassword(string password) { return BCrypt.Net.BCrypt.Verify(password, _passwordHash); } } // Domain Event public class PasswordChangedEvent : DomainEvent { public Guid UserId { get; private set; } public PasswordChangedEvent(Guid userId) { UserId = userId; } } //Application Service handling the logic and publishing the event. public class UserService { private readonly IUserRepository _userRepository; private readonly IDomainEventPublisher _domainEventPublisher; public UserService(IUserRepository userRepository, IDomainEventPublisher domainEventPublisher) { _userRepository = userRepository; _domainEventPublisher = domainEventPublisher; } public void ChangePassword(Guid userId, string newPassword) { var user = _userRepository.GetById(userId); if (user == null) throw new ArgumentException($"User with id {userId} not found."); user.SetPassword(newPassword); //This call raises the domain event _userRepository.Update(user); _domainEventPublisher.Publish(new PasswordChangedEvent(userId)); // Explicitly push domain event. } } """ ### 2.2. Authorization and Access Control **Standard:** Implement fine-grained access control based on domain concepts. **Do This:** * Define domain-specific roles and permissions (e.g., "OrderAdministrator", "CustomerSupport"). * Use these roles and permissions to control access to domain entities and operations. * Implement authorization checks within the domain layer to ensure that users/services can only perform authorized actions. **Don't Do This:** * Rely solely on technical roles (e.g., "admin", "user") for authorization. * Implement authorization logic in the presentation or application layer. **Why:** Fine-grained access control ensures that users and services can only access the resources and perform the actions they are authorized to, minimizing the impact of potential security breaches. **Example (C# with Policies):** """csharp // Domain Entity public class Order : AggregateRoot { public Guid Id { get; private set; } public Guid CustomerId { get; private set; } public List<OrderItem> Items { get; private set; } public OrderStatus Status { get; private set; } //Authorization requirement example. public void Approve(Guid approverId, IRoleChecker roleChecker) { // Domain Rule: Only OrderAdministrators can approve orders. if (!roleChecker.IsInRole(approverId, "OrderAdministrator")) { throw new UnauthorizedAccessException("Only Order Administrators can approve orders."); } Status = OrderStatus.Approved; AddDomainEvent(new OrderApprovedEvent(Id)); } } //Role Checker abstraction interface (Infrastructure implementation) public interface IRoleChecker { bool IsInRole(Guid userId, string roleName); } //Application Service example to authorize the role check public class OrderService { private readonly IOrderRepository _orderRepository; private readonly IDomainEventPublisher _domainEventPublisher; private readonly IRoleChecker _roleChecker; public OrderService(IOrderRepository orderRepository, IDomainEventPublisher domainEventPublisher, IRoleChecker roleChecker) { _orderRepository = orderRepository; _domainEventPublisher = domainEventPublisher; _roleChecker = roleChecker; } public void ApproveOrder(Guid orderId, Guid approverId) { var order = _orderRepository.GetById(orderId); if (order == null) throw new ArgumentException($"Order with id {orderId} not found."); order.Approve(approverId, _roleChecker); _orderRepository.Update(order); _domainEventPublisher.Publish(new OrderApprovedEvent(orderId)); // Explicitly push domain event. } } """ ### 2.3. Input Validation and Sanitization **Standard:** Validate and sanitize all inputs to prevent injection attacks. **Do This:** * Use strong typing to define the expected data types. * Implement validation rules to check the format, length, and range of inputs. * Sanitize inputs to remove potentially malicious characters or code. * Use parameterized queries or ORM frameworks to prevent SQL injection. **Don't Do This:** * Trust user inputs. * Construct SQL queries by concatenating strings. **Why:** Input validation and sanitization prevent attackers from injecting malicious code or data into the system. **Example (C# with DataAnnotations and AntiXSS):** """csharp using System.ComponentModel.DataAnnotations; using Microsoft.Security.Application; // Install AntiXSS NuGet package // Data Transfer Object (DTO) public class CreateProductRequest { [Required(ErrorMessage = "Product Name is required")] [StringLength(200, MinimumLength = 3, ErrorMessage = "Product Name must be between 3 and 200 characters")] public string ProductName { get; set; } [Required(ErrorMessage = "Description is required")] [StringLength(1000, ErrorMessage = "Description cannot exceed 1000 characters")] [DataType(DataType.MultilineText)] public string Description { get; set; } [Range(0.01, double.MaxValue, ErrorMessage = "Price must be greater than 0")] public decimal Price { get; set; } } [ApiController] [Route("api/[controller]")] public class ProductController : ControllerBase { [HttpPost] public IActionResult CreateProduct([FromBody] CreateProductRequest request) { if (!ModelState.IsValid) { return BadRequest(ModelState); } // Sanitize the description to prevent XSS attacks request.Description = Sanitizer.GetSafeHtmlFragment(request.Description); // Pass the sanitized data to the application/domain layer. return Ok("Product Created"); } } """ ## 3. Infrastructure Layer Security ### 3.1. Secure Data Access **Standard:** Secure access to databases and other data sources. **Do This:** * Use parameterized queries or ORM frameworks to prevent SQL injection. * Encrypt sensitive data at rest and in transit. * Limit database access permissions to the minimum required. * Regularly audit database access logs. **Don't Do This:** * Store database credentials in plain text. * Grant excessive database access permissions. **Why:** Secure data access ensures that sensitive data is protected from unauthorized access and modification. **Example (C# with Entity Framework Core and Parameterized Queries):** """csharp // Entity Framework Core DbContext public class MyDbContext : DbContext { public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { } public DbSet<Product> Products { get; set; } } //Example using parameterized queries to avoid SQL Injection public class ProductRepository : IProductRepository { private readonly MyDbContext _dbContext; public ProductRepository(MyDbContext dbContext) { _dbContext = dbContext; } public Product GetProductByName(string productName) { return _dbContext.Products.FromSqlRaw("SELECT * FROM Products WHERE ProductName = @p0", productName).FirstOrDefault(); } } """ ### 3.2. Logging and Auditing **Standard:** Implement comprehensive logging and auditing to detect and investigate security incidents. **Do This:** * Log all security-related events (e.g., authentication attempts, authorization failures, data modifications). * Include relevant information in log entries (e.g., timestamp, user ID, affected resource). * Securely store and protect log files. * Regularly review and analyze log data. **Don't Do This:** * Disable or reduce logging in production environments. * Store logs in the same location as application files. **Why:** Logging and auditing provide visibility into system activity, allowing developers and security professionals to identify and respond to security incidents. **Example (C# with Serilog):** """csharp using Serilog; using Serilog.Events; public class Program { public static void Main(string[] args) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Override("Microsoft", LogEventLevel.Information) .Enrich.FromLogContext() .WriteTo.Console() .WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); try { Log.Information("Starting web application"); CreateHostBuilder(args).Build().Run(); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); } } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSerilog() //Configure Serilog as the logging provider .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); } //Later in your application public class AuthService { public bool Authenticate(string username, string password) { // Authentication logic here if (username == "test" && password == "password") { Log.Information("User {Username} successfully authenticated", username); return true; } else { Log.Warning("Authentication failed for user {Username}", username); return false; } } } """ ### 3.3 Dependency Management **Standard:** Secure your application's dependencies. **Do This:** * Use a dependency management tool (e.g., NuGet for .NET) to manage your application's dependencies. * Regularly update dependencies to the latest versions to patch security vulnerabilities. * Use a vulnerability scanning tool to identify vulnerable dependencies. * Consider using a private repository to host your own dependencies. **Don't Do This:** * Use dependencies from untrusted sources. * Ignore vulnerability warnings from your dependency management tool. **Why:** Dependencies can contain security vulnerabilities that can be exploited by attackers. Keeping dependencies up-to-date helps to mitigate this risk. **Example (Using NuGet to update dependencies):** """powershell # Update all packages in the project dotnet nuget update # Update a specific package dotnet add package <PackageName> -v <Version> """ ## 4. Common Anti-Patterns and Mistakes * **Ignoring Security Reviews:** Skipping security reviews during the development process. * **Over-Reliance on Framework Security:** Assuming that the framework handles all security concerns. * **Hardcoding Secrets:** Storing passwords, API keys, or other sensitive information directly in the code. * **Insufficient Error Handling:** Failing to handle errors and exceptions properly, which can expose sensitive information or lead to denial-of-service attacks. ## 5. Conclusion These security best practices are vital for building secure and resilient domain-driven applications. By integrating these standards into your development process, you can significantly reduce the risk of security vulnerabilities and protect your systems and data from unauthorized access and modification. These recommendations will evolve, so staying current is important.
# Deployment and DevOps Standards for Domain Driven Design This document outlines coding standards related to Deployment and DevOps specifically within the context of Domain Driven Design (DDD). It aims to provide clear guidance on building, deploying, and managing DDD applications effectively, while maintaining domain integrity and agility. ## 1. Build Processes and CI/CD for DDD ### 1.1 Standards * **Do This:** Implement Continuous Integration (CI) and Continuous Delivery/Deployment (CD) pipelines. This automates building, testing, and deploying your application, reducing manual errors and accelerating delivery. * **Don't Do This:** Rely on manual build and deployment processes. This is error-prone, time-consuming, and hinders agility. * **Why:** CI/CD ensures frequent and reliable releases, early detection of integration issues, and faster feedback loops for developers. ### 1.2 Build Process Considerations * **Standard:** Use build tools that support dependency management and reproducible builds (e.g., Maven/Gradle for Java, npm/yarn for JavaScript, NuGet for .NET). * **Standard:** Ensure the build process includes static code analysis, unit tests, integration tests, and potentially performance tests. * **Standard:** Version your artifacts (e.g., Docker images, JAR files) with semantic versioning, facilitating dependency management and rollback capabilities. * **Why:** Repeatable builds are crucial for consistent deployment and easy rollback strategies. Unit tests within the build process guarantee quality, and static code analysis can find potential errors early. ### 1.3 CI Pipeline Examples **Example (GitHub Actions):** """yaml name: CI/CD Pipeline on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle run: ./gradlew build - name: Run Static Analysis - SonarQube run: ./gradlew sonarqube -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_TOKEN - name: Docker Build & Push if: github.ref == 'refs/heads/main' run: | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_TOKEN }} docker build -t your-app:${GITHUB_SHA} . docker tag your-app:${GITHUB_SHA} your-app:latest docker push your-app:${GITHUB_SHA} docker push your-app:latest """ **Example (Jenkins):** Configure a pipeline job: 1. **Source Code Management:** Connect to your code repository (e.g. Git). 2. **Build Triggers:** Configure triggers like "Poll SCM" on pushes to the main branch. 3. **Build Steps**: * Execute shell scripts (e.g., "gradle clean build", "docker build -t my-app .", "docker push my-app"). * Use Jenkins plugins for SonarQube or other static analysis tools. ### 1.4 CD Pipeline Examples **Example (ArgoCD with Kubernetes):** 1. Create ArgoCD application definition yaml file: """yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: my-ddd-app namespace: argocd spec: project: default source: repoURL: https://github.com/your-org/your-ddd-repo.git targetRevision: HEAD path: k8s/deploy destination: server: https://kubernetes.default.svc namespace: my-namespace syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true """ 2. Directory Structure: Your Git repository should include a "/k8s/deploy" directory with your Kubernetes manifests (Deployment, Service, etc.). **Example (Terraform deployment):** Terraform can be used to provision infrastructure for your DDD application """terraform resource "aws_instance" "app_server" { ami = "ami-xxxxxxxxxxxxx" # Replace with your AMI ID instance_type = "t2.medium" key_name = "your_key" vpc_security_group_ids = ["sg-xxxxxxxxxxxxx"] tags = { Name = "my-ddd-app-server" } user_data = <<-EOF #!/bin/bash # Install dependencies, pull image, run docker sudo apt-get update sudo apt-get install -y docker.io sudo systemctl start docker sudo systemctl enable docker docker login -u your-dockerhub-username -p your-dockerhub-password # Store credentials securely docker pull your-app:latest docker run -d -p 80:8080 your-app:latest EOF } """ ## 2. Production Considerations for DDD ### 2.1 Database Migrations * **Do This:** Use database migration tools (e.g., Flyway, Liquibase, Entity Framework Migrations) to manage schema changes. * **Don't Do This:** Apply schema changes manually. * **Why:** Automating database migrations ensures consistent schema updates, reduces downtime, and supports version control of schema changes. * **Standard:** Integrate database migrations into your CI/CD pipeline. Use idempotent migration scripts. These scripts should only apply changes that are not already applied ensuring deployments can be rerun without breaking. **Example (Flyway):** 1. Create Flyway migration scripts in the "src/main/resources/db/migration" directory: "V1__Create_Order_Table.sql" """sql CREATE TABLE orders ( id UUID PRIMARY KEY, customer_id UUID NOT NULL, order_date TIMESTAMP NOT NULL ); """ "V2__Add_Order_Status.sql" """sql ALTER TABLE orders ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'PENDING'; """ 2. Configure Flyway in your application configuration (e.g., "application.properties" if using Spring Boot): """properties spring.flyway.url=jdbc:postgresql://localhost:5432/your_database spring.flyway.user=your_user spring.flyway.password=your_password """ ### 2.2 Infrastructure as Code (IaC) * **Do This:** Use IaC tools like Terraform, CloudFormation, or Azure Resource Manager to provision and manage infrastructure resources. * **Don't Do This:** Manually configure infrastructure resources. * **Why:** IaC provides a declarative way to define infrastructure, ensures consistent environments, and facilitates automation and version control. **Example (Terraform - AWS):** """terraform resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" tags = { Name = "main-vpc" } } resource "aws_subnet" "public_subnet" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = "us-east-1a" tags = { Name = "public-subnet" } } """ ### 2.3 Monitoring and Logging * **Do This:** Implement comprehensive monitoring and logging for your application. Use structured logging and integrate with centralized logging systems (e.g., ELK stack, Splunk, Datadog). * **Don't Do This:** Rely on ad-hoc logging or manual monitoring. * **Why:** Monitoring and logging provide insights into application performance, identify issues early, and facilitate troubleshooting. * **Standard:** Monitor key DDD metrics such as aggregate processing time, event handling latency, and command execution successes/failures. **Example (Structured Logging using Logback with Spring Boot):** 1. Add Logback dependency (it's usually included by default in Spring Boot). 2. Configure a "logback-spring.xml" file: """xml <?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>application.log</file> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="FILE" /> </root> </configuration> """ 3. Log events using a centralized logger: """java import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class OrderService { private static final Logger logger = LoggerFactory.getLogger(OrderService.class); public void placeOrder(Order order) { logger.info("Placing order with ID: {}", order.getId()); // ... Order placement logic ... logger.debug("Order {} : {} placed successfully!", order.getId(), order.getDescription()); if (order.getTotalAmount() > 1000) { logger.warn("Large order placed with ID: {} and amount: {}", order.getId(), order.getTotalAmount()); } try { // Simulate an exception throw new RuntimeException("Simulated error"); } catch (RuntimeException e) { logger.error("Error processing order with ID: {}", order.getId(), e); } } } """ ### 2.4 Security Best Practices * **Do This:** Implement security best practices throughout the entire application lifecycle. This includes authentication, authorization, encryption, vulnerability scanning, and penetration testing. * **Don't Do This:** Neglect security considerations or rely on default security configurations. * **Why:** Ensuring security is crucial to protect sensitive data, prevent unauthorized access, and maintain application integrity. * **Standard:** Follow the principle of least privilege. Grant only the necessary permissions to users and services. * **Standard:** Regularly update dependencies to address known vulnerabilities. * **Standard:** Utilize tools like OWASP ZAP or SonarQube for static and dynamic security analysis. * **Standard:** Encrypt sensitive data both in transit and at rest. Use HTTPS for all communication. **Example (Spring Security):** """java @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authz) -> authz .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/user/**").hasRole("USER") .anyRequest().authenticated() ) .httpBasic(withDefaults()); // Basic Authentication return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); UserDetails admin = User.withDefaultPasswordEncoder() .username("admin") .password("admin") .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } } """ ### 2.5 Technology-Specific Details * **Cloud Platforms:** Leverage cloud-native services like AWS Lambda, Azure Functions, or Google Cloud Functions for serverless components within your DDD application. * **Containerization:** Always containerize your DDD application using Docker. This provides consistency across environments. Optimize Docker images for size and security. Use multi-stage builds to reduce final image size and exclude build dependencies. * **Orchestration:** Use Kubernetes or Docker Swarm for container orchestration. This ensures scalability, resilience, and automated deployment. * **Message Queues:** Utilize message queues like RabbitMQ or Kafka for asynchronous communication between bounded contexts. Configure proper message durability and retry mechanisms. ## 3. Domain Driven Design Specific Considerations for Deployment ### 3.1 Bounded Context Deployment * **Standard:** Deploy each bounded context as an independent service (microservice architecture is well-suited). * **Why:** This aligns with DDD principles, promoting autonomy, independent evolution, and reduced coupling between domain areas. * **Deployment Strategy:** Each bounded context should have its own CI/CD pipeline, infrastructure, and database. This enables independent scaling and release cycles. * **Inter-service Communication:** Choose appropriate inter-service communication patterns such as: * **RESTful APIs:** Simple and widely understood. * **Message Queues (e.g., RabbitMQ, Kafka):** Asynchronous and loosely coupled. Essential for eventual consistency. * **gRPC:** High-performance, contract-based communication, especially suited for internal services. ### 3.2 Eventual Consistency * **Standard:** Embrace eventual consistency when propagating data changes between bounded contexts. Use domain events and message queues to ensure reliable delivery. * **Standard:** Implement idempotent event handlers to avoid duplicate processing. Ensure handling logic is unaffected by processing the same message multiple times. * **Standard:** Monitor message queue health and latency to detect and resolve issues promptly. Implement dead-letter queues to handle undeliverable messages. **Example (Event-Driven deployment using Kafka):** 1. Order Service publishes "OrderCreatedEvent" to Kafka 2. Shipping Service subscribes to "OrderCreatedEvent" and creates shipment. """java // Order Service @Service public class OrderService { private final KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate; @Autowired public OrderService(KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate) { this.kafkaTemplate = kafkaTemplate; } public void createOrder(Order order) { // ... order creation logic OrderCreatedEvent event = new OrderCreatedEvent(order.getId(), order.getCustomerId()); kafkaTemplate.send("order-created-topic", event); } } // Shipping Service @Service @KafkaListener(topics = "order-created-topic", groupId = "shipping-group") public class ShippingService { public void handleOrderCreatedEvent(OrderCreatedEvent event) { // ... create shipment System.out.println("Received OrderCreatedEvent: " + event.getOrderId()); } } """ ### 3.3 Versioning Strategies * **Standard:** Use API versioning to manage changes to public APIs. This allows clients to upgrade at their own pace. Consider both URI versioning ("/v1/orders") and header-based versioning ("Accept: application/vnd.yourcompany.orders.v1+json"). * **Standard:** Implement data versioning for domain events. This helps ensure compatibility between event publishers and consumers over time. Include a version field in the event payload. * **Blue/Green Deployments:** Consider blue/green deployments to minimize downtime during releases. Deploy the new version (green) alongside the existing version (blue). Switch traffic to the green environment after verifying the new version is working correctly. ## 4. Common Anti-Patterns and Mistakes * **Shared Database Across Bounded Contexts:** This violates DDD principles and tightly couples the services. Each bounded context should have its own database or schema to ensure autonomy. * **God Object/Service:** Avoid creating large, monolithic services that span multiple domains. Break down large services into smaller, more manageable bounded contexts. * **Ignoring Domain Events:** Domain events are a crucial part of DDD. Failing to use them can lead to tight coupling and make it difficult to maintain eventual consistency. * **Inadequate Testing:** Neglecting unit, integration, and end-to-end tests can lead to undetected bugs and deployment failures. ## 5. Performance Optimization Techniques * **Caching:** Implement caching strategies at various levels (e.g., HTTP caching, in-memory caching, database caching) to reduce latency and improve performance. Use tools like Redis or Memcached for distributed caching. * **Database Optimization:** Optimize database queries, use indexes appropriately, and consider database sharding or partitioning for large datasets. * **Asynchronous Processing:** Use message queues for long-running or resource-intensive operations to avoid blocking the user interface or other services. * **Load Balancing:** Distribute traffic across multiple instances of your services using load balancers to ensure high availability and scalability. ## 6. Security Best Practices specific to DDD * **Authentication and Authorization:** Implement robust authentication and authorization mechanisms. Use industry-standard protocols like OAuth 2.0 or OpenID Connect. * **Input Validation:** Thoroughly validate all input data to prevent injection attacks (e.g., SQL injection, cross-site scripting). * **Data Encryption:** Encrypt sensitive data both in transit and at rest. Use HTTPS for all communication. * **Secure Configuration Management:** Store sensitive configuration data (e.g., passwords, API keys) securely using secrets management tools like HashiCorp Vault or AWS Secrets Manager. * **Regular Security Audits:** Perform regular security audits and penetration testing to identify and address potential vulnerabilities.