Skip to content

PRD: Corrigir e otimizar arquitetura da Bible API #2

Description

@onetogregorio

Problem Statement

A Bible API pública (api.midvash.com) cresceu organicamente e acumulou dívida arquitetural que torna mudanças custosas e propensas a bugs. O mesmo pipeline de domínio (buscar capítulo/versículo) é implementado três vezes com cache boilerplate repetido ~12 vezes. Módulos são rasos — sua interface é tão complexa quanto sua implementação — e não há testes.

Cada correção ou feature nova exige editar múltiplos arquivos, e a falta de seams claros impede testar a lógica de domínio isoladamente.

Solution

Extrair um módulo VerseService profundo que concentre todo o pipeline de capítulo/versículo atrás de uma interface simples. Extrair o boilerplate de cache em um middleware reutilizável. Unificar a construção de erros. Decompor o monólito da landing page. O resultado é um código com alta localidade (uma mudança, um arquivo) e alta alavancagem (mudanças no pipeline ou cache tocam um módulo, não três).

User Stories

  1. Como desenvolvedor da API, quero que alterações no pipeline de versículos (validação, parsing, formatação) toquem um único módulo, para que bugs não precisem ser corrigidos em três lugares.
  2. Como desenvolvedor da API, quero que a estratégia de cache (cache key, ETag, HEAD handling) seja configurada em um único lugar, para que mudanças no cache não exijam editar 12 ocorrências.
  3. Como desenvolvedor da API, quero testar a lógica de domínio (buscar capítulo, extrair versículos, formatar referência) sem infraestrutura de rede, para que os testes rodem rápido e de forma determinística.
  4. Como desenvolvedor da API, quero que handlers HTTP sejam thin adapters que só traduzem request → chamada de domínio → response, para que cada handler tenha uma única responsabilidade.
  5. Como desenvolvedor da API, quero que erros HTTP sigam um padrão único independente da versão da API (legacy vs v1), para que adicionar um novo endpoint não exiga escolher entre 3 padrões de erro.
  6. Como desenvolvedor da API, quero navegar e editar a landing page sem precisar entender 1700 linhas num arquivo só, para que mudanças de copy/CSS/JS sejam localizadas.
  7. Como mantenedor, quero que a adição de um novo locale (idioma) exija mudança em um único ponto de definição, para que o risco de inconsistência entre books.ts, locale.ts e i18n.ts seja eliminado.
  8. Como desenvolvedor, quero que cache keys para endpoints inexistentes (/characters, /dictionary) não sejam normalizados com lógica morta, para que endpoints futuros não tenham cache key corrompida.

Implementation Decisions

Módulo VerseService (profundo)

  • Novo módulo src/lib/verse-service.ts com interface:
    fetchVerses(ctx, params: { version, bookSlug, chapter, verses? }) → VerseResult
    where VerseResult = { data: ChapterData, meta: Meta } | { error: ApiError }
    
  • O módulo é profundo: sua interface (uma única função) esconde cache strategy, LRU, R2 multi-format parsing, verse parsing, extração de range, formatação de referência.
  • O teste de deleção: deletar VerseService forçaria os 3 handlers a reimplementar todo o pipeline — a complexidade se concentraria, não se moveria.
  • Os 3 handlers atuais (legacy.ts, v1/chapters.ts, votd.ts) tornam-se adapters finos que mapeiam HTTP request → chamada ao VerseService → HTTP response com envelope apropriado.

Middleware de cache (aspecto transversal)

  • Extrair o boilerplate de cache (~12 ocorrências) para um wrapper:
    withCache(ctx, request, label, handler: (request) => Response) → Response
    
  • O wrapper é um aspecto transversal: normalize cache key → check cache → serve from cache → executa handler → armazena no cache → adapta HEAD/304.
  • Seam: a interface Cache API do Cloudflare é um adapter. Adicionar um segundo adapter (ex: MemoryCache para testes) tornaria o seam real ("one adapter = hypothetical seam, two = real").

Unificação de erros

  • Função única apiError(request, code, message, details?) que constrói o envelope correto (legacy { error } vs v1 { error, meta }) baseado no path.
  • Elimina as 3 implementações paralelas atuais.

Decomposição da landing page

  • landing/page.ts (1713 linhas) dividido em:
    • landing/page.css.ts — CSS como template strings
    • landing/page.svg.ts — ícones SVG como componentes
    • landing/page.client.ts — JavaScript client-side (tabs, runner, copy)
    • landing/page.templates.ts — funções de template HTML puras
    • landing/page.ts — composer fino que importa e orquestra
  • Cada sub-módulo é profundo para sua preocupação: uma interface (1 export) esconde toda a complexidade de CSS/SVG/JS/templates.

Consolidação do tipo Locale

  • Tipo Locale (presente em 3 arquivos: books.ts, locale.ts, i18n.ts) movido para src/lib/locale.ts como fonte única da verdade.
  • Os outros dois arquivos importam o tipo.

Remoção de código morto

  • Remover referências a /characters e /dictionary em normalizeCacheKey (lib/cache.ts:28-30) — endpoints que não existem.

Testing Decisions

  • O que faz um bom teste: testar comportamento externo, não implementação. Testar a interface do módulo, não suas funções internas. Um teste do VerseService chama fetchVerses() com params conhecidos e verifica o resultado — não testa se parseVerseParam foi chamado ou se o LRU cache foi usado.
  • Módulos testáveis via interface pública: VerseService (mock R2 + mock Cache API via withCache), middleware withCache, funções puras em lib/chapter.ts (parseVerseParam, extractVerses, formatReference), lib/locale.ts (normalizeLocale), lib/response.ts (okResponse, errorResponse), lib/votd-pool.ts (pickVotdForDate, dayOfYearUtc).
  • Seam de teste principal: o middleware withCache é o seam para testar handlers sem cache real. Um adapter MemoryCache implementa a mesma interface do Cache API.
  • Test runner: Vitest (já é o mais comum em projetos Cloudflare Workers, e há prior art na comunidade).

Out of Scope

  • Adicionar novos endpoints ou versões de API.
  • Mudar o schema de dados no R2.
  • Alterar o formato de resposta da API (breaking changes).
  • Adicionar autenticação ou rate limiting.
  • Migrar para outro runtime que não Cloudflare Workers.
  • Refatorar o conteúdo dos arquivos de tradução (i18n.ts, docs.ts) — apenas sua estrutura.

Further Notes

  • Este PRD não propõe interfaces finais para os módulos — as interfaces serão detalhadas durante a implementação via grillagem com o desenvolvedor.
  • Nenhum ADR existente é contradito por este PRD (não há ADRs no repositório).
  • A ordem de implementação recomendada: (1) middleware withCache, (2) VerseService, (3) unificação de erros, (4) landing page decomposition, (5) locale type consolidation, (6) dead code removal. A ordem prioriza criar o seam de cache primeiro, pois o VerseService depende dele.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions