Annotation Interface InterceptApply


@Documented @Retention(RUNTIME) @Target(METHOD) public @interface InterceptApply
Indicates that a method should intercept and potentially transform an update before it is applied to an entity.

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:

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 @Model loaded for the current model commit, either as its value or as a lazy Graph<T>
  • The update object (if defined on the entity side)
  • Context like Metadata, Message, or User
Injected models are read inputs unless one of the emitted updates later targets and returns them from an @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: