Getting Started With Specifications

May 13, 2022#Software Development
Article
Author image.

Kyle McMaster, Senior Consultant

Specification is a pattern in Domain Driven Design that helps encapsulate domain knowledge in a reusable way. The term Specification was coined by Martin Fowler and Eric Evans in this paper on the subject. A specification typically contains the criteria necessary for validation of an entity or retrieval of an entity from a persistence layer. Rather than defining this domain knowledge in the data access or validation layers separately, a specification allows developers to define that common logic in the domain layer. This enables developers to reuse this logic across the many layers and components of a system. This blog post aims to get the reader started with the Ardalis.Specification implementation of the Specification pattern.

Introducing Ardalis.Specification

The Ardalis.Specification package provides a base class for getting developers started with creating specifications inside of a DDD context. The package also includes interfaces for creating specification compatible repositories as well as interfaces that customize the behavior of the specifications. The Ardalis.Specification.EntityFramework and Ardalis.Specification.EntityFrameworkCore packages include implementations of the repository interfaces targeting their respective data acess technologies. The main functionality of the specification package is implemented by the Specification<T> abstract base class which implements the ISpecification<T> interface. In the following sections, we will look at how to use this class to create specifications for retrieving and validating entities.

Creating Specifications

This section will demonstrate some of the common use cases of the specification pattern for accessing data. First, let’s implement a specfication that will retrieve a list of Projects with a given Priority Status. This entity comes from the sample domain of the Clean Architecture repository. Another project which demonstrates Ardalis.Specification in action is eShopOnWeb, Microsoft’s reference for creating modern .NET web applications. Our first example specification is defined below. Notice that this specification makes use of a Guard Clause for ensuring the ProjectStatus is not null.

public class ProjectByProjectStatusSpec : Specification<Project>
{
  public ProjectByProjectStatusSpec(ProjectStatus projectStatus)
  {
    Guard.Against.Null(projectStatus, nameof(projectStatus));
    
    Query.Where(p => p.Status == projectStatus);
  }
}

When this specification is evaluated against a list of Projects either in memory or against a database, it will return a collection of Projects filtered with the given ProjectStatus. The Query property is used by the ISpecificationBuilder to define the query expression that will be evaluated against the data source and has a similar API to IQueryable commonly used with LINQ. For example, if we want to modify our specification to include a related entity, we can do so by adding an Include clause to the Query property. This should be familiar to anyone who has worked with EF and EF Core.

public class ProjectByProjectStatusWithToDoItemsSpec : Specification<Project>
{
  public ProjectByProjectStatusWithToDoItemsSpec(ProjectStatus projectStatus)
  {
    Guard.Against.Null(projectStatus, nameof(projectStatus));
    
    Query.Where(p => p.Status == projectStatus)
      .Include(p => p.Items);
  }
}

For the next example, we’ll narrow our result set to return a single Project by querying by Id. For this scenario, the ISingleResultSpecification interface is used to indicate that the specification will return a single instance of an entity.

public class ProjectByIdSpec : Specification<Project>, ISingleResultSpecification
{
  public ProjectByIdSpec(long id)
  {
    Guard.Against.Default(id, nameof(id));

    Query.Where(p => p.Id == id);
  }
}

The examples above show how to create specifications for some common data retrieval scenarios. If you’re interested in how the specifications can be applied directly to your data source, check out the Specification docs on How to use Specifications with the Repository Pattern or How to use Specifications with In Memory Collections for examples on compiling and applying the underlying query expression.

Specifications As Validators

Specifications can also be used as validators to ensure a given object meets the specification’s criteria. Let’s imagine a domain constraint exists where Projects with status Completed must have all ToDoItems marked with IsDone set as true. We can define a specification that will validate this constraint.

public class ValidateCompletedProjectSpec : Specification<Project>
{
  public ValidateCompletedProjectSpec()
  {
    Query
      .Include(p => p.Items)
      .Where(p => p.Status == ProjectStatus.Complete && p.Items.All(i => i.IsDone));
  }
}

To validate a given instance meets the above constraints, we can call the IsSatisfiedBy method on the specification instance with the Project instance as a parameter. This returns a boolean indicating whether the project is valid by the given specification. Since the specification and entity instances are in memory, this example makes a great candidate for a unit test.

[Fact]
public void ShouldReturnTrueWhenTodoItemsAreDone()
{
  var project = new Project("Blog", PriorityStatus.Critical);
  
  project.AddItem(
    new ToDoItem
      {
          Title = "Write Specification Blog",
          Description = "Write a blog post about the specification pattern",
      });
  
  var spec = new ValidateCompletedProjectSpec();
  
  bool result = spec.IsSatisfiedBy(project); // returns false

  Assert.True(result);
}

This test would fail because the Project’s single ToDoItem is created with a value of false for IsDone. If we call MarkComplete() on the ToDoItem instance, the result variable will then be true. Now, we can write our passing test.

[Fact]
public void ShouldReturnTrueWhenTodoItemsAreDone()
{
  var project = new Project("Blog", PriorityStatus.Critical);
  
  project.AddItem(
    new ToDoItem
      {
          Title = "Write Specification Blog",
          Description = "Write a blog post about the specification pattern",
      });
  
  project.Items.Single().MarkComplete();

  var spec = new ValidateCompletedProjectSpec();
  
  bool result = spec.IsSatisfiedBy(project); // returns true

  Assert.True(result);
}

Specifications can be a powerful tool for accessing and validating data inside a DDD context. The Ardalis.Specification package provides a solid foundation for creating specifications that can be used with common ORMs like Entity Framework and Entity Framework Core. These packages can also be helpful for validating objects in an application using the same business rules defined in an applications domain layer. Get started with your own specifications today using the Ardalis.Specification package!

Resources


Copyright © 2024 NimblePros - All Rights Reserved