Re[12]: DDD протаскивание других слоев через параметры методов Domain
От: #John Европа https://github.com/ichensky
Дата: 26.11.20 22:10
Оценка:
Здравствуйте, samius, Вы писали:

S>Бизнес логики я в этом примере не увидел. Хотелось посмотреть на чуть более сложном примере, где взаимодействуют покупатель, корзина товаров, набор акций на отдельные группы товаров, накопительные бонусные программы или что-то в этом роде.

S>Я не прошу приводить код, я его довольно ясно представляю, в том числе с тестами и обилием моков.

J>>Если придется работать с внешним сервисом, придется создавать DomainService и дробить бизнес логику в rich-моделях.

S>Проблема рич в том, что чем больше сценариев, тем больше придется дробить бизнес логику в рич моделях.
S>Та же валидация внутри рич моделей говорит "ой", когда внезапно выясняется, что для одних процессов должна быть одна валидация, а для других — другая.

В DDD есть такие понятия как Entity, Value Object, Aggregate, Aggregate Root.
Entity/Value Object — это объекты. Концепция эквивалентности ссылок относится к Entity (User), в то время как структурая эквивалентность — к Value Object(Contact Information). швабра.
Aggregate: набор Entities или Value Objects, связанных друг с другом через объект Aggregate Root.
Aggregate Root: каждый агрегат имеет корень (в примере выше: User) и границу, агрегатный корень владеет агрегатом и служит шлюзом для всех модификаций внутри агрегата,
т.е. из Application Layer нельзя написать код:
var contactInformation = new ContactInformation(...);
repo.Save(contactInformaion);

Все изменения нужно делать только через агрегат рут(User):
var user = userRepository.Load(userId);
user.UpdateInformation(userInformation.Info, userInformation.Email);
userRepository.Save(user);


Бизнес логику желательно писать в самых конечных объектах (Value Objects).
В моделях валидацию можно рассматривать как инварианты. Проверка того что объекты всегда в правильном состоянии,
весь остальной код в моделях относится только к бизнес логике.
Валидация типа
if(repo.GetUserById(id) != null)
{
    throw new NotFoundException(); 
}

будет в Application Layer-e.

Бизнес логика — это все что говорит заказчик. Сначала определяется уникальный язык(термины) которые заказчик/менеджеры/бизнес аналитик/девы будут использовать чтобы понимать друг друга.
Потом договаривают что напр. "Хотим изменить `Info` и `email` у пользователя".
Т.к. это относится к сущности User, в моделе User создается метод `UpdateInformation(string info, string email)`, который является прокси методом к бизнес логике,
которая будет писаться во вложеных Entitites и Value Objects.

--

Покупатель, корзина товаров, набор акций — это все будут разные агрегаты/агрегат руты.
Для взаимодействия агрегатов, что бы проще было расширять бизнес логику и меньше было локов при сохранении данных в бд, в DDD есть понятие как DomainEvents. msdn
Напр. у нас есть агрегат руты: User, Order, Product. Пользователь делает заказ.

В модель `Order` при создании будет передается 'userId' (Id агрегат рута пользователя) и кидается event создания заказа.
После мы можем добавить товар или изменить статус, при изменении статуса будет кидаться другой event.
  код
полный пример


public class Order
{
  // ..

  public Order(string userId, string userName, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
                string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) : this()
        {
            _buyerId = buyerId;
            _paymentMethodId = paymentMethodId;
            _orderStatusId = OrderStatus.Submitted.Id;
            _orderDate = DateTime.UtcNow;
            Address = address;

            // Add the OrderStarterDomainEvent to the domain events collection 
            // to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ]
            AddOrderStartedDomainEvent(userId, userName, cardTypeId, cardNumber,
                                       cardSecurityNumber, cardHolderName, cardExpiration);
        }

  public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, int units = 1)
        {
            var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId)
                .SingleOrDefault();

            if (existingOrderForProduct != null)
            {
                //if previous line exist modify it with higher discount  and units..

                if (discount > existingOrderForProduct.GetCurrentDiscount())
                {
                    existingOrderForProduct.SetNewDiscount(discount);
                }

                existingOrderForProduct.AddUnits(units);
            }
            else
            {
                //add validated new order item

                var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
                _orderItems.Add(orderItem);
            }
        }

    // ...

     public void SetPaidStatus()
        {
            if (_orderStatusId == OrderStatus.StockConfirmed.Id)
            {
                AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems));

                _orderStatusId = OrderStatus.Paid.Id;
                _description = "The payment was performed at a simulated \"American Bank checking bank account ending on XX35071\"";
            }
        }
}

В ApplicationLayer-e мы можем подписаться на все эти события и на каждое событие написать свой обработчик (DomainHandler). github


Из примеров проектов которые можно посмотреть:
https://github.com/aspnetboilerplate/aspnetboilerplate
https://github.com/zkavtaskin/Domain-Driven-Design-Example
https://github.com/dotnet-architecture/eShopOnContainers

и книга по 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)"
Підтримати Україну у боротьбі з країною-терористом.

https://prytulafoundation.org/
https://u24.gov.ua/

Слава Збройним Силам України!!! Героям слава!!!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.