| name | wire-event |
| description | Phase 4 driver — wire one event-consumer inter-invariant: event class, subscription, handler, polling, EventProcessing chain, tracked field, update functionality, service method, test, tick plan.md. Arguments: "<ConsumerAggregate> <EventName>" |
Wire Event: $ARGUMENTS
You are wiring one Phase 4 inter-invariant for the (consumer, event) pair in $ARGUMENTS.
Complete all steps in order. Tick the checkbox in plan.md after the test passes.
Layer 4 only. The consumer caches state from the publisher via domain events (eventually
consistent, ~1 s lag). No operation is blocked. If blocking is needed, use Layer 3 instead.
Step 0 — Gather context
Parse $ARGUMENTS:
- ConsumerAggregate (e.g.,
CourseExecution) — caches external state
- EventName (e.g.,
CourseUpdatedEvent) — event that triggers the update
Read before writing any code:
plan.md — find the entry under Phase 4. Confirm the test class name.
- The aggregate-grouping template (
*-aggregate-grouping.md) — §2 for the consumer. Shows
exactly which fields to cache from this event and which aggregate is the publisher.
- The consumer aggregate class and service — understand existing fields and methods.
- The
events/ top-level directory — check if the event class already exists.
docs/concepts/events.md — canonical wiring snippet (subscription anchor rules).
applications/quizzes/microservices/<consumer>/events/ — reference for event wiring structure.
Step 1 — Event class
If the event class does not already exist, create events/<EventName>.java:
public class <EventName> extends Event {
private Integer entityAggregateId;
private Integer anchorAggregateId;
public <EventName>(Integer entityAggregateId, Integer anchorAggregateId) {
super(anchorAggregateId);
this.entityAggregateId = entityAggregateId;
this.anchorAggregateId = anchorAggregateId;
}
public <EventName>() {}
}
If the event class already exists (wired for another consumer earlier), reuse it — do not create
a duplicate.
Step 2 — Publish event from publisher service
In <Publisher>Service.java, at the operation that triggers this event:
unitOfWorkService.registerEvent(new <EventName>(entityId, anchorId), unitOfWork);
If already registered (another consumer was wired first), skip.
Step 3 — Add tracked state to ConsumerAggregate
In microservices/<consumer>/aggregate/<Consumer>.java:
- Add the tracked state field(s) with getter/setter, initialised to a safe default
(
null / 0 / false / new HashSet<>())
- Copy them in the copy constructor
- Add a private helper and call it from
getEventSubscriptions():
@Override
public Set<EventSubscription> getEventSubscriptions() {
Set<EventSubscription> subs = new HashSet<>();
if (getState() == AggregateState.ACTIVE) {
interInvariant<Xxx>(subs);
}
return subs;
}
private void interInvariant<Xxx>(Set<EventSubscription> subs) {
subs.add(new <Consumer>Subscribes<Xxx>(this.relevantRef));
}
For per-entity subscriptions (one per member of a collection):
for (MemberType member : this.memberCollection) {
subs.add(new <Consumer>Subscribes<Xxx>(member));
}
Step 4 — Subscription class
Create microservices/<consumer>/events/subscribe/<Consumer>Subscribes<Xxx>.java:
public class <Consumer>Subscribes<Xxx> extends EventSubscription {
public <Consumer>Subscribes<Xxx>(SomeRef ref) {
super(ref.getAnchorAggregateId(), ref.getAnchorVersion(), <EventName>.class);
}
public <Consumer>Subscribes<Xxx>() {}
@Override
public boolean filter(Event event) {
<EventName> e = (<EventName>) event;
return e.getAnchorAggregateId().equals(this.subscribedAggregateId);
}
}
subscribedAggregateId must match the anchorAggregateId used in the event constructor.
Step 5 — Command class
Create command/<consumer>/Update<Xxx>Command.java:
public class Update<Xxx>Command extends Command {
private final Integer consumerAggregateId;
public Update<Xxx>Command(UnitOfWork unitOfWork, String serviceId,
Integer consumerAggregateId, ...) {
super(unitOfWork, serviceId);
this.consumerAggregateId = consumerAggregateId;
}
}
Step 6 — Event handler
Create microservices/<consumer>/events/handling/handlers/<Xxx>EventHandler.java.
Extend the existing base handler for this consumer if one exists:
public class <Xxx>EventHandler extends <Consumer>EventHandler {
public <Xxx>EventHandler(<Consumer>Repository repo,
<Consumer>EventProcessing processing) {
super(repo, processing);
}
@Override
public void handleEvent(Integer aggregateId, Event event) {
this.consumerEventProcessing.process<Xxx>Event(aggregateId, (<EventName>) event);
}
}
Step 7 — Polling method in EventHandling
In microservices/<consumer>/events/handling/<Consumer>EventHandling.java, add:
@Scheduled(fixedDelay = 1000)
public void handle<Xxx>Events() {
eventApplicationService.handleSubscribedEvent(<EventName>.class,
new <Xxx>EventHandler(consumerRepository, eventProcessing));
}
Step 8 — EventProcessing → Functionalities wiring
In <Consumer>EventProcessing.java:
public void process<Xxx>Event(Integer aggregateId, <EventName> event) {
consumerFunctionalities.update<Xxx>(aggregateId, event);
}
In <Consumer>Functionalities.java, add update<Xxx> method that creates a UoW and dispatches
Update<Xxx>Command.
Step 9 — Update functionality (Sagas)
Sagas (coordination/sagas/Update<Xxx>FunctionalitySagas.java): single-step workflow that
sends Update<Xxx>Command to the consumer service.
Step 10 — State update in ConsumerService
Add the method called by the command handler. Load, copy, update tracked field(s), register:
public void update<Xxx>(Integer aggregateId, <EventName> event, UnitOfWork unitOfWork) {
<Consumer> old = aggregateLoadAndRegisterRead(aggregateId, unitOfWork);
<Consumer> next = factory.copy(old);
unitOfWorkService.registerChanged(next, unitOfWork);
}
Step 11 — Run the test
Add T3 test cases to src/test/groovy/.../sagas/<consumer>/<Consumer>InterInvariantTest.groovy
(or the existing file if prior inter-invariants for this consumer were already wired).
Follow the T3 template in docs/concepts/testing.md.
Cover:
- Event received → consumer aggregate reflects the change
- Event for an unrelated entity → state unchanged
Trigger event processing manually: call <Consumer>EventHandling.handle<Xxx>Events() directly
(@Scheduled does not run in @DataJpaTest).
Run the full test class — all cases must pass:
cd applications/<appName>
mvn clean -Ptest-sagas test -Dtest=<ConsumerAggregate>InterInvariantTest
Step 12 — Tick plan.md
Done
Report:
- Event class: created or reused
<EventName>.java
- Consumer aggregate: tracked field(s) added and wired
- Test: green —
<ConsumerAggregate>InterInvariantTest