TG Telegram Group & Channel
.NET Разработчик | United States America (US)
Create: Update:

День 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

День 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


>>Click here to continue<<

.NET Разработчик




Share with your best friend
VIEW MORE

United States America Popular Telegram Group (US)