Annotation Interface Member
Entities marked with @Member share the persistence boundary of their owning root. Within an
@Aggregate or @Model, they share the root's stream, cache, search document,
snapshots, and lifecycle. When an update targets a nested entity, Fluxzero traverses the root structure to locate
the correct entity (or entities). In @Stateful handlers, member objects may also declare
@Handle... methods and their own @Association properties; updates are written back by storing the
parent stateful handler.
@Member remains supported and is not deprecated, including its use in @Stateful handlers.
Prefer separate Model types for new domain state so histories and relationships can evolve independently.
Choose @Member when the nested value deliberately has no independent persistence lifecycle:
creation, change history, retention, and deletion all belong to the root. State that can live or evolve
independently is another Model, connected through Parent, even when it is normally rendered as an
item in a root collection. A meaningful identity is strong evidence of that independent lifecycle, but the identity
may be parent-scoped; lack of a globally unique functional ID is not a reason to
embed. Searchability, update frequency, load strategy, and convenient collection placement are persistence or query
choices, not lifecycle boundaries.
This annotation supports modeling persistence roots with deliberately embedded values, for example:
@Model
public class Invoice {
@EntityId
String invoiceId;
@Member
List<InvoiceLine> lines;
}
Here, a line exists only as part of its invoice and deliberately shares the invoice's entire lifecycle. Updates
targeting InvoiceLine values are routed by matching the identifier declared inside that class.
Support for new entities
For independently stored Models, address the owning Model using a typed command ID or
Fluxzero.loadGraph(ownerId).assertAndApply(update). A member ID alone does not select its root.
Payload-root applies run first, embedded member applies next, and root-model applies last. The composed state
commits to the root stream; members do not acquire separate event memberships. Before/after assertions on members
and payload assertions requiring a member participate in the same operation, including their Model read dependencies.
Replay executes the same member applies without re-running assertions.
Model planning discovers handlers on the declared member type and its sealed permitted subtypes. Explicitly addressed owners also support subtype-only handlers in open hierarchies, selected from the loaded member value. Their dependencies share the commit boundary and use historical values during replay. Automatic command subscriptions still need a discoverable handler contract; the SDK does not scan for arbitrary implementations of an open member interface. Corrected member replay also applies to existing RC events; snapshots produced with previously skipped member handlers must not be treated as equivalent to a fresh reconstruction of that history.
If no matching entity is found for a given update, Fluxzero will still evaluate the update against applicable
@Apply and @AssertLegal methods. This allows new entity creation directly from the update payload
when appropriate logic is defined.
For example:
@Apply
InvoiceLine create() {
return new InvoiceLine(lineId, amount);
}
will be used to create a new InvoiceLine if no matching line exists in the lines member list.
Immutability and parent updates
Fluxzero assumes immutability by default. When a nested entity is added, removed, or modified, Fluxzero will attempt
to create a new version of the parent entity by copying and updating the annotated container field (list, map, etc.).
The parent is not modified directly.
This behavior ensures safe update propagation and accurate change tracking, especially during event sourcing.
For example, if Invoice has a list of InvoiceLines:
@Member
List<InvoiceLine> lines;
and one line is updated, Fluxzero will replace the lines list with a new list containing the updated value.
For record owners, Fluxzero rebuilds the owner through the canonical constructor when the member is a record
component. For Kotlin data classes, Fluxzero can use the generated copy(...) method. A type can still expose
an explicit wither such as withLines(...) or configure wither() when custom update behavior is
needed.
public record Invoice(@EntityId String id, @Member List<InvoiceLine> lines) {
public Invoice withLines(List<InvoiceLine> lines) {
return new Invoice(id, lines);
}
}
Optional attributes
idProperty(default: empty):
Use this to explicitly specify the identifier property name on the nested entity. By default, Fluxzero locates the identifier via theEntityIdannotation.wither(default: empty):
Defines a method (by name) that should be invoked to update the container when the entity is added, removed, or replaced. Normally, Fluxzero will update the container (e.g., list or map) automatically. This setting is useful for immutable containers or cases requiring side effects during updates.
Supported container types:
- Single nested entities (e.g.,
Product product) - Collections of entities (e.g.,
List<Product>). Non-nullEntityIdvalues must be unique within one member collection. - Maps of entities keyed by their identifier. Newly added map members use
EntityIdoridProperty()as the map key.
- See Also:
-
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionSpecifies the name of the identifier property on the nested entity, if different from the default detected one.Optionally defines the name of a method that should be used to apply updates to the container of the nested entity.
-
Element Details
-
idProperty
String idPropertySpecifies the name of the identifier property on the nested entity, if different from the default detected one.- Default:
""
-
wither
String witherOptionally defines the name of a method that should be used to apply updates to the container of the nested entity.Normally, Fluxzero automatically updates the container (for lists, maps, or singletons). This attribute is only necessary if a custom update method must be invoked instead.
- Default:
""
-