Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Domain validation in layered architecture

4.67/5 (4 votes)
31 May 2015CPOL7 min read 20.3K   648  
An approach in domain validation in layerd architecture

Introduction

With many types of architecture available, validation is not a simple topic. There’s no standard guidelines to how, what, and where to validate. In this article, I try my best to summarize my approach to business validation in a layered application. 

Scope

The code example going with this article is given as an ASP.NET MVC web application, although I do think majority of the code should work well in all types of application with some minor adjustment.

Background

To follow the article, I assume you have some basic understanding of layered architecture, common layers in a typical web application. Some knowledge on Domain Driven Design is good. Although nothing in this article is particularly specific to Domain Driven Design, I do reference some articles on validation used in Domain Driven Design.

System Architecture

Here's a common layered architecture that I commonly use in my applications:

Image 1

The diagram not only shows conceptual architecture but also shows actual dependency between different projects (.i.e. how projects reference each other in the solution)

And here's the same architecture layout in the demo solution:

Image 2

The solution consists of 6 projects:

  • DomainValidation.UI.Web: Presentation layer, ASP.NET MVC 5
  • DomainValidation.Service.Contracts: DTO, messages, as well as application service interfaces
  • DomainValidation.Service: Implementation of application service layer
  • DomainValidation.Model: business models, repository interfaces
  • DomainValidation.Repository.Fake: In-memory implementation of repositories
  • DomainValidation.Infrastructure.DependencyResolution: Inversion of Control container (Autofac) registration and setup.

Some reasons why the projects are organized this way:

  • The system follows layered architecture commonly found in Domain Driven Design: UI -> Application Service -> Domain Service + Model -> Repository
  • Ideally, application service layer (Service project) and layers below it can be reused by different types of UI projects (Web, Desktop, Mobile). The UI project is free to use any presentation pattern that is suitable (MVC, MVP, MVVM, etc). Application service layer acts as façade that hides the complexity of below layers from UI layer. As in the diagram, Web project only knows about service contracts project and dependency resolution project. Other projects are loosely coupled, and only known at runtime.
  • As in DDD, Model project is the core of the application. It has no dependency to but be depended on by other projects. This should contain rich business domain logic in real application.
  • UI is not dependent on Model. It only depends on DTO and message contracts from service layer (I use the term contract in a general way here, it has no implication of WCF service contracts)
  • Since the project focuses on validation and architecture, I just implement a simple in-memory implementation of repositories, but that implementation can be easily replaced with Entity Framework/NHibernate/ADO.NET implementation. The only dependency it has is to Model project.
  • Since composition root is at Web layer, the Web project can reference all other
  • The system follows dependency inversion principle, with the composition root is at Web project.

Places to perform validation

With reference from the architecture diagram above, there are several places that validation can happen:

UI: your UI-specific validation. E.g. Standard web application will typically have javascript validation before posting to server. ASP.NET MVC application will have model state validation in addition to javascript validation, etc. The validation here just simply verify whether a property is missing, maximum length of a string, etc, and will depend on the technology used in your UI. It should not contain business validation that involves different business rules, database calls. Although in sometimes, for better user experience, some applications duplicate business logic validation in this layer, however it should not replace actual business logic validation in lower layers.

Application Service: In my opinion, this is the best place to initiate business-related validation for several reasons:

  • Validation logic put here can be shared by different UI clients, e.g. web UI, mobile UI.
  • If your application uses dependency injection, the validation can fit nicely with dependency injection structure here.
  • Validation can be done per operation and before any actual update happens.
  • Notice I use the word "initiate" here. The business logic validation can be initiated in this layer but it doesn't mean all the validation have to be put in this layer. Due to my approach in structuring validation logic, it's not possible to push all the business-related validation to Core layer where all the business logic should be, but I believe we should try to push as much common business logic validation to Core layer. This can be done by moving common validation logic into Specification class (Specification pattern) in Core layer.

Validation structure

The application uses an operation oriented validation approach. The article "Validation in DDD world" by Jimmy Bogard [1] below is the one that gives me this idea. For a validation for an operation to make sense, it needs to know the context of the operation. E.g. for the same Product entity, validation for Update operation and for Delete operation can be completely different from each other.

In the demo solution, I use Fluent Validation library (https://fluentvalidation.codeplex.com/) together with Autofac for dependency injection. Structure of validation classes are illustrated below:

Image 3

Among these, IValidatorFactory, ValidatorFactorybase, IValidator, AbstractValidator are provided by FluentValidation library.

AutofacValidatorFactory is our custom validator factory class to use Autofac to resolve instances of validators classes. This class just needs to be setup once and registered to Autofac IoC container and is very simple:

C#
public class AutofacValidatorFactory : ValidatorFactoryBase
{
    private readonly IComponentContext _context;

    public AutofacValidatorFactory(IComponentContext context)
    {
        _context = context;
    }

    public override IValidator CreateInstance(Type validatorType)
    {
        return _context.Resolve(validatorType) as IValidator;
    }
}

So for each new validation scenario, we just need to write a validator class to put actual validation logic for that scenario, .e.g. for Delete Product operation, here's our validator class:

C#
public class DeleteProductValidator : AbstractValidator<DeleteProductRequest>
{
    public DeleteProductValidator(IProductRepository productRepository)
    {
        Custom(p =>
        {
            var productFromDb = productRepository.Find(p.Id);
            return productFromDb.Price > 200
                ? new ValidationFailure("Price", "Cannot delete product with price greater than 200")
                : null;
        });
    }
}

In this scenario, I just make up some business logic rule to demonstrate the validation, .e.g. user cannot delete a product that has price more than 200. Also notice, this validator class can use constructor injection to take any dependency it needs to perform its task (in this case it takes a product repository as dependency from constructor. This dependency will be resolved by Autofac automatically).

FluentValidation has a one-to-one mapping from validator to the instance that it validates. In other words, one validator can only be used to validate one class. Therefore for each operation, I have a convention of using one input model class to hold all the input parameters to perform that operation (.e.g. to delete a product, I'll have DeleteProductRequest input model that holds just Id field of the product to be deleted).

To use this validator in application service class:

  • Register validator factory, validator classes to IoC containers. Here's example in Autofac:
C#
builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().InstancePerRequest();

builder.RegisterType<UpdateProductValidator>().As<IValidator<UpdateProductRequest>>().InstancePerRequest();
builder.RegisterType<DeleteProductValidator>().As<IValidator<DeleteProductRequest>>().InstancePerRequest();
  • Inject IValidatorFactory in application service class constructor:
C#
public ManageProductService(ICategoryRepository categoryRepository, IProductRepository productRepository, IValidatorFactory validatorFactory)
{
    _categoryRepository = categoryRepository;
    _productRepository = productRepository;
    _validatorFactory = validatorFactory;
}
  • In each update operation of application service, use validator factory to get instance of validator class, based on input model type, and use validator to validate input. In this case I choose to let FluentValidation to throw exception if any of business rule is violated
C#
public void Delete(DeleteProductRequest request)
{
    var validator = _validatorFactory.GetValidator<DeleteProductRequest>();
    validator.ValidateAndThrow(request);

    _productRepository.Delete(request.Id);
}

 

Handle business exception

As you see above, the application service will throw exception of type ValidationException when any business rule is violated. So in UI layer, it should capture this exception and display appropriate response to user. Here's the action Delete() in the ProductController that handles the exception:

C#
[HandleBusinessException]
public ActionResult Delete(DeleteProductRequest request)
{
    _manageProductService.Delete(request);
    return RedirectToAction("Index")
        .WithSuccess("Product deleted successfully");
}

There are 2 interesting things here that I would like to point out:

  • I create a simple HandleBusinessException that inherits from ASP.NET MVC HandleErrorAttribute. This attribute will capture any ValidationException from the controller, and either:
    • Redirect to a specified action in a controller with business errors in TempData if it's a normal page request
    • Return a json object with 2 fields Success and Errors if it's an ajax request

There are 3 parameters you can config the attribute: ForAjaxRequest, ControllerToRedirectIfInvalid, ActionToRedirectIfInvalid

  • The controller uses extension WithSuccess("message") to display a bootstrap success alert (or WithError("error") in HandleBusinessException to display error alert). Behind the screen these extension methods WithSuccess(), WithError(), etc will create custom ActionResult, store the message in TempData and render it in _Layout.cshtml page. Details of how it's done can be checked in the demo solution. This is a nice trick that I take from the Pluralsight course "Build Your Own Application Framework with ASP.NET MVC 5"

Here's how it looks when a business logic is violated:

Image 4

References

[1]: https://lostechies.com/jimmybogard/2009/02/15/validation-in-a-ddd-world/

History

30/5/2015: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)