Index Β· Additional notes
2 min readRapid 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).
Recommended folder structure (feature-local, scalable)
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
Option 2 (recommended): Specification Factory
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
newonly 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
DbContextinto 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β.