@seedgrid/ai-toolkit 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -6
- package/dist/cli.js +7 -1
- package/dist/commands/selfinstall.js +63 -0
- package/docs/formato.md +1 -1
- package/docs/instalacao.md +1 -1
- package/package.json +1 -1
- package/seedgridKB/SeedGrid/Extensions/Catalogo-de-propriedades.md +971 -971
- package/seedgridKB/SeedGrid/Extensions/audit/Configuracao-Rust.md +93 -93
- package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao.md +186 -186
- package/seedgridKB/SeedGrid/Extensions/resilience/Visao-geral.md +112 -112
- package/seedgridKB/SeedGrid/SeedGrid-components/Indice.md +323 -323
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/API.md +103 -103
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/Visao-geral.md +86 -86
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Exemplos.md +629 -629
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Visao-geral.md +123 -123
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Exemplos.md +59 -59
- package/seedgridKB/manifest.json +1 -1
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
---
|
|
2
|
-
tags: [seedgrid, extension, resilience]
|
|
3
|
-
fonte: java/seedgrid-quarkus-ext-resilience
|
|
4
|
-
branch: main
|
|
5
|
-
commit: eff41304f8
|
|
6
|
-
analisado-em: 2026-10-14
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# SeedGrid Resilience (Quarkus) — Visão geral
|
|
10
|
-
|
|
11
|
-
Extension Quarkus de **rate limiting** e **circuit breaker** declarativos. O desenvolvedor anota métodos/classes CDI com `@SeedGridRateLimit` ou `@SeedGridCircuitBreaker`; interceptors CDI aplicam a regra e guardam o estado no `SeedGridCacheProvider` (lib `seedgrid-cache`), de modo que, com um provider distribuído (Redis/Valkey/Dragonfly), o limite/circuito é compartilhado entre instâncias. A anotação define os valores padrão; com `configKey` (rate limit) ou `name` (circuit breaker) os valores podem ser sobrescritos por properties em runtime.
|
|
12
|
-
|
|
13
|
-
- Configuração completa: [[SeedGrid/Extensions/resilience/Configuracao|Configuração do resilience]]
|
|
14
|
-
- Enums (`RateLimitAlgorithm`, `KeyScope`, `SlidingWindowType`, `CircuitState`): [[SeedGrid/Extensions/resilience/Enums|Enums do resilience]]
|
|
15
|
-
- Exemplos: [[SeedGrid/Extensions/resilience/Exemplos|Exemplos do resilience]]
|
|
16
|
-
- Crate equivalente: [[SeedGrid/Extensions/resilience/Rust|Porte Rust]]
|
|
17
|
-
- Dependência de cache: [[SeedGrid/Extensions/cache/Visao-geral|Visão geral do cache]]
|
|
18
|
-
|
|
19
|
-
> Não é baseada em MicroProfile Fault Tolerance / SmallRye Fault Tolerance: não há dependência `quarkus-smallrye-fault-tolerance` nem uso de `@Retry`/`@CircuitBreaker` do MP. Propriedades no formato `<classe>/<método>/<Anotação>/<param>` **não** se aplicam. **Confirmado** (poms e imports).
|
|
20
|
-
|
|
21
|
-
## Módulos e coordenadas
|
|
22
|
-
|
|
23
|
-
| Módulo | groupId | artifactId | Versão | Papel |
|
|
24
|
-
|---|---|---|---|---|
|
|
25
|
-
| raiz (`pom.xml`) | `br.com.seedgrid` | `seedgrid-resilience-core-parent` | `2026.10.13` | POM agregador; parent `seedgrid-parent:2026.07.02`. Módulos: `deployment`, `runtime` |
|
|
26
|
-
| `runtime` | `br.com.seedgrid` | `seedgrid-resilience-core` | `2026.10.13` | Anotações, interceptors, serviços, SPI, filtros JAX-RS. Gera `META-INF/jandex.idx` (jandex-maven-plugin) |
|
|
27
|
-
| `deployment` | `br.com.seedgrid` | `seedgrid-resilience-core-deployment` | `2026.10.13` | `SeedgridResilienceCoreProcessor`: só registra a feature `seedgrid-resilience-core` |
|
|
28
|
-
| `integration-tests` | `br.com.seedgrid` | `seedgrid-resilience-core-integration-tests` | — | **Não** listado em `<modules>` do POM raiz; contém apenas um recurso "hello" de template |
|
|
29
|
-
|
|
30
|
-
Observações:
|
|
31
|
-
- `quarkus-extension.yaml`: nome "SeedGrid Resilience Core", status `preview`.
|
|
32
|
-
|
|
33
|
-
## Dependências
|
|
34
|
-
|
|
35
|
-
| Dependência | Escopo | Uso |
|
|
36
|
-
|---|---|---|
|
|
37
|
-
| `io.quarkus:quarkus-arc` | compile | CDI / interceptors |
|
|
38
|
-
| `br.com.seedgrid:seedgrid-cache-core:2026.10.13` | compile | `SeedGridCacheProvider` (`increment`, `getBytes`, `putBytes`, `withLock`) |
|
|
39
|
-
| `jakarta.ws.rs:jakarta.ws.rs-api` | provided | `RateLimitExceptionMapper`, `RateLimitContainerResponseFilter` |
|
|
40
|
-
| `io.quarkus:quarkus-vertx-http` | provided | `IpIdentityResolver` (injeção opcional de `HttpServerRequest`) |
|
|
41
|
-
| `seedgrid-cache-redis`, `quarkus-redis-client`, testcontainers, mockito | test | Testes |
|
|
42
|
-
|
|
43
|
-
Provider de cache: o `seedgrid-cache-core` traz `DefaultSeedGridCache` como `@DefaultBean` (em memória); os providers `seedgrid-cache-redis`/`-valkey`/`-dragonfly` são `@Alternative`. Sem um provider distribuído no classpath, o estado de rate limit/circuit breaker fica **por processo** (`DefaultSeedGridCache`, `@DefaultBean` em `seedgrid-cache-core`; o teste `SeedgridResilienceCoreTest` sobe sem provider nem config). **Confirmado**.
|
|
44
|
-
|
|
45
|
-
## Arquitetura e fluxo
|
|
46
|
-
|
|
47
|
-
```mermaid
|
|
48
|
-
flowchart LR
|
|
49
|
-
CALL["Chamada a método anotado"] --> RLI["RateLimitInterceptor<br/>prioridade LIBRARY_BEFORE"]
|
|
50
|
-
RLI --> RLS["RateLimitService<br/>(lê seedgrid.ratelimit.{configKey}.*)"]
|
|
51
|
-
RLS --> IDR["IdentityResolver"]
|
|
52
|
-
RLS --> ALG["Fixed / Sliding / Token bucket"]
|
|
53
|
-
ALG --> CACHE[("SeedGridCacheProvider")]
|
|
54
|
-
RLI -->|"negado"| EX429["RateLimitExceededException → 429"]
|
|
55
|
-
RLI --> CBI["CircuitBreakerInterceptor<br/>prioridade LIBRARY_BEFORE+10"]
|
|
56
|
-
CBI --> CBR["CircuitBreakerConfigResolver<br/>(lê seedgrid.circuitbreaker.{name}.*)"]
|
|
57
|
-
CBI --> STORE["CacheBackedCircuitBreakerStore"] --> CACHE
|
|
58
|
-
CBI --> M["método de negócio / fallbackMethod"]
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Rate limit (`@SeedGridRateLimit`)
|
|
62
|
-
1. `RateLimitInterceptor` resolve a anotação: método → método na classe alvo → hierarquia de classes → interfaces.
|
|
63
|
-
2. `RateLimitService#tryAcquire`: se `configKey` não vazio e `seedgrid.ratelimit.{configKey}.enabled=false`, permite sem contar. Senão resolve identidade, permits, janela, min-spacing, burst e algoritmo (config vence anotação quando há `configKey`), valida (`permits > 0`, `window > 0`, `burstCapacity >= 0`, `minSpacing >= 0`, senão `IllegalArgumentException`).
|
|
64
|
-
3. Chave no cache: `rl:{tenant}:{operation}:{scope}:{identity}` (sem `operation` quando vazio). `tenant` vem do atributo `tenantId` do `SeedGridInvocationContext` (padrão `no-tenant`); `operation` do atributo `operation`. `scope`: `METHOD` → assinatura do método; `CLASS` → nome da classe alvo; `GROUP` → `group` (ou assinatura se vazio).
|
|
65
|
-
4. Algoritmos:
|
|
66
|
-
- `FIXED_WINDOW`: `cache.increment(key, window)`. **Não aplica `minSpacing`** (o campo é ignorado nesse algoritmo — **Confirmado**).
|
|
67
|
-
- `SLIDING_WINDOW`: lista de timestamps sob `withLock`; aplica `minSpacing`.
|
|
68
|
-
- `TOKEN_BUCKET`: capacidade = `burstCapacity` (ou `permits` se 0), reposição `permits/window`; aplica `minSpacing`.
|
|
69
|
-
5. Negado → `RateLimitExceededException`; `RateLimitExceptionMapper` (JAX-RS) responde **429** com `X-RateLimit-Limit`, `X-RateLimit-Remaining: 0`, `X-RateLimit-Reset` e `Retry-After` (segundos, arredondados para cima).
|
|
70
|
-
6. Permitido e `emitHeaders=true` → `RateLimitHeaderCarrier` (ThreadLocal) e `RateLimitContainerResponseFilter` acrescentam `X-RateLimit-Limit/Remaining/Reset` à resposta.
|
|
71
|
-
|
|
72
|
-
### Circuit breaker (`@SeedGridCircuitBreaker`)
|
|
73
|
-
1. `CircuitBreakerInterceptor` resolve a anotação (método → método na classe alvo → hierarquia).
|
|
74
|
-
2. Kill switch: `seedgrid.circuitbreaker.{name}.enabled=false` → chama o método direto.
|
|
75
|
-
3. Nome do circuito = `name` ou, se vazio, assinatura `Classe#metodo(tipos)`. Chave no cache: `cb:{nome}`.
|
|
76
|
-
4. `CacheBackedCircuitBreakerStore` (sob `withLock` de 1 s): estados `CLOSED` → `OPEN` (taxa de falha ≥ `failureRateThreshold` ou, se `slowCallDurationThresholdMs > 0`, taxa de lentas ≥ `slowCallRateThreshold`, após `minimumNumberOfCalls`) → `HALF_OPEN` após `openStateDurationMs` → `CLOSED` quando `permittedCallsInHalfOpen` sondas têm sucesso, ou volta a `OPEN` na primeira falha.
|
|
77
|
-
5. Janela: ring buffer de tamanho `slidingWindowSize`. Em `TIME_BASED`, `slidingWindowSize` é **também** a duração em segundos da janela (e o tamanho máximo do buffer). Alterar `slidingWindowSize` descarta o estado salvo. TTL do estado: `max(openStateDurationMs*2, 1h)`.
|
|
78
|
-
6. Exceções: conta como falha se for subclasse de algum `recordExceptions` e não de `ignoreExceptions` (ignore vence).
|
|
79
|
-
7. Circuito aberto → `CircuitBreakerOpenException(circuitName, retryAfterMs)`; com `fallbackMethod`, chama o fallback. O fallback também é chamado em **qualquer** exceção do método (mesmo as ignoradas). Assinaturas procuradas, em ordem: `(params..., TipoDaExceção ou superclasse)`, `(params...)`, `()`; precisa ser método **público** (`Class#getMethod`).
|
|
80
|
-
|
|
81
|
-
## APIs públicas e pontos de integração
|
|
82
|
-
|
|
83
|
-
| Tipo | Símbolo | Descrição |
|
|
84
|
-
|---|---|---|
|
|
85
|
-
| Anotação (InterceptorBinding) | `...ratelimiter.annotations.SeedGridRateLimit` | `permits`, `window`, `windowUnit`, `algorithm`, `burstCapacity`, `minSpacing`, `minSpacingUnit`, `identity`, `group`, `scope`, `emitHeaders`, `configKey` (detalhes em [[SeedGrid/Extensions/resilience/Configuracao\|Configuração]]) |
|
|
86
|
-
| Anotação (InterceptorBinding) | `...circuitbreaker.annotations.SeedGridCircuitBreaker` | `name`, `slidingWindowSize`, `windowType`, `failureRateThreshold`, `slowCallDurationThresholdMs`, `slowCallRateThreshold`, `minimumNumberOfCalls`, `openStateDurationMs`, `permittedCallsInHalfOpen`, `recordExceptions`, `ignoreExceptions`, `fallbackMethod` |
|
|
87
|
-
| SPI | `...ratelimiter.spi.IdentityResolver` | `String resolve(SeedGridInvocationContext ctx)`; prontos: `IpIdentityResolver` (X-Forwarded-For → X-Real-IP → remote address → `anonymous`), `AnonymousIdentityResolver` |
|
|
88
|
-
| SPI | `...circuitbreaker.spi.CircuitBreakerStore` | `allowCall`, `onSuccess`, `onError`; implementação `CacheBackedCircuitBreakerStore` (`@ApplicationScoped`, sem `@DefaultBean`) |
|
|
89
|
-
| SPI | `...ratelimiter.algorithm.RateLimitAlgorithmStrategy` | `acquire(SeedGridCacheProvider, RateLimitRequest)` |
|
|
90
|
-
| Contexto | `...core.SeedGridInvocationContext` / `ThreadLocalSeedGridInvocationContext` | `set/clear/runWith/callWith`; atributos `tenantId` e `operation` entram na chave do rate limit. A extension **não** popula esse contexto: o host precisa fazê-lo (senão usa `EmptySeedGridInvocationContext`) |
|
|
91
|
-
| Beans CDI | `RateLimitService`, `RateLimitService.Clock`, `CircuitBreakerConfigResolver`, algoritmos | `@ApplicationScoped` |
|
|
92
|
-
| Exceções | `RateLimitExceededException` (mapeada para 429), `CircuitBreakerOpenException` | `CircuitBreakerOpenException` **não** tem `ExceptionMapper` na extension — **Inferência**: em endpoint REST sem fallback vira erro 500 se o host não mapear |
|
|
93
|
-
| JAX-RS | `RateLimitExceptionMapper`, `RateLimitContainerResponseFilter` (`@Provider`) | Headers `X-RateLimit-*`, `Retry-After` |
|
|
94
|
-
|
|
95
|
-
Endpoints REST próprios: nenhum no runtime (o `integration-tests` tem só `GET /seedgrid-resilience-core` de template). Migrations: nenhuma. Eventos: nenhum.
|
|
96
|
-
|
|
97
|
-
## Testes existentes (não executados)
|
|
98
|
-
|
|
99
|
-
| Local | Tipo | Cobre |
|
|
100
|
-
|---|---|---|
|
|
101
|
-
| `runtime/src/test/.../ratelimiter/RateLimitAlgorithmsIT.java` | IT com Redis (Testcontainers, `disabledWithoutDocker`) | Os 3 algoritmos: limite, reset, min-spacing, burst, refill |
|
|
102
|
-
| `runtime/src/test/.../circuitbreaker/CircuitBreakerStoreIT.java` | IT com Redis | Transições de estado, slow calls, record/ignore, janela por tempo, persistência |
|
|
103
|
-
| `runtime/src/test/.../ratelimiter/services/RateLimitServiceConfigOverrideTest.java` | Unitário (Mockito `Config`) | Overrides `seedgrid.ratelimit.checkout.*`, kebab-case em `algorithm`, sem `configKey` ignora config |
|
|
104
|
-
| `runtime/src/test/.../circuitbreaker/configs/CircuitBreakerConfigResolverTest.java` | Unitário | Overrides `seedgrid.circuitbreaker.payments.*`, sem `name` ignora config, `enabled` |
|
|
105
|
-
| `runtime/src/test/.../ratelimiter/spi/IpIdentityResolverTest.java`, `AnonymousIdentityResolverTest.java` | Unitário | Resolução de identidade |
|
|
106
|
-
| `deployment/src/test/.../SeedgridResilienceCoreTest.java`, `SeedgridResilienceCoreDevModeTest.java` | QuarkusUnitTest / DevModeTest | Boot sem camada HTTP e sem config |
|
|
107
|
-
|
|
108
|
-
## Limitações
|
|
109
|
-
- `minSpacing` não tem efeito com `FIXED_WINDOW`.
|
|
110
|
-
- O kill switch do rate limiter (`enabled=false`) ainda emite headers `X-RateLimit-*` (limite = `permits` da anotação), quando `emitHeaders=true`.
|
|
111
|
-
- Valores inválidos de `algorithm`/`window-type` só falham na chamada (`IllegalArgumentException` de `Enum.valueOf`), não no boot.
|
|
112
|
-
- `CircuitBreakerOpenException` sem mapper HTTP.
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, resilience]
|
|
3
|
+
fonte: java/seedgrid-quarkus-ext-resilience
|
|
4
|
+
branch: main
|
|
5
|
+
commit: eff41304f8
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# SeedGrid Resilience (Quarkus) — Visão geral
|
|
10
|
+
|
|
11
|
+
Extension Quarkus de **rate limiting** e **circuit breaker** declarativos. O desenvolvedor anota métodos/classes CDI com `@SeedGridRateLimit` ou `@SeedGridCircuitBreaker`; interceptors CDI aplicam a regra e guardam o estado no `SeedGridCacheProvider` (lib `seedgrid-cache`), de modo que, com um provider distribuído (Redis/Valkey/Dragonfly), o limite/circuito é compartilhado entre instâncias. A anotação define os valores padrão; com `configKey` (rate limit) ou `name` (circuit breaker) os valores podem ser sobrescritos por properties em runtime.
|
|
12
|
+
|
|
13
|
+
- Configuração completa: [[SeedGrid/Extensions/resilience/Configuracao|Configuração do resilience]]
|
|
14
|
+
- Enums (`RateLimitAlgorithm`, `KeyScope`, `SlidingWindowType`, `CircuitState`): [[SeedGrid/Extensions/resilience/Enums|Enums do resilience]]
|
|
15
|
+
- Exemplos: [[SeedGrid/Extensions/resilience/Exemplos|Exemplos do resilience]]
|
|
16
|
+
- Crate equivalente: [[SeedGrid/Extensions/resilience/Rust|Porte Rust]]
|
|
17
|
+
- Dependência de cache: [[SeedGrid/Extensions/cache/Visao-geral|Visão geral do cache]]
|
|
18
|
+
|
|
19
|
+
> Não é baseada em MicroProfile Fault Tolerance / SmallRye Fault Tolerance: não há dependência `quarkus-smallrye-fault-tolerance` nem uso de `@Retry`/`@CircuitBreaker` do MP. Propriedades no formato `<classe>/<método>/<Anotação>/<param>` **não** se aplicam. **Confirmado** (poms e imports).
|
|
20
|
+
|
|
21
|
+
## Módulos e coordenadas
|
|
22
|
+
|
|
23
|
+
| Módulo | groupId | artifactId | Versão | Papel |
|
|
24
|
+
|---|---|---|---|---|
|
|
25
|
+
| raiz (`pom.xml`) | `br.com.seedgrid` | `seedgrid-resilience-core-parent` | `2026.10.13` | POM agregador; parent `seedgrid-parent:2026.07.02`. Módulos: `deployment`, `runtime` |
|
|
26
|
+
| `runtime` | `br.com.seedgrid` | `seedgrid-resilience-core` | `2026.10.13` | Anotações, interceptors, serviços, SPI, filtros JAX-RS. Gera `META-INF/jandex.idx` (jandex-maven-plugin) |
|
|
27
|
+
| `deployment` | `br.com.seedgrid` | `seedgrid-resilience-core-deployment` | `2026.10.13` | `SeedgridResilienceCoreProcessor`: só registra a feature `seedgrid-resilience-core` |
|
|
28
|
+
| `integration-tests` | `br.com.seedgrid` | `seedgrid-resilience-core-integration-tests` | — | **Não** listado em `<modules>` do POM raiz; contém apenas um recurso "hello" de template |
|
|
29
|
+
|
|
30
|
+
Observações:
|
|
31
|
+
- `quarkus-extension.yaml`: nome "SeedGrid Resilience Core", status `preview`.
|
|
32
|
+
|
|
33
|
+
## Dependências
|
|
34
|
+
|
|
35
|
+
| Dependência | Escopo | Uso |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `io.quarkus:quarkus-arc` | compile | CDI / interceptors |
|
|
38
|
+
| `br.com.seedgrid:seedgrid-cache-core:2026.10.13` | compile | `SeedGridCacheProvider` (`increment`, `getBytes`, `putBytes`, `withLock`) |
|
|
39
|
+
| `jakarta.ws.rs:jakarta.ws.rs-api` | provided | `RateLimitExceptionMapper`, `RateLimitContainerResponseFilter` |
|
|
40
|
+
| `io.quarkus:quarkus-vertx-http` | provided | `IpIdentityResolver` (injeção opcional de `HttpServerRequest`) |
|
|
41
|
+
| `seedgrid-cache-redis`, `quarkus-redis-client`, testcontainers, mockito | test | Testes |
|
|
42
|
+
|
|
43
|
+
Provider de cache: o `seedgrid-cache-core` traz `DefaultSeedGridCache` como `@DefaultBean` (em memória); os providers `seedgrid-cache-redis`/`-valkey`/`-dragonfly` são `@Alternative`. Sem um provider distribuído no classpath, o estado de rate limit/circuit breaker fica **por processo** (`DefaultSeedGridCache`, `@DefaultBean` em `seedgrid-cache-core`; o teste `SeedgridResilienceCoreTest` sobe sem provider nem config). **Confirmado**.
|
|
44
|
+
|
|
45
|
+
## Arquitetura e fluxo
|
|
46
|
+
|
|
47
|
+
```mermaid
|
|
48
|
+
flowchart LR
|
|
49
|
+
CALL["Chamada a método anotado"] --> RLI["RateLimitInterceptor<br/>prioridade LIBRARY_BEFORE"]
|
|
50
|
+
RLI --> RLS["RateLimitService<br/>(lê seedgrid.ratelimit.{configKey}.*)"]
|
|
51
|
+
RLS --> IDR["IdentityResolver"]
|
|
52
|
+
RLS --> ALG["Fixed / Sliding / Token bucket"]
|
|
53
|
+
ALG --> CACHE[("SeedGridCacheProvider")]
|
|
54
|
+
RLI -->|"negado"| EX429["RateLimitExceededException → 429"]
|
|
55
|
+
RLI --> CBI["CircuitBreakerInterceptor<br/>prioridade LIBRARY_BEFORE+10"]
|
|
56
|
+
CBI --> CBR["CircuitBreakerConfigResolver<br/>(lê seedgrid.circuitbreaker.{name}.*)"]
|
|
57
|
+
CBI --> STORE["CacheBackedCircuitBreakerStore"] --> CACHE
|
|
58
|
+
CBI --> M["método de negócio / fallbackMethod"]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Rate limit (`@SeedGridRateLimit`)
|
|
62
|
+
1. `RateLimitInterceptor` resolve a anotação: método → método na classe alvo → hierarquia de classes → interfaces.
|
|
63
|
+
2. `RateLimitService#tryAcquire`: se `configKey` não vazio e `seedgrid.ratelimit.{configKey}.enabled=false`, permite sem contar. Senão resolve identidade, permits, janela, min-spacing, burst e algoritmo (config vence anotação quando há `configKey`), valida (`permits > 0`, `window > 0`, `burstCapacity >= 0`, `minSpacing >= 0`, senão `IllegalArgumentException`).
|
|
64
|
+
3. Chave no cache: `rl:{tenant}:{operation}:{scope}:{identity}` (sem `operation` quando vazio). `tenant` vem do atributo `tenantId` do `SeedGridInvocationContext` (padrão `no-tenant`); `operation` do atributo `operation`. `scope`: `METHOD` → assinatura do método; `CLASS` → nome da classe alvo; `GROUP` → `group` (ou assinatura se vazio).
|
|
65
|
+
4. Algoritmos:
|
|
66
|
+
- `FIXED_WINDOW`: `cache.increment(key, window)`. **Não aplica `minSpacing`** (o campo é ignorado nesse algoritmo — **Confirmado**).
|
|
67
|
+
- `SLIDING_WINDOW`: lista de timestamps sob `withLock`; aplica `minSpacing`.
|
|
68
|
+
- `TOKEN_BUCKET`: capacidade = `burstCapacity` (ou `permits` se 0), reposição `permits/window`; aplica `minSpacing`.
|
|
69
|
+
5. Negado → `RateLimitExceededException`; `RateLimitExceptionMapper` (JAX-RS) responde **429** com `X-RateLimit-Limit`, `X-RateLimit-Remaining: 0`, `X-RateLimit-Reset` e `Retry-After` (segundos, arredondados para cima).
|
|
70
|
+
6. Permitido e `emitHeaders=true` → `RateLimitHeaderCarrier` (ThreadLocal) e `RateLimitContainerResponseFilter` acrescentam `X-RateLimit-Limit/Remaining/Reset` à resposta.
|
|
71
|
+
|
|
72
|
+
### Circuit breaker (`@SeedGridCircuitBreaker`)
|
|
73
|
+
1. `CircuitBreakerInterceptor` resolve a anotação (método → método na classe alvo → hierarquia).
|
|
74
|
+
2. Kill switch: `seedgrid.circuitbreaker.{name}.enabled=false` → chama o método direto.
|
|
75
|
+
3. Nome do circuito = `name` ou, se vazio, assinatura `Classe#metodo(tipos)`. Chave no cache: `cb:{nome}`.
|
|
76
|
+
4. `CacheBackedCircuitBreakerStore` (sob `withLock` de 1 s): estados `CLOSED` → `OPEN` (taxa de falha ≥ `failureRateThreshold` ou, se `slowCallDurationThresholdMs > 0`, taxa de lentas ≥ `slowCallRateThreshold`, após `minimumNumberOfCalls`) → `HALF_OPEN` após `openStateDurationMs` → `CLOSED` quando `permittedCallsInHalfOpen` sondas têm sucesso, ou volta a `OPEN` na primeira falha.
|
|
77
|
+
5. Janela: ring buffer de tamanho `slidingWindowSize`. Em `TIME_BASED`, `slidingWindowSize` é **também** a duração em segundos da janela (e o tamanho máximo do buffer). Alterar `slidingWindowSize` descarta o estado salvo. TTL do estado: `max(openStateDurationMs*2, 1h)`.
|
|
78
|
+
6. Exceções: conta como falha se for subclasse de algum `recordExceptions` e não de `ignoreExceptions` (ignore vence).
|
|
79
|
+
7. Circuito aberto → `CircuitBreakerOpenException(circuitName, retryAfterMs)`; com `fallbackMethod`, chama o fallback. O fallback também é chamado em **qualquer** exceção do método (mesmo as ignoradas). Assinaturas procuradas, em ordem: `(params..., TipoDaExceção ou superclasse)`, `(params...)`, `()`; precisa ser método **público** (`Class#getMethod`).
|
|
80
|
+
|
|
81
|
+
## APIs públicas e pontos de integração
|
|
82
|
+
|
|
83
|
+
| Tipo | Símbolo | Descrição |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| Anotação (InterceptorBinding) | `...ratelimiter.annotations.SeedGridRateLimit` | `permits`, `window`, `windowUnit`, `algorithm`, `burstCapacity`, `minSpacing`, `minSpacingUnit`, `identity`, `group`, `scope`, `emitHeaders`, `configKey` (detalhes em [[SeedGrid/Extensions/resilience/Configuracao\|Configuração]]) |
|
|
86
|
+
| Anotação (InterceptorBinding) | `...circuitbreaker.annotations.SeedGridCircuitBreaker` | `name`, `slidingWindowSize`, `windowType`, `failureRateThreshold`, `slowCallDurationThresholdMs`, `slowCallRateThreshold`, `minimumNumberOfCalls`, `openStateDurationMs`, `permittedCallsInHalfOpen`, `recordExceptions`, `ignoreExceptions`, `fallbackMethod` |
|
|
87
|
+
| SPI | `...ratelimiter.spi.IdentityResolver` | `String resolve(SeedGridInvocationContext ctx)`; prontos: `IpIdentityResolver` (X-Forwarded-For → X-Real-IP → remote address → `anonymous`), `AnonymousIdentityResolver` |
|
|
88
|
+
| SPI | `...circuitbreaker.spi.CircuitBreakerStore` | `allowCall`, `onSuccess`, `onError`; implementação `CacheBackedCircuitBreakerStore` (`@ApplicationScoped`, sem `@DefaultBean`) |
|
|
89
|
+
| SPI | `...ratelimiter.algorithm.RateLimitAlgorithmStrategy` | `acquire(SeedGridCacheProvider, RateLimitRequest)` |
|
|
90
|
+
| Contexto | `...core.SeedGridInvocationContext` / `ThreadLocalSeedGridInvocationContext` | `set/clear/runWith/callWith`; atributos `tenantId` e `operation` entram na chave do rate limit. A extension **não** popula esse contexto: o host precisa fazê-lo (senão usa `EmptySeedGridInvocationContext`) |
|
|
91
|
+
| Beans CDI | `RateLimitService`, `RateLimitService.Clock`, `CircuitBreakerConfigResolver`, algoritmos | `@ApplicationScoped` |
|
|
92
|
+
| Exceções | `RateLimitExceededException` (mapeada para 429), `CircuitBreakerOpenException` | `CircuitBreakerOpenException` **não** tem `ExceptionMapper` na extension — **Inferência**: em endpoint REST sem fallback vira erro 500 se o host não mapear |
|
|
93
|
+
| JAX-RS | `RateLimitExceptionMapper`, `RateLimitContainerResponseFilter` (`@Provider`) | Headers `X-RateLimit-*`, `Retry-After` |
|
|
94
|
+
|
|
95
|
+
Endpoints REST próprios: nenhum no runtime (o `integration-tests` tem só `GET /seedgrid-resilience-core` de template). Migrations: nenhuma. Eventos: nenhum.
|
|
96
|
+
|
|
97
|
+
## Testes existentes (não executados)
|
|
98
|
+
|
|
99
|
+
| Local | Tipo | Cobre |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `runtime/src/test/.../ratelimiter/RateLimitAlgorithmsIT.java` | IT com Redis (Testcontainers, `disabledWithoutDocker`) | Os 3 algoritmos: limite, reset, min-spacing, burst, refill |
|
|
102
|
+
| `runtime/src/test/.../circuitbreaker/CircuitBreakerStoreIT.java` | IT com Redis | Transições de estado, slow calls, record/ignore, janela por tempo, persistência |
|
|
103
|
+
| `runtime/src/test/.../ratelimiter/services/RateLimitServiceConfigOverrideTest.java` | Unitário (Mockito `Config`) | Overrides `seedgrid.ratelimit.checkout.*`, kebab-case em `algorithm`, sem `configKey` ignora config |
|
|
104
|
+
| `runtime/src/test/.../circuitbreaker/configs/CircuitBreakerConfigResolverTest.java` | Unitário | Overrides `seedgrid.circuitbreaker.payments.*`, sem `name` ignora config, `enabled` |
|
|
105
|
+
| `runtime/src/test/.../ratelimiter/spi/IpIdentityResolverTest.java`, `AnonymousIdentityResolverTest.java` | Unitário | Resolução de identidade |
|
|
106
|
+
| `deployment/src/test/.../SeedgridResilienceCoreTest.java`, `SeedgridResilienceCoreDevModeTest.java` | QuarkusUnitTest / DevModeTest | Boot sem camada HTTP e sem config |
|
|
107
|
+
|
|
108
|
+
## Limitações
|
|
109
|
+
- `minSpacing` não tem efeito com `FIXED_WINDOW`.
|
|
110
|
+
- O kill switch do rate limiter (`enabled=false`) ainda emite headers `X-RateLimit-*` (limite = `permits` da anotação), quando `emitHeaders=true`.
|
|
111
|
+
- Valores inválidos de `algorithm`/`window-type` só falham na chamada (`IllegalArgumentException` de `Enum.valueOf`), não no boot.
|
|
112
|
+
- `CircuitBreakerOpenException` sem mapper HTTP.
|