Reactor
A Reactor in comby is an augmented type of Readmodel
designed to actively react to domain events and trigger side effects beyond state updates. Unlike traditional readmodels that primarily focus on maintaining queryable projections of domain data, a Reactor listens for domain events and takes specific actions in response.
Key Responsibilities of a Reactor:
Event Reaction: Reactors subscribe to domain events, processing them through registered event handlers. Each handler encapsulates the logic for reacting to a specific event. For example, a Reactor might handle an
OrderPlacedEvent
to send a confirmation email to the customer or notify downstream systems.Command Generation: Reactors can create and dispatch new commands as a reaction to domain events. This enables the Reactor to initiate additional workflows or trigger further changes in the domain. For instance, after processing an
OrderPaidEvent
, a Reactor might issue aShipOrderCommand
to move the order to the next stage.Side Effects: Beyond command generation, Reactors can be used for initiating side effects such as:
- Sending emails or SMS notifications.
- Logging or tracking events in external systems.
- Triggering third-party integrations.
Implementation Details
A Reactor builds upon the BaseReadmodel
, inheriting its event-handling and state-restoration capabilities. Event handlers in the Reactor are registered in the same way as in a readmodel, using comby.AddDomainEventHandler
. However, these handlers are specifically designed to perform actions or generate new domain interactions rather than just updating internal state.
type Reactor struct {
*comby.BaseReadmodel
// ...
}
Dependencies such as the Facade
or AggregateRepository
can be injected during instantiation, enabling the Reactor to access external services or repositories as needed.
// Facade, AggregateRepository or any other dependency can be passed from the caller
func NewReactor(fc comby.Facade, ... ) *Reactor {
rm := &Reactor{}
rm.BaseReadmodel = comby.NewBaseReadmodel(fc.GetEventRepository())
rm.Domain = "Order"
rm.Name = "SimpleOrderReactor"
// register domain event handlers
comby.AddDomainEventHandler(rm, rm.SendEmailWhenOrderPlaced)
return rm
}
The NewReactor
function initializes a new Reactor instance:
It sets the Domain ("Order") and assigns a unique Name ("SimpleOrderReactor"). It registers the domain event handler SendEmailWhenOrderPlaced using comby.AddDomainEventHandler
, ensuring the Reactor listens for OrderPlacedEvent
events.
func (rm *Reactor) SendEmailWhenOrderPlaced(ctx context.Context, evt comby.Event, domainEvt *aggregate.OrderPlacedEvent) error {
// send email to customer ...
return nil
}
The SendEmailWhenOrderPlaced method defines the behavior for handling the OrderPlacedEvent
. When this event is received. The Reactor processes the event, extracting the necessary information. It triggers an action, such as sending an email to the customer.
func (rm *Reactor) RestoreState(ctx context.Context, restoreFromZero bool) (comby.RestorationDoneCh, error) {
// do not restore state in any reactor
return nil, nil
}
Unlike typical readmodels, Reactors do not maintain state or need to restore state from historical events. The RestoreState
method is overridden to ensure no restoration logic is applied.