Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DDD Auditing? Domain Layer or Repository Layer?

The application I'm currently working on requires every change made to the database be audited in another table:

ex: Employee Table has EmployeeAuditTable

I've been debating on where I should put the auditing functionality! Following DDD, could anyone offer me their advice and opinion?

The options that come to mind to me are the following

  1. When a "Repository" calls save changes, I should perform the auditing logic from the repository (Is this bad design/practice to have the Repository not only persist changes but also persist auditing details as well? Is it even good practice to have a service being called from within a repository (IAuditService in this case))?

Example 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. Should I call IAuditService within my application services

Example 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      
   }
}
like image 398
user2309367 Avatar asked Dec 17 '13 22:12

user2309367


2 Answers

I understand that you want to have an audit trail for all objects in your database, but I don't oversee the full complexity of your problem. It is not quite clear what your Employee and your EmployeeAuditTable look like, but the naming convention chosen suggests that it contains the same columns as the employee table.

Auditing can be, and often is, considered a "cross cutting concern". This is especially the case when you have requirements like "all changes should be audited". If auditing is not a business concern (or use case or whatever you call it), you should NOT put it in your entities, services or repositories; if only for the reason that it is very, very easy to forget to code in the audit, leaving you with an incorrect audit trail - which some consider worse than no audit trail at all.

Form your code example, I see you are using a unit of work. I imagine that at

// commit transaction

you commit the changes tracked by the unit of work:

// commit transaction
_unitOfWork.Commit();

Again, it commits the changes tracked by the unit of work. There is your auditing hook point and it does not need to involve coding in any of your services, entities or repositories.

In fact, when you're using an ORM framework like (N)Hibernate, you can let the ORM track the changes for you (it will hook into its unit of work for this), see for instance the wiki page "creating an audit log using events" or the Envers auditing framework for NHibernate, also discussed in this answer on SO. I highly recommend reading the Envers docs, even if you implement your own auditing solution.

like image 76
Marijn Avatar answered Nov 01 '22 09:11

Marijn


I've faced similar design problems in my own work, and here is my current understanding.

The solution you choose should be based on your business rules/problem set.

In Example 1, you tightly couple the EmployeeRepository with the IAuditService. This can be good if you absolutely must audit every Employee change, and forgetting to do so could mean dire consequences. Anyone using the repository (including unit tests) needs to consciously opt-out of auditing if you don't want to audit anymore by using some sort of FakeAuditService.

In Example 2, you make the AuditService as a responsibility of the EmployeeService. By doing so, you will only place database access logic in EmployeeRepository and let the EmployeeService worry about auditing. This should make the implementation and usage of EmployeeRepository simpler, and you'll have more flexibility when using EmployeeRepository. However, if someone creates another service that depends on an IEmployeeRepository, they may forget to add the auditing logic to that service.

I personally prefer Example 2. Thinking about the single responsibility principle, the responsibility of EmployeeRepository should just be simple data access, and the EmployeeService should hold your business logic about employees. This business logic includes auditing changes.

like image 4
jsambuo Avatar answered Nov 01 '22 10:11

jsambuo