Index Β· Additional notes

2 min read
Mid-level3 min read
Rapid overview

Additional notes

Specifications Pattern (Query Encapsulation)

Use specifications to centralize query intent (what to fetch), keep handlers focused on actions (what to do), and protect Domain purity.

When to use it

  • You have repeated query logic (filters/includes/order) spread across handlers/services.
  • You want query intent to be named and reusable (InactiveTemplates, ByName, CreatedBefore, ...).
  • You want to unit test handlers without depending on EF Core query details.

Where specifications live (Clean Architecture rule)

  • Domain: Only if the spec expresses a domain rule you want to protect as part of the model (rare).
  • Application (typical): When the spec is a persistence/query concern used by use cases (common with EF Core + Ardalis.Specification).
  • Infrastructure: Implementations of repositories and query execution; avoid putting spec definitions here (it makes use cases harder to read and test).
Questioner.Domain
 └── QuestionerAggregate
     └── QuestionerTemplate.cs

Questioner.Application
 └── QuestionerTemplates
     β”œβ”€β”€ Commands
     β”‚   └── DeleteInactiveQuestionerTemplates
     β”‚       └── DeleteInactiveQuestionerTemplatesHandler.cs
     β”œβ”€β”€ Specifications
     β”‚   β”œβ”€β”€ InactiveQuestionerTemplatesSpec.cs
     β”‚   β”œβ”€β”€ IQuestionerTemplateSpecifications.cs
     β”‚   └── QuestionerTemplateSpecifications.cs
     └── Abstractions
         └── IQuestionerTemplateRepository.cs

Questioner.Infrastructure
 └── Persistence
     └── Repositories
         └── QuestionerTemplateRepository.cs

Goal: handlers stop doing new SomeSpec() and depend on a single abstraction that produces specs.

Specification (Application):

using Ardalis.Specification;
using Questioner.Domain.QuestionerAggregate;

public sealed class InactiveQuestionerTemplatesSpec : Specification<QuestionerTemplate>
{
    public InactiveQuestionerTemplatesSpec()
    {
        Query.Where(x => !x.IsActive);
    }
}

Factory abstraction (Application):

using Ardalis.Specification;
using Questioner.Domain.QuestionerAggregate;

public interface IQuestionerTemplateSpecifications
{
    ISpecification<QuestionerTemplate> Inactive();
}

Factory implementation (Application):

using Ardalis.Specification;
using Questioner.Domain.QuestionerAggregate;

public sealed class QuestionerTemplateSpecifications : IQuestionerTemplateSpecifications
{
    public ISpecification<QuestionerTemplate> Inactive()
        => new InactiveQuestionerTemplatesSpec();
}

DI registration (Infrastructure / composition root):

services.AddScoped<IQuestionerTemplateSpecifications, QuestionerTemplateSpecifications>();

Handler usage (Application):

public sealed class DeleteInactiveQuestionerTemplatesHandler
{
    private readonly IQuestionerTemplateRepository _repository;
    private readonly IQuestionerTemplateSpecifications _specs;

    public DeleteInactiveQuestionerTemplatesHandler(
        IQuestionerTemplateRepository repository,
        IQuestionerTemplateSpecifications specs)
    {
        _repository = repository;
        _specs = specs;
    }

    public Task<int> Handle(CancellationToken cancellationToken)
    {
        return _repository.DeleteRangeAsync(_specs.Inactive(), cancellationToken);
    }
}

Best practices (interview-ready)

  • Keep specs focused on query definition (filters/includes/order/paging), not orchestration.
  • Prefer intent-based names: InactiveQuestionerTemplatesSpec, TemplatesByNameSpec, CreatedBeforeSpec.
  • Keep handlers free of query construction; they should read like business intent.
  • Allow new only inside the factory (or inside the spec itself); handlers depend on abstractions.
  • Register factories in the composition root (Infrastructure), not in Domain/Application.
  • Don’t inject DbContext into specs; specs should describe what, repositories decide how to execute.

Quick interview Q&A

Q: Why use a specification factory instead of new SomeSpec() in handlers?

A: It removes coupling to concrete specs, makes handlers easier to mock/test, and centralizes spec creation so intent stays consistent.

Q: What’s the rule of thumb?

A: Specifications describe β€œwhat to select”, factories describe β€œwhich spec to use”, handlers describe β€œwhat to do”.