Introduciendo el Repository Pattern en DelphiMVCFramework: Clean Architecture Simplificada
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:
- repository_showcase - Demuestra cada característica del Repository Pattern con más de 20 ejemplos
- 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: 🔗
- DelphiMVCFramework en GitHub
- ITDevCon 2025 - 6-7 de Noviembre, Milán
- Proyectos de ejemplo en:
/samples/repository_showcase
y/samples/simple_api_using_repository_with_injection
¡Feliz codificación! 👨💻
Daniele Teti
Comments
comments powered by Disqus