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
CartRecommendedItemsdentro doGetCartInfoAsyncexistente - B: criar um método separado
GetRecommendedItemsAsyncchamado 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
ICoreSqlRepositoryrecebe novo método:Task<List<CartInfoDTO.CartItemsInfoDTO>> GetRecommendedItemsAsync(int cartId)CoreSqlRepositoryimplementa a query flat (sem multi-map)GetInfoStoreAsyncpassa a chamar 3 tasks emTask.WhenAllCartInfoDTOrecebeDateCreated(para RF-02) eRecommendedItems(para retorno ao handler)GetCartInfoAsyncnão é alteradoGetInfoEcommAsyncnã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.