Become a member!

Introduciendo el Repository Pattern en DelphiMVCFramework: Clean Architecture Simplificada

  • 👉 This article is available in english too.
  • 👉 Questo articolo è disponibile anche in italiano.
  • 👉 Este artículo también está disponible en español.

Introducción

Después de años trabajando con DelphiMVCFramework y ayudando a desarrolladores a construir mejores REST APIs, he notado un patrón recurrente: aunque la implementación de ActiveRecord es poderosa y conveniente, muchos equipos necesitaban una separación más clara de responsabilidades, especialmente al construir aplicaciones más grandes con lógica de negocio compleja.

Déjame ser claro desde el principio: ActiveRecord sigue siendo la tecnología fundamental para el acceso a datos en DelphiMVCFramework 🏗️. Todo lo que puedes hacer con repositorios, también puedes lograrlo usando ActiveRecord directamente - solo requiere un poco más de código. El Repository Pattern es simplemente una capa de abstracción conveniente que hace más fácil implementar ciertos patrones arquitectónicos, particularmente cuando necesitas inyección de dependencias, una separación más limpia de responsabilidades, o código más testeable.

Es por eso que estoy emocionado de introducir el soporte nativo del Repository Pattern en DelphiMVCFramework 🚀. Esta nueva característica trae patrones arquitectónicos de nivel profesional a tus aplicaciones Delphi manteniendo la simplicidad y elegancia que esperas del framework. Bajo el capó, los repositorios delegan todas las operaciones a ActiveRecord, asegurando cero duplicación de código y aprovechando la implementación probada de ActiveRecord que ya conoces y en la que confías.

¿Por qué el Repository Pattern?

El Repository Pattern actúa como mediador entre tu lógica de dominio/negocio y las capas de mapeo de datos. Piénsalo como una interfaz similar a una colección para acceder a entidades de dominio. Estos son los beneficios clave:

1. Separación de Responsabilidades 🎯

Tus controladores ya no necesitan conocer los detalles de ActiveRecord. Trabajan con una interfaz limpia (IMVCRepository<T>) que puede ser fácilmente simulada para pruebas.

2. Listo para Inyección de Dependencias 💉

Los repositorios son perfectos para la inyección de dependencias. Puedes inyectar IMVCRepository<TCustomer> directamente en tus controladores, haciendo tu código más testeable y mantenible.

3. Testabilidad

Los mocks de repositorios son fáciles de crear para pruebas unitarias. Puedes probar tu lógica de negocio sin tocar la base de datos.

4. Flexibilidad 🔧

¿Necesitas consultas personalizadas? Simplemente extiende el repositorio base con métodos específicos del dominio manteniendo todas las operaciones CRUD estándar.

Comenzando: Uso Básico

Empecemos con un ejemplo simple. Así es como defines una entidad usando ActiveRecord:

[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
TCustomer = class(TMVCActiveRecord)
private
  [MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
  fID: NullableInt64;
  [MVCTableField('code')]
  fCode: NullableString;
  [MVCTableField('company_name')]
  fCompanyName: NullableString;
  [MVCTableField('city')]
  fCity: string;
  [MVCTableField('rating')]
  fRating: NullableInt32;
public
  property ID: NullableInt64 read fID write fID;
  property Code: NullableString read fCode write fCode;
  property CompanyName: NullableString read fCompanyName write fCompanyName;
  property City: string read fCity write fCity;
  property Rating: NullableInt32 read fRating write fRating;
end;

Ahora, en lugar de usar métodos de ActiveRecord directamente en tu controlador, tienes dos opciones:

Opción 1: Usar el Repositorio Genérico Directamente

Si no necesitas lógica personalizada o métodos específicos del dominio, puedes simplemente usar IMVCRepository<T> directamente sin crear una clase personalizada:

// En tu controlador o servicio
procedure RegisterServices(Container: IMVCServiceContainer);
begin
  Container.RegisterType(
    TMVCRepository<TCustomer>,
    IMVCRepository<TCustomer>,
    TRegistrationType.SingletonPerRequest
  );
end;

// Luego inyéctalo directamente
function TCustomersController.GetCustomers(
  [MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
  Result := OKResponse(CustomersRepository.GetAll);
end;

Esto es perfecto para operaciones CRUD simples donde los métodos estándar del repositorio son suficientes. ⚡

Opción 2: Crear una Clase de Repositorio Personalizada

Si necesitas agregar métodos personalizados o lógica específica del dominio, crea tu propia clase de repositorio:

type
  TCustomerRepository = class(TMVCRepository<TCustomer>)
  public
    // Agrega métodos personalizados aquí si es necesario
    function GetCustomersByCity(const City: string): TObjectList<TCustomer>;
    function GetTopRatedCustomers: TObjectList<TCustomer>;
  end;

Implementar Métodos de Repositorio Personalizados

Una de las características más poderosas es la capacidad de extender repositorios con métodos específicos del dominio. Así es como:

function TCustomerRepository.GetCustomersByCity(const City: string): TObjectList<TCustomer>;
begin
  Result := GetWhere('city = ?', [City]);
end;

function TCustomerRepository.GetTopRatedCustomers: TObjectList<TCustomer>;
begin
  Result := SelectByNamedQuery('BestCustomers', [], []);
end;

Nota cómo estos métodos usan las capacidades del repositorio base (GetWhere, SelectByNamedQuery) pero proporcionan una API más expresiva y específica del dominio para tus controladores.

Usar Repositorios en Controladores con Inyección de Dependencias

Aquí es donde las cosas se ponen realmente interesantes. El contenedor de inyección de dependencias integrado en DelphiMVCFramework hace increíblemente fácil usar repositorios en tus controladores:

[MVCPath('/api/customers')]
TCustomersController = class(TMVCController)
public
  [MVCPath]
  [MVCHTTPMethods([httpGET])]
  function GetCustomers(
    [MVCFromQueryString('rql','')] RQLFilter: String;
    [MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;

  [MVCPath('/($ID)')]
  [MVCHTTPMethods([httpGET])]
  function GetCustomerByID(
    const ID: Integer;
    [MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;

  [MVCPath]
  [MVCHTTPMethods([httpPOST])]
  function CreateCustomer(
    [MVCFromBody] const Customer: TCustomer;
    [MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
end;

La implementación es limpia y enfocada en las preocupaciones HTTP:

function TCustomersController.GetCustomers(
  RQLFilter: String;
  CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
  Result := OKResponse(CustomersRepository.SelectRQL(RQLFilter));
end;

function TCustomersController.CreateCustomer(
  const Customer: TCustomer;
  CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
  CustomersRepository.Store(Customer);
  Result := CreatedResponse('/api/customers/' + Customer.ID.Value.ToString);
end;

¡Nota cómo los métodos del controlador reciben el repositorio como parámetro con el atributo [MVCInject]. El framework resuelve e inyecta automáticamente la dependencia! 🎉

Registrar Repositorios en el Contenedor

Para habilitar la inyección de dependencias, necesitas registrar tus repositorios en el contenedor. Esto típicamente se hace en tu WebModule:

procedure RegisterServices(Container: IMVCServiceContainer);
begin
  Container.RegisterType(
    TMVCRepository<TCustomer>,
    IMVCRepository<TCustomer>,
    TRegistrationType.SingletonPerRequest
  );
end;

El tipo de registro SingletonPerRequest asegura que cada petición HTTP obtenga su propia instancia del repositorio, que se destruye automáticamente cuando la petición se completa.

Ricas Capacidades de Consulta

La interfaz del repositorio proporciona múltiples formas de consultar tus datos:

Cláusulas SQL Where

lCustomers := Repository.GetWhere('city = ? AND rating >= ?', ['Rome', 4]);

RQL (Resource Query Language)

lCustomers := Repository.SelectRQL('eq(rating,5)');
lCustomers := Repository.SelectRQL('ge(rating,4)&sort(+code)');

SQL Crudo

lCustomers := Repository.Select(
  'SELECT * FROM customers WHERE city = ? AND rating >= ? ORDER BY code',
  ['Rome', 3]
);

Consultas Nombradas

lCustomers := Repository.SelectByNamedQuery('BestCustomers', [], []);

Soporte de Transacciones

Los repositorios funcionan perfectamente con transacciones usando el conveniente patrón UseTransactionContext:

function TCustomersController.BulkCreateCustomers(
  const Customers: TObjectList<TCustomer>;
  CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
  var lCtx := TMVCRepository.UseTransactionContext;
  for var lCustomer in Customers do
  begin
    CustomersRepository.Insert(lCustomer);
  end;
  Result := CreatedResponse();
end;

¡La transacción se confirma automáticamente cuando lCtx sale del ámbito, o se revierte si ocurre una excepción. Limpio y simple! 🔄

Características Avanzadas

Gestión de Conexiones Personalizadas

¿Necesitas usar una conexión de base de datos específica? Usa TMVCRepositoryWithConnection:

var
  lConnection: TFDConnection;
  lRepo: IMVCRepository<TCustomer>;
begin
  lConnection := TFDConnection.Create(nil);
  lConnection.ConnectionDefName := 'SecondaryDB';
  lConnection.Connected := True;

  lRepo := TMVCRepositoryWithConnection<TCustomer>.Create(lConnection, True);
  // Usa el repositorio con la conexión personalizada
end;

El Método Store

El método Store es particularmente útil - determina automáticamente si insertar o actualizar basándose en si la entidad tiene una clave primaria:

// Insert si PK es null
lCustomer := TCustomer.Create;
lCustomer.Code := 'NEW001';
Repository.Store(lCustomer);  // INSERT

// Update si PK existe
lCustomer := Repository.GetByPK(lID);
lCustomer.CompanyName := 'Updated Name';
Repository.Store(lCustomer);  // UPDATE

Métodos Utilitarios

El repositorio incluye varios métodos utilitarios útiles:

// Verifica existencia sin cargar la entidad
if Repository.Exists(CustomerID) then
  // Haz algo

// Cuenta registros
lCount := Repository.Count('ge(rating,4)');

// Elimina todo (¡usar con precaución!)
lDeleted := Repository.DeleteAll;

Ejemplo Completo: Repository Showcase

He creado dos ejemplos completos en el framework:

  1. repository_showcase - Demuestra cada característica del Repository Pattern con más de 20 ejemplos
  2. simple_api_using_repository_with_injection - Una REST API completa usando repositorios e inyección de dependencias

Estos ejemplos cubren:

  • Operaciones CRUD básicas
  • Métodos de consulta (SQL, RQL, Consultas Nombradas)
  • Métodos de repositorio personalizados
  • Gestión de transacciones
  • Manejo de errores
  • Operaciones masivas
  • Conexiones personalizadas
  • ¡Y mucho más!

Migrando de ActiveRecord a Repository Pattern 🔄

Una de las mejores características de esta implementación es la ruta de migración sin interrupciones. Como el Repository Pattern está construido sobre ActiveRecord, no necesitas rehacer los mapeos de tus entidades o cambiar la estructura de la base de datos. ¡Tus entidades ActiveRecord existentes funcionan inmediatamente con repositorios! ✨

Esto es lo que significa en la práctica:

Antes (usando ActiveRecord directamente):

function TCustomersController.GetCustomers: IMVCResponse;
begin
  Result := OKResponse(TMVCActiveRecord.All<TCustomer>);
end;

Después (usando Repository Pattern):

function TCustomersController.GetCustomers(
  [MVCInject] CustomersRepository: IMVCRepository<TCustomer>): IMVCResponse;
begin
  Result := OKResponse(CustomersRepository.GetAll);
end;

¡Tu clase de entidad TCustomer permanece exactamente igual! Simplemente estás usando un patrón de acceso diferente. Esto significa que puedes:

  • Migrar incrementalmente 📈 - Convierte un controlador o servicio a la vez
  • Mezclar ambos enfoques 🔀 - Usa ActiveRecord en algunos lugares, Repository en otros
  • No se requieren reescrituras 🎨 - Tus mapeos de entidades, validaciones y lógica de negocio permanecen intactos
  • Elige lo que mejor se ajuste 👍 - Usa el patrón que tenga más sentido para cada caso específico

Si tu aplicación ya está usando ActiveRecord y quieres introducir inyección de dependencias o mejorar la testabilidad, puedes empezar a usar IMVCRepository<T> inmediatamente sin ninguna refactorización de tu capa de entidades.

Cuándo Usar Repositorios vs. ActiveRecord Directamente

Usa Repositorios cuando:

  • Estés construyendo aplicaciones más grandes con lógica de negocio compleja
  • Necesites inyección de dependencias para pruebas
  • Quieras separación clara entre capas
  • Múltiples desarrolladores estén trabajando en la misma base de código
  • Estés siguiendo principios de Domain-Driven Design
  • Estés migrando a una arquitectura más testeable

Usa ActiveRecord directamente cuando:

  • Estés construyendo aplicaciones CRUD simples
  • Estés haciendo prototipos o proyectos de prueba de concepto
  • La capa de abstracción adicional no sea necesaria
  • Prefieras la simplicidad del acceso directo a datos
  • Estés trabajando en un microservicio pequeño y enfocado

¡Ambos enfoques son válidos, e incluso puedes mezclarlos en la misma aplicación! Lo mejor es que cambiar entre ellos requiere cambios mínimos de código.

¡Nos Vemos en ITDevCon 2025! 🎤

Presentaré una sesión profunda sobre el Repository Pattern y otras características avanzadas de DelphiMVCFramework en ITDevCon 2025 el 6-7 de Noviembre en Milán, Italia 🇮🇹.

Profundizaremos en:

  • Patrones de repositorio avanzados 🏛️
  • Estrategias de testing con repositorios 🧪
  • Domain-Driven Design con Delphi 📐
  • Mejores prácticas para aplicaciones a gran escala 🚀
  • Demostraciones de codificación en vivo 💻

¡Si te tomas en serio construir mejores REST APIs con Delphi, este es un evento que no puedes perderte! Regístrate ahora en www.itdevcon.it

Conclusión

El soporte del Repository Pattern en DelphiMVCFramework trae arquitectura de nivel empresarial a tus REST APIs sin sacrificar la simplicidad y productividad que los desarrolladores Delphi aman. Ya sea que estés construyendo un pequeño microservicio o una aplicación a gran escala, los repositorios te dan la flexibilidad y estructura que necesitas. 💪

La implementación es ligera, aprovechando la base existente de ActiveRecord, por lo que no hay duplicación de código y sobrecarga mínima. Obtienes código limpio, testeable y mantenible que sigue las mejores prácticas de la industria. ✨

¡Pruébalo en tu próximo proyecto y déjame saber qué piensas! El framework es de código abierto, así que explora los ejemplos, experimenta y no dudes en contribuir con mejoras. 🤝

¡Nos vemos en ITDevCon 2025! 🎉


Enlaces: 🔗

¡Feliz codificación! 👨‍💻

Daniele Teti

Comments

comments powered by Disqus