【问题标题】:Where to put the save/pre save methods in a domain object?将保存/预保存方法放在域对象中的什么位置?
【发布时间】:2011-09-15 10:30:15
【问题描述】:

我想在每次保存域对象时强制执行一些规则,但我不知道实现此目的的最佳方法。正如我所看到的,我有两个选择:向域对象添加保存方法,或者在保存到应用层之前处理规则。请参阅下面的代码示例:

using System;

namespace Test
{

    public interface IEmployeeDAL
    {
        void Save(Employee employee);
        Employee GetById(int id);
    }

    public class EmployeeDALStub : IEmployeeDAL
    {
        public void Save(Employee employee)
        {

        }

        public Employee GetById(int id)
        {
            return new Employee();
        }
    }

    public interface IPermissionChecker
    {
        bool IsAllowedToSave(string user);
    }

    public class PermissionCheckerStub : IPermissionChecker
    {
        public bool IsAllowedToSave(string user)
        {
            return false;
        }
    }

    public class Employee
    {
        public virtual IEmployeeDAL EmployeeDAL { get; set; }
        public virtual IPermissionChecker PermissionChecker { get; set; }

        public int Id { get; set; }
        public string Name { get; set; }

        public void Save()
        {
            if (PermissionChecker.IsAllowedToSave("the user"))  // Should this be called within EmployeeDAL?
                EmployeeDAL.Save(this);
            else
                throw new Exception("User not permitted to save.");
        }
    }

    public class ApplicationLayerOption1
    {
        public virtual IEmployeeDAL EmployeeDAL { get; set; }
        public virtual IPermissionChecker PermissionChecker { get; set; }

        public ApplicationLayerOption1()
        {
            //set dependencies
            EmployeeDAL = new EmployeeDALStub();
            PermissionChecker = new PermissionCheckerStub();
        }

        public void UnitOfWork()
        {
            Employee employee = EmployeeDAL.GetById(1);

            //set employee dependencies (it doesn't seem correct to set these in the DAL);
            employee.EmployeeDAL = EmployeeDAL;
            employee.PermissionChecker = PermissionChecker;

            //do something with the employee object
            //.....

            employee.Save();
        }
    }

    public class ApplicationLayerOption2
    {
        public virtual IEmployeeDAL EmployeeDAL { get; set; }
        public virtual IPermissionChecker PermissionChecker { get; set; }

        public ApplicationLayerOption2()
        {
            //set dependencies
            EmployeeDAL = new EmployeeDALStub();
            PermissionChecker = new PermissionCheckerStub();
        }

        public void UnitOfWork()
        {
            Employee employee = EmployeeDAL.GetById(1);

            //do something with the employee object
            //.....

            SaveEmployee(employee);
        }

        public void SaveEmployee(Employee employee)
        {
            if (PermissionChecker.IsAllowedToSave("the user"))  // Should this be called within EmployeeDAL?
                EmployeeDAL.Save(employee);
            else
                throw new Exception("User not permitted to save.");
        }
    }
}

遇到这种情况你会怎么做?

【问题讨论】:

    标签: oop object domain-driven-design


    【解决方案1】:

    我更喜欢第二种方法,其中关注点之间有明确的分离。有一个类负责 DAL,还有一个类负责验证,还有一个类负责编排这些。

    在您的第一种方法中,您将 DAL 和验证注入到业务实体中。我可以争论将验证器注入实体是否是一种好的做法,将 DAL 注入业务实体绝对不是一个好的实践恕我直言(但我知道这只是一个演示,在一个真实的项目中你会至少为此使用服务定位器)。

    【讨论】:

      【解决方案2】:

      如果必须选择,我会选择第二个选项,这样我的实体就不会与任何 DAL 基础架构相关联,而是完全专注于域逻辑。

      但是,我不太喜欢这两种方法。我更喜欢通过向我的应用程序服务方法添加属性来对安全性和角色采取更多 AOP 方法。

      我要改变的另一件事是摆脱“CRUD”的心态。如果您针对特定命令/用例进行保护,则可以提供更精细的安全选项。例如,我会做到:

      public class MyApplicationService
      {
          [RequiredCommand(EmployeeCommandNames.MakeEmployeeRedundant)]
          public MakeEmployeeRedundant(MakeEmployeeRedundantCommand command)
          {
              using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
              {
                  Employee employee = _employeeRepository.GetById(command.EmployeeId);
      
                  employee.MakeRedundant();
      
                  _employeeRepository.Save();
              }
          }
      }
      
      public void AssertUserHasCorrectPermission(string requiredCommandName)
      {
          if (!Thread.CurrentPrincipal.IsInRole(requiredCommandName))
              throw new SecurityException(string.Format("User does not have {0} command in their role", requiredCommandName));
      }
      

      您将拦截对第一个方法的调用并调用第二个方法,传递他们在其角色中必须拥有的东西。

      这里有一个关于如何使用unity进行拦截的链接:http://litemedia.info/aop-in-net-with-unity-interception-model

      【讨论】:

      • 所以如果我想做一些类似的事情,'IssueP45'。我会把这个放在哪里?在 employee.MakeRedundant() 方法中或作为 MyApplicationService 中的单独命令。另外,如果允许用户调用 MakeEmployeeRedundant 但不允许调用 IssueP45,会发生什么情况?
      • 我不确定如何回答这些问题,因为它们是针对您的域的。但这取决于发布 p45 是否完全是 MakeEmployeeRedundant 用例的一部分(即,它可以单独完成吗?)。如果它是独占的,那么开发人员创建单独的权限来发布 p45 会很愚蠢吗?我想发布 P45 将成为 MakeEmployeeRedundant、AcceptEmployeeResignation、FireEmloyee 等几个用例的一部分,每个用例都有与之关联的权限。
      • 好吧,这是有道理的。但是向员工添加方法有什么意义,因为 MakeRedundant 可能有不同的实现。所以我会创建一个 IMakeRedundant 接口,然后使用属性将它注入到员工类中。然后,employee.MakeRedundant 方法将调用该接口。或者我应该只调用 MyApplicationClass 中的 IMakeRedundant.MakeRedundant(employee) 吗?我想我要问的是依赖类的最佳位置在哪里?
      • 我对你在那里所说的话有点困惑,也许认为你误解了我的例子。我的示例只显示了一个将由客户端/UI 调用的应用服务,并调用域模型上的逻辑。而已。我只是建议您使用属性为您的应用服务方法添加权限。
      【解决方案3】:

      将保存/预保存方法放在域对象中的什么位置?

      域对象在 DDD 中是持久无知的。他们不知道有时他们会被“冷冻”运输到一些仓库然后恢复。他们没有注意到这一点。换言之,域对象始终处于“有效”和可保存状态。

      权限也应该是持久无知的,并且基于域和通用语言,例如:

      只有 Sales 组的用户可以将 OrderLines 添加到订单中 待处理状态

      相对于:

      只有销售组的用户才能保存订单

      代码可能如下所示:

      internal class MyApplication {
      
          private IUserContext _userContext;
          private ICanCheckPermissions _permissionChecker;
      
          public void AddOrderLine(Product p, int quantity, Money price, ...) {
      
           if(!_permissionChecker.IsAllowedToAddOrderLines(_userContext.CurrentUser)) {
               throw new InvalidOperationException(
                  "User X is not allowed to add order lines to an existing order");
           }
      
           // add order lines
      
          }
      }
      

      【讨论】:

      • Order 上会有一个名为 AddProduct(Product p....) 的方法吗?或者应用程序服务类中是否会有一个名为 AddProductToOrder(Order o,Product p...) 的方法调用?规则的最佳位置在哪里?按顺序还是应用服务类?
      猜你喜欢
      • 2017-09-23
      • 1970-01-01
      • 2015-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多