Event Handler
The Readmodel interface in comby defines the contract for managing queryable representations of domain state. It combines two key responsibilities: event handling and state restoration, enabling the read model to remain up-to-date and consistent with the event-sourced domain.r validating the command and emitting events as a result of the command being executed.
In comby an EventHandler
is defined as an interface
as follows:
type DomainEventHandler struct {
DomainEvtPath string
DomainEvtName string
DomainEvt DomainEvt
DomainEvtHandlerFunc DomainEventHandlerFunc
}
type EventHandler interface {
Identifier
// GetDomainEventHandlers retrieves the list of all events handlers for a given domain event.
GetDomainEventHandlers(domainEvt DomainEvt) []*DomainEventHandler
// GetDomainEvents returns a list of all supported domain events.
GetDomainEvents() []DomainEvt
}
The GetDomainEventHandlers method retrieves a list of event handlers (DomainEventHandler
) for a specific domain event (DomainEvt
). This dynamic retrieval enables the framework to process domain events by invoking the appropriate handlers, ensuring that events are managed according to their type and context.
The GetDomainEvents method returns a list of all domain events that the event handler supports. This provides a clear overview of the event types the event handler is designed to process, facilitating event routing and system introspection.
By implementing the EventHandler
interface, developers can create modular and reusable event-handling components, ensuring that domain events are consistently and efficiently managed within the comby framework.
Event Handler Orderer
As inconspicuous as this interface may look, it can have a very big impact. Typically, event handlers are registered in the facade and the facade calls them individually. However, there are situations where we as users want to gain control over the order, for example to prevent cascading effects or to force certain processes to run in the correct order. The EventHandlerOrderer
interface allows us to do just that and looks like this:
// v2.3+
type EventHandlerOrderer interface {
GetOrder() int
}
Once we've implemented an EventHandler
—be it a Reactor
, ReadModel
, or similar—we can easily specify the global sort order with a receiver method called GetOrder. The smaller the number, the earlier the EventHandler
's individual DomainEventHandlers
will be executed.
type AnyReactor struct {
*comby.BaseReadmodel
}
type AnyReadmodel struct {
*comby.BaseReadmodel
}
func (r *AnyReactor) AnyEvent(ctx context.Context, evt comby.Event, domainEvt *aggregate.AnyCreatedEvent) error {
// (2) AnyReactor.AnyEvent will be called AFTER AnyReadmodel.AnyEvent
}
func (r *AnyReadmodel) AnyEvent(ctx context.Context, evt comby.Event, domainEvt *aggregate.AnyCreatedEvent) error {
// (1) update internal state ...
}
func (r *AnyReactor) GetOrder() int {
return 2
}
func (r *AnyReadmodel) GetOrder() int {
return 1
}
This allows us to prevent cascading effects as described here: Cascading Effects
Ordering in Default
Since comby is an application framework, the default values (domains such as Account
, Tenant
, Identity
, etc.) should normally be called earlier than the user-implemented domains. Therefore, the default domains have been assigned a negative order value. This ensures that they are executed before any user-defined domains, which typically have a default order of 0
unless explicitly specified otherwise.
These values can be adjusted either via an environment variable:
COMBY_DEFAULT_ORDER_READMODEL=-20
COMBY_DEFAULT_ORDER_REACTOR=-10
or directly at comby runtime:
comby.DEFAULT_ORDER_READMODEL = -20
comby.DEFAULT_ORDER_REACTOR = -10