Skip to content

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:

go
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:

go
// 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.

go
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:

go
COMBY_DEFAULT_ORDER_READMODEL=-20
COMBY_DEFAULT_ORDER_REACTOR=-10

or directly at comby runtime:

go
comby.DEFAULT_ORDER_READMODEL = -20
comby.DEFAULT_ORDER_REACTOR = -10