Annotation Interface Model
Unlike an 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 an independent public projection as well; when event sourcing is absent, the internal current source
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.
Start with this default: event sourcing without a direct document or periodic snapshots. Add DOCUMENT for
an application-wide Fluxzero.search(Product.class) list, or to make the current document authoritative when
event sourcing is omitted. Relationship-scoped search may already use an internal component document supplied by
an explicit Parent.pathInParent() or materializeGraph(); it does not by itself require DOCUMENT.
A pathless parent reference supplies navigation but no component document. See the
Model and Graph query guide
for the capability matrix and current versus event-bound result semantics.
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:
-
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionControls whether applies producing this model may be exposed as automatic command handlers.booleanWhether the latest model state should be stored in the shared application cache.intNumber of older model versions retained in the shared cache.intFrequency at which intermediate states are checkpointed within one reconstruction session.Controls when model changes are committed and whether completion-phase commits may run concurrently.io.fluxzero.common.api.modeling.ModelConflictPolicyConflict handling for this model when an apply does not provide an explicit override.Advanced configuration for the Model's direct current document.Controls whether an applied update produces an event, including unchanged results.Advanced configuration for the materialized whole-graph search document.booleanWhether unknown events should be ignored while reconstructing an event-sourced model.booleanWhether Fluxzero should asynchronously materialize the complete model graph as a separate search document.intMaximum number of snapshots retained for this model.Stable logical name of this Model in persisted Model metadata.Durable representations and authoritative load path for this Model.Controls whether applied events are stored, published, or both.intNumber of stored model events between snapshots.
-
Element Details
-
name
String nameStable 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; prefixbillingand nameInvoicebecomebillingInvoice.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
io.fluxzero.common.api.modeling.ModelConflictPolicy conflictPolicyConflict handling for this model when an apply does not provide an explicit override.ModelConflictPolicy.DEFAULTinherits the application policy, which defaults toModelConflictPolicy.RETRYfor 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 automaticHandlingControls whether applies producing this model may be exposed as automatic command handlers.- Default:
DEFAULT
-
persistence
ModelPersistence[] persistenceDurable representations and authoritative load path for this Model.The set must contain at least one unique value. When
EVENT_SOURCEDis present, the event stream is authoritative. OtherwiseDOCUMENTis authoritative. To useGraph.previous()for historical values, keepEVENT_SOURCEDenabled:DOCUMENTalone stores current state, not previous versions. AddingDOCUMENTto event sourcing preserves history; replacing event sourcing withDOCUMENTdoes not.This setting does not suppress storing or publishing events produced by
Applymethods. A state-changing event-sourced Model apply must store its reconstructing event; aPUBLISH_ONLYorNEVERtransition that would change state is rejected before commit. A publish-only no-op remains a valid domain notification when publication is explicitly set toALWAYS.- Default:
{EVENT_SOURCED}
-
ignoreUnknownEvents
boolean ignoreUnknownEventsWhether unknown events should be ignored while reconstructing an event-sourced model.- Default:
false
-
snapshotPeriod
int snapshotPeriodNumber of stored model events between snapshots. The default0disables periodic snapshots. Enable only to bound measured replay work; snapshots do not create a searchable current document.- Default:
0
-
maxSnapshotCount
int maxSnapshotCountMaximum number of snapshots retained for this model. Values below1are treated as1.- Default:
1
-
cached
boolean cachedWhether 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.
- Default:
true
-
cachingDepth
int cachingDepthNumber of older model versions retained in the shared cache.-1retains all available cached versions;0retains 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 checkpointPeriodFrequency 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 commitPolicyControls when model changes are committed and whether completion-phase commits may run concurrently.The default value resolves from
fluxzero.model.commitPolicywhen present and otherwise usesModelCommitPolicy.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 eventPublicationControls 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. UseALWAYSwhen an unchanged apply intentionally represents a domain event. This setting is evaluated beforepublicationStrategy().NEVERpermits 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 publicationStrategyControls whether applied events are stored, published, or both.PUBLISH_ONLYmay 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
-
document
DocumentProjection documentAdvanced configuration for the Model's direct current document.Include
ModelPersistence.DOCUMENTinpersistence()to enable this projection. Every direct document uses the configured collection, which defaults to the resolved logical Model name. A reference-only document is excluded from unrestricted typed Model search while remaining retrievable via relationships. Model loads, verified state and Graph composition use the separate internal source, unaffected by ordinary public document rewrites. Timestamps default to the applied event timestamp when no paths are configured.Non-searchability is not authorization or encryption. Model state and documents do not inherit
@ProtectDataprotection from input messages; enforce sensitive-state access and storage separately.- Default:
@io.fluxzero.sdk.modeling.DocumentProjection
-
materializeGraph
boolean materializeGraphWhether Fluxzero should asynchronously materialize the complete model graph as a separate search document.Fluxzero retains the root's current source in type-isolated internal storage, independently of any public document projection. Only the separately named whole-graph collection is allowed to lag; its high-watermark is exposed through the model repository. The collection defaults to the resolved direct-model collection plus
-graphswhen present, or to<logical Model name>-graphsotherwise.- Default:
false
-
graphProjection
GraphProjection graphProjectionAdvanced configuration for the materialized whole-graph search document.This configuration does not enable materialization by itself; set
materializeGraph()totrue.- Default:
@io.fluxzero.sdk.modeling.GraphProjection
-