DDD审计?领域层还是仓储层?

6
我目前正在开发的应用程序需要在另一个表中记录数据库所做的每一个更改:
例如:员工表有一个员工审核表。
我一直在考虑应该把审计功能放在哪里!按照DDD,有人能给我提供他们的建议和意见吗?
以下是我考虑到的选项:
1.当“存储库”调用保存更改时,我应该从存储库执行审计逻辑(将Repository负责持久化更改以及持久化审计细节是否是不良设计/实践?将服务从存储库内部调用(在这种情况下为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
   }
}
  1. 我是否应该在我的应用服务中调用 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个回答

5
我知道您想要为数据库中所有对象设置审计追踪,但我并不完全了解您的问题的复杂性。不是很清楚您的EmployeeEmployeeAuditTable是什么样子的,但所选择的命名约定表明它包含与员工表相同的列。
审计可以被认为是“横切关注点”,尤其是当您有“所有更改都应进行审计”之类的要求时。如果审计不是业务关注点(或用例或任何您称之为的东西),则不应将其放在实体、服务或存储库中;即使只是因为非常容易忘记编写审计,从而给您留下不正确的审计追踪记录——有些人认为这比没有审计追踪记录更糟糕。
从您的代码示例中,我看到您正在使用一个工作单元。我想象在...
// commit transaction

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

// commit transaction
_unitOfWork.Commit();

再次强调,它提交了工作单元跟踪的更改。这是您的审计钩子点,不需要涉及任何服务、实体或存储库的编码。

事实上,当您使用像(N)Hibernate这样的ORM框架时,可以让ORM替您跟踪更改(它将挂钩到其工作单元),请参阅例如wiki页面“使用事件创建审计日志”Envers审核框架用于NHibernate,还在此SO答案中进行了讨论。我强烈建议阅读Envers文档,即使您实现自己的审计解决方案。


4
我在自己的工作中也遇到过类似的设计问题,以下是我的一些了解。
您选择的解决方案应基于您的业务规则/问题集。
在示例1中,您将 EmployeeRepositoryIAuditService 紧密耦合。如果您绝对必须审核每个员工更改,并且忘记这样做可能会导致严重后果,那么这样做可能是有益的。使用存储库的任何人(包括单元测试)都需要有意识地选择退出审核,如果您不再想进行审核,则需要使用某种 FakeAuditService
在示例2中,您将 AuditService 作为 EmployeeService 的职责。这样做只会将数据库访问逻辑放置在 EmployeeRepository 中,让 EmployeeService 担心审核。这应该使实现和使用 EmployeeRepository 更简单,并且在使用 EmployeeRepository 时会更加灵活。但是,如果有人创建了另一个依赖于 IEmployeeRepository 的服务,则他们可能会忘记向该服务添加审核逻辑。
我个人更喜欢示例2。考虑到单一职责原则,EmployeeRepository 的职责应该只是简单的数据访问,而 EmployeeService 应该包含有关员工的业务逻辑。这个业务逻辑包括审核更改。

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,都有一个非常重要的细节需要注意:确保它发生在与业务操作相同的数据库事务中。这样,所有内容都会一起提交或回滚,你就不会发现不存在的审计条目或缺少审计条目的事件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接