Where to put the save/pre save methods in a domain object?
I want to enforce some rules every time a domain object is saved but i don't know the best way to achieve this. As, i see it, i have two options: add a save method to the domain object, or handle the rules before saving in the application layer. See code sample below:
using System;
namespace Test
{
public interface IEmployeeDAL
{
void Save(Employee employee);
Employee GetById(int id);
}
public class EmployeeDALStub : IEmployeeDA开发者_高级运维L
{
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.");
}
}
}
What do you do in this situation?
I would prefer the second approach where there's a clear separation between concerns. There's a class responsible for the DAL, there's another one responsible for validation and yet another one for orchestrating these.
In your first approach you inject the DAL and the validation into the business entity. Where I could argue if injecting a validator into the entity could be a good practice, injecting the DAL into the business entity is is definitely not a good practive IMHO (but I understand that this is only a demonstration and in a real project you would at least use a service locator for this).
If I had to choose, I'd choose the second option so that my entities were not associated to any DAL infrastructure and purely focused on the domain logic.
However, I don't really like either approach. I prefer taking more of an AOP approach to security & roles by adding attributes to my application service methods.
The other thing I'd change is moving away from the 'CRUD' mindset. You can provide much granular security options if you secure against specific commands/use cases. For example, I'd make it:
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));
}
Where you'd intercept the call to the first method and invoke the second method passing the thing that they must have in their role.
Here's a link on how to use unity for intercepting: http://litemedia.info/aop-in-net-with-unity-interception-model
Where to put the save/pre save methods in a domain object?
Domain objects are persistent-ignorant in DDD. They are unaware of the fact that sometimes they get 'frozen' transported to some storage and then restored. They do not notice that. In other words, domain objects are always in a 'valid' and savable state.
Permission should also be persistent-ignorant and based on domain and Ubiquitous Language, for example:
Only users from Sales group can add OrderLines to an Order in a Pending state
As opposed to:
Only users from Sales group can save Order.
The code can look like this:
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
}
}
精彩评论