Skip to main content

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;
  • CartModel herda BaseEntity e IAggregateRoot (Core.OrgDB/Entities/CartModel.cs)
  • Tecnicamente pode chamar AddEvent(...) na entidade

Onde domain events funcionam hoje

DbContextDespacha events?Entidades exemplo
CoreDbContext (Core.DB)Sim — após SaveChangesAsync, via MediatRUserModel
CoreOrgDbContext (Core.OrgDB)NãoCartModel, 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 CoreOrgDbContext em coezzion-db-core (pacote compartilhado)
  • Injetar IZZMediator no 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:

CampoOrigem
SchemaIUserProvider / tenant do request
UrlEndpoint HTTP (api/cart, api/cart/items)
RequestCommand serializado
ResponseResultado da operação (DTO ou erro)
StatusCode200 ou 400 conforme outcome
EventTypeEnum 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

AbordagemViável?Overhead
Domain events via BaseEntity.AddEventNão recomendadoAlto — alterar CoreOrgDbContext + acoplar entidade a HTTP
Event Handler / Publisher explícitoRecomendadoBaixo — alinhado a AllLogs e convenção dotnet-skill
Manter lógica inline no CartServiceFunciona masMédio — duplicação, difícil manter

Decisão: usar ICartRecommendationHistoryPublisher (padrão ILogQueueService), chamado explicitamente nos pontos de negócio.