Domain-Driven Design (DDD) is a software development approach that emphasizes deep collaboration between technical and domain experts to create software that accurately reflects business reality. Intent Architect provides comprehensive support for DDD patterns through its Domain Designer and various modules.
DDD Core Principles
Focus on the core domain and domain logic, use models to solve complex business problems, and maintain a ubiquitous language shared between developers and domain experts.
Entities are objects with a distinct identity that persists throughout their lifecycle.Module:Intent.Entities
[IntentManaged(Mode.Merge, Signature = Mode.Fully)]public class Order{ public Guid Id { get; set; } // Identity public string OrderNumber { get; set; } public DateTime OrderDate { get; set; } public Guid CustomerId { get; set; } public OrderStatus Status { get; private set; } // Rich behavior - not just data public void MarkAsShipped() { if (Status != OrderStatus.Confirmed) throw new InvalidOperationException( "Only confirmed orders can be shipped"); Status = OrderStatus.Shipped; // Domain event could be raised here }}
Key Characteristics:
Unique Identity: Distinguished by ID, not attributes
Mutable State: Can change over time
Rich Behavior: Encapsulates business logic
Lifecycle Management: Created, modified, deleted
Two entities are equal if they have the same identity, even if all their attributes differ.
Value Objects are immutable objects defined by their attributes, with no conceptual identity.Module:Intent.ValueObjects
public class Address{ public Address(string street, string city, string postalCode, string country) { Street = street; City = city; PostalCode = postalCode; Country = country; } public string Street { get; } public string City { get; } public string PostalCode { get; } public string Country { get; } // Value objects are compared by their attributes public override bool Equals(object obj) { if (obj is not Address other) return false; return Street == other.Street && City == other.City && PostalCode == other.PostalCode && Country == other.Country; }}
Key Characteristics:
Immutable: Once created, cannot be changed
No Identity: Defined entirely by attributes
Interchangeable: Two value objects with same attributes are equal
Aggregates are clusters of domain objects treated as a single unit, with an Aggregate Root controlling access.
public class Order // Aggregate Root{ public Guid Id { get; set; } public string OrderNumber { get; set; } // Private backing field - encapsulation private readonly List<OrderItem> _items = new(); // Public read-only access public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly(); // All modifications go through the aggregate root public void AddItem(Guid productId, int quantity, decimal unitPrice) { // Business rule: cannot add items with zero quantity if (quantity <= 0) throw new ArgumentException("Quantity must be positive"); var existingItem = _items.FirstOrDefault(i => i.ProductId == productId); if (existingItem != null) { existingItem.IncreaseQuantity(quantity); } else { var newItem = new OrderItem(productId, quantity, unitPrice); _items.Add(newItem); } } public void RemoveItem(Guid productId) { var item = _items.FirstOrDefault(i => i.ProductId == productId); if (item != null) { _items.Remove(item); } }}
Modeling Aggregates in Intent Architect:In the Domain Designer, use composition (black diamond) to model ownership:
Order (Aggregate Root) └─── OrderItem (owned entity) └─── ShippingAddress (owned value object)
Aggregates define transaction boundaries. All changes within an aggregate are saved together in a single transaction.
Name classes, methods, and properties using exact business terminology:
// Good - Uses business languagepublic class Order{ public void Place() { } public void Ship() { } public void Cancel() { }}// Bad - Technical languagepublic class OrderEntity{ public void SetStatus(int status) { }}
Model Real Business Processes
Structure code to reflect actual business workflows:
// Business process: Order Fulfillmentorder.Confirm(); // Business approves orderawait payment.Process(); // Payment is collectedorder.MarkAsShipped(); // Warehouse ships itemsorder.Complete(); // Customer receives delivery
Avoid Technical Abstractions
Don’t hide business concepts behind generic patterns:
// Good - Clear business intentpublic interface ICustomerRepository{ Task<Customer> FindByEmailAsync(string email); Task<List<Customer>> FindPremiumCustomersAsync();}// Less clear - Generic abstractionpublic interface IRepository<T>{ Task<T> FindByPropertyAsync(string property, object value);}
A critical distinction in DDD is separating domain entities from data transfer objects:
Domain Entity
Data Transfer Object (DTO)
Purpose: Encapsulate business logic and rules
public class Customer // Domain Entity{ public Guid Id { get; private set; } public string Name { get; private set; } public CustomerStatus Status { get; private set; } private readonly List<Order> _orders = new(); public IReadOnlyCollection<Order> Orders => _orders.AsReadOnly(); // Business behavior public void PlaceOrder(Order order) { if (Status == CustomerStatus.Suspended) throw new InvalidOperationException( "Suspended customers cannot place orders"); _orders.Add(order); } public void Suspend(string reason) { Status = CustomerStatus.Suspended; // Could raise CustomerSuspendedEvent }}
Characteristics:
Contains business logic
Enforces invariants
Has identity and lifecycle
Never exposed directly through APIs
Purpose: Transfer data across boundaries
public class CustomerDto // Data Transfer Object{ public Guid Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string Status { get; set; } public int TotalOrders { get; set; } public decimal LifetimeValue { get; set; }}
Characteristics:
No business logic
Simple data container
Can have different structure than entity
Designed for specific use cases
Never expose domain entities through APIs. Always map to DTOs. This protects your domain model from API changes and prevents over-posting vulnerabilities.
public class OrderRepository : RepositoryBase<Order, Order, ApplicationDbContext>, IOrderRepository{ public OrderRepository(ApplicationDbContext dbContext) : base(dbContext) { } // Saving the aggregate root saves all owned entities public void Add(Order order) { // EF Core tracks the entire aggregate graph base.Add(order); }}