Add event-modeling JSON to Akces aggregate code generator#260
Add event-modeling JSON to Akces aggregate code generator#260
Conversation
…enerator Creates a new main/codegen module that: - Parses event-modeling JSON schema (extended with Akces config) - Generates Command records, Event records, State records, and Aggregate classes - Includes crypto-trading Account and Wallet aggregate JSON definitions - All 16 tests pass verifying structural equivalence with actual crypto-trading code Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com> Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/b53bdf75-8ad8-4f53-8221-e4538be6810f
Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com> Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/b53bdf75-8ad8-4f53-8221-e4538be6810f
Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com> Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/b53bdf75-8ad8-4f53-8221-e4538be6810f
main/codegen/src/main/java/org/elasticsoftware/akces/codegen/AkcesCodeGenerator.java
Fixed
Show fixed
Hide fixed
|
@copilot Am I correct that you augmented the schema for this? if so please add the augmented schema to the code. Also create a test suite that validates that the json files adhere to the schema |
- Add akces-event-model.schema.json: extends the event-modeling spec with Akces-specific properties (packageName, aggregateConfig with stateFields, piiData, indexing, GDPR key generation) - Add SchemaValidationTest with 13 tests validating JSON files conform to the augmented schema, plus negative tests for invalid inputs - Add everit-json-schema test dependency for JSON Schema validation Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com> Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/62653830-241d-483b-b0f5-18bb05af8e7f
Yes, the schema was augmented with Akces-specific extensions ( In
|
There was a problem hiding this comment.
Pull request overview
Adds a new main/codegen module that parses event-modeling JSON (with Akces-specific extensions) and generates Akces aggregate scaffolding, plus tests/resources that validate the generator against the crypto-trading example aggregates.
Changes:
- Registers a new Maven module (
main/codegen) and publishes it via the BOM. - Introduces codegen model types and
AkcesCodeGeneratorfor parsing definitions and generating command/event/state/aggregate sources. - Adds crypto-trading JSON definitions and TestNG tests validating generated output structure and filesystem writing.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| main/pom.xml | Adds codegen to the main multi-module build. |
| bom/pom.xml | Adds akces-codegen to dependency management for consumers. |
| main/codegen/pom.xml | New module POM with Jackson 3 databind + TestNG; includes JSON test resources. |
| main/codegen/src/main/java/org/elasticsoftware/akces/codegen/AkcesCodeGenerator.java | Core parser + generator for commands/events/state/aggregate scaffolding. |
| main/codegen/src/main/java/org/elasticsoftware/akces/codegen/GeneratedFile.java | Simple value type representing generated sources. |
| main/codegen/src/main/java/org/elasticsoftware/akces/codegen/model/*.java | JSON-mapped input model (event-modeling slices + Akces aggregate config). |
| main/codegen/src/test/java/org/elasticsoftware/akces/codegen/CryptoTradingCodeGenTest.java | TestNG coverage validating generation for Account/Wallet and writing to disk. |
| main/codegen/src/test/resources/crypto-trading-account.json | Example Account slice/config input for tests. |
| main/codegen/src/test/resources/crypto-trading-wallet.json | Example Wallet slices/config input for tests. |
| String packageName, | ||
| Map<String, AggregateConfig> aggregateConfig, | ||
| List<Slice> slices | ||
| ) { |
There was a problem hiding this comment.
EventModelDefinition doesn't defensively normalize aggregateConfig/slices when they are absent in JSON. AkcesCodeGenerator.groupByAggregate() iterates definition.slices() directly, so a missing/null slices field will cause an NPE. Consider adding a compact constructor that defaults aggregateConfig to Map.of() and slices to List.of() (and optionally validates packageName).
| ) { | |
| ) { | |
| public EventModelDefinition { | |
| if (aggregateConfig == null) { | |
| aggregateConfig = Map.of(); | |
| } | |
| if (slices == null) { | |
| slices = List.of(); | |
| } | |
| } |
| // Generate state record | ||
| if (config != null && !config.stateFields().isEmpty()) { | ||
| files.add(generateStateRecord(aggregatePackage, aggregateName, config)); | ||
| } | ||
|
|
||
| // Generate aggregate class | ||
| files.add(generateAggregateClass( | ||
| aggregatePackage, aggregateName, config, slices, | ||
| allCommands, allSuccessEvents, allErrorEvents)); | ||
|
|
There was a problem hiding this comment.
generateAggregate() only generates the state record when config != null && !config.stateFields().isEmpty(), but it always generates the aggregate class referencing <AggregateName>State. If an input JSON omits aggregateConfig (or just stateFields), the generated output won't compile because the state type is missing. Either always generate a minimal state record, or fail fast with a clear error when state config is absent for an aggregate.
| // Find events produced by this command (events in the same slice) | ||
| List<String> producesEvents = sliceData.successEvents().stream() | ||
| .map(e -> e.title() + "Event.class") | ||
| .toList(); | ||
| List<String> errorEventNames = sliceData.errorEvents().stream() | ||
| .map(e -> e.title() + "Event.class") | ||
| .toList(); |
There was a problem hiding this comment.
When generating @CommandHandler, the code currently assumes every command in a slice produces all success/error events from that slice (sliceData.successEvents() / sliceData.errorEvents()), and it ignores Element.dependencies. This will produce incorrect produces/errors lists as soon as a slice contains multiple commands or unrelated events. Consider using the dependencies to associate a command with its outbound events (or validate/enforce the invariant of a single command per slice and fail fast otherwise).
| // Find events produced by this command (events in the same slice) | |
| List<String> producesEvents = sliceData.successEvents().stream() | |
| .map(e -> e.title() + "Event.class") | |
| .toList(); | |
| List<String> errorEventNames = sliceData.errorEvents().stream() | |
| .map(e -> e.title() + "Event.class") | |
| .toList(); | |
| // Find events produced by this command. | |
| // Prefer events whose dependencies reference this command; if none are found, | |
| // fall back to all events in the slice to preserve existing behavior. | |
| List<String> producesEvents = sliceData.successEvents().stream() | |
| .filter(evt -> { | |
| var deps = evt.dependencies(); | |
| var cmdId = cmd.id(); | |
| return deps != null && cmdId != null && deps.contains(cmdId); | |
| }) | |
| .map(e -> e.title() + "Event.class") | |
| .toList(); | |
| if (producesEvents.isEmpty()) { | |
| producesEvents = sliceData.successEvents().stream() | |
| .map(e -> e.title() + "Event.class") | |
| .toList(); | |
| } | |
| List<String> errorEventNames = sliceData.errorEvents().stream() | |
| .filter(evt -> { | |
| var deps = evt.dependencies(); | |
| var cmdId = cmd.id(); | |
| return deps != null && cmdId != null && deps.contains(cmdId); | |
| }) | |
| .map(e -> e.title() + "Event.class") | |
| .toList(); | |
| if (errorEventNames.isEmpty()) { | |
| errorEventNames = sliceData.errorEvents().stream() | |
| .map(e -> e.title() + "Event.class") | |
| .toList(); | |
| } |
| } else { | ||
| return "null /* " + sf.name() + " */"; |
There was a problem hiding this comment.
generateStateConstructorArgsFromEvent() emits null /* field */ for any state field not present on the create event. This results in generated @EventSourcingHandler(create = true) code constructing a state object with null values even for fields annotated @NotNull, which is both misleading scaffolding and a likely runtime NPE. Prefer generating sensible defaults (e.g., empty collections) based on type/cardinality, or mark these arguments as TODOs without producing nulls for non-optional fields, or require the create event to supply all non-optional state fields.
| } else { | |
| return "null /* " + sf.name() + " */"; | |
| } else if (sf.optional()) { | |
| // Optional state field: using null is acceptable scaffolding. | |
| return "null /* " + sf.name() + " (optional) */"; | |
| } else { | |
| // Non-optional state field is not present on create event: | |
| // emit a TODO placeholder that fails at compile time so the | |
| // developer must map this field explicitly instead of | |
| // silently constructing a @NotNull field with null. | |
| String placeholder = | |
| "MISSING_REQUIRED_STATE_FIELD_" + sf.name().toUpperCase(Locale.ROOT); | |
| return "/* TODO: initialize non-optional field '" + sf.name() | |
| + "' from event */ " + placeholder; |
|
@copilot can you create another test that ouputs the files and then compiles them? to verify that the generated code actually compiles |
- Add CompilationTest that generates Java source files from event-modeling JSON, writes them to disk, and compiles them using javax.tools.JavaCompiler to verify the generated code actually compiles - Add akces-api test dependency to provide Akces interfaces and annotations on the test compilation classpath - Fix generateCommandHandler to map event constructor args from the event's fields (not the command's fields), using typed default values for event fields not present in the command Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com> Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/53fc3b58-a104-49f1-8c00-051a5c41fb69
Added in |
main/codegen/src/main/java/org/elasticsoftware/akces/codegen/AkcesCodeGenerator.java
Fixed
Show fixed
Hide fixed
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
|
@copilot can you also create the java files in the |
…emp dir Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com> Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/97be6153-b154-4b46-9232-b8ed79d27306
Done in |
|
@copilot compile into the test-classes directory in the target folder |
|
@copilot the code must be written in the root of the source folder (and not in the |
Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com> Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/a17a7419-3fd2-4bc7-b872-46b0d2da5fa1
Fixed in |
|
@copilot you're missing the piiData annotation on the Command and Event definitions. |
…definitions Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/4721cb7c-f433-4df8-9027-40e317c87a39 Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com>
Fixed in |
main/codegen/src/main/java/org/elasticsoftware/akces/codegen/AkcesCodeGenerator.java
Fixed
Show fixed
Hide fixed
main/codegen/src/main/java/org/elasticsoftware/akces/codegen/AkcesCodeGenerator.java
Fixed
Show fixed
Hide fixed
main/codegen/src/main/java/org/elasticsoftware/akces/codegen/AkcesCodeGenerator.java
Fixed
Show fixed
Hide fixed
|
@copilot create a markdown document describing the differences between our json schema and the original schema of martin dilger found here: https://github.com/dilgerma/event-modeling-spec/blob/main/eventmodeling.schema.json |
…odeling spec schema Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/7e9ace42-a4f7-4a15-b965-222abbf6d814 Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com>
Added in |
…support Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/e48b9560-faa9-428c-ba26-c745c77a9035 Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com>
Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/4dacb82e-8135-4bb7-a9b9-79a4f73e59c0 Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
main/codegen/src/main/java/org/elasticsoftware/akces/codegen/AkcesCodeGenerator.java
Fixed
Show fixed
Hide fixed
… Element array Agent-Logs-Url: https://github.com/elasticsoftwarefoundation/akces-framework/sessions/cfed3c79-d934-4609-9b5d-5e8a48c5bdc1 Co-authored-by: jwijgerd <914840+jwijgerd@users.noreply.github.com>
external_eventsproperty toSlicein JSON schema (referencesElement)externalEventHandlersfromAggregateConfigin JSON schemaExternalEventHandlerdefinition from$defsin JSON schemaSlice.javamodel to addexternalEventsfield with@JsonProperty("external_events")AkcesCodeGenerator.javato handle external events from slices (groupByAggregate, SliceData)SchemaValidationTest.java- replace ExternalEventHandler tests with external_events testsSCHEMA_DIFFERENCES.mddocumentation⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.