| marp | author | title | paginate |
|---|---|---|---|
true |
Zack Kite |
DDD in Practice |
true |
- Domain Core
- Entities and Value Objects
- Aggregates and Boundaries
- Respositories
- Application vs. Domain Services
- Questions
Domain driven design is an approach that leverages strategic and tactical design patterns to model the business logic domain core
The Tactical Design, is a set of technical resources used in the construction of your Domain Model, these resources must be applied to work in a single Bounded Context.
Represents the nucleus of the domain and contains the most critical business logic and rules. The domain core is comprised of aggregates and domain services.
- Identify Business Capabilities: Recognize the core functionalities crucial to the domain. For example, OPS's capabilities include manage payment plans etc.; Bondlife's capabilities include generating digital documents for policies
- Define Boundaries: Encapsulate related business capabilities within bounded contexts. For example, in case of Bondlife; claims management is separate from document generation
- Aggregate Design: Implement aggregates to represent cohesive, transactional business units and inforce invariants.
public class CurrencyConverter {
public double convert(String fromCurrency, String toCurrency, double amount) {
double exchangeRate = getExchangeRate(fromCurrency, toCurrency);
return amount * exchangeRate;
}
private double getExchangeRate(String fromCurrency, String toCurrency) { ... }
}Primitive obsession is an anti-pattern that refers to the excessive use of basic data types (such as integers, strings, or booleans) to represent more complex domain concepts
Value objects are small, immutable objects that represent a concept in your domain and have value equality based on their attributes.
- Install the CSharpFunctionalExtensions NuGet package:
Install-Package CSharpFunctionalExtensionsusing CSharpFunctionalExtensions;
public class Money : ValueObject
{
public decimal Amount { get; }
public string Currency { get; }
private Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
public static Result<Money> Create(decimal amount, string currency)
{
if (amount < 0)
return Result.Failure<Money>("Amount cannot be negative");
if (string.IsNullOrWhiteSpace(currency))
return Result.Failure<Money>("Currency is required");
return Result.Success(new Money(amount, currency));
}
}- Add Lombok to your project dependencies.
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>import lombok.Value;
@Value
public class Money {
private final BigDecimal amount;
private final String currency;
}public record Money(BigDecimal amount, String currency) {
// No need for explicit getters, equals, hashcode, and toString
}- Immutability
- Reusability
- Error Handling
- Clear Semantics
- Testability
Domain entities represent objects with distinct identities. Identity is maintained even if the attributes change.
public class Customer : Entity
{
public Guid Id { get; private set; }
public string Name { get; private set; }
...
}public class OrderId : ValueObject { ... }
public class Order : Entity<OrderId>
{
public OrderId OrderId { get; private set; }
public OrderNumber OrderNumber { get; private set; }
public DateTime OrderDate { get; private set; }
}import java.util.UUID;
public class Order {
private UUID orderId;
private int orderNumber;
private Date orderDate;
}An aggregate is a design pattern that groups related entities and value objects, forming a cohesive unit within the domain model.
- Gather related entities under a single abstraction.
- Maintain invariants over their lifetime.
- Every aggregate has a root entity.
- Restrict external access to maintain invariants.
public class PaymentPlan : AggregateRoot {...}- Identify clusters of related entities.
- Entities within an aggregate share strong semantic relationships.
- Entities work together to fulfill specific business capabilities.
- Aggregates are often transactional boundaries.
Explicitly show aggregate boundaries in the domain model.
public abstract class AggregateRoot : Entity
{
public virtual int Version { get; protected set; }
private List<DomainEvent> _events = new List<DomainEvent>();
}One-to-one correspondence between aggregates and repositories.
public abstract class Repository<T> where T : AggregateRoot
{
// Repository methods
}public class Cargo : AggregateRoot {
public int MaxWeight { get; protected set; }
protected IList<Product> Items { get; }
public Cargo(int maxWeight)
{
MaxWeight = maxWeight;
Items = new List<Product>();
}
public void AddItem(Product product)
{
int currentWeight = Items.Sum(x => x.Weight);
if (currentWeight + product.Weight > MaxWeight)
{
throw new InvalidOperationException();
}
Items.Add(product);
}
}public class Cargo : AggregateRoot {
public int MaxWeight { get; protected set; }
protected IList<Product> Items { get; }
public Cargo(int maxWeight)
{
MaxWeight = maxWeight;
Items = new List<Product>();
}
public void AddItem(Product product)
{
Items.Add(product);
}
public bool IsValid()
{
int currentWeight = Items.Sum(x => x.Weight);
return currentWeight <= MaxWeight;
}
}- Reside inside the domain layer.
- Contain domain logic
- Do not communicate with the outside world.
- Outside the domain layer.
- Communicate with the outside world.
- Do not contain domain logic.
