Course contentsShow
← C#
Lesson 328 of 1,9083. Generics and Collections ArchitectureFree lesson

Specification Pattern Architectures

Encapsulate business logic in reusable generic specification objects for query building.

Reusable Query Logic with the Specification Pattern

In this lesson, you will learn how to encapsulate complex business rules into reusable objects to keep your data-access layer clean and expressive.

When building applications, you often find yourself repeating the same filtering logic—like finding "active users" or "overdue orders"—across different repositories or services. Instead of scattering LINQ expressions everywhere, the Specification Pattern allows you to wrap that logic into a dedicated, generic class.

Building a Generic Specification

The goal is to create a structure that describes a rule. By leveraging C# generics and Expression trees (which we touched on in lesson 20), we can create a base interface that enforces a consistent structure for our specifications.

public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
    bool IsSatisfiedBy(T entity);
}

By implementing this interface, you create an object that defines exactly what it means for an entity to "match." For example:

public class ActiveUserSpecification : ISpecification<User>
{
    public Expression<Func<T, bool>> Criteria => user => user.Status == Status.Active;
    
    public bool IsSatisfiedBy(User user) => user.Status == Status.Active;
}

Why use this architecture?

By encapsulating logic in these specification objects, you gain three immediate architectural benefits:

  1. Reusability: Use the same ActiveUserSpecification in your Repository (to filter database queries) and in your Domain Logic (to validate business rules).
  2. Testability: Because the specification is a standalone class, you can unit test your business rules in total isolation from the database or external services.
  3. Combinability: You can expand this architecture to include AndSpecification or OrSpecification decorators (using the Decorator pattern from lesson 21), allowing you to chain simple rules together to build complex, dynamic queries without manually concatenating strings or LINQ calls.

This approach transforms your "raw" data access into a declarative language where your code reads like your business requirements, rather than a collection of implementation details.

Key Takeaway: The Specification Pattern turns fragmented business rules into reusable objects, promoting clean, testable, and composable query logic across your entire architecture.