Pesquisa — Domain Events na Coezzion
Data: 2026-07-01
Contexto: US 195746 — avaliar viabilidade de domain events para histórico de recomendações no Cart.
Infraestrutura existente
BaseEntity e DomainEvent
Localização: coezzion-nuget-common/src/Coezzion.Common/DomainObjects/
// BaseEntity.cs
protected void AddEvent(DomainEvent @event) => _domainEvents.Add(@event);
public ICollection<DomainEvent> GetEvents() => _domainEvents;
// DomainEvent.cs
public abstract record DomainEvent(Guid Id, DateTime Timestamp) : INotification;
CartModelherdaBaseEntityeIAggregateRoot(Core.OrgDB/Entities/CartModel.cs)- Tecnicamente pode chamar
AddEvent(...)na entidade
Onde domain events funcionam hoje
| DbContext | Despacha events? | Entidades exemplo |
|---|---|---|
CoreDbContext (Core.DB) | Sim — após SaveChangesAsync, via MediatR | UserModel |
CoreOrgDbContext (Core.OrgDB) | Não | CartModel, StoreModel, etc. |
Fluxo em CoreDbContext.SaveChangesAsync:
var events = ChangeTracker.Entries<BaseEntity>()
.SelectMany(e => e.Entity.GetEvents()).ToList();
var result = await base.SaveChangesAsync(cancellationToken);
foreach (var @event in events)
await _mediator.SendNotification((dynamic)@event, cancellationToken);
Exemplo real — UserModel
UserModel dispara eventos em métodos de domínio:
AddEvent(new UserClearCacheEvent(Guid.NewGuid(), Id));
AddEvent(new UserForceLogoutEvent(Guid.NewGuid(), Id));
Handlers MediatR ficam no serviço consumidor (ex: Admin.API):
public class UserClearCacheNotificationHandler : INotificationHandler<UserClearCacheEvent>
O handler de notificação executa efeito colateral (limpar cache Redis) — não publica SQS diretamente da entidade.
Por que NÃO usar domain events para CartRecommendationsHistory
1. CoreOrgDbContext não despacha
Cart persiste via CoreOrgDbContext (multi-tenant Org DB). Esse contexto não tem override de SaveChangesAsync com despacho de events.
Para funcionar, seria necessário:
- Alterar
CoreOrgDbContextemcoezzion-db-core(pacote compartilhado) - Injetar
IZZMediatorno DbContext Org (hoje não existe) - Impacto em todas as entidades Org que herdam
BaseEntity
Blast radius alto para uma feature isolada de observabilidade.
2. Dados do evento de histórico não pertencem à entidade
CartRecommendationsEvent precisa de:
| Campo | Origem |
|---|---|
Schema | IUserProvider / tenant do request |
Url | Endpoint HTTP (api/cart, api/cart/items) |
Request | Command serializado |
Response | Resultado da operação (DTO ou erro) |
StatusCode | 200 ou 400 conforme outcome |
EventType | Enum do tipo de operação |
A entidade CartModel não tem (nem deveria ter) contexto HTTP. Domain event puro na entidade obrigaria:
- Passar request/response para métodos de domínio (
cart.RecordRecommendationAdded(request, response)) - Acoplar entidade de persistência a contrato de API
- Violar separação camadas da skill dotnet
3. Timing da publicação
Requisitos (task #196022 RF-02):
- Publicar após persistência bem-sucedida do carrinho
- Fire-and-forget — falha de observabilidade não deve impactar resposta ao cliente
- Duplicatas intencionais em retry de idempotência
Domain event dispara no SaveChanges — antes de handlers posteriores na chain (link, attendance, etc.). O PublishRecommendationHistoryHandler está depois de SaveLinkUrlHandler justamente para ter LinkUrl no response.
Evento de domínio no SaveCartHandler seria cedo demais para o payload de create.
4. Add/Remove não passam por chain de create
Handle(AddItemToCartCommand) e Handle(RemoveCartItemCommand) estão em CartService diretamente — sem pipeline de handlers. Domain events no CartModel exigiria:
- Métodos de domínio em
CartModel(AddRecommendedItem,RemoveRecommendedItem) que disparam events - Refatoração maior do fluxo atual
- Mesmo problema de contexto HTTP (schema, url, status code)
5. Padrão Coezzion para side-effects de integração
Para efeitos colaterais assíncronos (SQS, cache, integração), o ecossistema usa Event Handlers explícitos ou Queue Services, não domain events:
IChangeCartDataEventHandler(Integration)ILogQueueService/LogQueueService(Checkout — referência AllLogs)IPaymentCreatedEventHandler(Payments Reports)
Conclusão
| Abordagem | Viável? | Overhead |
|---|---|---|
Domain events via BaseEntity.AddEvent | Não recomendado | Alto — alterar CoreOrgDbContext + acoplar entidade a HTTP |
| Event Handler / Publisher explícito | Recomendado | Baixo — alinhado a AllLogs e convenção dotnet-skill |
| Manter lógica inline no CartService | Funciona mas | Médio — duplicação, difícil manter |
Decisão: usar ICartRecommendationHistoryPublisher (padrão ILogQueueService), chamado explicitamente nos pontos de negócio.