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ção | Onde publica | EventType | Status |
|---|---|---|---|
| Criar carrinho | PublishRecommendationHistoryHandler (fim da chain, após SaveCartHandler) | RecommendationCartCreated | Implementado |
| Adicionar recomendação | CartService.Handle(AddItemToCartCommand) | RecommendationProductAddedToOrder | Implementado |
| Remover recomendação | CartService.Handle(RemoveCartItemCommand) | RecommendationProductRemovedToOrder | Implementado |
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
| Aspecto | Avaliação |
|---|---|
| Chain de handlers no create-cart | Alinhado — segue padrão existente (LoadStoreModelHandler cedo, PublishRecommendationHistoryHandler após persistência) |
Mensagens de erro em pt-BR via AddError | Alinhado |
Decorator de cache (MessageBusServiceCachedDecorator) | Alinhado — espelha CacheStoreRepositoryDecorator |
| Verificação de nulidade antes de operar | Parcial — cart.Store.CodeStore sem null-check em add/remove |
| try/catch em handlers para erros de infra | Parcial — LoadStoreModelHandler não trata exceção do repositório (teste espera comportamento inexistente) |
| Logs em inglês | Ausente — falhas de publicação FIFO não são logadas |
Cobertura de testes (xunit)
| Arquivo | Cobertura do objetivo | Convenções xunit |
|---|---|---|
PublishRecommendationHistoryHandlerTest | Boa — verifica skip, erro de ordem, payload e EventType | Alinhado: DisplayName pt-BR, [Trait], AAA, mock com callback |
LoadStoreModelHandlerTest | Cobre loja encontrada/não encontrada | Parcial — teste RepositoryThrows espera AddError, mas handler não tem try/catch |
AddItemToCartHandlerTests | Não verifica publicação FIFO — mock configurado, Verify ausente | Parcial — [Trait] só na classe, não em todos os métodos |
RemoveCartItemHandlerTests | Não verifica publicação FIFO — idem | Parcial — [Trait] só na classe; idempotência documentada sem assert de evento |
Testes faltantes para fechar o objetivo:
- Add sucesso →
SendFifoAsyncchamado comEventType = RecommendationProductAddedToOrderemessageGroupIdcorreto - Add erro → evento publicado com
statusCode = 400 - Remove sucesso/erro → mesma verificação para
RecommendationProductRemovedToOrder LastForCartIdretornando GUID conhecido → evento correlacionado (semGuid.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
LastForCartIdretorna null (evento de create ainda na SQS, listener não terminou, ou carrinho semRecommendationsId), o código fazrecommendationsId ??= Guid.NewGuid(). Eventos de add/remove passam a usar um ID diferente, quebrando a correlação da sessão e a ordenação FIFO pormessageGroupId. Impacto direto no objetivo da feature. - Suggestion: Eliminar o fallback com GUID novo. Opções: persistir
RecommendationsIdnoCartModelno create (síncrono); receberRecommendationsIdnos commands de add/remove; ou aguardar/retry emLastForCartIdantes de publicar. - Status: open
Issue 2 -- Severity: bug
- File: src/Cart.API/Application/CartService.cs:881
- Description:
WithError/WithSucessochamam_ = SendEvent(evt)semawait.SendEventéasynce 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 deSendFifoAsync. - Status: open
Issue 3 -- Severity: bug
- File: src/Cart.API/Application/CartService.cs:1203
- Description:
SendEventdescarta o retorno deSendFifoAsynce não validaqueueUrlnulo/vazio retornado pelo decorator. - Suggestion: Validar
queueUrl, checarbooldeSendFifoAsync, 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:
SendFifoAsyncinvocado fire-and-forget (_ = ...) sem await nem checagem de resultado — mesmo risco doCartService.SendEventno evento de create-cart. - Suggestion:
awaita 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:
WithErroreWithSucessoacessamcart.Store.CodeStoresem null-check. Viola convenção dotnet de verificar nulidade;NullReferenceExceptionimpede publicação do evento. - Suggestion:
cart.Store?.CodeStore ?? string.Emptyou validarcart.Storee 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_AddsErrorMessageespera mensagem de exceção noBaseResult, masLoadStoreModelHandlernão possui try/catch — exceção propaga. Teste inconsistente com implementação e com convenção dotnet (handler deveria capturar erro de infra e usarAddError). - Suggestion: Adicionar try/catch em
LoadStoreModelHandlercomAddError(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
_messageBusServiceMocke_cartRecommendationHistoryRepositoryMockconfigurados emCreateService(), mas nenhum teste verificaSendFifoAsyncnem payload do evento. Lacuna direta na validação do objetivo de histórico. - Suggestion: Adicionar testes com callback no mock (padrão de
PublishRecommendationHistoryHandlerTest) assertandoEventType,RecommendationsIdemessageGroupId. - 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 deCartService.cs), publicando 200 — comportamento não assertado. - Suggestion: Decidir intenção de produto; documentar e cobrir com
Verifyno 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,
WithSucessoserializa oCartModelinteiro no response do evento. Payload grande, risco de PII.PublishRecommendationHistoryHandler.CreateResponsejá 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/GetTopicArnAsynccacheiam valor sem guardIsNullOrWhiteSpace(diferente deGetQueueUrlFifo). Falha transitória pode cachear ARN inválido por 3h. Fora do escopo FIFO imediato, mas inconsistente. - Suggestion: Adicionar mesmo guard de
GetQueueUrlFifoantes deSetAsync. - Status: open
Issue 11 -- Severity: suggestion
- File: src/Cart.Domain/Cart.Domain.csproj:13
- Description: Referência dupla a MessageBus — NuGet
ZZApp.MessageBus1.17.2 e submoduleCoezzion.MessageBus.ITenancyEventherda deCoezzion.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:
GetByStoreIdAsyncretornaTask<StoreModel>(non-nullable); interface declaraTask<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.Jsoneusing System.Text.Json.Serializationnã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
TResqem vez deTReq. - 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
| Prioridade | Issues | Motivo |
|---|---|---|
| P0 — bloqueia objetivo | 1, 2, 3, 4 | Correlação e entrega confiável são o núcleo da feature |
| P1 — bloqueia qualidade | 5, 6, 7, 8 | Null-safety, teste/handler inconsistente, cobertura do objetivo |
| P2 — melhoria | 9, 10, 11, 12 | Payload, cache, dependências, nullable |
| P3 — polish | 13, 14, 15 | Imports, nomenclatura, typo |
Veredito
| Aspecto | Status |
|---|---|
| Três eventos implementados | OK |
Correlação RecommendationsId | Fragil |
| Entrega confiável (await + log) | Não |
| Testes do objetivo | Parcial (só create-cart) |
| Convenções dotnet | Parcial |
| Convenções xunit | Parcial |
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.