Skip to content

Specifications

Info

At ConnectSoft, Specifications are a core tactical tool —
enabling us to express complex domain rules, encapsulate query logic,
and preserve domain model purity without leaking infrastructure concerns.


Introduction

In Domain-Driven Design (DDD), a Specification represents a business rule or a query criteria
that can be evaluated against one or more domain objects.

Specifications:

  • Express complex logic declaratively.
  • Enable validation, querying, and composition cleanly.
  • Keep business rules inside the domain model, not scattered across services, controllers, or repositories.
  • Can be composed dynamically to build powerful, flexible workflows.

At ConnectSoft, Specifications are foundational to:

  • Resilient domain integrity validation.
  • Dynamic, complex query construction across microservices and SaaS platforms.
  • Testable business rule enforcement.
  • Enabling CQRS and Event-Driven workflows cleanly.

Concept Definition

A Specification:

Aspect Description
Purpose Encapsulate a business rule or query predicate.
Type Can be executable (in-memory validation) or translatable (e.g., to LINQ, SQL, etc.).
Composability Specifications can be combined using logical operations (AND, OR, NOT).
Reusability Specifications can be reused across Aggregates, Services, and Repositories.
Testability Specifications are independently unit-testable.

📚 Why Specifications Matter at ConnectSoft

Preserve Domain Purity

  • Business logic stays inside the domain model, not scattered across layers.

Enable Dynamic Querying

  • Powerful query capabilities without bloating Repository interfaces.

Promote Reuse and Composition

  • Specifications can be combined dynamically depending on use cases.

Support CQRS and Event-Driven Designs

  • Query and validation logic remains structured and evolvable.

Simplify Validation Logic

  • Domain services and Aggregates validate complex business rules easily using Specification evaluations.

Specification vs Ad-hoc Queries or Validations

Aspect Specification Ad-hoc Validation
Separation of Concerns Business rule expressed independently Rule embedded into service, controller, or repository
Composability Can be composed (AND, OR, NOT) Hard to compose or reuse
Testability Easy to test in isolation Tightly coupled, hard to test
Reusability Highly reusable across services, use cases Duplicated across codebase
Evolution Readiness Easy to extend dynamically Hard to extend without rewriting logic

🧩 Visual: Specification Across Domain Layers

flowchart TD
    UI["UI/API Layer"]
    AppService["Application Service"]
    Specification["Domain Specification (Business Rule)"]
    Repository["Repository"]
    Database["Database"]

    UI --> AppService
    AppService --> Specification
    Specification --> Repository
    Repository --> Database
Hold "Alt" / "Option" to enable pan & zoom

✅ Application Services invoke Specifications directly or pass them into Repositories.
✅ Specifications remain inside the Domain Layer, governing logic cleanly.


Strategic Design Principles for Specifications

At ConnectSoft, we design Specifications to be clean, composable, reusable, and tactical
ensuring that business rules stay isolated, expressive, and testable.


📚 Core Principles for Good Specification Design

Keep Specifications Stateless

  • They should express logic without holding state — ensuring predictability and reusability.

Encapsulate One Clear Rule Per Specification

  • A Specification should encapsulate exactly one business rule or query intent.

Support Composition Natively

  • Enable combining Specifications easily via AND, OR, and NOT operators.

Evaluate Locally or Translate Remotely

  • Specifications should be evaluatable both in-memory and translatable to database queries (LINQ, SQL, ElasticSearch, etc.).

Design for Reuse

  • A good Specification can be reused across multiple Aggregates, Repositories, or Application Services without modification.

Enable Unit Testing in Isolation

  • Each Specification should be unit-tested independently.

Expose Meaningful Domain Language

  • Specification names should reflect business rules clearly (e.g., IsPremiumCustomer, HasOutstandingBalance, IsAppointmentOverlapping).

📚 Specification Composition Techniques

Specifications must support logical operations safely and fluently.

Composition Example
AND Composition Find active customers AND customers with loyalty points above 100.
OR Composition Find orders that are Pending OR AwaitingPayment.
NOT Composition Find appointments that are NOT yet confirmed.

C# Composition Example (Tactical)

public interface ISpecification<T>
{
    Expression<Func<T, bool>> ToExpression();
}

// Combinator
public static class SpecificationExtensions
{
    public static ISpecification<T> And<T>(this ISpecification<T> left, ISpecification<T> right)
    {
        return new AndSpecification<T>(left, right);
    }

    public static ISpecification<T> Or<T>(this ISpecification<T> left, ISpecification<T> right)
    {
        return new OrSpecification<T>(left, right);
    }

    public static ISpecification<T> Not<T>(this ISpecification<T> specification)
    {
        return new NotSpecification<T>(specification);
    }
}

✅ Fluent, composable Specifications
✅ Chain AND, OR, NOT operators easily.


📚 Practical Composition Usage Example

var spec = new ActiveCustomersSpecification()
                .And(new HighLoyaltyPointsSpecification(100));

✅ Find customers who are both active AND have high loyalty points.
✅ No manual filtering in services or repositories!


🛑 Common Anti-Patterns to Avoid with Specifications

Anti-Pattern Symptom Why It's Dangerous
Procedural Validation Logic spread across services without reusable Specifications. Hard to test, maintain, or extend.
Specification God Objects Huge Specifications modeling multiple unrelated rules. Breaks SRP, hard to reuse or compose.
Stateful Specifications Specification result depends on internal mutable fields. Unpredictable, hard to reason about.
Repository-Leaking Specifications Specification tied to database-specific operations (e.g., raw SQL inside spec). Breaks domain isolation, kills portability.
Duplicated Specifications Copy-pasted filters or rules in multiple places. Increases maintenance cost and bugs.

📚 Good vs Bad Specification Examples

✅ Good Specification 🚫 Bad Specification
Models one clear domain rule Bundles many unrelated checks
Stateless, pure logic Holds mutable fields affecting results
Easily composed (AND, OR, NOT) Hard-coded logic without composition support
Portable across contexts (validation, query) Tied to database or API infrastructure
Fluent, meaningful names Generic "Filter1", "Validator2" style names

🧩 Visual: Composing Specifications Graphically

flowchart TD
    ActiveCustomer["IsActiveCustomerSpecification"]
    HighPoints["HasHighLoyaltyPointsSpecification"]
    Composite["Active AND High Points"]

    ActiveCustomer --> Composite
    HighPoints --> Composite
Hold "Alt" / "Option" to enable pan & zoom

✅ Specifications combine safely to create higher-order business logic
✅ No duplication or fragile scripting inside services.


C# Examples: Real-World Specification Modeling at ConnectSoft

At ConnectSoft, Specifications are used across all core domains —
modeling complex queries and validations without bloating services or repositories.


🛠️ Example 1: E-Commerce — OrderStatusSpecification

public class OrderStatusSpecification : ISpecification<Order>
{
    private readonly OrderStatus _status;

    public OrderStatusSpecification(OrderStatus status)
    {
        _status = status;
    }

    public Expression<Func<Order, bool>> ToExpression()
    {
        return order => order.Status == _status;
    }
}

✅ Clear domain rule: Filter orders by their status.
✅ Can be composed with other Specifications.


🛠️ Example 2: Finance — HighBalanceSpecification

public class HighBalanceSpecification : ISpecification<BankAccount>
{
    private readonly decimal _threshold;

    public HighBalanceSpecification(decimal threshold)
    {
        _threshold = threshold;
    }

    public Expression<Func<BankAccount, bool>> ToExpression()
    {
        return account => account.Balance >= _threshold;
    }
}

✅ Flexible: threshold passed dynamically.
✅ Used to query VIP accounts, calculate interest tiers, etc.


🛠️ Example 3: Healthcare — UpcomingAppointmentsSpecification

public class UpcomingAppointmentsSpecification : ISpecification<Appointment>
{
    private readonly DateTime _fromDate;

    public UpcomingAppointmentsSpecification(DateTime fromDate)
    {
        _fromDate = fromDate;
    }

    public Expression<Func<Appointment, bool>> ToExpression()
    {
        return appointment => appointment.StartTime >= _fromDate;
    }
}

✅ Supports building dynamic appointment lists for dashboards, reminders, etc.


📚 Composing Complex Queries Using Specifications

Example: Find Active Customers with High Loyalty Points

var activeCustomers = new ActiveCustomerSpecification()
    .And(new HighLoyaltyPointsSpecification(100));
  • Combines two Specifications using AND.
  • Can be passed into Repository to build database queries dynamically.

📚 Using Specifications in Repositories

public interface ICustomerRepository
{
    Task<IEnumerable<Customer>> FindAsync(ISpecification<Customer> specification);
}

public class CustomerRepository : ICustomerRepository
{
    private readonly DbContext _context;

    public CustomerRepository(DbContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<Customer>> FindAsync(ISpecification<Customer> specification)
    {
        return await _context.Set<Customer>()
            .Where(specification.ToExpression())
            .ToListAsync();
    }
}

✅ Clean separation: Repository knows nothing about hardcoded queries.
✅ Business rule lives purely in the Specification.


📚 Validation with Specifications Inside Domain Services

public class AccountService
{
    private readonly IAccountRepository _accountRepository;

    public AccountService(IAccountRepository accountRepository)
    {
        _accountRepository = accountRepository;
    }

    public async Task CloseAccountAsync(Guid accountId)
    {
        var account = await _accountRepository.GetByIdAsync(accountId);

        var canCloseSpec = new AccountClosableSpecification();
        if (!canCloseSpec.ToExpression().Compile().Invoke(account))
            throw new InvalidOperationException("Account cannot be closed while balance is non-zero.");

        account.Close();
        await _accountRepository.UpdateAsync(account);
    }
}

public class AccountClosableSpecification : ISpecification<BankAccount>
{
    public Expression<Func<BankAccount, bool>> ToExpression()
    {
        return account => account.Balance == 0 && !account.IsClosed;
    }
}

✅ Domain Services validate using reusable Specifications.
✅ No magic conditions hardcoded inside services.


📚 Lessons from Real-World Specification Modeling

Good Practice Why It Matters
Keep specifications lightweight and focused Makes composition easy
Compose dynamically in Application Services or Repositories Supports flexible business workflows
Validate domain rules inside Aggregates/Services with Specifications Prevents invariant violations
Use fluent chaining (AND, OR, NOT) Improves readability and flexibility
Separate querying logic cleanly from domain operations Keeps architecture layered and maintainable

🧩 Visual: Specification Usage Across Query and Validation

flowchart TD
    Specification1["ActiveCustomerSpecification"]
    Specification2["HighLoyaltyPointsSpecification"]
    Composite["AND Composition"]

    Specification1 --> Composite
    Specification2 --> Composite
    Composite --> Repository["CustomerRepository.FindAsync()"]
Hold "Alt" / "Option" to enable pan & zoom

✅ Clean flow: Compose business rules ➔ Query via Repositories.
✅ No bloated Application Services.


Best Practices for Specifications

At ConnectSoft, Specifications are modeled strategically to ensure that
business rules are clean, reusable, testable, and naturally evolve with the domain.


📚 Best Practices Checklist

One Responsibility per Specification

  • Each Specification models a single clear rule or condition.

Support Logical Composition

  • Specifications should be combinable with AND, OR, and NOT operators.

Remain Stateless

  • No mutable state or runtime side-effects inside Specifications.

Be Easily Testable

  • Unit tests should validate Specification behavior independently.

Work for Queries and In-Memory Validations

  • Same Specification can be used for querying repositories and validating domain invariants.

Isolate from Infrastructure

  • Specifications operate on domain objects, not tied to persistence concerns like SQL syntax or EF-specific constructs.

Fluent API for Readability

  • Specification compositions (chained expressions) must read naturally and clearly.

Avoid Overloading

  • Do not build massive "mega specifications" combining unrelated rules.

Expose Meaningful Names

  • Specification class names must reflect the business rule they model.

Version Carefully if Exposed Outside

  • If Specifications power APIs or Event Streams, handle backward compatibility cautiously.

Conclusion

Specifications at ConnectSoft are not minor helpers
they are tactical first-class citizens of clean, scalable Domain-Driven systems.

When modeled properly:

  • Systems express complex business logic cleanly without cluttering Services or Repositories.
  • Queries and Validations share logic, reducing duplication and fragility.
  • New workflows evolve by composing existing building blocks, not by rewriting services.
  • Testing becomes easier, faster, and more resilient to change.
  • Business rule documentation emerges naturally from the codebase.

When neglected:

  • Services bloat with if-else chaos.
  • Queries leak domain assumptions into infrastructure.
  • Business rules become fragile, hidden, and duplicated.
  • Scaling and changing systems becomes dangerous.

At ConnectSoft, Specifications empower our developers to build
adaptive, expressive, domain-driven architectures
ready to evolve with our businesses, industries, and customers.

"Specifications are not filters —
they are the language of business rules,
flowing naturally through code and system behavior.
"


References

  • Books and Literature

    • Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software
    • Vaughn Vernon — Implementing Domain-Driven Design
    • Jimmy Nilsson — Applying Domain-Driven Design and Patterns
  • Online Resources

  • ConnectSoft Internal Standards

    • ConnectSoft Specification Pattern Playbook
    • ConnectSoft Resilient Querying and Domain Validation Strategies
    • ConnectSoft CQRS and Specification Composition Guidelines