Annotation Interface Apply


Indicates that a method or constructor applies an update to an entity, or creates or deletes an entity.

An @Apply method defines how a specific update modifies an entity. This update is typically the payload of a command or other message expressing intent. Once validated and applied, the update may be published and/or stored as an event, depending on the publication configuration.

@Apply can be placed:

  • On a method inside the update class (e.g. UpdateProduct#apply(Product)), which receives the current state of the entity and returns the updated version
  • On a method or static factory in the entity class itself (e.g. Product#update(UpdateProduct)), which describes how the entity processes a given update
  • On a constructor or static method of the entity, if the update creates a new instance

For deletions, returning null signals that the entity should be removed. The applied event is still stored and/or published according to the configured publication settings.

For independently stored models, the return value identifies the target model and its resulting state. An apply may also return an ordered Collection of models; every value then joins the same atomic model commit. A typed collection is validated against its declared element type. Collection<Object> is supported when heterogeneous model types are useful and validates every returned value as a model at runtime. Collection elements must be non-null and each persisted model identity may occur only once. Use Graph.delete() for deletion instead of a null collection element. A void apply is invalid for a model. Legacy mutable entities inside aggregates may continue to use void, although immutable return values are strongly preferred.

When an update has applicable applies on both its payload type and an independently stored model, payload applies run first. Model applies then receive the complete intermediate state produced by all payload applies, including a model that the payload has just created. The two phases produce one atomic transition per model identity. Static model applies remain supported as factories or stateless transformations; an independent static factory is a fallback when the payload phase did not already produce its target. Explicit per-apply settings compose field by field, with an explicit model-side value overriding an explicit payload-side value and DEFAULT inheriting the earlier value. Aggregates retain their existing entity-first, payload-fallback selection contract.

When the entity is part of a larger aggregate, Fluxzero automatically routes the update to the correct entity instance using matching identifier fields, typically annotated with EntityId.

@Apply methods are also used during event sourcing to reconstruct an entity's state from past updates.

Method parameters are injected automatically. Supported parameters include:

  • The current entity instance (for non-static apply methods)
  • Any parent, grandparent, or other ancestor entity in the aggregate hierarchy
  • Any independently stored model loaded for the current model commit, either as its value or as Entity<T>
  • The update object itself
  • The full Message or its Metadata
  • Other context such as the User performing the update
Injected models are read inputs. Only models returned by an apply are targeted by that apply. A singular Model-returning apply may use an injected parent or further ancestor as its write target when no direct write-target ID is supplied. The existing @Parent relation supplies that identity at the pinned pre-apply boundary, including when another apply deletes the child in the same atomic commit. An explicit direct write ID retains precedence; ambiguous ancestors must be qualified with @Association.

Note that empty entities (where the value of the entity is null) are not injected unless the parameter is annotated with @Nullable.

Examples

1. Creating a new entity from an @Apply method inside the update class

@Apply
Issue create() {
    return Issue.builder()
                .issueId(issueId)
                .count(1)
                .status(IssueStatus.OPEN)
                .details(issueDetails)
                .firstSeen(lastSeen)
                .lastSeen(lastSeen)
                .build();
}

2. Updating an entity with a new state

@Apply
Product apply(Product product) {
    return product.toBuilder().details(details).build();
}

3. Deleting an entity

@Apply
Product apply(Product product) {
    return null;
}

4. Defining apply methods inside the entity class

@Apply
static Product create(CreateProduct update) {
    return Product.builder()
                  .productId(update.getProductId())
                  .details(update.getDetails())
                  .build();
}

@Apply
Product update(UpdateProduct update) {
    return this.toBuilder().details(update.getDetails()).build();
}

@Apply
Product delete(DeleteProduct update) {
    return null;
}

Routing example with aggregates and nested entities

@Aggregate
class ProductCategory {
    String categoryId;

    @Member
    List<Product> products;
}
Updates targeting `Product` will automatically be routed based on `@EntityId` inside `Product`.
See Also:
  • Element Details

    • conflictPolicy

      io.fluxzero.common.api.modeling.ModelConflictPolicy conflictPolicy
      Overrides conflict handling for the model produced by this apply.
      Default:
      DEFAULT
    • automaticHandling

      AutomaticModelHandling automaticHandling
      Overrides whether this apply participates in automatic model command handling.
      Default:
      DEFAULT
    • graphProjectionCompletion

      GraphProjectionCompletion graphProjectionCompletion
      Overrides command-result completion for graph projections affected by this apply.
      Default:
      DEFAULT
    • eventPublication

      EventPublication eventPublication
      Controls whether the update should result in a published update, depending on whether the entity was actually modified.

      This overrides the default from the enclosing aggregate, if set.

      Returns:
      update publication behavior
      Default:
      DEFAULT
    • publicationStrategy

      EventPublicationStrategy publicationStrategy
      Controls how the applied update is stored and/or published, and whether publish-only updates advance aggregate state for the owning aggregate type. This strategy takes precedence over eventPublication() if explicitly set. A publish-only apply cannot change an event-sourced Model; Fluxzero rejects such a transition before commit because its stored stream could not reconstruct the new state. Publish-only no-ops and changes to document-loaded models remain supported.
      Returns:
      strategy for persisting and/or publishing the applied update
      Default:
      DEFAULT
    • eventRouting

      AggregateEventRouting eventRouting
      Controls how an update event published by this apply method is assigned to a message segment.

      This setting applies to aggregates and defaults to the enclosing aggregate configuration. Events produced by independent Models use ordinary message routing; annotate their payload with @RoutingKey when related events should share a segment.

      Returns:
      routing behavior for the applied update event
      Default:
      DEFAULT
    • disableCompatibilityCheck

      boolean disableCompatibilityCheck
      Disables apply-compatibility checking for this method.

      Unless property fluxzero.assert.legal.apply-compatibility is explicitly set to false, Fluxzero verifies that at least one @Apply method is compatible with the current entity state.

      Setting this flag to true exempts this method from that check.

      Default:
      false