Когда сущности оживают: применение State в доменной модели DDD

Когда сущности оживают: применение State в доменной модели DDD

Сущность в Domain Driven Design — это сердце бизнес-логики. Ее цель - моделирование бизнес-процессов и их жизненного цикла. Сущность инкапсулирует состояние и поведение важное для бизнеса.

Очень часто в DDD поведение сущности, доступные операции и бизнес-правила начинают определяться через состояние самого объекта. При использовании традиционной "плоской" структуры методы изменения состояния объекта начинают содержать множество условных операторов (if-else, switch). Проверки состояния объекта дублируются в каждом методе. Также со временем сущность превращается в God-object - огромные классы, внутри которых находится вся логика поведения:

class Order { private OrderStatus status; public void pay() { if (status != OrderStatus.CREATED) { throw new IllegalStateException("Can't pay now"); } status = OrderStatus.PAID; // остальная логика } public void ship() { if (status != OrderStatus.PAID) { throw new IllegalStateException("Can't ship before paid"); } status = OrderStatus.SHIPPED; } }

Выход: использование паттерна State для управления поведением сущности! State позволяет "извлечь" состояние сущности в отдельные классы, каждый из которых инкапсулирует своё поведение и валидные переходы. Результат? Читаемый, безопасный и легко тестируемый код доменной модели.

Сущность с состоянием

Как будет выглядеть пример выше с использованием паттерна State:

interface OrderState { void pay(Order order); void ship(Order order); } class CreatedState implements OrderState { @Override public void pay(Order order) { order.setState(new PaidState()); } @Override public void ship(Order order) { throw new IllegalStateException("Can't ship before payment"); } } class PaidState implements OrderState { @Override public void pay(Order order) { throw new IllegalStateException("Already paid"); } @Override public void ship(Order order) { order.setState(new ShippedState()); } } class ShippedState implements OrderState { public void pay(Order order) { throw new IllegalStateException("Already shipped"); } public void ship(Order order) { throw new IllegalStateException("Already shipped"); } } class Order { private OrderState state = new CreatedState(); public void setState(OrderState state) { this.state = state; } public void pay() { state.pay(this); } public void ship() { state.ship(this); } }

Преимущества использования состояния

⮞ Ясность бизнес-логики - убираем нарушение принципа Single Responsibility. Сущность делегирует поведение Состоянию. Каждый State отвечает только за свое состояние.

⮞ Защита от недопустимых состояний - объект не перейдет в недопустимое состояние, все переходы контроллируются на уровне модели. Переход - это всегда явная операция.

⮞ Легко тестировать - каждое состояние это небольшой класс, выполняющий одно действие. И поэтому легко тестируются независимо друг от друга.

⮞ Просто расширять - добавление нового состояния это просто добавление нового класса и нескольких переходов.

Когда стоит использовать State вместе с Domain Entity

🠶 Система имеет четкие ограниченные состояния

🠶 Поведение сильно зависит от состояния

🠶 Сложный жизненный цикл с множеством переходов

🠶 Планируется расширение логики

Итог

Использование State при реализации Entity позволит создать читаемую и безопасную доменную модель. Это способ придать доменной модели структуру, сделать её предсказуемой и устойчивой к росту сложности.

Что думаете по этому поводу? Использовали State в Entity?

Мой телеграм-канал можно найти по ссылке

Начать дискуссию