Проверка модели на основе контекстного домена
В нашем приложении есть сценарий, в котором нам необходимо проверить обновление свойства на основе бизнес-правил и контекста текущего пользователя. Я пытаюсь определить лучший способ сделать проверку, потому что я думаю, что модель домена не должна знать о текущем пользователе. Наша обычная авторизация отделена от домена и отличается от этого сценария.
Где должна происходить эта проверка, и есть ли лучший способ справиться с ней? Должна ли модель домена знать о пользователе? Любая помощь или вклад приветствуется.
Простой пример: у нас есть заказ с утвержденным количеством. Только определенные типы пользователей могут обновлять количество только в определенных направлениях. Это правильный способ проверки в совокупности доменов?
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, но это только начало.