DDD审计?域层或存储库层?

时间:2013-12-17 22:43:30

标签: design-patterns domain-driven-design repository-pattern

我目前正在处理的应用程序要求在数据库中对每个数据库进行审计:

例如:员工表有EmployeeAuditTable

我一直在讨论我应该把审计功能放在哪里!在DDD之后,任何人都可以向我提供他们的建议和意见。

我想到的选项如下 1.)当“存储库”调用保存更改时,我应该从存储库执行审计逻辑(这个糟糕的设计/实践是否使存储库不仅保持更改而且还保持审计细节?这是否是良好的做法从存储库中调用服务(在本例中为IAuditService))

示例1:

public class EmployeeRepository : IRepository
{
    DbContext _db;
    IAuditService _auditService;

    EmployeeRepository(IAuditService auditService)
    {
      _auditService = auditService
    }
    void Update(Employee empl)
    {
        // Update Employee with dbcontext entity framework

        // Perform Audit using AuditService
    }

   void SaveChanges()
   {
      // Call save changes on dbcontext
   }

}

2.。)我应该在我的应用程序服务中调用IAuditService

示例2:

public class EmployeeService
{
   IAuditService _auditService;
   IUnitOfWork   _unitOfWork;
   IEmployeeRepository _repository;

   EmployeeService(IAuditService auditService, IUnitOfWork  unitOfWork, IEmployeeRepository repo)
   {
       _auditService = auditService;
       _unitOfWork= unitOfWork;
       _repo  = repo;
   }

   void UpdateEmployee(int id, string name, int age)
   {
      // Get Employee

      // Update Employee

      // Audit Changes

      // Commit Transaction

   }


}

3 个答案:

答案 0 :(得分:4)

我知道您希望对数据库中的所有对象进行审计跟踪,但我不会监视问题的完整复杂性。您EmployeeEmployeeAuditTable的外观并不十分清楚,但所选的命名约定表明它包含与employee表相同的列。

审计可能并且经常被视为“跨领域的关注”。当您有“应审核所有更改”等要求时尤其如此。如果审计不是业务问题(或用例或您称之为的任何内容),则 NOT 将其放入您的实体,服务或存储库中;如果只是因为在审计中忘记编码是非常非常容易的,那么你会留下一个不正确的审计线索 - 有些人认为这比审计线索更糟糕。

形成您的代码示例,我看到您正在使用一个工作单元。

我想象的那样
// commit transaction

您提交工作单元跟踪的更改:

// commit transaction
_unitOfWork.Commit();

同样,提交工作单元跟踪的更改。有您的审计挂钩点,它不需要涉及任何服务,实体或存储库的编码。

事实上,当你使用像(N)Hibernate这样的ORM框架时,你可以让ORM为你跟踪更改(它会挂钩到它的工作单元),例如参见{{3} }或the wiki page "creating an audit log using events" NHibernate的审核框架,也在Envers中讨论过。我强烈建议您阅读Envers文档,即使您实施自己的审核解决方案。

答案 1 :(得分:3)

我在自己的工作中遇到过类似的设计问题,这是我目前的理解。

您选择的解决方案应基于您的业务规则/问题集。

在示例1中,您将EmployeeRepositoryIAuditService紧密联系在一起。如果您绝对必须审核每个员工的变更,这可能会很好,而忘记这样做可能意味着可怕的后果。使用存储库的任何人(包括单元测试)如果您不想再使用某种FakeAuditService进行审核,则需要有意识地选择退出审核。

在示例2中,您将AuditService作为EmployeeService的责任。通过这样做,您只需在EmployeeRepository中放置数据库访问逻辑,并让EmployeeService担心审计。这应该使EmployeeRepository的实现和使用更简单,并且在使用EmployeeRepository时您将具有更大的灵活性。但是,如果某人创建了依赖于IEmployeeRepository的其他服务,他们可能会忘记将审核逻辑添加到该服务。

我个人更喜欢示例2.考虑单一责任原则,EmployeeRepository的责任应该是简单的数据访问,而EmployeeService应该保留关于员工的业务逻辑。此业务逻辑包括审计更改。

答案 2 :(得分:0)

public class EmployeeRepository : IRepository
{
    DbContext _db;
    IAuditService _auditService;

    EmployeeRepository(IAuditService auditService)
    {
      _auditService = auditService
    }

通过让DAO使用AuditService,我不会在DAO和服务包之间创建循环依赖关系。我猜AuditService依赖于AuditEventRepository或类似的东西?软件包之间的循环依赖关系(例如SomeRepository => AuditService => AuditRepository)使得将来很难重构。

我对您的问题域一无所知,但如果您正在考虑创建一个单独的AuditService,那么问题域可能有自己的审计业务规则吗?例如,按用户类型审核事件,自动填充相关的上下文信息等。如果是这种情况,我会坚持使用封装逻辑的单独服务。

您是否考虑过面向方面的方法,而不是修改每个DAO或服务类?基于AOP的方法将减少您需要编写的代码量。它还可以轻松地从拦截服务呼叫切换到将来拦截DAO呼叫。

无论您在何处放置审核代码,无论您是在每个服务中手动调用审核还是使用AOP,都需要注意一个超级重要细节:确保它在与业务操作相同的数据库事务。这样,所有事务一起提交或回滚,您将不会有从未发生过的事件或缺少审计条目的事件的审计条目。