No desenvolvimento de APIs modernas e microsserviços com Delphi e Lazarus, o Horse se consolidou como o framework mais querido devido à sua simplicidade, leveza e alta velocidade. No entanto, à medida que os sistemas corporativos crescem, a necessidade de organização arquitetural, controle físico sobre conexões e conformidade com nuvens (como Docker e Kubernetes) torna-se crucial.
Recentemente, o ecossistema do Horse recebeu uma atualização de peso, focada justamente em infraestrutura corporativa: o suporte a Server Lifecycle Hooks (Ganchos de Ciclo de Vida do Servidor), o Graceful Shutdown (Desligamento Suave) unificado e o padrão de inicialização estruturada via UseStartup. Neste artigo, vamos explorar essas novidades e construir um exemplo didático e completo.
1. Ciclo de Vida do Servidor (Server Lifecycle Hooks) & Graceful Shutdown
Diferente dos hooks de requisição tradicionais (como onRequest ou onSend), os novos Server Lifecycle Hooks atuam no ciclo de vida físico do servidor de socket. Eles permitem que você intercepte momentos críticos:
- OnBeforeListen: Disparado imediatamente antes do socket físico abrir no sistema operacional (ideal para registrar portas em registries ou verificar configurações).
- OnAfterListen: Executado logo após o socket estar ativo e escutando conexões.
- OnBeforeStop: Chamado antes de iniciar a parada física do servidor.
- OnAfterStop: Disparado após a liberação total da porta física no sistema.
program ServerLifecycleDemo;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Horse;
type
TServerEventListener = class
public
// Método de objeto compatível com THorseServerLifecycleMethod
procedure OnBeforeListen(const AInstance: THorseInstance);
procedure OnAfterStop(const AInstance: THorseInstance);
end;
{ TServerEventListener }
procedure TServerEventListener.OnBeforeListen(const AInstance: THorseInstance);
begin
Writeln(' -> [MÉTODO] 1. Antes de escutar: Configurando firewalls locais no host...');
end;
procedure TServerEventListener.OnAfterStop(const AInstance: THorseInstance);
begin
Writeln(' -> [MÉTODO] 4. Após parar: Conexões encerradas de forma limpa.');
end;
var
LListener: TServerEventListener;
begin
try
LListener := TServerEventListener.Create;
try
// ========================================================
// 1. REGISTRO VIA MÉTODOS DE OBJETO (Method of Object)
// ========================================================
THorse.AddOnBeforeListen(LListener.OnBeforeListen);
THorse.AddOnAfterStop(LListener.OnAfterStop);
// ========================================================
// 2. REGISTRO VIA PROCEDIMENTOS ANÔNIMOS (Anonymous Proc)
// ========================================================
// Executa imediatamente após o socket abrir na rede
THorse.AddOnAfterListen(
procedure(const AInstance: THorseInstance)
begin
Writeln(' -> [ANÔNIMO] 2. Servidor online! Escutando em http://', AInstance.Host, ':', AInstance.Port);
end);
// Executa assim que a sinalização de Stop é recebida, antes de fechar o socket
THorse.AddOnBeforeStop(
procedure(const AInstance: THorseInstance)
begin
Writeln(' -> [ANÔNIMO] 3. Sinal de parada capturado! Iniciando drenagem de requisições...');
end);
// Rota de teste
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc)
begin
Res.Send('pong');
end);
// Inicializa a escuta física (dispara OnBeforeListen e OnAfterListen)
THorse.Listen(9000);
finally
LListener.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Aliado a isso, temos o Graceful Shutdown. Em ambientes de microsserviços modernos, os balanceadores de carga precisam que um contêiner escoe as requisições em trânsito antes de ser finalizado. A nova infraestrutura gerencia a contagem de conexões ativas e garante um encerramento seguro sob um tempo limite (timeout) configurável.
2. Organização Arquitetural com UseStartup
Inspirado no padrão de bootstrapping adotado pelo ASP.NET Core, a funcionalidade UseStartup resolve um dos maiores problemas de projetos Delphi que crescem demais: o arquivo principal do projeto (o .dpr) virar um “monólito de rotas” gigante e bagunçado.
Ao implementar a interface IHorseStartup, isolamos a configuração de middlewares, hooks e rotas dentro de uma classe dedicada, mantendo o ponto de entrada da aplicação extremamente limpo e focado no controle de inicialização.
3. Exemplo Prático e Didático
Vamos criar uma aplicação console clássica estruturada. Primeiramente, criamos a unit de Startup que encapsulará todas as regras de registro do nosso servidor:
unit Startup;
interface
uses
Horse, System.SysUtils;
type
TMyStartup = class(TInterfacedObject, IHorseStartup)
private
procedure RegisterMiddlewares(const AInstance: THorseInstance);
procedure RegisterRoutes(const AInstance: THorseInstance);
procedure RegisterLifecycleHooks(const AInstance: THorseInstance);
public
procedure Configure(const AInstance: THorseInstance);
end;
implementation
{ TMyStartup }
procedure TMyStartup.Configure(const AInstance: THorseInstance);
begin
// Configurações Globais da Instância
ReportMemoryLeaksOnShutdown := True;
RegisterLifecycleHooks(AInstance);
RegisterMiddlewares(AInstance);
RegisterRoutes(AInstance);
end;
procedure TMyStartup.RegisterLifecycleHooks(const AInstance: THorseInstance);
begin
// Monitoramento físico do ciclo de vida
AInstance.AddOnBeforeListen(
procedure(const AInst: THorseInstance)
begin
Writeln(' -> [LOG] Preparando para abrir o socket físico...');
end);
AInstance.AddOnAfterListen(
procedure(const AInst: THorseInstance)
begin
Writeln(' -> [LOG] Servidor online e ativo na porta: ', AInst.Port);
end);
AInstance.AddOnBeforeStop(
procedure(const AInst: THorseInstance)
begin
Writeln(' -> [LOG] Iniciando Graceful Shutdown (aguardando finalização de requisições ativas)...');
end);
end;
procedure TMyStartup.RegisterMiddlewares(const AInstance: THorseInstance);
begin
// Adicione seus middlewares favoritos aqui (JSON Parser, CORS, etc.)
// AInstance.Use(HorseJhonson);
end;
procedure TMyStartup.RegisterRoutes(const AInstance: THorseInstance);
begin
AInstance.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc)
begin
Res.Send('pong');
end);
AInstance.Get('/api/v1/status',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TNextProc)
begin
Res.Send('{"status":"online"}');
end);
end;
end.
Agora, veja como o arquivo principal do projeto (.dpr) fica absurdamente elegante, legível e profissional:
program AppServer;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Horse,
Startup in 'Startup.pas';
var
LStartup: IHorseStartup;
begin
try
Writeln('====================================================');
Writeln(' Servidor Corporativo Delphi (UseStartup) ');
Writeln('====================================================');
// Instanciamos o configurador isolado
LStartup := TMyStartup.Create;
// Injetamos a inicialização e ativamos a escuta
THorse.UseStartup(LStartup).Listen(9000);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Conclusão
Organizar o código de inicialização de suas APIs em Delphi não é apenas uma questão de estética, mas de manutenibilidade e escalabilidade técnica. Ao isolar a configuração lógica na interface IHorseStartup e acoplar logs e monitoramentos APM diretamente nos Lifecycle Hooks, seu projeto ganha robustez para rodar em produção e facilidade para testes automatizados.
O que você achou dessa nova abordagem de bootstrapping no Delphi? Deixe seu comentário e compartilhe nas suas redes sociais!
Descubra mais sobre Régys Borges da Silveira
Assine para receber nossas notícias mais recentes por e-mail.
Dê-nos sua opinião, seu comentário ajuda o site a crescer e melhorar a qualidade dos artigos.