Add auto-create hook for AgenticAggregate singleton state initialization#332
Conversation
- Add getCreateDomainEvent() to AgenticAggregate interface - Add handleAutoCreateDomainEvent() to KafkaAggregateRuntime - Add initializeState() to AgenticAggregateRuntime interface - Implement initializeState() in KafkaAgenticAggregateRuntime - Update AgenticAggregateRuntimeFactory to pass aggregate reference - Hook auto-create into AgenticAggregatePartition during INITIALIZING Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/040fd779-8f79-4ca7-a05f-5788f1dfcf04 Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com>
…enticAggregateAutoCreateTest - Update existing AgenticAggregate test implementations to implement getCreateDomainEvent() - Update KafkaAgenticAggregateRuntimeTest for new constructor signature - Add comprehensive AgenticAggregateAutoCreateTest with TestBot aggregate implementation - All 87 agentic tests and 54 api tests pass Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/040fd779-8f79-4ca7-a05f-5788f1dfcf04 Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a framework-level auto-create hook so agentic singleton aggregates can bootstrap initial state automatically when a partition starts with no state, instead of requiring an external create command.
Changes:
- Introduces
AgenticAggregate#getCreateDomainEvent()and updates docs/tests to satisfy the new contract. - Adds
KafkaAggregateRuntime#handleAutoCreateDomainEvent(...)and wiresinitializeState()throughKafkaAgenticAggregateRuntime. - Updates
AgenticAggregatePartitionto auto-create state duringINITIALIZINGwhen the state topic is empty, plus adds/updates tests.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| main/runtime/src/main/java/org/elasticsoftware/akces/kafka/KafkaAggregateRuntime.java | Adds handleAutoCreateDomainEvent(...) to create initial state from a domain event without command materialization. |
| main/api/src/main/java/org/elasticsoftware/akces/aggregate/AgenticAggregate.java | Adds required getCreateDomainEvent() hook for singleton bootstrap. |
| main/api/src/main/java/org/elasticsoftware/akces/annotations/AgenticAggregateInfo.java | Updates annotation docs to reflect event-based singleton creation contract. |
| main/agentic/src/main/java/org/elasticsoftware/akces/agentic/AgenticAggregateRuntime.java | Adds initializeState(...) API for partitions to trigger auto-create. |
| main/agentic/src/main/java/org/elasticsoftware/akces/agentic/runtime/KafkaAgenticAggregateRuntime.java | Stores aggregate reference and delegates initializeState() to KafkaAggregateRuntime#handleAutoCreateDomainEvent. |
| main/agentic/src/main/java/org/elasticsoftware/akces/agentic/runtime/AgenticAggregatePartition.java | Implements the INITIALIZING-time auto-create transactional flow when no state exists. |
| main/agentic/src/main/java/org/elasticsoftware/akces/agentic/beans/AgenticAggregateRuntimeFactory.java | Passes the aggregate instance into KafkaAgenticAggregateRuntime. |
| main/agentic/src/test/java/org/elasticsoftware/akces/agentic/runtime/AgenticAggregateAutoCreateTest.java | Adds end-to-end unit test coverage for the new hook and delegation behavior. |
| main/agentic/src/test/java/org/elasticsoftware/akces/agentic/runtime/KafkaAgenticAggregateRuntimeTest.java | Updates constructor signature expectations and adds null-aggregate validation. |
| main/api/src/test/java/org/elasticsoftware/akces/aggregate/AgenticAggregateInfoTest.java | Updates test aggregates to implement getCreateDomainEvent(). |
| main/api/src/test/java/org/elasticsoftware/akces/aggregate/MemoryAwareStateTest.java | Updates test aggregates to implement getCreateDomainEvent(). |
| main/eventcatalog/src/test/java/org/elasticsoftware/akces/eventcatalog/EventCatalogProcessorTest.java | Updates embedded agentic aggregate example to implement getCreateDomainEvent(). |
| } | ||
| stateRepository.rollback(); | ||
| } | ||
| processState = PROCESSING; | ||
| } else { |
There was a problem hiding this comment.
In INITIALIZING, processState is set to PROCESSING unconditionally even if initializeState() fails and the transaction is aborted/rolled back. This can leave the partition running without any state (subsequent commands will see missing state) and also hides the initialization failure. Only transition to PROCESSING after a successful auto-create; on failure either keep INITIALIZING (with retry/backoff) or switch to SHUTTING_DOWN to fail fast.
There was a problem hiding this comment.
create a new ProcessState for INITIALIZING_STATE and implement the State initialization logic there
There was a problem hiding this comment.
Added INITIALIZING_STATE to AggregatePartitionState enum. The INITIALIZING phase now transitions to INITIALIZING_STATE when no state exists, and the auto-create logic runs in the dedicated INITIALIZING_STATE phase. On success → PROCESSING, on failure → SHUTTING_DOWN (fail-fast). Also catches broad Exception to handle unchecked SerializationException. See commit a52240f.
There was a problem hiding this comment.
Added INITIALIZING_STATE to AggregatePartitionState enum. The INITIALIZING phase now transitions to INITIALIZING_STATE when no state exists, and the auto-create logic runs in the dedicated INITIALIZING_STATE phase. On success → PROCESSING, on failure → SHUTTING_DOWN (fail-fast). Also catches broad Exception to handle unchecked SerializationException. See commit a52240f.
| void initializeState(java.util.function.Consumer<org.elasticsoftware.akces.protocol.ProtocolRecord> protocolRecordConsumer, | ||
| java.util.function.BiConsumer<org.elasticsoftware.akces.protocol.DomainEventRecord, org.elasticsoftware.akces.aggregate.IndexParams> domainEventIndexer) | ||
| throws IOException; |
There was a problem hiding this comment.
initializeState(...) is declared with fully-qualified parameter types, unlike the rest of this interface which uses imports. This makes the public API harder to read and is inconsistent with the surrounding style. Prefer importing ProtocolRecord, DomainEventRecord, IndexParams, Consumer, and BiConsumer and using those simple names in the method signature.
There was a problem hiding this comment.
Fixed — added proper imports for Consumer, BiConsumer, ProtocolRecord, DomainEventRecord, and IndexParams, replacing the fully-qualified types. See commit a52240f.
There was a problem hiding this comment.
Fixed — added proper imports for Consumer, BiConsumer, ProtocolRecord, DomainEventRecord, and IndexParams, replacing the fully-qualified types. See commit a52240f.
| try { | ||
| producer.beginTransaction(); | ||
| runtime.initializeState(this::send, this::index); | ||
| producer.commitTransaction(); | ||
| stateRepository.commit(); |
There was a problem hiding this comment.
The auto-create transaction only catches KafkaException and checked IOException. initializeState() / serialization can also throw unchecked exceptions (e.g., org.apache.kafka.common.errors.SerializationException), which would bypass the abort/rollback path and leave the producer transaction open until the partition thread dies. Consider catching Exception (or at least RuntimeException) around the transactional block and always aborting + rolling back when the transaction isn’t committed.
| } | ||
| stateRepository.rollback(); | ||
| } | ||
| processState = PROCESSING; | ||
| } else { |
There was a problem hiding this comment.
create a new ProcessState for INITIALIZING_STATE and implement the State initialization logic there
| void initializeState(java.util.function.Consumer<org.elasticsoftware.akces.protocol.ProtocolRecord> protocolRecordConsumer, | ||
| java.util.function.BiConsumer<org.elasticsoftware.akces.protocol.DomainEventRecord, org.elasticsoftware.akces.aggregate.IndexParams> domainEventIndexer) | ||
| throws IOException; |
…rove error handling - Add INITIALIZING_STATE to AggregatePartitionState enum - Move auto-create logic from INITIALIZING into dedicated INITIALIZING_STATE phase - Only transition to PROCESSING on success; transition to SHUTTING_DOWN on failure - Catch broad Exception (not just KafkaException/IOException) to handle unchecked SerializationException and other runtime exceptions - Fix fully-qualified parameter types in AgenticAggregateRuntime.initializeState() to use proper imports matching the interface style Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/17027302-044b-4bba-b18f-85bef160366d Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com>
AgenticAggregate is a singleton (one instance per partition) but had no mechanism to automatically bootstrap its initial state—it required an external command to trigger creation. This adds a framework-level hook that auto-creates state when the partition detects none exists.
API (
AgenticAggregateinterface)DomainEvent getCreateDomainEvent()— the hook implementors provide to supply the create event for their singleton aggregateRuntime (
KafkaAggregateRuntime)handleAutoCreateDomainEvent(DomainEvent, Consumer<ProtocolRecord>, BiConsumer<DomainEventRecord, IndexParams>)— same flow ashandleCreateCommandbut without command materializationINITIALIZING_STATEtoAggregatePartitionStateenum for the dedicated auto-create phaseAgentic module
AgenticAggregateRuntime— addedinitializeState()interface methodKafkaAgenticAggregateRuntime— storesAgenticAggregate<?>ref, changed delegate type toKafkaAggregateRuntime, implementsinitializeState()by callingaggregate.getCreateDomainEvent()then delegatingAgenticAggregatePartition— duringINITIALIZING, whenendOffsets == 0L, transitions toINITIALIZING_STATE. TheINITIALIZING_STATEphase opens a Kafka transaction, callsruntime.initializeState(), and on success transitions toPROCESSING; on failure (catching broadExceptionincluding uncheckedSerializationException) aborts the transaction and transitions toSHUTTING_DOWNto fail fastAgenticAggregateRuntimeFactory— passes aggregate instance toKafkaAgenticAggregateRuntimeconstructorUsage
Test
AgenticAggregateAutoCreateTest— completeTestBotaggregate implementation exercising the hook contract, event sourcing handler, full create flow,initializeState()delegation, and null-safety. Existing tests updated for the newgetCreateDomainEvent()contract.