Skip to main content

ADR-002 — Query separada para GetRecommendedItemsAsync em vez de JOIN no GetCartInfoAsync

Data: 2026-05-14
Status: Aceito
Contexto: Task #193232 — Ajuste endpoint GET api/payment/v2/{id} (GetInfo) — RF-05 Busca Paralela de Itens Recomendados


Contexto

A RF-05 exige que os itens recomendados sejam buscados em paralelo com as demais queries do GetInfoStoreAsync. Duas alternativas foram avaliadas:

  • A: adicionar um JOIN em CartRecommendedItems dentro do GetCartInfoAsync existente
  • B: criar um método separado GetRecommendedItemsAsync chamado em paralelo no handler

Pesquisa completa em pesquisa-rf05-query-paralela-vs-join.md.


Decisão

Adotar a Alternativa B: query separada GetRecommendedItemsAsync, chamada em Task.WhenAll junto com GetPaymentInfoAsync e GetCartInfoAsync no handler GetInfoStoreAsync.


Justificativa

1. O GetCartInfoAsync já está no limite de complexidade aceitável para Dapper multi-map

A query atual usa um 5-way multi-map com splitOn em 4 pontos de corte. Adicionar uma 6ª entidade com alias duplicado de Products (que já é JOINado para CartItems) tornaria o splitOn frágil: qualquer reordenação de colunas no SELECT quebraria o mapeamento silenciosamente, sem erro de compilação.

2. Produto cartesiano

A combinação CartItems × InstallmentRules × RecommendedItems faz o número de linhas crescer cubicamente. Com 3 de cada, seriam 27 linhas onde hoje são 9. O Dapper deduplica via dicionário, mas o banco materializa e transmite todas as linhas.

3. Isolamento de fluxos (RNF-02)

GetCartInfoAsync é compartilhado entre GetInfoStoreAsync (store) e GetInfoEcommAsync (ecomm). O fluxo ecomm não deve retornar recomendados. Um JOIN na query compartilhada exigiria flag ou método duplicado. Com a query separada, o isolamento é natural: GetRecommendedItemsAsync é chamado exclusivamente de GetInfoStoreAsync.

4. Latência adicional é desprezível

A desvantagem da Alternativa B — uma roundtrip a mais — é mitigada pelo paralelismo via Task.WhenAll. A tabela CartRecommendedItems é pequena (poucos itens por carrinho), sem agregações. O custo marginal é irrelevante frente ao ganho de simplicidade e segurança.

5. Padrão idiomático no codebase

Task.WhenAll com 3+ tarefas já é usado em CreatePaymentCommandHandler (linha 130) e PosCaptureService (linha 72). A solução segue o padrão estabelecido.


Consequências

  • ICoreSqlRepository recebe novo método: Task<List<CartInfoDTO.CartItemsInfoDTO>> GetRecommendedItemsAsync(int cartId)
  • CoreSqlRepository implementa a query flat (sem multi-map)
  • GetInfoStoreAsync passa a chamar 3 tasks em Task.WhenAll
  • CartInfoDTO recebe DateCreated (para RF-02) e RecommendedItems (para retorno ao handler)
  • GetCartInfoAsync não é alterado
  • GetInfoEcommAsync não é alterado

Alternativa rejeitada

Alternativa A — JOIN no GetCartInfoAsync:
Descartada por explosão cartesiana, fragilidade do splitOn com alias duplicado de Products, e acoplamento indesejado com o fluxo ecomm.