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
Unless current-input checks are enabled, 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
With current-input checks disabled, 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, enable assertCurrent(): the current input's immediate assertions
run before interception and its after-handler assertions see final state. Suppressed inputs are checked too.
Every replacement keeps its own normal assertions; a bare unchanged input is not checked twice.
Model reads participate in atomic conflict checks; RETRY reevaluates and guarded ACCEPT becomes FAIL.
DEFAULT follows fluxzero.interceptApply.assertCurrent (FLUXZERO_INTERCEPT_APPLY_ASSERT_CURRENT),
enabled from defaults version 2026.10.04. Explicit method overrides take precedence.
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:
-
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionValidates the selected interceptor's current input within the same commit attempt, before interception.
-
Element Details
-
assertCurrent
AssertCurrent assertCurrentValidates the selected interceptor's current input within the same commit attempt, before interception. Immediate assertions see the current state; after-handler assertions retain this input and see final state. A suppressed input is still checked. An unchanged input is checked once per scope and phase. Only operations with legality checks enabled use this option; replay and unchecked apply remain unchanged. DEFAULT usesfluxzero.interceptApply.assertCurrent, enabled from defaults version2026.10.04.- Default:
DEFAULT
-