Delphi Moderno con Dext: Del RAD al Desacoplamiento
“Simplicity is Complicated.” — Rob Pike
Si has programado en Delphi durante años, probablemente has escuchado términos como “acoplamiento”, “inyección de dependencias” y “testabilidad”. Pero, ¿qué significan realmente en la práctica? ¿Y por qué deberíamos preocuparnos si nuestro código “siempre funcionó”?
Este artículo es para ti — el desarrollador Delphi que quiere entender estos conceptos sin jerga complicada, y descubrir cómo el Dext Framework puede transformar la forma en que escribes código.
La Belleza del RAD: Lo Que Delphi Hace Por Ti
Sección titulada «La Belleza del RAD: Lo Que Delphi Hace Por Ti»Antes de hablar de “problemas”, reconozcamos lo que Delphi hace muy bien. El RAD (Rapid Application Development) es una de las mayores fortalezas de la plataforma.

El Concepto de Ownership
Sección titulada «El Concepto de Ownership»Cuando arrastras un TButton a un TForm, algo mágico sucede detrás de escenas:
// Delphi genera esto automáticamente en el .DFM:object Button1: TButton Left = 100 Top = 50 Caption = 'Clic'endEl TForm se convierte en el Owner (propietario) del TButton. Esto significa:
- ✅ Gestión automática de memoria: Cuando el Form se destruye, todos los componentes hijos se destruyen junto con él.
- ✅ Serialización de estado: El archivo
.DFMguarda y restaura todas las propiedades. - ✅ Diseño visual: Ves lo que estás construyendo en tiempo real.
Por Qué Esto Funciona Bien
Sección titulada «Por Qué Esto Funciona Bien»Para aplicaciones simples y medianas, este modelo es perfecto:
procedure TForm1.ButtonGuardarClick(Sender: TObject);begin FDQuery1.SQL.Text := 'INSERT INTO Clientes (Nombre) VALUES (:Nombre)'; FDQuery1.ParamByName('Nombre').AsString := EditNombre.Text; FDQuery1.ExecSQL; ShowMessage('¡Guardado exitosamente!');end;Funciona. Es rápido de escribir. El cliente recibe el software funcionando en días, no meses.
El Problema: Cuando el RAD Se Vuelve Una Trampa
Sección titulada «El Problema: Cuando el RAD Se Vuelve Una Trampa»¿Pero qué pasa cuando tu sistema crece? Cuando tienes 50 formularios, 200 queries, y necesitas:
- ¿Agregar una nueva base de datos?
- ¿Escribir tests automatizados?
- ¿Cambiar el componente de envío de emails?
- ¿Hacer que el código funcione en móvil?

Qué Es Acoplamiento (Explicación Simple)
Sección titulada «Qué Es Acoplamiento (Explicación Simple)»Acoplamiento significa que partes de tu código están “pegadas” — una depende directamente de la otra.
Mira este ejemplo típico:
unit UFormClientes;
interfaceuses Vcl.Forms, FireDAC.Comp.Client, Data.DB;
type TFormClientes = class(TForm) FDQuery1: TFDQuery; // <-- Dependencia directa FDConnection1: TFDConnection; // <-- Dependencia directa procedure ButtonGuardarClick(Sender: TObject); end;
implementation
procedure TFormClientes.ButtonGuardarClick(Sender: TObject);begin // El Form sabe EXACTAMENTE cómo guardar en la base de datos FDQuery1.Connection := FDConnection1; FDQuery1.SQL.Text := 'INSERT INTO Clientes...'; FDQuery1.ExecSQL;end;Problemas con este código:
| Problema | Consecuencia |
|---|---|
| Form conoce SQL | No puede reutilizar lógica en otro lugar |
| Form conoce FireDAC | Cambiar a dbExpress requiere reescribir todo |
| Lógica en evento | Imposible testear sin abrir el formulario |
| Todo junto | Cualquier cambio puede romper algo inesperado |
La Metáfora del Chicle
Sección titulada «La Metáfora del Chicle»Piensa en código acoplado como chicle mascado: todo se pega a todo. Cuando intentas tirar de una parte, las otras vienen junto.
Código desacoplado es como piezas de LEGO: cada pieza tiene una función clara, encaja en un lugar específico, y puede ser intercambiada sin destruir la estructura.
La Solución: Interfaces e Inyección de Dependencias
Sección titulada «La Solución: Interfaces e Inyección de Dependencias»La solución al acoplamiento tiene dos pilares:
- Interfaces: Contratos que definen QUÉ hace algo, no CÓMO lo hace
- Inyección de Dependencias (DI): En lugar de crear dependencias, las recibes

Ejemplo Práctico: Antes y Después
Sección titulada «Ejemplo Práctico: Antes y Después»❌ ANTES (Acoplado):
procedure TFormClientes.Guardar;var Query: TFDQuery;begin Query := TFDQuery.Create(nil); // <-- Crea directamente try Query.Connection := FDConnection1; Query.SQL.Text := 'INSERT INTO Clientes...'; Query.ExecSQL; finally Query.Free; end;end;✅ DESPUÉS (Desacoplado):
type IClienteRepository = interface ['{GUID}'] procedure Guardar(const Cliente: TCliente); end;
procedure TClienteService.Guardar(const Cliente: TCliente);begin FRepository.Guardar(Cliente); // <-- No sabe cómo funciona internamenteend;La diferencia fundamental:
- Antes: El código decide CÓMO hacerlo (crea query, conecta, ejecuta)
- Después: El código solo pide a alguien que lo haga (
FRepository)
Dext en la Práctica: Código Real
Sección titulada «Dext en la Práctica: Código Real»El Dext Framework trae toda esta arquitectura moderna a Delphi de forma simple y elegante.

Hello World con Dext
Sección titulada «Hello World con Dext»program HelloDext;
{$APPTYPE CONSOLE}
uses Dext.Web;
begin TDextWebHost.CreateDefaultBuilder .Configure(procedure(App: IApplicationBuilder) begin App.MapGet('/hello', procedure(Ctx: IHttpContext) begin Ctx.Response.Write('¡Hola, Dext!'); end); end) .Build .Run;end.Es todo. En 15 líneas tienes un servidor HTTP funcionando.
Registrando Servicios con DI
Sección titulada «Registrando Servicios con DI»La magia de Dext está en el contenedor de DI:
TDextWebHost.CreateDefaultBuilder .ConfigureServices(procedure(Services: IServiceCollection) begin // Interface → Implementación Services.AddScoped<IClienteRepository, TClienteRepository>; Services.AddScoped<IClienteService, TClienteService>; Services.AddSingleton<ILogger, TConsoleLogger>; end) .Configure(procedure(App: IApplicationBuilder) begin App.MapPost('/clientes', procedure(Ctx: IHttpContext) var Service: IClienteService; begin // Dext inyecta automáticamente el servicio correcto Service := Ctx.Services.GetRequiredService<IClienteService>; Service.Crear(Ctx.Request.Body.FromJson<TCliente>); Ctx.Response.StatusCode := 201; end); end) .Build .Run;Inyección Automática en el Constructor
Sección titulada «Inyección Automática en el Constructor»La forma más elegante es usar inyección por constructor:
type TClienteService = class(TInterfacedObject, IClienteService) private FRepository: IClienteRepository; FLogger: ILogger; public constructor Create(Repository: IClienteRepository; Logger: ILogger); procedure Crear(const Cliente: TCliente); end;
constructor TClienteService.Create(Repository: IClienteRepository; Logger: ILogger);begin FRepository := Repository; // Recibido, no creado FLogger := Logger; // Recibido, no creadoend;
procedure TClienteService.Crear(const Cliente: TCliente);begin FLogger.Info('Creando cliente: ' + Cliente.Nombre); FRepository.Guardar(Cliente);end;Cuando registras TClienteService en el contenedor, Dext automáticamente:
- Encuentra las dependencias (
IClienteRepository,ILogger) - Resuelve cada una
- Las pasa al constructor
- Retorna la instancia lista
Tests Unitarios: La Prueba de Fuego
Sección titulada «Tests Unitarios: La Prueba de Fuego»La mayor ventaja del código desacoplado es la testabilidad. Mira cómo testear TClienteService sin base de datos:
[TestFixture]TClienteServiceTest = classpublic [Test] procedure Crear_DebeLoguearYLlamarRepositorio;end;
procedure TClienteServiceTest.Crear_DebeLoguearYLlamarRepositorio;var MockRepo: Mock<IClienteRepository>; MockLogger: Mock<ILogger>; Service: IClienteService; Cliente: TCliente;begin // Arrange: Crea mocks MockRepo := Mock<IClienteRepository>.Create; MockLogger := Mock<ILogger>.Create;
// Crea servicio con dependencias falsas Service := TClienteService.Create(MockRepo.Instance, MockLogger.Instance);
Cliente := TCliente.Create; Cliente.Nombre := 'Juan';
// Act: Ejecuta Service.Crear(Cliente);
// Assert: Verifica comportamiento MockLogger.Received.Info(Arg.Contains('Juan')); MockRepo.Received.Guardar(Cliente);end;Lo que este test prueba:
- El servicio funciona correctamente
- Loguea la acción
- Llama al repositorio
- Todo esto sin base de datos, sin servidor, sin red
Scopes de Vida: Singleton, Scoped, Transient
Sección titulada «Scopes de Vida: Singleton, Scoped, Transient»Dext gestiona automáticamente el ciclo de vida de los objetos:
| Scope | Comportamiento | Uso Común |
|---|---|---|
| Singleton | Una instancia para toda la aplicación | Logger, Configuración, Cache |
| Scoped | Una instancia por request HTTP | DbContext, UserSession |
| Transient | Nueva instancia cada vez que pides | Validadores, Factories |
Services.AddSingleton<ILogger, TConsoleLogger>; // Creado una vezServices.AddScoped<IDbContext, TAppDbContext>; // Uno por requestServices.AddTransient<IValidator, TValidator>; // Siempre nuevoConclusión: Lo Mejor de Ambos Mundos
Sección titulada «Conclusión: Lo Mejor de Ambos Mundos»Dext no vino para reemplazar el RAD — vino para complementarlo.
| Usa RAD Cuando | Usa Dext Cuando |
|---|---|
| Prototipos rápidos | Aplicaciones enterprise |
| CRUDs simples | APIs REST y microservicios |
| Herramientas internas | Código que necesita tests |
| Una persona en el proyecto | Equipos colaborando |
La belleza está en poder elegir la herramienta correcta para cada situación. Y cuando necesites arquitectura sólida, testabilidad y mantenibilidad, Dext está listo para ayudar.
Próximos Pasos
Sección titulada «Próximos Pasos»- 📖 Documentación Completa de Dext
- 🚀 Ejemplo: Hello World Minimal API
- 🏗️ Enterprise Patterns con Dext
- 🔄 Programación Asíncrona
Dext Framework — Performance nativa, productividad moderna.