Skip to main content

Review — Histórico de Recomendações (API)

Objetivo da implementação: publicar eventos de histórico de recomendações para três operações — criar carrinho, adicionar recomendação e remover recomendação — correlacionados por RecommendationsId via fila SQS FIFO.

Escopo revisado: mudanças locais não commitadas em coezzion-service-cart (branch QA/SP-2026-13).

Convenções consideradas: dotnet-conventions, xunit-tests.


Summary

A implementação cobre os três eventos previstos e segue a arquitetura existente do projeto (chain de handlers no create-cart, decorator de cache no message bus, factories em CartRecommendationsEvent). O fluxo geral está correto: API publica na SQS FIFO com messageGroupId = RecommendationsId → listener assíncrono → CartRecommendationHistoryService → banco.

Porém, não deve ir para merge enquanto dois riscos centrais ao objetivo não forem resolvidos: (1) correlação frágil de RecommendationsId em add/remove, com fallback para Guid.NewGuid() quando o histórico ainda não foi persistido; e (2) publicação fire-and-forget do message bus, que pode perder eventos de auditoria sem log. Os testes cobrem bem o create-cart, mas não validam a publicação FIFO nos fluxos de add/remove — lacuna direta em relação ao objetivo.


Cobertura do objetivo

OperaçãoOnde publicaEventTypeStatus
Criar carrinhoPublishRecommendationHistoryHandler (fim da chain, após SaveCartHandler)RecommendationCartCreatedImplementado
Adicionar recomendaçãoCartService.Handle(AddItemToCartCommand)RecommendationProductAddedToOrderImplementado
Remover recomendaçãoCartService.Handle(RemoveCartItemCommand)RecommendationProductRemovedToOrderImplementado

Fluxo end-to-end:

CreateCart (RecommendationsId no command)
→ PublishRecommendationHistoryHandler → SQS FIFO
→ CartRecommendationHistoryListenerService → CartRecommendationHistoryService → DB

AddItem / RemoveItem
→ LastForCartId(cartId) → SendEvent → SQS FIFO
→ (mesmo listener) → DB

Alinhamento com convenções .NET

AspectoAvaliação
Chain de handlers no create-cartAlinhado — segue padrão existente (LoadStoreModelHandler cedo, PublishRecommendationHistoryHandler após persistência)
Mensagens de erro em pt-BR via AddErrorAlinhado
Decorator de cache (MessageBusServiceCachedDecorator)Alinhado — espelha CacheStoreRepositoryDecorator
Verificação de nulidade antes de operarParcialcart.Store.CodeStore sem null-check em add/remove
try/catch em handlers para erros de infraParcialLoadStoreModelHandler não trata exceção do repositório (teste espera comportamento inexistente)
Logs em inglêsAusente — falhas de publicação FIFO não são logadas

Cobertura de testes (xunit)

ArquivoCobertura do objetivoConvenções xunit
PublishRecommendationHistoryHandlerTestBoa — verifica skip, erro de ordem, payload e EventTypeAlinhado: DisplayName pt-BR, [Trait], AAA, mock com callback
LoadStoreModelHandlerTestCobre loja encontrada/não encontradaParcial — teste RepositoryThrows espera AddError, mas handler não tem try/catch
AddItemToCartHandlerTestsNão verifica publicação FIFO — mock configurado, Verify ausenteParcial — [Trait] só na classe, não em todos os métodos
RemoveCartItemHandlerTestsNão verifica publicação FIFO — idemParcial — [Trait] só na classe; idempotência documentada sem assert de evento

Testes faltantes para fechar o objetivo:

  • Add sucesso → SendFifoAsync chamado com EventType = RecommendationProductAddedToOrder e messageGroupId correto
  • Add erro → evento publicado com statusCode = 400
  • Remove sucesso/erro → mesma verificação para RecommendationProductRemovedToOrder
  • LastForCartId retornando GUID conhecido → evento correlacionado (sem Guid.NewGuid())
  • Remove item inexistente → decidir se publica 200 (idempotência) e assertar comportamento do bus

Issues

Issue 1 -- Severity: bug

  • File: src/Cart.API/Application/CartService.cs:802
  • Description: Quando LastForCartId retorna null (evento de create ainda na SQS, listener não terminou, ou carrinho sem RecommendationsId), o código faz recommendationsId ??= Guid.NewGuid(). Eventos de add/remove passam a usar um ID diferente, quebrando a correlação da sessão e a ordenação FIFO por messageGroupId. Impacto direto no objetivo da feature.
  • Suggestion: Eliminar o fallback com GUID novo. Opções: persistir RecommendationsId no CartModel no create (síncrono); receber RecommendationsId nos commands de add/remove; ou aguardar/retry em LastForCartId antes de publicar.
  • Status: open

Issue 2 -- Severity: bug

  • File: src/Cart.API/Application/CartService.cs:881
  • Description: WithError/WithSucesso chamam _ = SendEvent(evt) sem await. SendEvent é async e faz I/O. Falhas viram exceções não observadas e a resposta da API pode retornar antes do evento ser enviado. Para feature de auditoria, evento perdido = objetivo não cumprido. Mesmo padrão nas linhas 898, 960 e 977.
  • Suggestion: Tornar helpers async, await SendEvent(evt), logar falhas em inglês com structured logging. Não engolir resultado de SendFifoAsync.
  • Status: open

Issue 3 -- Severity: bug

  • File: src/Cart.API/Application/CartService.cs:1203
  • Description: SendEvent descarta o retorno de SendFifoAsync e não valida queueUrl nulo/vazio retornado pelo decorator.
  • Suggestion: Validar queueUrl, checar bool de SendFifoAsync, logar falha. Histórico não pode se perder silenciosamente.
  • Status: open

Issue 4 -- Severity: bug

  • File: src/Cart.API/Application/Handlers/CreateCart/PublishRecommendationHistoryHandler.cs:36
  • Description: SendFifoAsync invocado fire-and-forget (_ = ...) sem await nem checagem de resultado — mesmo risco do CartService.SendEvent no evento de create-cart.
  • Suggestion: await a chamada, verificar sucesso, logar falha. Avaliar se falha de bus deve propagar erro no pipeline de create.
  • Status: open

Issue 5 -- Severity: bug

  • File: src/Cart.API/Application/CartService.cs:875
  • Description: WithError e WithSucesso acessam cart.Store.CodeStore sem null-check. Viola convenção dotnet de verificar nulidade; NullReferenceException impede publicação do evento.
  • Suggestion: cart.Store?.CodeStore ?? string.Empty ou validar cart.Store e retornar erro de negócio antes de publicar.
  • Status: open

Issue 6 -- Severity: bug

  • File: src/Cart.UnitTests/Application/Handlers/CreateCart/LoadStoreModelHandlerTest.cs:78
  • Description: Teste RepositoryThrows_Executed_AddsErrorMessage espera mensagem de exceção no BaseResult, mas LoadStoreModelHandler não possui try/catch — exceção propaga. Teste inconsistente com implementação e com convenção dotnet (handler deveria capturar erro de infra e usar AddError).
  • Suggestion: Adicionar try/catch em LoadStoreModelHandler com AddError (convenção dotnet) ou corrigir/remover o teste. Preferível corrigir o handler.
  • Status: open

Issue 7 -- Severity: suggestion

  • File: src/Cart.UnitTests/Application/Commands/AddItemToCartHandlerTests.cs:49
  • Description: Mocks de _messageBusServiceMock e _cartRecommendationHistoryRepositoryMock configurados em CreateService(), mas nenhum teste verifica SendFifoAsync nem payload do evento. Lacuna direta na validação do objetivo de histórico.
  • Suggestion: Adicionar testes com callback no mock (padrão de PublishRecommendationHistoryHandlerTest) assertando EventType, RecommendationsId e messageGroupId.
  • Status: open

Issue 8 -- Severity: suggestion

  • File: src/Cart.UnitTests/Application/Commands/RemoveCartItemHandlerTests.cs:120
  • Description: Testes de idempotência (item/carrinho não encontrado) não verificam se evento FIFO é publicado. Implementação atual chama WithSucesso() quando item é null (linha 925 de CartService.cs), publicando 200 — comportamento não assertado.
  • Suggestion: Decidir intenção de produto; documentar e cobrir com Verify no message bus. Se no-op não deve emitir histórico, ajustar implementação.
  • Status: open

Issue 9 -- Severity: suggestion

  • File: src/Cart.API/Application/CartService.cs:896
  • Description: Em add sucesso, WithSucesso serializa o CartModel inteiro no response do evento. Payload grande, risco de PII. PublishRecommendationHistoryHandler.CreateResponse já usa DTO mínimo.
  • Suggestion: Publicar DTO enxuto (cart id, item ids, totais) nos três fluxos.
  • Status: open

Issue 10 -- Severity: suggestion

  • File: src/Cart.API/Application/Decorators/MessageBusServiceCachedDecorator.cs:79
  • Description: GetTopicArn/GetTopicArnAsync cacheiam valor sem guard IsNullOrWhiteSpace (diferente de GetQueueUrlFifo). Falha transitória pode cachear ARN inválido por 3h. Fora do escopo FIFO imediato, mas inconsistente.
  • Suggestion: Adicionar mesmo guard de GetQueueUrlFifo antes de SetAsync.
  • Status: open

Issue 11 -- Severity: suggestion

  • File: src/Cart.Domain/Cart.Domain.csproj:13
  • Description: Referência dupla a MessageBus — NuGet ZZApp.MessageBus 1.17.2 e submodule Coezzion.MessageBus. ITenancyEvent herda de Coezzion.MessageBus.Events.Event.
  • Suggestion: Consolidar em uma única fonte para evitar conflito de assembly em CI.
  • Status: open

Issue 12 -- Severity: suggestion

  • File: src/Cart.Infrastructure/Data/Repositories/StoreRepository.cs:23
  • Description: GetByStoreIdAsync retorna Task<StoreModel> (non-nullable); interface declara Task<StoreModel?>.
  • Suggestion: Alinhar assinatura em interface, implementação e CacheStoreRepositoryDecorator.
  • Status: open

Issue 13 -- Severity: nit

  • File: src/Cart.API/Application/CartService.cs:1
  • Description: using System.Text.Json e using System.Text.Json.Serialization não utilizados.
  • Suggestion: Remover imports.
  • Status: open

Issue 14 -- Severity: nit

  • File: src/Cart.Domain/Events/CartRecommendationsEvent.cs:45
  • Description: Parâmetro genérico nomeado TResq em vez de TReq.
  • Suggestion: Renomear para TReq.
  • Status: open

Issue 15 -- Severity: nit

  • File: src/Cart.API/Application/Handlers/CreateCart/PublishRecommendationHistoryHandler.cs:22
  • Description: Typo na mensagem de erro: "carinho" → "carrinho". Constante espelhada no teste.
  • Suggestion: Corrigir em handler e PublishRecommendationHistoryHandlerTest.
  • Status: open

Priorização para merge

PrioridadeIssuesMotivo
P0 — bloqueia objetivo1, 2, 3, 4Correlação e entrega confiável são o núcleo da feature
P1 — bloqueia qualidade5, 6, 7, 8Null-safety, teste/handler inconsistente, cobertura do objetivo
P2 — melhoria9, 10, 11, 12Payload, cache, dependências, nullable
P3 — polish13, 14, 15Imports, nomenclatura, typo

Veredito

AspectoStatus
Três eventos implementadosOK
Correlação RecommendationsIdFragil
Entrega confiável (await + log)Não
Testes do objetivoParcial (só create-cart)
Convenções dotnetParcial
Convenções xunitParcial

Conclusão: direção arquitetural correta para o objetivo, mas implementação atual não garante histórico correlacionado e confiável. Resolver P0 e P1 antes do merge.