Desde La Capa De Dominio
← Volver al blog

Criteria: filtros dinámicos, paginación y DDD

Cómo el patrón Criteria + Specification resuelve el problema de los filtros dinámicos en repositorios sin contaminar el dominio con infraestructura. Ejemplo real en Code Finances.

Criteria Specification Pattern DDD Clean Architecture TypeScript Repositorios

Cuando trabajas en aplicaciones reales, hay un momento inevitable: necesitas hacer búsquedas complejas.

"Quiero buscar usuarios por rol, estado, nombre… añadir ordenación… paginar resultados… y que además el código sea limpio."

Y ahí es donde muchas arquitecturas empiezan a deteriorarse.


⚠️ El problema


En cuanto empiezas a añadir filtros dinámicos en tus repositorios:

El resultado: código difícil de mantener, extender y testear.


🚀 La solución: Criteria + Specification Pattern


✅ Criteria

Un objeto que encapsula completamente una búsqueda: filtros, ordenación y paginación.

🧱 Specification Pattern

Cada filtro se modela como una specification: independiente, reutilizable y componible.

🔗 Composición dinámica

Puedes combinar filtros sin modificar el repositorio:

criteria = new Criteria(filters, order, pageSize, pageNumber)

🎯 Resultado

El repositorio deja de "pensar" y solo ejecuta:

repository.match(criteria)
flowchart TB
    subgraph Dominio
        direction TB
        F1[Filter: role=admin]
        F2[Filter: status=active]
        O[Order: name ASC]
        P[Page: 1 / size: 20]
        C[Criteria]
        F1 --> C
        F2 --> C
        O --> C
        P --> C
    end

    subgraph Infraestructura
        direction TB
        R[Repository.match]
        QB[QueryBuilder]
        DB[(PostgreSQL)]
        R --> QB
        QB --> DB
    end

    C --> R

    style C fill:#1e1b4b,stroke:#818cf8,color:#e0e7ff
    style R fill:#0f172a,stroke:#818cf8,color:#e0e7ff
    style QB fill:#0f172a,stroke:#818cf8,color:#e0e7ff
    style DB fill:#1e1b4b,stroke:#818cf8,color:#e0e7ff

⚡️ Implementación DDD-friendly


📦 Shared Domain

Aquí vive el core reutilizable entre todos los contextos:

🧩 Interfaz de repositorio en el dominio

Clave: el dominio no sabe nada de SQL, ORM ni infraestructura.

interface ContextRepository {
  match(criteria: Criteria): Promise<ContextAggregate[]>
}

🏆 Ejemplo real: Code Finances


Así se organiza en el proyecto Code Finances aplicando Screaming Architecture:

Shared/Domain/Criteria/
  Criteria.ts
  Filters.ts · Field.ts · Operator.ts · Value.ts
  Order.ts · OrderBy.ts · OrderType.ts
  PageSize.ts · PageNumber.ts

CashFlow/Revenue/Domain/
  RevenueRepository.ts  ← interface match(criteria)

CashFlow/Revenue/Infra/
  TypeOrmRevenueRepository.ts  ← implementa la interfaz

CashFlow/Revenue/App/Search/
  RevenuesSearcher.ts  ← caso de uso, construye el Criteria

🧠 Beneficios reales



💬 Conclusión


El patrón Criteria no es solo una mejora técnica: es un cambio de mentalidad.

Dejas de escribir queries específicas para cada caso y empiezas a construir un sistema flexible que evoluciona contigo. Y lo mejor: una vez lo implementas bien, no quieres volver atrás.


Publicado originalmente en LinkedIn.