Здравствуйте, #John, Вы писали:
J>Здравствуйте, samius, Вы писали:
S>>Бизнес логики я в этом примере не увидел. Хотелось посмотреть на чуть более сложном примере, где взаимодействуют покупатель, корзина товаров, набор акций на отдельные группы товаров, накопительные бонусные программы или что-то в этом роде.
S>>Я не прошу приводить код, я его довольно ясно представляю, в том числе с тестами и обилием моков.
J>>>Если придется работать с внешним сервисом, придется создавать DomainService и дробить бизнес логику в rich-моделях.
S>>Проблема рич в том, что чем больше сценариев, тем больше придется дробить бизнес логику в рич моделях.
S>>Та же валидация внутри рич моделей говорит "ой", когда внезапно выясняется, что для одних процессов должна быть одна валидация, а для других — другая.
J>В DDD есть такие понятия как Entity, Value Object, Aggregate, Aggregate Root.
J>Entity/Value Object — это объекты. Концепция эквивалентности ссылок относится к Entity (User), в то время как структурая эквивалентность — к Value Object(Contact Information). швабра.
J>Aggregate: набор Entities или Value Objects, связанных друг с другом через объект Aggregate Root.
J>Aggregate Root: каждый агрегат имеет корень (в примере выше: User) и границу, агрегатный корень владеет агрегатом и служит шлюзом для всех модификаций внутри агрегата,
J>Бизнес логику желательно писать в самых конечных объектах (Value Objects).
Что? Бизнес логика в ContactInformation? ИмейлАдрес.Отправляйся(), Земля.Копайся() ???
J>В моделях валидацию можно рассматривать как инварианты. Проверка того что объекты всегда в правильном состоянии,
J>весь остальной код в моделях относится только к бизнес логике.
Объекты всегда в правильном состоянии для чего? Для какого процесса?
J>Валидация типа
J>J>if(repo.GetUserById(id) != null)
J>{
J> throw new NotFoundException();
J>}
J>
J>будет в Application Layer-e.
J>Бизнес логика — это все что говорит заказчик. Сначала определяется уникальный язык(термины) которые заказчик/менеджеры/бизнес аналитик/девы будут использовать чтобы понимать друг друга.
J>Потом договаривают что напр. "Хотим изменить `Info` и `email` у пользователя".
J>Т.к. это относится к сущности User, в моделе User создается метод `UpdateInformation(string info, string email)`, который является прокси методом к бизнес логике,
J>которая будет писаться во вложеных Entitites и Value Objects.
метод UpdateInformation вероятно будет и в анемике тоже. Ключевой поинт анемика лишь в том, нахрена этот метод нужен User-у? Не отменяя того, что User- агрегат для изменения его информации, можно записать
UpdateInformation(User user, string info, string email),
Не говоря уж о том, что можно сделать из этого целый workflow с подтверждением имейла и мобилы, не затрагивая код User-а.
J>Покупатель, корзина товаров, набор акций — это все будут разные агрегаты/агрегат руты.
J>Для взаимодействия агрегатов, что бы проще было расширять бизнес логику и меньше было локов при сохранении данных в бд, в DDD есть понятие как DomainEvents. msdn
J>Напр. у нас есть агрегат руты: User, Order, Product. Пользователь делает заказ.
J>В модель `Order` при создании будет передается 'userId' (Id агрегат рута пользователя) и кидается event создания заказа.
J>После мы можем добавить товар или изменить статус, при изменении статуса будет кидаться другой event.
Вот, DomainEvents — это такой мост между ричем и анемиком в моем понимании. Т.е. появляется обработчик — это что-то внешнее по отношению к модели данных.
J> | | код |
| | J>полный пример
J>
J>public class Order
J>{
J> // ..
J> public Order(string userId, string userName, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
J> string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) : this()
J> {
J> _buyerId = buyerId;
J> _paymentMethodId = paymentMethodId;
J> _orderStatusId = OrderStatus.Submitted.Id;
J> _orderDate = DateTime.UtcNow;
J> Address = address;
J> // Add the OrderStarterDomainEvent to the domain events collection
J> // to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ]
J> AddOrderStartedDomainEvent(userId, userName, cardTypeId, cardNumber,
J> cardSecurityNumber, cardHolderName, cardExpiration);
J> }
J> public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, int units = 1)
J> {
J> var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId)
J> .SingleOrDefault();
J> if (existingOrderForProduct != null)
J> {
J> //if previous line exist modify it with higher discount and units..
J> if (discount > existingOrderForProduct.GetCurrentDiscount())
J> {
J> existingOrderForProduct.SetNewDiscount(discount);
J> }
J> existingOrderForProduct.AddUnits(units);
J> }
J> else
J> {
J> //add validated new order item
J> var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
J> _orderItems.Add(orderItem);
J> }
J> }
J> // ...
J> public void SetPaidStatus()
J> {
J> if (_orderStatusId == OrderStatus.StockConfirmed.Id)
J> {
J> AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems));
J> _orderStatusId = OrderStatus.Paid.Id;
J> _description = "The payment was performed at a simulated \"American Bank checking bank account ending on XX35071\"";
J> }
J> }
J>}
J>
Но здесь обработчик лишь вызывает метод объекта, хотя мог бы иметь такой метод сам. Ну и какой смысл совать все такие методы в объекты?
|
| | |
J>В ApplicationLayer-e мы можем подписаться на все эти события и на каждое событие написать свой обработчик (DomainHandler). github
J>Из примеров проектов которые можно посмотреть:
J>https://github.com/aspnetboilerplate/aspnetboilerplate
J>https://github.com/zkavtaskin/Domain-Driven-Design-Example
J>https://github.com/dotnet-architecture/eShopOnContainers
J>и книга по ddd без воды: "Alexey Zimarev, Hands-On Domain-Driven Design with .NET Core — Tackling complexity in the heart of software by putting DDD principles into practice (2019)"
Эти примеры не отвечают на вопрос протаскивания других слоев через параметры методов?