Validation and authorization in layered architecture

domain-driven-designlayersvalidation

I know you are thinking (or maybe yelling), "not another question asking where validation belongs in a layered architecture?!?" Well, yes, but hopefully this will be a little bit of a different take on the subject.

I am a firm believer that validation takes many forms, is context-based and varies at each level of the architecture. That is the basis for the post – helping to identify what type of validation should be performed in each layer. In addition, a question that often comes up is where authorization checks belong.

The example scenario comes from an application for a catering business. Periodically during the day, a driver may turn in to the office any excess cash they've accumulated while taking the truck from site to site. The application allows a user to record the 'cash drop' by collecting the driver's ID, and the amount. Here's some skeleton code to illustrate the layers involved:

public class CashDropApi  // This is in the Service Facade Layer
{
    [WebInvoke(Method = "POST")]
    public void AddCashDrop(NewCashDropContract contract)
    {
        // 1
        Service.AddCashDrop(contract.Amount, contract.DriverId);
    }
}

public class CashDropService  // This is the Application Service in the Domain Layer
{
    public void AddCashDrop(Decimal amount, Int32 driverId)
    {
        // 2
        CommandBus.Send(new AddCashDropCommand(amount, driverId));
    }
}

internal class AddCashDropCommand  // This is a command object in Domain Layer
{
    public AddCashDropCommand(Decimal amount, Int32 driverId)
    {
        // 3
        Amount = amount;
        DriverId = driverId;
    }

    public Decimal Amount { get; private set; }
    public Int32 DriverId { get; private set; }
}

internal class AddCashDropCommandHandler : IHandle<AddCashDropCommand>
{
    internal ICashDropFactory Factory { get; set; }       // Set by IoC container
    internal ICashDropRepository CashDrops { get; set; }  // Set by IoC container
    internal IEmployeeRepository Employees { get; set; }  // Set by IoC container

    public void Handle(AddCashDropCommand command)
    {
        // 4
        var driver = Employees.GetById(command.DriverId);
        // 5
        var authorizedBy = CurrentUser as Employee;
        // 6
        var cashDrop = Factory.CreateCashDrop(command.Amount, driver, authorizedBy);
        // 7
        CashDrops.Add(cashDrop);
    }
}

public class CashDropFactory
{
    public CashDrop CreateCashDrop(Decimal amount, Employee driver, Employee authorizedBy)
    {
        // 8
        return new CashDrop(amount, driver, authorizedBy, DateTime.Now);
    }
}

public class CashDrop  // The domain object (entity)
{
    public CashDrop(Decimal amount, Employee driver, Employee authorizedBy, DateTime at)
    {
        // 9
        ...
    }
}

public class CashDropRepository // The implementation is in the Data Access Layer
{
    public void Add(CashDrop item)
    {
        // 10
        ...
    }
}

I've indicated 10 locations where I've seen validation checks placed in code. My question is what checks you would, if any, be performing at each given the following business rules (along with standard checks for length, range, format, type, etc):

  1. The amount of the cash drop must be greater than zero.
  2. The cash drop must have a valid Driver.
  3. The current user must be authorized to add cash drops (current user is not the driver).

Please share your thoughts, how you have or would approach this scenario and the reasons for your choices.

Best Answer

I agree that what you are validating will be different in each layer of the application. I typically only validate what is required to execute the code in the current method. I try to treat the underlying components as black boxes and don't validate based on how those components are implemented.

So, as an example, in your CashDropApi class, I would only verify that 'contract' is not null. This prevents NullReferenceExceptions and is all that is needed to ensure this method executes properly.

I don't know that I'd validate anything in the service or command classes and the handler would only verify that 'command' is not null for the same reasons as in the CashDropApi class. I've seen (and done) validation both ways wrt to the factory and entity classes. One or the other is where you'd want to validate the value of 'amount' and that the other parameters are not null (your business rules).

The repository should only validate that the data contained in the object is consistent with the schema defined in your database and the daa operation will succeed. For example, if you have a column that can't be null or has a max length, etc.

As for the security check, I think it is really a question of intent. Since the rule is intended to prevent unauthorized access, I would want to make this check as early in the process as possible to reduce the number of unnecessary steps I've taken if the user is not authorized. I'd probably put it in the CashDropApi.