13

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.

6
  • SE isn't exactly the right platform to "foster a theoretical and subjective discussion". Voting to close.
    – tdammers
    Apr 20, 2012 at 9:59
  • Poorly worded statement. I'm really looking for best practices. Apr 20, 2012 at 12:25
  • 2
    @tdammers - Yes it is the right place. At least it wants to be. From the FAQ: 'Subjective questions are allowed.' That's why they made this site instead of Stack Overflow. Don't be a Close Nazi. If the question sucks, it will fade into obscurity.
    – FastAl
    Apr 20, 2012 at 12:54
  • @FastAI: It's not so much the 'subjective' part, but rather the 'discussion' that bothers me.
    – tdammers
    Apr 21, 2012 at 10:32
  • I think that you could leverage value objects here by having a CashDropAmount value object rather than using a Decimal. Checking whether the driver exists or not would be done in the command handler and the same goes for authorization rules. You could get authorization for free by doing something like Approver approver = approverService.findById(employeeId) where it throws if the employee in not in the approver role. Approver would just be a value object, not an entity. You could also get rid of your factory or use factory method on an AR instead: cashDrop = driver.dropCash(...).
    – plalx
    Aug 2, 2015 at 21:06

3 Answers 3

2

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.

1

Your first business rule

The amount of the cash drop must be greater than zero.

looks like an invariant of your CashDrop entity and of your AddCashDropCommand class. There are a couple of ways that I enforce an invariant like this:

  1. Take the Design By Contract route and use Code Contracts with a combination of Preconditions, Postconditions and a [ContractInvariantMethod] depending on your case.
  2. Write explicit code in the constructor / setters that throws an ArgumentException if you pass in an amount that is less than 0.

Your second rule is broader in nature (in light of the details in the question): does valid mean that the Driver entity has a flag indicating that they can drive (i.e. not had their driving license suspended), does it mean that the driver was actually working that day or does it mean simply that the driverId, passed in to the CashDropApi, is valid in the persistence store.

In any of these cases you will need to navigate your domain model and get the Driver instance from your IEmployeeRepository, as you do in location 4 in your code example. So, here you need to make sure that the call to the repository does not return null, in which case your driverId was not valid and you can not proceed with the processing any further.

For the other 2 (my hypothetical) checks (does the driver have a valid driving license, was the driver working today) you are executing business rules.

What I tend to do here is use a collection of validator classes that operate on entities (just like the specification pattern from Eric Evans book - Domain Driven Design). I have used FluentValidation to build these rules and validators. I can then compose (and therefore reuse) more complex / more complete rules from simpler rules. And I can decide which layers in my architecture to run them. But I have them all encoded in one place, not scattered across the system.

Your third rule relates to a cross-cutting concern: authorization. Since you are already using an IoC container (assuming that your IoC container supports method interception) you can do some AOP. Write an apsect that does the authorization and you can use your IoC container to inject this authorization behavior where it needs to be. The big win here is that you have written the logic once, but you can reuse it across your system.

To use interception via a dynamic proxy (Castle Windsor, Spring.NET, Ninject 3.0, etc) your target class needs to implement an interface or inherit from a base class. You would intercept before the call to the target method, check the authorization of the user and prevent the call from proceeding to the actual method (throw an excption, log, return a value indicating failure, or something else) if the user does not have the right roles to perform the operation.

In your case you could intercept the call to either

CashDropService.AddCashDrop(...) 

AddCashDropCommandHandler.Handle(...)

Problems here maybe that CashDropService can not be intercepted because there is no interface / base class. Or AddCashDropCommandHandler is not being created by your IoC, therefore your IoC can not create a dynamic proxy to intercept the call. Spring.NET has a useful feature where you can target a method on a class in an assembly via a regex, so this may work.

Hope this gives you some ideas.

4
  • Can you explain how I would "use your IoC container to inject this authorization behavior where it needs to be"? This sounds appealing but getting AOP and IoC to work together escapes me so far. Jun 29, 2012 at 20:12
  • As for the rest, I agree with placing validation in the constructor and/or setters to prevent the object from entering an invalid state (handling invariants). But beyond that and a reference to the null check after going to the IEmployeeRepository to locate the driver, you don't provide any details where you would perform the rest of the validation. Given the use of FluentValidation and the reuse, etc it provides, where would you apply the rules in the given model? Jun 29, 2012 at 20:16
  • I have edited my answer - see if this helps. As for " where would you apply the rules in the given model?"; probably around 4, 5, 6, 7 in your command handler. You have access to the repositories that can yield the information you need to perform business level validation. But I think there are others that would disagree with me here.
    – RobertMS
    Jul 2, 2012 at 14:25
  • To clarify, all of the dependencies are being injected. I left that off to keep the reference code brief. My inquiry has more to do with having a dependency within the aspect since aspects are not injected via the container. So, how does the AuthorizationAspect get a reference to the AuthorizationService, for instance? Jul 3, 2012 at 17:13
1

For the rules:

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).

I would do validation in location (1) for business rule (1) and make sure the Id is not null or negative (assuming zero is valid) as pre-check for rule (2). The reasons is my rule of "Don't cross a layer boundary with wrong data that you can check with available information". An exception to this would be if the service does the validation as part of its duty to other callers. In which case, it will be sufficient to have the validation only there.

For rules (2) and (3), this must be done at the database access layer (or db layer itself) only since it involves db access. No need to travel between layers intentionally.

In particular rule (3) can be avoided if we let the GUI prevent unauthorized users from pushing the button enabling this scenario. While this is harder to code, it is better.

Good question!

3
  • +1 for authorization - putting it in the UI is an alternative that I did not mention in my answer.
    – RobertMS
    Jul 3, 2012 at 18:07
  • 1
    While having authorization checks in the UI does provide a more interactive experience for the user, I am developing a service-based API and cannot make any assumptions about what rules the caller has or has not implemented. It is because so many of these checks can be easily delegated to the UI that I chose to use the API project as the basis for the post. I'm looking for best practices rather than textbook quick-and-easy. Jul 4, 2012 at 1:29
  • @SonOfPirate, INMO, the UI needs to do validations because it is faster and it has more data than the service (in some cases). Now the service should not send data outside its boundary without doing its own validations since this is part of its responsibilities as long as you want the service to not trust the client. Accordingly, I suggest that non-db checks be made in the service (again) prior to sending data to the database for further processing.
    – NoChance
    Jul 4, 2012 at 2:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.