docs(api): add Feature Control section to API_Mobile_App.md
Full contract for iOS integration: - POST /feature-control/bootstrap: request/response, context fields, flags camelCase mapping table, source/fallback strategy - POST /feature-control/telemetry/exposure: batch event format - Available flags reference table (fc.ios, fc.android, fc.promo, etc.) - Recommended session flow with cache guidance (15 min client-side)
This commit is contained in:
@@ -950,4 +950,229 @@ Para dashboard da loja e análise de produto, usar:
|
|||||||
- O painel `/store/reviews` segue funcional com os campos legados (`rate`, `message`, `itemFeedback`, `improvementFeedback`, `deliveryFeedback`).
|
- O painel `/store/reviews` segue funcional com os campos legados (`rate`, `message`, `itemFeedback`, `improvementFeedback`, `deliveryFeedback`).
|
||||||
- Campos novos coexistem para evolução gradual sem quebrar o fluxo atual.
|
- Campos novos coexistem para evolução gradual sem quebrar o fluxo atual.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Control (Feature Flags)
|
||||||
|
|
||||||
|
O Feature Control permite ativar/desativar funcionalidades do app remotamente sem publicar uma nova versão. O app nunca chama o Atomenta diretamente — usa um **BFF (Backend for Frontend)** que protege o `Atomenta-Token` do cliente.
|
||||||
|
|
||||||
|
### Arquitetura
|
||||||
|
|
||||||
|
```
|
||||||
|
App iOS → POST /feature-control/bootstrap → BFF → Atomenta /api/feature-control/evaluate
|
||||||
|
```
|
||||||
|
|
||||||
|
O BFF mantém cache de 60 segundos. Em caso de timeout ou erro upstream, retorna flags de fallback estáticas definidas nas variáveis de ambiente do BFF.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. Bootstrap — Avaliar flags na inicialização
|
||||||
|
|
||||||
|
**`POST /feature-control/bootstrap`**
|
||||||
|
|
||||||
|
Deve ser chamado uma vez por sessão do app (ao abrir ou ao autenticar). Retorna os valores de todos os flags solicitados para o contexto do usuário.
|
||||||
|
|
||||||
|
**Headers:**
|
||||||
|
```
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <JWT_Token> ← opcional, mas recomendado
|
||||||
|
```
|
||||||
|
|
||||||
|
> Se o `Authorization` for enviado, o BFF extrai `subjectId` automaticamente do JWT (`sub` / `customerId` / `id`). Se não for enviado, `context.subjectId` é obrigatório.
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"keys": [
|
||||||
|
"fc.ios",
|
||||||
|
"fc.android",
|
||||||
|
"fc.promo",
|
||||||
|
"fc.promo-codes",
|
||||||
|
"fc.city-aguai"
|
||||||
|
],
|
||||||
|
"context": {
|
||||||
|
"subjectType": "customer",
|
||||||
|
"subjectId": "e30188cf-cf69-471e-bc59-4c92e0f9ad7b",
|
||||||
|
"platform": "ios",
|
||||||
|
"appVersion": "2.3.1",
|
||||||
|
"attributes": {
|
||||||
|
"city": "Aguaí"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Campo | Tipo | Obrigatório | Descrição |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `environment` | string | não | `"production"` (default) ou `"sandbox"` |
|
||||||
|
| `keys` | string[] | sim | Lista de flags a avaliar (máx 100) |
|
||||||
|
| `context.subjectType` | string | sim | `"customer"` \| `"user"` \| `"store"` \| `"anonymous"` |
|
||||||
|
| `context.subjectId` | string | sim* | ID do usuário. *Dispensável se JWT enviado no header |
|
||||||
|
| `context.platform` | string | não | `"ios"` ou `"android"` |
|
||||||
|
| `context.appVersion` | string | não | Versão semântica, ex: `"2.3.1"` |
|
||||||
|
| `context.storeId` | string | não | Para flags segmentadas por loja |
|
||||||
|
| `context.attributes` | object | não | Atributos extras (ex: `city`, `tier`) |
|
||||||
|
|
||||||
|
**Response 200 — fonte live:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"source": "live",
|
||||||
|
"configVersion": 7,
|
||||||
|
"evaluatedAt": "2026-06-05T12:00:00.000Z",
|
||||||
|
"flags": {
|
||||||
|
"ios": true,
|
||||||
|
"android": true,
|
||||||
|
"promo": true,
|
||||||
|
"promoCodes": false,
|
||||||
|
"cityAguai": true
|
||||||
|
},
|
||||||
|
"raw": {
|
||||||
|
"fc.ios": {
|
||||||
|
"enabled": true,
|
||||||
|
"variant": "on",
|
||||||
|
"payload": null,
|
||||||
|
"reason": "rollout"
|
||||||
|
},
|
||||||
|
"fc.promo-codes": {
|
||||||
|
"enabled": false,
|
||||||
|
"variant": "off",
|
||||||
|
"payload": null,
|
||||||
|
"reason": "default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response 200 — fonte fallback** (quando BFF não consegue atingir Atomenta):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"source": "fallback",
|
||||||
|
"configVersion": 0,
|
||||||
|
"evaluatedAt": "2026-06-05T12:00:00.000Z",
|
||||||
|
"flags": { "ios": true, "promo": true },
|
||||||
|
"raw": { ... },
|
||||||
|
"upstreamError": {
|
||||||
|
"status": 504,
|
||||||
|
"code": "FEATURE_CONTROL_UPSTREAM_TIMEOUT",
|
||||||
|
"message": "Timeout no upstream"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Como usar `flags` no app
|
||||||
|
|
||||||
|
Os flags são retornados em `flags` com chaves **camelCase** derivadas das chaves originais (prefixo `fc.` removido, hífen/underscore viram camelCase):
|
||||||
|
|
||||||
|
| Chave original | Chave em `flags` |
|
||||||
|
|---|---|
|
||||||
|
| `fc.ios` | `flags.ios` |
|
||||||
|
| `fc.android` | `flags.android` |
|
||||||
|
| `fc.promo` | `flags.promo` |
|
||||||
|
| `fc.promo-codes` | `flags.promoCodes` |
|
||||||
|
| `fc.city-aguai` | `flags.cityAguai` |
|
||||||
|
|
||||||
|
**Regra de leitura:**
|
||||||
|
- `true` → flag habilitado
|
||||||
|
- `false` ou `"off"` → flag desabilitado
|
||||||
|
- Qualquer outro string → variante multivariate (ex: `"variant_a"`, `"variant_b"`)
|
||||||
|
|
||||||
|
**Exemplo Swift:**
|
||||||
|
```swift
|
||||||
|
if flags["ios"] as? Bool == true {
|
||||||
|
// mostrar funcionalidade exclusiva iOS
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `source` e estratégia de fallback
|
||||||
|
|
||||||
|
| `source` | Significado | Ação recomendada |
|
||||||
|
|---|---|---|
|
||||||
|
| `"live"` | Dados frescos do Atomenta | Usar normalmente |
|
||||||
|
| `"fallback"` | BFF usou defaults estáticos (timeout/erro) | Usar com cautela; re-tentar na próxima sessão |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Telemetria de Exposição
|
||||||
|
|
||||||
|
**`POST /feature-control/telemetry/exposure`**
|
||||||
|
|
||||||
|
Registra quais flags o usuário visualizou (para análise de rollout). Envio em batch, **fire-and-forget** — não bloquear UX aguardando resposta.
|
||||||
|
|
||||||
|
**Headers:**
|
||||||
|
```
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <JWT_Token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"featureKey": "fc.promo",
|
||||||
|
"variant": "on",
|
||||||
|
"subjectType": "customer",
|
||||||
|
"storeId": "store_1777215327582_akum6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"featureKey": "fc.ios",
|
||||||
|
"variant": "on",
|
||||||
|
"subjectType": "customer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Campo | Tipo | Obrigatório | Descrição |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `featureKey` | string | sim | Chave original com prefixo `fc.` |
|
||||||
|
| `variant` | string | não | Variante exposta (default `"off"`) |
|
||||||
|
| `subjectType` | string | não | Default `"customer"` |
|
||||||
|
| `storeId` | string | não | Quando relevante |
|
||||||
|
|
||||||
|
Máximo de **100 eventos por request**.
|
||||||
|
|
||||||
|
**Response 200:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"code": "FEATURE_CONTROL_EXPOSURE_ACCEPTED",
|
||||||
|
"result": { "count": 2 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Flags disponíveis (referência)
|
||||||
|
|
||||||
|
| Flag | Descrição | Default |
|
||||||
|
|---|---|---|
|
||||||
|
| `fc.ios` | Funcionalidades exclusivas iOS habilitadas | `true` |
|
||||||
|
| `fc.android` | Funcionalidades exclusivas Android habilitadas | `true` |
|
||||||
|
| `fc.promo` | Módulo de promoções ativo | `true` |
|
||||||
|
| `fc.promo-codes` | Cupons de desconto habilitados | `true` |
|
||||||
|
| `fc.city-aguai` | Expansão para cidade de Aguaí/SP | `true` |
|
||||||
|
|
||||||
|
> Novos flags são criados pelo time Pedi Foods no painel `/feature-control`. Consulte o time antes de codificar um flag inexistente.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Fluxo recomendado no app
|
||||||
|
|
||||||
|
```
|
||||||
|
1. App abre / usuário autentica
|
||||||
|
2. POST /feature-control/bootstrap (com JWT no header)
|
||||||
|
3. Salvar flags em memória/UserDefaults para a sessão
|
||||||
|
4. Renderizar UI baseada nos flags
|
||||||
|
5. POST /feature-control/telemetry/exposure (fire-and-forget, por flag visualizado)
|
||||||
|
6. Ao fechar sessão ou após 15 min, repetir passo 2 na próxima abertura
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Não** chamar bootstrap a cada tela — apenas na inicialização da sessão. O BFF tem cache de 60s no servidor; o app deve ter cache local mínimo de 15 minutos para evitar latência desnecessária.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Autor: Daniel Arantes Loverde
|
Autor: Daniel Arantes Loverde
|
||||||
|
|||||||
Reference in New Issue
Block a user