Ir al contenido

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.

Ownership de Componentes en Delphi

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'
end

El 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 .DFM guarda y restaura todas las propiedades.
  • Diseño visual: Ves lo que estás construyendo en tiempo real.

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?

Acoplamiento en RAD Tradicional

Acoplamiento significa que partes de tu código están “pegadas” — una depende directamente de la otra.

Mira este ejemplo típico:

unit UFormClientes;
interface
uses
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:

ProblemaConsecuencia
Form conoce SQLNo puede reutilizar lógica en otro lugar
Form conoce FireDACCambiar a dbExpress requiere reescribir todo
Lógica en eventoImposible testear sin abrir el formulario
Todo juntoCualquier cambio puede romper algo inesperado

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:

  1. Interfaces: Contratos que definen QUÉ hace algo, no CÓMO lo hace
  2. Inyección de Dependencias (DI): En lugar de crear dependencias, las recibes

Arquitectura Desacoplada con DI

❌ 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 internamente
end;

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)

El Dext Framework trae toda esta arquitectura moderna a Delphi de forma simple y elegante.

Reducción de Código 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.

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;

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 creado
end;
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:

  1. Encuentra las dependencias (IClienteRepository, ILogger)
  2. Resuelve cada una
  3. Las pasa al constructor
  4. Retorna la instancia lista

La mayor ventaja del código desacoplado es la testabilidad. Mira cómo testear TClienteService sin base de datos:

[TestFixture]
TClienteServiceTest = class
public
[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:

ScopeComportamientoUso Común
SingletonUna instancia para toda la aplicaciónLogger, Configuración, Cache
ScopedUna instancia por request HTTPDbContext, UserSession
TransientNueva instancia cada vez que pidesValidadores, Factories
Services.AddSingleton<ILogger, TConsoleLogger>; // Creado una vez
Services.AddScoped<IDbContext, TAppDbContext>; // Uno por request
Services.AddTransient<IValidator, TValidator>; // Siempre nuevo

Dext no vino para reemplazar el RAD — vino para complementarlo.

Usa RAD CuandoUsa Dext Cuando
Prototipos rápidosAplicaciones enterprise
CRUDs simplesAPIs REST y microservicios
Herramientas internasCódigo que necesita tests
Una persona en el proyectoEquipos 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.



Dext Framework — Performance nativa, productividad moderna.