TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #2784 2.55K
День 2309. #ЗаметкиНаПолях
Уходим от Анемичных Моделей. Пример DDD-Рефакторинга. Начало
Если вы когда-либо работали с устаревшей кодовой базой C#, вы знаете боль анемичной модели домена. Вы открывали какой-нибудь OrderService и думали: «Этот класс делает всё: логика ценообразования, правила скидок, проверка остатков, запись в БД и т.п.» Это работает… какое-то время. Но новые функции превращаются в регрессионную рулетку, а тестовое покрытие резко падает, потому что доменная логика погребена под инфраструктурной.

Это классический симптом анемичной модели домена, где сущности — это всего лишь держатели данных, а вся логика находится в другом месте. Это затрудняет понимание системы, и каждое изменение превращается в угадайку.

В этой серии мы:
- Изучим типичную анемичную реализацию.
- Определим скрытые бизнес-правила, которые делают её хрупкой.
- Выполним рефакторинг в сторону агрегата с богатым поведением.
- Выделим конкретные выгоды, чтобы вы могли обосновать изменение для членов команды.

Начало: Божественный Класс Сервиса
Ниже приведён (к сожалению, распространённый пример) OrderService. Помимо расчёта итоговых сумм он также:
- применяет скидку VIP 5%,
- выбрасывает исключение, если какой-то товар отсутствует на складе,
- отклоняет заказы, которые превышают кредитный лимит клиента.

// OrderService.cs
public void PlaceOrder(
Guid customerId,
IEnumerable<OrderItemDto> items)
{
var customer = _db.Customers.Find(customerId);
if (customer is null)
throw new ArgumentException("Клиент не найден");

var order = new Order { CustomerId = customerId };

foreach (var dto in items)
{
var inventory = _invService
.GetStock(dto.ProductId);
if (inventory < dto.Quantity)
throw new InvalidOperationException("Товара недостаточно");

var price = _pricingService
.GetPrice(dto.ProductId);
var lineTotal = price * dto.Quantity;
// скидка 5% для VIP
if (customer.IsVip)
lineTotal *= 0.95m;

order.Items.Add(new OrderItem
{
ProductId = dto.ProductId,
Quantity = dto.Quantity,
UnitPrice = price,
LineTotal = lineTotal
});
}

order.Total = order.Items
.Sum(i => i.LineTotal);

if (customer.CreditUsed + order.Total > customer.CreditLimit)
throw new InvalidOperationException("Кредитный лимит превышен ");

_db.Orders.Add(order);
_db.SaveChanges();
}


Что здесь не так?
1. Разрозненные правила: применение скидок, проверка остатков и проверки кредитного лимита зарыты внутри сервиса.
2. Тесная связь: OrderService должен знать о ценах, запасах и EF Core только для того, чтобы разместить заказ.
3. Болезненное тестирование: каждому модульному тесту нужны моки для доступа к БД, ценообразования, запасов и потоки для VIP и не VIP клиентов.

Наша цель - внедрить эти правила в домен, чтобы прикладной уровень занимался только оркестрацией.

Руководящие принципы
1. Размещаем защитные инварианты близко к данным.
Проверки остатков, скидок и кредита относятся к месту, где находятся данные — внутри агрегата Order.
2. Раскрываем намерение, скрываем механику.
Прикладной уровень должен читаться как история: «разместить заказ», а не «рассчитать итоги, проверить кредит, записать в БД».
3. Выполняем рефакторинг в срезах.
Каждый ход безопасен и компилируется; никаких больших переписываний.
4. Балансируем чистоту с прагматизмом.
Изменяйте правила только тогда, когда выгода (ясность, безопасность, тестируемость) перевешивает дополнительные строки кода.

Продолжение следует…

Источник:
https://www.milanjovanovic.tech/blog/from-anemic-models-to-behavior-driven-models-a-practical-ddd-refactor-in-csharp
  • 👍 28
  • 👎 2
More from @netdeveloperdiary
  1. Oct 2, 2026День 2802. #Карьера #Юмор Секреты Программирования, Известные Только Легендам Ещё один пос…
  2. Oct 1, 2026Post #3357
  3. Sep 30, 2026Post #3356
  4. Sep 29, 2026Фото 3 (с) Анатолий Кулаков
  5. Sep 29, 2026День 2799. Конференция DotNext 2026. Часть 1 25 и 26 сентября в Москве прошла очередная ко…
  6. Sep 28, 2026День 2798. #Оффтоп Утиная Типизация в C# с Помощью Перехватчиков. Часть 2 Некоторое время…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →