Annotation Interface Model


Marks an independently identified and stored domain model.

Prefer separate models for new domain state. Each model keeps its own history as relationships change; a model can move to another parent or gain additional relationships without transferring a containing root's history. Several models can still participate in one atomic operation through Graph.

Unlike an embedded member in a legacy Aggregate, a model is its own persistence and lifecycle boundary. Loading or updating it does not require loading a parent, sibling, child, or an artificial aggregate root. A model may still contain embedded entities declared with @Member; those members share the model's stream, cache, search document, snapshots, and lifecycle.

Choose this boundary from domain lifecycle first: state that can be created, changed, retained, deleted, or whose history matters independently is a separate model, even when it is normally displayed in a parent's collection. Connect such a child with @Parent. A meaningful domain identity is strong evidence for that boundary, not an additional gate: an independently living child may use a globally unique ID or a parent-scoped ID. Collection shape, searchability, storage format, update frequency, and convenient embedding do not make independently living state a Member.

Model identity is the repository representation of its @EntityId. Applications can use a typed Id, annotation-level prefix/postfix affixes, or both to isolate otherwise equal functional identifiers.

An @Apply method targets a model by returning that model. Returning null deletes the targeted model while retaining the applied event according to the configured publication settings. Returning void is invalid for model applies because it does not identify a stored result. Legacy mutable aggregate applies remain supported.

Persistence

persistence() makes the durable representations and authoritative load path explicit. Event-sourced models are reconstructed from their model stream, optionally from a snapshot. Adding ModelPersistence.DOCUMENT maintains internal current state; when event sourcing is absent, that current state is authoritative. Event storage and publication remain independent and are controlled by eventPublication(), publicationStrategy(), and per-apply overrides. Internal component documents used for Graph composition are likewise orthogonal and never change the selected load path.

Example

@Model
public record Product(@EntityId ProductId productId, ProductDetails details) {
    @Apply
    Product rename(RenameProduct command) {
        return new Product(productId, details.withName(command.name()));
    }
}
Here ProductDetails is an immutable business value with a copy method such as Lombok's generated withName. Use a cohesive details value even when the only descriptive field is a name; keep identity, relationships and simple status distinct. A plain details value needs neither @Model nor Member. A focused rename command may carry a scalar while replacing only that field in the existing details. An update may instead create or update the model from a payload-side @Apply. When both sides define an applicable apply, Fluxzero applies the payload first and invokes the model method against that intermediate state. This lets one instance method consistently enforce model-owned behavior for both creation and later updates.

Persistence and searchability are separate choices. Enable searchable() to query this Model and its composed descendants; SearchSettings.includeDescendants() limits a root's Graph scope. An explicit Parent.pathInParent() only describes composition, never storage. The default Graph mode retains separate indexed node documents and composes them when read. Choose GraphProjectionMode.ASYNC or GraphProjectionMode.AWAIT to additionally store the composed Graph. Model declarations are indexed at compilation by ModelTypeProcessor. Enable SDK annotation processing (Kotlin: kapt) in each contract module and preserve ModelTypes.INDEX when packaging. This index discovers classes without registering message handlers; the optional serialization @RegisterType is not required. Contract JARs built before this index was introduced must be rebuilt for cold discovery. A shaded JAR must append all contributing Model indexes rather than retain only one. Identified abstract/interface contracts remain discoverable; identity-less abstract/interface inheritance templates are not standalone Models and are omitted from the runtime catalog.

See Also:
  • Element Details

    • name

      String name
      Stable logical name of this Model in persisted Model metadata.

      The default is the simple name of the concrete Model class. This name deliberately does not contain the Java package, so moving or renaming a class remains possible by retaining its previous logical name explicitly. Applications sharing one Runtime namespace can prepend an application-scoped prefix with fluxzero.model.namePrefix; the prefix is concatenated literally and should therefore include any desired separator; prefix billing and name Invoice become billingInvoice.

      This is a durable identity. Changing it for an existing Model creates a different Model type and requires an application-managed data transition.

      Default:
      ""
    • conflictPolicy

      ModelConflictPolicy conflictPolicy
      Conflict handling for this model when an apply does not provide an explicit override. ModelConflictPolicy.DEFAULT inherits the application policy, which defaults to ModelConflictPolicy.RETRY for updates and creations alike, independently of the defaults version. Factory compatibility is checked again after retry; retry does not implicitly turn a factory into an upsert.
      Default:
      DEFAULT
    • automaticHandling

      AutomaticModelHandling automaticHandling
      Controls whether applies producing this model may be exposed as automatic command handlers.
      Default:
      DEFAULT
    • persistence

      ModelPersistence[] persistence
      Durable representations and authoritative load path for this Model.

      The set must contain at least one unique value. When EVENT_SOURCED is present, the event stream is authoritative. Otherwise DOCUMENT is authoritative. To use Graph.previous() for historical values, keep EVENT_SOURCED enabled: DOCUMENT alone stores current state, not previous versions. Adding DOCUMENT to event sourcing preserves history; replacing event sourcing with DOCUMENT does not.

      This setting does not suppress storing or publishing events produced by Apply methods. A state-changing event-sourced Model apply must store its reconstructing event; a PUBLISH_ONLY or NEVER transition that would change state is rejected before commit. A publish-only no-op remains a valid domain notification when publication is explicitly set to ALWAYS.

      Default:
      {EVENT_SOURCED}
    • ignoreUnknownEvents

      boolean ignoreUnknownEvents
      Whether unknown events should be ignored while reconstructing an event-sourced model.
      Default:
      false
    • snapshotPeriod

      int snapshotPeriod
      Number of stored model events between snapshots. The default 0 disables periodic snapshots. Enable only to bound measured replay work; snapshots do not create a searchable current document.
      Default:
      0
    • maxSnapshotCount

      int maxSnapshotCount
      Target number of newest snapshots retained for this model. Any negative value retains all periodic snapshots; 0 is treated as 1. This does not enable snapshots: snapshotPeriod() must be positive. Unlimited retention does not prevent explicit physical erasure and does not change event-history retention. Enable negative values only after upgrading the Runtime to support unlimited Model snapshot retention. Concurrent Runtime writes may retain more; this is a cleanup target, not an exact count at commit completion. A later bounded snapshot write also trims earlier excess. An idle Model may retain excess until another such write; snapshot storage still completes before the commit is acknowledged.
      Default:
      1
    • cached

      boolean cached
      Whether the latest model state should be stored in the shared application cache.

      Models participating in a commit may additionally be retained in a commit-local cache until the commit completes. A document revision alone is not a namespace snapshot: simple DOCUMENT writes verify that revision when an additional transactional read first needs a shared boundary; complex contexts verify before evaluation.

      Default:
      true
    • cachingDepth

      int cachingDepth
      Number of older model versions retained in the shared cache. -1 retains all available cached versions; 0 retains only the latest version.

      Independent models retain one previous version by default so event handlers can compare the event-visible model with Entity.previous(). Retaining an unbounded revision chain must be an explicit choice because model caches are expected to contain far more keys than aggregate caches.

      Default:
      1
    • checkpointPeriod

      int checkpointPeriod
      Frequency at which intermediate states are checkpointed within one reconstruction session.

      Checkpoints avoid replaying the same prefix for repeated historical dependency loads. They are bounded by the reconstruction session and are not retained as document revisions.

      Default:
      100
    • commitPolicy

      ModelCommitPolicy commitPolicy
      Controls when model changes are committed and whether completion-phase commits may run concurrently.

      The default value resolves from fluxzero.model.commitPolicy when present and otherwise uses ModelCommitPolicy.ASYNC_AFTER_HANDLER_AWAIT_AFTER_BATCH. Independent models were introduced with this default, so it does not depend on the active defaults version.

      Default:
      DEFAULT
    • eventPublication

      EventPublication eventPublication
      Controls whether an applied update produces an event, including unchanged results.

      Independent models default to IF_MODIFIED, so a no-op apply does not create a model-stream or globally published event. Use ALWAYS when an unchanged apply intentionally represents a domain event. This setting is evaluated before publicationStrategy(). NEVER permits eventless state changes only for document-loaded Models, not event-sourced Models. It does not suppress incoming command/webrequest logs, results or application logging.

      Default:
      IF_MODIFIED
    • publicationStrategy

      EventPublicationStrategy publicationStrategy
      Controls whether applied events are stored, published, or both.

      PUBLISH_ONLY may mutate a document-loaded model. For an event-sourced model it may only publish an unchanged result, because otherwise the next reconstruction could not reproduce the committed state.

      Default:
      DEFAULT
    • searchable

      boolean searchable
      Whether this Model independently activates search.

      Setting this to true starts a searchable scope at this Model and, by default, makes composed descendants effectively searchable as well. Setting it to false does not mean that this Model can never be searched; it only means that this Model does not activate search by itself. A searchable ancestor can still include it.

      searchable() starts a searchable scope. Parent.propagateSearch() controls whether that scope may propagate across a parent relationship. Set @Parent(propagateSearch = false) to block inherited searchability through one edge while leaving the child's own search activation and other parent relationships independent. SearchSettings.includeDescendants() can limit this Model's own searchable scope to the Model itself; it does not prevent the Model from participating in a broader searchable ancestor scope.

      Search activation does not require every property to participate in text search. Use @SearchExclude on individual properties or types to exclude them from text indexing and matching while retaining them in the stored and returned document.

      Whether the complete composed Graph is materialized is a separate choice controlled by graphProjection(). Its default mode is NONE: Fluxzero keeps the separate indexed node documents and composes Graph results live when queried, without storing a complete Graph document. Use ASYNC or AWAIT only when a stored composed Graph is desired.

      Default:
      false
    • searchSettings

      SearchSettings searchSettings
      Per-node index settings and the scope of Graph queries rooted at this Model. Does not activate search.
      Default:
      @io.fluxzero.sdk.modeling.SearchSettings
    • graphProjection

      GraphProjection graphProjection
      Optional materialization of a complete composed Graph, configured independently from search activation. The default NONE stores no complete Graph document and composes searchable Graphs live from the separate indexed nodes. ASYNC/AWAIT additionally maintain a stored composed Graph.
      Default:
      @io.fluxzero.sdk.modeling.GraphProjection