Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ Este documento estabelece as regras de design e desenvolvimento do framework Hor
* Para encerramentos coordenados e seguros (probes de saúde no Kubernetes/Load Balancers), use sempre `THorse.StopListenGraceful(TimeoutMS)`.
* As propriedades `ActiveRequests` e `IsShuttingDown` estão expostas estaticamente no facade `THorse` e devem ser usadas em endpoints de `/health` ou monitoramento de observabilidade APM.

## 🟢 Gerenciamento e Injeção de Dependências (Request Scope)
* O Horse expõe a propriedade `Services` na classe de requisição `THorseRequest`, provendo um container IoC local e thread-safe para o escopo do request.
* Para serviços que devem ser destruídos automaticamente ao final da requisição (evitando vazamento de memória), registre-os usando `Req.Services.Add(TClass, Instance)`.
* Para inicialização sob demanda (Lazy Loading), use `Req.Services.AddFactory(TClass, FactoryMethod)`. A instância só será criada no momento da chamada de `Resolve`.
* Para obter um serviço previamente injetado, chame `Req.Services.Resolve(TClass)` e faça a coerção de tipo necessária.
* Nunca instancie dicionários de escopo ou de serviços paralelos dentro das closures de rotas; utilize sempre a infraestrutura nativa do `Services` para garantir o ciclo de vida e thread-safety coordenados pelo framework.

## 🧪 Padrões de Testes e Concorrência
* Ao escrever testes de integração que envolvam o encerramento do servidor ou simulação de tráfego, utilize sempre a biblioteca HTTP nativa do Delphi (`System.Net.HttpClient` e `System.Net.URLClient`) para garantir compatibilidade multiplataforma nativa no FPC (Lazarus/Linux) sem depender de pacotes externos.
* Em testes de shutdown ou concorrência física, utilize o cabeçalho `Connection: close` na requisição do cliente HTTP para forçar a liberação imediata do socket no sistema operacional, evitando travamento de pools de conexão físicos.
160 changes: 160 additions & 0 deletions doc/dependency-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Contextual Dependency Injection (Request Scope)

*Read this in [English](./dependency-injection.md) or [Português (BR)](./dependency-injection.pt-BR.md).*

**Contextual Dependency Injection (Request Scope)** in Horse enables the deterministic lifecycle management of service instances and classes directly coupled to the active HTTP request lifecycle.

By registering request-scoped services, developers ensure complete thread-safe state isolation between concurrent requests and benefit from the automatic disposal of instantiated resources at the end of the HTTP routing pipeline. This eliminates memory leaks and the need for manual `try/finally` blocks inside route closures.

---

## 🗺️ Dependency Injection Lifecycle

The lifecycle of the `Services` request property is described in the sequence diagram below:

```mermaid
sequenceDiagram
autonumber
participant Client as HTTP Client
participant Horse as THorseRequest (Services)
participant Core as THorseRequestContext (Dictionary)
participant Disposer as Automatic Disposal

Client->>Horse: HTTP Request starts
Note over Horse: Services (Lazy-initialized on first access)

rect rgb(20, 20, 30)
Note over Horse: Services Registration
Horse->>Core: Req.Services.Add(TMyService, Instance)
Note right of Core: Registered as owned instance
end

rect rgb(20, 30, 20)
Note over Horse: Services Resolution
Horse->>Core: Req.Services.Resolve(TMyService)
Core-->>Horse: Returns typed instance
end

Client->>Horse: HTTP Routing pipeline finishes
Horse->>Disposer: THorseRequest.Clear or Destroy triggered
Disposer->>Core: Triggers FreeAndNil(FServices)
Note over Core: Destroys all owned instances (doOwnsValues)
Note over Core: Disposal Completed (Zero Memory Leaks!)
```

---

## 🛠️ Injection and Registration Modes

The `Services` property offers two main ways to register dependencies, each with specific behaviors:

### 1. Direct Instance Injection (`Add`)
Registers a previously created object instance in the context of the current request. By default, the context class takes ownership of the object and destroys it automatically at the end of the request.

```delphi
Req.Services.Add(TMyService, TMyService.Create);
```

### 2. Lazy Injection via Factory (`AddFactory`)
Registers a factory delegate that defines how to create the service on-demand (*Lazy Loading*). The service is only physically instantiated at the exact moment it is resolved (when calling `Resolve`). Once instantiated, it is cached in the context of the current request and automatically destroyed when the request ends.

```delphi
Req.Services.AddFactory(TMyService,
function: TObject
begin
Result := TMyService.Create;
end);
```

---

## 💻 Complete Practical Example

```delphi
program ConsoleDependencyInjection;

{$APPTYPE CONSOLE}

uses
Horse, Horse.Commons, System.SysUtils;

type
TMyService = class
private
FId: string;
public
constructor Create(const AId: string);
destructor Destroy; override;
function GetMessage: string;
end;

{ TMyService }

constructor TMyService.Create(const AId: string);
begin
inherited Create;
FId := AId;
Writeln(Format('[TMyService] Instantiated with ID: %s', [FId]));
end;

destructor TMyService.Destroy;
begin
Writeln(Format('[TMyService] Destroyed with ID: %s (Automatically cleaned up)', [FId]));
inherited Destroy;
end;

function TMyService.GetMessage: string;
begin
Result := 'Hello from a Contextual Service! ID: ' + FId;
end;

begin
// Route 1: Using Direct Instance Injection
THorse.Get('/resolve',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LService: TMyService;
begin
LService := TMyService.Create('Direct');
Req.Services.Add(TMyService, LService);
Next();
end,
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LService: TMyService;
begin
LService := TMyService(Req.Services.Resolve(TMyService));
Res.Send(LService.GetMessage);
end);

// Route 2: Using Lazy Factory (Lazy Loading)
THorse.Get('/lazy',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Req.Services.AddFactory(TMyService,
function: TObject
begin
Result := TMyService.Create('Lazy');
end);
Next();
end,
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LService: TMyService;
begin
// The factory will be executed and the service instantiated only on the line below!
LService := TMyService(Req.Services.Resolve(TMyService));
Res.Send(LService.GetMessage);
end);

THorse.Listen(9000);
end.
```

---

## 📈 Architectural Benefits

1. **Deterministic and Automatic Lifecycle:** Ensures resources are safely disposed of at the end of the HTTP routing pipeline, eliminating memory leaks.
2. **Concurrent State Isolation:** Fully thread-safe, allowing each concurrent request to process its service instances in complete isolation, avoiding race conditions.
3. **Native Lazy Initialization:** Reduced RAM footprint and faster request processing overheads using `AddFactory`, instantiating services only when required by the executed route.
160 changes: 160 additions & 0 deletions doc/dependency-injection.pt-BR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Injeção de Dependência Contextual (Request Scope)

*Read this in [English](./dependency-injection.md) or [Português (BR)](./dependency-injection.pt-BR.md).*

O **Gerenciamento e Injeção de Dependência Contextual (Request Scope)** no Horse permite o gerenciamento determinístico do ciclo de vida de instâncias de serviços e classes acopladas diretamente ao ciclo de vida da requisição HTTP ativa.

Ao registrar serviços em escopo de requisição, o desenvolvedor garante o isolamento completo de estado entre requisições concorrentes (thread-safe) e conta com o descarte automático dos recursos instanciados ao final do pipeline de roteamento HTTP, eliminando por completo vazamentos de memória (memory leaks) e a necessidade de blocos `try/finally` manuais nas closures de rotas.

---

## 🗺️ Ciclo de Vida da Injeção de Dependências

O ciclo de vida da propriedade `Services` na requisição segue a sequência descrita no diagrama abaixo:

```mermaid
sequenceDiagram
autonumber
participant Cliente as Cliente HTTP
participant Horse as THorseRequest (Services)
participant Core as THorseRequestContext (Dicionário)
participant Destrutor as Destruição Automática

Cliente->>Horse: Requisição HTTP iniciada
Note over Horse: Services (Lazy-initialized no primeiro acesso)

rect rgb(20, 20, 30)
Note over Horse: Registro de Serviços
Horse->>Core: Req.Services.Add(TMyService, Instance)
Note right of Core: Registrado como instância pertencente
end

rect rgb(20, 30, 20)
Note over Horse: Resolução de Serviços
Horse->>Core: Req.Services.Resolve(TMyService)
Core-->>Horse: Retorna instância tipada
end

Cliente->>Horse: Pipeline de roteamento é finalizado
Horse->>Destrutor: THorseRequest.Clear ou Destroy é acionado
Destrutor->>Core: Dispara FreeAndNil(FServices)
Note over Core: Destrói todas as instâncias pertencentes (doOwnsValues)
Note over Core: Descarte Concluído (Zero Memory Leaks!)
```

---

## 🛠️ Modos de Injeção e Registro

A propriedade `Services` fornece duas formas principais de registro de dependências com comportamentos específicos:

### 1. Injeção de Instância Direta (`Add`)
Registra uma instância de objeto previamente criada no contexto da requisição corrente. Por padrão, a classe gerenciadora assume a propriedade (ownership) do objeto, descartando-o automaticamente ao final do request.

```delphi
Req.Services.Add(TMyService, TMyService.Create);
```

### 2. Injeção Preguiçosa via Fábrica (`AddFactory`)
Registra um delegate de fábrica (factory method) que define como criar o serviço sob demanda (*Lazy Loading*). O serviço só é instanciado fisicamente no primeiro momento em que for resolvido (chamada de `Resolve`). Uma vez instanciado, ele é cacheado no contexto da requisição corrente e destruído automaticamente ao término da requisição.

```delphi
Req.Services.AddFactory(TMyService,
function: TObject
begin
Result := TMyService.Create;
end);
```

---

## 💻 Exemplo Prático Completo

```delphi
program ConsoleDependencyInjection;

{$APPTYPE CONSOLE}

uses
Horse, Horse.Commons, System.SysUtils;

type
TMyService = class
private
FId: string;
public
constructor Create(const AId: string);
destructor Destroy; override;
function GetMessage: string;
end;

{ TMyService }

constructor TMyService.Create(const AId: string);
begin
inherited Create;
FId := AId;
Writeln(Format('[TMyService] Instanciado com ID: %s', [FId]));
end;

destructor TMyService.Destroy;
begin
Writeln(Format('[TMyService] Destruído com ID: %s (Limpo de forma automática)', [FId]));
inherited Destroy;
end;

function TMyService.GetMessage: string;
begin
Result := 'Olá de um Serviço Contextual! ID: ' + FId;
end;

begin
// Rota 1: Usando Injeção de Instância Direta
THorse.Get('/resolve',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LService: TMyService;
begin
LService := TMyService.Create('Direto');
Req.Services.Add(TMyService, LService);
Next();
end,
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LService: TMyService;
begin
LService := TMyService(Req.Services.Resolve(TMyService));
Res.Send(LService.GetMessage);
end);

// Rota 2: Usando Lazy Factory (Carregamento Preguiçoso)
THorse.Get('/lazy',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Req.Services.AddFactory(TMyService,
function: TObject
begin
Result := TMyService.Create('Lazy');
end);
Next();
end,
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LService: TMyService;
begin
// A fábrica só será executada e o serviço só será instanciado na linha abaixo!
LService := TMyService(Req.Services.Resolve(TMyService));
Res.Send(LService.GetMessage);
end);

THorse.Listen(9000);
end.
```

---

## 📈 Benefícios Arquiteturais

1. **Ciclo de Vida Determinístico e Automático:** Garante o descarte seguro de recursos ao término do pipeline HTTP da requisição ativa, eliminando memory leaks.
2. **Isolamento de Estado Concorrente:** Totalmente thread-safe, permitindo que cada thread/request trate suas instâncias de serviços de forma isolada, evitando race conditions.
3. **Lazy Initialization nativa:** Redução no consumo de RAM e no tempo de inicialização de recursos pesados por meio do `AddFactory`, carregando somente o que é realmente demandado pela rota executada.
2 changes: 2 additions & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ graph TD
| [Request & Response](./request-response.md) | `THorseRequest` (body, params, query, headers, cookies, sessions, multipart). `THorseResponse` (`Send`, `Status`, `ContentType`, `AddHeader`, `RedirectTo`, `SendFile`, `Download`, `RawWebResponse`). |
| [Middleware](./middleware.md) | The `Next` proc model; built-in vs custom; registration order; per-route vs global. |
| [Lifecycle Hooks](./lifecycle-hooks.md) | Request lifecycle hooks (`onRequest`, `preParsing`, `preValidation`, `onSend`, `onResponse`) to extend and intercept request/response pipelines. |
| [Dependency Injection](./dependency-injection.md) | Lifecycle management and IoC on request scope (direct and lazy injectors). |
| [Graceful Shutdown](./graceful-shutdown.md) | Coordinated connection shutdown in production environments (cloud/Kubernetes); telemetry properties `ActiveRequests` and flag `IsShuttingDown`. |
| [Writing a Middleware](./writing-middleware.md) | Authoring a production-quality middleware: skeleton, configuration patterns, thread safety, Provider-neutral coding, cross-compiler pitfalls, testing matrix, Boss packaging, publishing. |
| [Providers & Application types](./providers.md) | The two-axis model: **Provider** (transport — Indy default; CrossSocket, mORMot2, ICS optional; HttpSys, epoll and IOCP built-in) × **Application type** (Console / VCL / Daemon / LCL / HTTPApplication, plus host-managed Apache / ISAPI / CGI / FCGI). Compatibility matrix and selection guidance. |
Expand All @@ -62,6 +63,7 @@ doc/
├── request-response.md ← THorseRequest and THorseResponse API
├── middleware.md ← chaining handlers
├── lifecycle-hooks.md ← request lifecycle hooks (onRequest, etc.)
├── dependency-injection.md ← contextual dependency injection on request scope
├── graceful-shutdown.md ← graceful connection shutdown in production
├── providers.md ← choosing a transport
├── iocp.md ← Windows async I/O completion ports
Expand Down
2 changes: 2 additions & 0 deletions doc/index.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ graph TD
| [Request e Response](./request-response.pt-BR.md) | `THorseRequest` (body, params, query, headers, cookies, sessions, multipart). `THorseResponse` (`Send`, `Status`, `ContentType`, `AddHeader`, `RedirectTo`, `SendFile`, `Download`, `RawWebResponse`). |
| [Middleware](./middleware.pt-BR.md) | O modelo `Next` proc; built-in vs custom; ordem de registro; por-rota vs global. |
| [Ganchos de Ciclo de Vida](./lifecycle-hooks.pt-BR.md) | Ganchos de ciclo de vida (`onRequest`, `preParsing`, `preValidation`, `onSend`, `onResponse`) para estender e interceptar requisições. |
| [Injeção de Dependência](./dependency-injection.pt-BR.md) | Gerenciamento de ciclo de vida e IoC no request scope (injetores direct e lazy). |
| [Desligamento Suave](./graceful-shutdown.pt-BR.md) | Encerramento coordenado de conexões em ambientes produtivos (nuvem/Kubernetes); propriedades de telemetria `ActiveRequests` e flag `IsShuttingDown`. |
| [Criando um Middleware](./writing-middleware.pt-BR.md) | Criando um middleware de qualidade de produção: esqueleto, padrões de configuração, thread safety, código neutro a Provider, armadilhas entre compiladores, matriz de testes, empacotamento Boss, publicação. |
| [Providers e Tipos de aplicação](./providers.pt-BR.md) | O modelo de dois eixos: **Provider** (transporte — Indy padrão; CrossSocket, mORMot2, ICS opcionais; HttpSys, epoll e IOCP embutidos) × **Tipo de aplicação** (Console / VCL / Daemon / LCL / HTTPApplication, mais host-managed Apache / ISAPI / CGI / FCGI). Matriz de compatibilidade e guia de escolha. |
Expand All @@ -62,6 +63,7 @@ doc/
├── request-response.*.md ← API de THorseRequest e THorseResponse
├── middleware.*.md ← encadeamento de handlers
├── lifecycle-hooks.*.md ← ganchos de ciclo de vida (onRequest, etc.)
├── dependency-injection.*.md ← injeção de dependências no request scope
├── graceful-shutdown.*.md ← desligamento suave em produção
├── providers.*.md ← escolha de transporte
├── iocp.*.md ← portas de conclusão assíncronas (Windows)
Expand Down
8 changes: 4 additions & 4 deletions doc/roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ Este documento detalha o planejamento de melhorias arquiteturais de longo prazo

### 8. Roteamento Avançado (Regex e Parâmetros Opcionais)
* **Descrição:** Permitir parâmetros opcionais (`/users/:id?`) e restrições de rotas baseadas em Expressões Regulares (`/users/:id(\d+)`) na árvore do Radix Router.
### 10. Injeção de Dependência Contextual (*Request Scope / Context*)
* **Descrição:** Prover um mecanismo estruturado para gerência de dependências cujo ciclo de vida está acoplado ao ciclo da requisição (ex: uma transação de banco de dados ou conexão FireDAC ativa).
* **Ganhos:**
* Facilidade na gerência de concorrência com encerramento e liberação automática de recursos após o fim da requisição.


## ✅ Evolução Arquitetural Entregue (Concluído)
Expand Down Expand Up @@ -72,6 +68,10 @@ Este documento detalha o planejamento de melhorias arquiteturais de longo prazo
* **Status:** 🟢 **Concluído e Liberado**
* **Implementação:** Desenvolvido o mecanismo de encerramento coordenado no Core e Provedor de Console (Indy), permitindo interromper novas escutas físicas do socket enquanto as requisições ativas (`ActiveRequests`) são concluídas de forma suave sob um timeout de segurança, expondo as propriedades de telemetria `ActiveRequests` e `IsShuttingDown` (sinalização para Kubernetes/Load Balancers).

### 8. Injeção de Dependência Contextual (Request Scope)
* **Status:** 🟢 **Concluído e Liberado**
* **Implementação:** Desenvolvida a propriedade de ciclo de vida `Services` na classe `THorseRequest`, provendo um container de inversão de controle (IoC) thread-safe que permite injeção direta de instâncias e carregamento preguiçoso (lazy loading) via fábricas com descarte físico e destruição automáticos e determinísticos ao término do pipeline HTTP da requisição ativa.

---

## ✅ Entregas Recentes de Testes & CI/CD (Concluído)
Expand Down
Loading