我目前正在处理的应用程序要求在数据库中对每个数据库进行审计:
例如:员工表有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
}
}
答案 0 :(得分:4)
我知道您希望对数据库中的所有对象进行审计跟踪,但我不会监视问题的完整复杂性。您Employee
和EmployeeAuditTable
的外观并不十分清楚,但所选的命名约定表明它包含与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中,您将EmployeeRepository
与IAuditService
紧密联系在一起。如果您绝对必须审核每个员工的变更,这可能会很好,而忘记这样做可能意味着可怕的后果。使用存储库的任何人(包括单元测试)如果您不想再使用某种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,都需要注意一个超级重要细节:确保它在与业务操作相同的数据库事务。这样,所有事务一起提交或回滚,您将不会有从未发生过的事件或缺少审计条目的事件的审计条目。