Проверка модели на основе контекстного домена

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

Где должна происходить эта проверка, и есть ли лучший способ справиться с ней? Должна ли модель домена знать о пользователе? Любая помощь или вклад приветствуется.

Простой пример: у нас есть заказ с утвержденным количеством. Только определенные типы пользователей могут обновлять количество только в определенных направлениях. Это правильный способ проверки в совокупности доменов?

public enum UserType
{
    ViewUserType,
    RequesterUserType,
    SupplierUserType
}

public class Order
{
    public int OrderId {get; private set}
    public int RequestedQuantity {get; private set}
    public int ApprovedQuantity {get; private set}

    public void RequestQuantity(int quantity, UserType userType)
    {
        if (userType == UserType.RequesterUserType)
        {
            this.RequestedQuantity = quantity;
        }
    }

    // Question: The direction that the approved quantity can change is a business rule
    // but directly deals with the context of the user.  Should the model know about the user
    // or should this validation be pulled out to either the application service, a model extension,
    // or maybe a specification?
    public void ApproveQuantity(int quantity, UserType userType)
    {
        if (userType == UserType.RequesterUserType)
        {
            if (quantity <= this.ApprovedQuantity)
            {
                // Requester type user can only update if lowering the approved quantity
                this.ApprovedQuantity = quantity;
            }
        }
        else if(userType == UserType.SupplierUserType)
        {
            if (quantity >= this.ApprovedQuantity)
            {
                // Supplier type user can only update if increasing the approved quantity
                this.ApprovedQuantity = quantity;
            }
        }
    }
}

2 ответа

Решение

Это слегка вдохновлено ответом Ива и вашими ответами на него.

Моя личная мантра состоит в том, чтобы сделать неявные вещи явными, так как мне нравится, как получается код после применения этого принципа:

public interface IProvideCurrentIdentityRoles
{
   bool CanRequestQuantity()
   bool CanApproveQuantity();
   bool CanOverruleQuantityOnSubmittedOrder();
   bool CanIncreaseQuantityOnFinalOrder();
   bool CanDecreaseQuantityOnFinalOrder();
}

public class Order
{
    public int OrderId {get; private set}
    public int RequestedQuantity {get; private set}
    public int ApprovedQuantity {get; private set}

    public void RequestQuantity(int quantity, IProvideCurrentIdentityRoles requester)
    {
        Guard.That(requester.CanRequestQuantity());
        this.RequestedQuantity = quantity;
    }

    public void ApproveQuantity(int quantity, IProvideCurrentIdentityRoles  approver)
    {
        if (quantity == this.RequestedQuantity)
        {
           Guard.That(approver.CanApproveQuantity());
        }
        else 
        {
           if (orderType == OrderType.Submitted)
           {
              Guard.That(approver.CanOverruleQuantityOnSubmittedOrder());
           }
           else if (orderType == OrderType.Final)
           {
              if (quantity > this.ApprovedQuantity)
              {
                 Guard.That(approver.CanIncreaseQuantityOnFinalOrder());
              }
              else 
              {
                 Guard.That(approver.CanDecreaseQuantityOnFinalOrder());
              }
           }
        }
        this.ApprovedQuantity = quantity;
     }
}

Вместо того, чтобы иметь этот enum-подобный тип (UserType), почему бы не превратить эти РОЛИ в полноценные объекты? Вас интересует роль, которую играет пользователь, а не конкретный пользователь. Это подталкивает аутентификацию и проверку того, что пользователь действительно является ПОСТАВЩИКОМ или ЗАПРОСОМ на вышеприведенный уровень (ну, собственно, вызывающий код, в данном случае, вероятно, какая-то прикладная служба). Ниже очень грубая, первая итерация того, как это может выглядеть:

public class Order {
  public void RequestQuantity(int quantity, UserType userType)
  {
    this.RequestedQuantity = quantity;
  }

  public void ApproveToLowerOrEqualQuantity(int quantity) {
    if (quantity <= this.ApprovedQuantity)
    {
      // Requester type user can only update if lowering the approved quantity
      this.ApprovedQuantity = quantity;
    }
  }

  public void ApproveToHigherOrEqualtQuantity(int quantity) {
    if (quantity >= this.ApprovedQuantity)
    {
      // Supplier type user can only update if increasing the approved quantity
      this.ApprovedQuantity = quantity;
    }
  }
}

//Calling code
public class ApplicationServiceOfSomeSort {
   public void RequestQuantity(UserId userId, OrderId orderId, int quantity) {
     var requester = requesterRepository.FromUser(userId);
     requester.MustBeAbleToRequestQuantity();

     var order = orderRepository.GetById(orderId);
     order.RequestQuantity(quantity);
   }

   public void ApproveQuantityAsRequester(UserId userId, OrderId orderId, int quantity) {
     var requester = requesterRepository.FromUser(userId);
     requester.MustBeAbleToApproveQuantity();

     var order = orderRepository.GetById(orderId);
     order.ApproveToLowerOrEqualQuantity(quantity);
   }

   public void ApproveQuantityAsSupplier(UserId userId, OrderId orderId, int quantity) {
     var supplier = supplierRepository.FromUser(userId);
     supplier.MustBeAbleToApproveQuantity();

     var order = orderRepository.GetById(orderId);
     order.ApproveToHigherOrEqualQuantity(quantity);
   }
}

Конечно, есть еще много "плохого запаха", окружающего этот API, но это только начало.

Другие вопросы по тегам