From 09396ebcf302e660133d7bdca183cc77a9e19278 Mon Sep 17 00:00:00 2001 From: Daniel Arantes Loverde Date: Fri, 5 Jun 2026 16:19:26 -0300 Subject: [PATCH] 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) --- API_Mobile_App.md | 225 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) diff --git a/API_Mobile_App.md b/API_Mobile_App.md index bf52a20..f721402 100644 --- a/API_Mobile_App.md +++ b/API_Mobile_App.md @@ -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`). - 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 ← 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 +``` + +**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