Annotation Interface InterceptApply
This annotation is typically used to:
- Suppress updates that should be ignored
- Rewrite or correct invalid updates
- Split a single update into multiple updates
Interceptors are invoked before any @Apply or @AssertLegal
methods. If multiple interceptors match, they are invoked recursively until the result stabilizes.
For independently stored models, an interceptor declared on the payload runs before an applicable interceptor
declared on the model. The model interceptor receives the payload interceptor's actual output. A replacement with
a different payload type starts the same payload-then-model selection for that new type.
Interceptors can return:
- The original update (no change)
nullorvoidto suppress the update- An
Optional,Collection, orStreamto emit zero or more updates - A staged
Graphreturned byGraph.update(java.util.function.UnaryOperator)orGraph.delete()to change that independently stored model in the same commit - A different object to replace the update
Interaction with legality assertions
Interception fully determines the effective update sequence before@AssertLegal and
@Apply are considered:
- A retained update runs its matching immediate assertions before it is applied.
- A suppressed update runs neither its assertions nor its apply methods.
- A replacement runs assertions for the replacement, not for the original update.
- Expanded updates are asserted and applied in encounter order. Each update's immediate assertions see the state produced by earlier updates.
@AssertLegal(afterHandler = true) remain deferred until handler completion. If
an invariant must survive replacement, define it for the effective replacement (or in shared/entity-side logic)
rather than relying on an assertion that only matches the original update.
Method parameters are automatically injected and may include:
- The current entity (if it exists)
- Any parent or ancestor entity in the aggregate
- Any independently stored
@Modelloaded for the current model commit, either as its value or as a lazyGraph<T> - The update object (if defined on the entity side)
- Context like
Metadata,Message, orUser
@Apply.
Note that empty entities (where the value is null) are not injected unless the parameter is annotated with
@Nullable.
Examples
1. Rewrite a duplicate create into an update (inside the update class)
@InterceptApply
UpdateProject resolveDuplicateCreate(Project project) {
// If this method is invoked, the Project already exists
return new UpdateProject(projectId, details);
}
2. Suppress a no-op update
@InterceptApply
Object ignoreNoChange(Product product) {
if (product.getDetails().equals(details)) {
return null; // suppress update
}
return this;
}
Note: You typically do not need to implement this kind of check manually if the
enclosing @Aggregate or specific
@Apply method is configured with
IF_MODIFIED.
That configuration ensures that no event is stored or published if the entity is not modified.
3. Expand a bulk update into individual operations
@InterceptApply
List<CreateTask> explodeBulkCreate() {
return tasks;
}
4. Change graph models in the same commit
@InterceptApply
List<?> move(Graph<Order> order) {
Graph<OrderLine> line = order.find(lineId, OrderLine.class)
.orElseThrow();
return List.of(this, line.update(value -> value.withOrderId(targetOrderId)));
}
The original domain update remains the stored event shared by every changed model. Direct graph updates are replayed
against a fresh pinned state after an accepted conflict, so their update function must be deterministic and free of
external side effects. Return ordinary domain updates when their @Apply publication configuration
should govern the transition; returning a graph produced by apply(...) is deliberately unsupported.
5. Recursive interception
If the result of one @InterceptApply method is a new update object, Fluxzero will look for matching
interceptors for the new value as well — continuing recursively until no further changes occur.
- See Also: