Как применять Domain-Driven Design в разработке?
Введение
Domain-Driven Design (DDD) – это мощный подход к проектированию сложных программных систем, который помогает разработчикам сосредоточиться на бизнес-логике и избежать излишней сложности архитектуры. Этот подход особенно полезен в условиях постоянно меняющихся требований и сложных предметных областей. В данной статье я поделюсь своим опытом применения DDD в реальных проектах, расскажу о ключевых концепциях и приведу практические примеры.
Основные концепции DDD
  1. Ubiquitous Language (Единый язык) – общий язык между разработчиками, аналитиками и бизнес-экспертами.
  2. Bounded Context (Ограниченный контекст) – область, в рамках которой определенные термины и модели имеют однозначное значение.
  3. Entities (Сущности) – объекты с идентичностью, важной для бизнеса.
  4. Value Objects (Объекты-значения) – неизменяемые объекты, которые характеризуются своим состоянием, а не идентичностью.
  5. Aggregates (Агрегаты) – группы связанных объектов с четко определенным корневым элементом.
  6. Domain Events (События домена) – факты, значимые для бизнеса.
  7. Repositories (Репозитории) – интерфейсы для работы с агрегатами.
Применение DDD на практике
1. Выявление доменной области и построение модели
Первый шаг – это понимание бизнеса. В одном из моих проектов по автоматизации бухгалтерского учета мы начали с воркшопов с бизнес-экспертами, где выделили основные сущности: Счет, Транзакция, Контрагент.
public class Account
{
    public Guid Id { get; private set; }
    public string Number { get; private set; }
    public decimal Balance { get; private set; }

    private List<Transaction> _transactions = new();
    public IReadOnlyCollection<Transaction> Transactions => _transactions.AsReadOnly();

    public Account(string number, decimal initialBalance)
    {
        Id = Guid.NewGuid();
        Number = number;
        Balance = initialBalance;
    }

    public void AddTransaction(Transaction transaction)
    {
        _transactions.Add(transaction);
        Balance += transaction.Amount;
    }
}
2. Определение ограниченных контекстов
На этом этапе мы определяем границы системы. В нашем случае было два ограниченных контекста:
  • Финансовый контекст (управление счетами, балансами, транзакциями)
  • Контекст отчетности (генерация отчетов, аналитика)
  • Каждый контекст взаимодействует через Domain Events:
public class TransactionCreatedEvent : IDomainEvent
{
    public Guid AccountId { get; }
    public decimal Amount { get; }

    public TransactionCreatedEvent(Guid accountId, decimal amount)
    {
        AccountId = accountId;
        Amount = amount;
    }
}
3. Проектирование агрегатов и репозиториев
Агрегаты помогают управлять целостностью данных. В нашем случае Счет является корнем агрегата:
public class AccountAggregate : AggregateRoot
{
    public Account Account { get; private set; }
    
    public AccountAggregate(Account account)
    {
        Account = account;
    }
}
Репозиторий отвечает за получение агрегатов:
public interface IAccountRepository
{
    Account GetById(Guid id);
    void Save(Account account);
}
4. Использование доменных событий для согласованности данных
Когда создается новая транзакция, мы отправляем событие:
public class TransactionService
{
    private readonly IEventDispatcher _eventDispatcher;
    
    public TransactionService(IEventDispatcher eventDispatcher)
    {
        _eventDispatcher = eventDispatcher;
    }

    public void CreateTransaction(Account account, decimal amount)
    {
        var transaction = new Transaction(amount);
        account.AddTransaction(transaction);
        _eventDispatcher.Raise(new TransactionCreatedEvent(account.Id, amount));
    }
}
5. Интеграция с CQRS
Часто DDD применяется вместе с CQRS (Command Query Responsibility Segregation). Команды изменяют состояние, а запросы его читают.
Пример запроса на получение баланса:
public class GetAccountBalanceQueryHandler : IQueryHandler<GetAccountBalanceQuery, decimal>
{
    private readonly IAccountRepository _repository;
    
    public GetAccountBalanceQueryHandler(IAccountRepository repository)
    {
        _repository = repository;
    }
    
    public decimal Handle(GetAccountBalanceQuery query)
    {
        var account = _repository.GetById(query.AccountId);
        return account.Balance;
    }
}
6. Автоматизация тестирования модели
DDD хорошо сочетается с TDD. Пример теста агрегата Account:
[Test]
public void AddTransaction_ShouldUpdateBalance()
{
    var account = new Account("123456", 1000);
    account.AddTransaction(new Transaction(200));
    
    Assert.AreEqual(1200, account.Balance);
}
Заключение
DDD – это не просто набор шаблонов проектирования, а подход к разработке, который позволяет лучше понять бизнес, построить надежную архитектуру и облегчить сопровождение системы. Важно помнить:
  1. Используйте единый язык для коммуникации с бизнесом.
  2. Четко определяйте ограниченные контексты.
  3. Структурируйте доменную модель с учетом сущностей, объектов-значений, агрегатов и событий.
  4. Инкапсулируйте бизнес-логику внутри агрегатов.
  5. Применяйте событийный подход для согласованности данных.
  6. Интегрируйте с CQRS и Event Sourcing, если это необходимо.
Эта тема подробно рассматривается на курсе "DDD и Clean Architecture на практике"
Понравилась статья? Поделись в соцсетях!
Our Website is Almost Ready
Launch a targeted campaign.
Scale your infrastructure with our simple service.
Days
Hours
Minutes
Seconds