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!