|
| 1 | +# Plan: Axon 5 Serializer Adapter (type-alias-axon-5-serializer) |
| 2 | + |
| 3 | +## Context / Key Findings |
| 4 | + |
| 5 | +### Why a new artifact (not single v4+v5) |
| 6 | +- Axon 4: `Serializer` at `org.axonframework.serialization.Serializer` (in `axon-configuration`) |
| 7 | +- Axon 5: `Serializer` moved to `org.axonframework.conversion.Serializer` — different package = binary incompatible |
| 8 | +- `axon-configuration` does not exist in Axon 5; replaced by `axon-messaging`, `axon-conversion`, etc. |
| 9 | +- Two artifacts accepted: `type-alias-axon-5-serializer` + `type-alias-axon-5-serializer-integration-test` |
| 10 | + |
| 11 | +### Axon 5 type aliasing hook |
| 12 | +- **Axon 4**: hook via `Serializer.typeForClass(Class)` (store alias) and `classForType(SerializedType)` (resolve alias) |
| 13 | +- **Axon 5**: hook is `MessageTypeResolver` (in `axon-messaging`) |
| 14 | + - `@FunctionalInterface` with `Optional<MessageType> resolve(Class<?> payloadType)` |
| 15 | + - Used for BOTH write path (storing type name) AND handler routing (read path) |
| 16 | + - Class resolution at handler invocation uses the handler's declared parameter type directly — no `classForType` needed |
| 17 | + - Existing implementations: `ClassBasedMessageTypeResolver` (FQCN), `AnnotationMessageTypeResolver` (@Event/@Command annotations), `NamespaceMessageTypeResolver` (explicit map), `HierarchicalMessageTypeResolver` (chain) |
| 18 | +- The new library is actually **simpler** than the v4 one (single hook point vs. two) |
| 19 | + |
| 20 | +### Key decisions |
| 21 | +- Java 17+ for v5 library (Axon 5 requires Java 17) |
| 22 | +- Depend only on `type-alias` (core module) + `axon-messaging` 5.1.0 (both optional scope) |
| 23 | +- No dependency on `type-alias-axon-serializer` |
| 24 | +- Alias format: flat, e.g. `MessageType("AccountCreated", MessageType.DEFAULT_VERSION)` — alias as full qualified name |
| 25 | +- No Axon-internal type aliases (only user domain types) |
| 26 | +- Integration test: standalone JUnit 5, no WildFly/CDI/Arquillian |
| 27 | +- Integration test scope: minimal — prove aliasing works end-to-end |
| 28 | + |
| 29 | +### Axon 5.1.0 Maven artifacts (org.axonframework) |
| 30 | +- `axon-messaging` 5.1.0 — MessageTypeResolver, MessageType, QualifiedName |
| 31 | +- `axon-modelling` 5.1.0 — aggregate support (@AggregateRoot, @CommandHandler, @EventSourcingHandler) |
| 32 | +- `axon-eventsourcing` 5.1.0 — event storage |
| 33 | +- `axon-test` 5.1.0 — AggregateTestFixture |
| 34 | +- `axon-conversion` 5.1.0 — JacksonConverter (new) and deprecated Serializer (stash/legacy) |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +## Plan: type-alias-axon-5-serializer + integration-test |
| 39 | + |
| 40 | +### Phase 1: Core library — type-alias-axon-5-serializer |
| 41 | + |
| 42 | +**Step 1.1** Create `type-alias-axon-5-serializer/pom.xml` |
| 43 | +- Parent: `alias-parent 2.0.1-SNAPSHOT` |
| 44 | +- Java 17 source/target (unlike v4's Java 1.8) |
| 45 | +- Dependencies (all optional): |
| 46 | + - `type-alias` (provided scope) — for @TypeAlias annotation + ResourceBundle structure |
| 47 | + - `axon-messaging` 5.1.0 (optional) — MessageTypeResolver, MessageType |
| 48 | +- Test dependencies: junit-jupiter, hamcrest-core, mockito-core |
| 49 | +- Build: `-proc:none` (no annotation processing), maven-compiler-plugin Java 17 |
| 50 | + |
| 51 | +**Step 1.2** Create `AliasableMessageTypeResolver.java` |
| 52 | +- Package: `org.alias.axon.serializer` (same base package as v4 for consistency) |
| 53 | +- `implements MessageTypeResolver` |
| 54 | +- Fields: `MessageTypeResolver delegate`, `Function<Class<?>, Optional<String>> aliasLookup` |
| 55 | +- Factory methods (analogous to TypeDecoratableSerializer): |
| 56 | + - `aliasThroughDefaultResourceBundle(MessageTypeResolver delegate)` — uses "TypeAlias" ResourceBundle |
| 57 | + - `aliasThroughResourceBundle(MessageTypeResolver delegate, ResourceBundle resourceBundle)` — explicit bundle |
| 58 | +- `resolve(Class<?> payloadType)`: |
| 59 | + - Look up `payloadType.getName()` in ResourceBundle; if a String value exists → it's the alias |
| 60 | + - Return `Optional.of(new MessageType(alias, MessageType.DEFAULT_VERSION))` |
| 61 | + - Else → `delegate.resolve(payloadType)` |
| 62 | +- Note: ResourceBundle lookup is self-contained (20 lines), no dependency on v4 library |
| 63 | + |
| 64 | +**Step 1.3** Unit tests for `AliasableMessageTypeResolver` — **MUST HAVE** in BDD/TDD style |
| 65 | +- `AliasableMessageTypeResolverTest.java` (Behavior Driven Development style with clear spec): |
| 66 | + - **Given** a ResourceBundle with an alias mapping, **when** resolving a type, **then** return MessageType with alias name |
| 67 | + - **Given** a ResourceBundle without an alias mapping, **when** resolving a type, **then** delegate to fallback resolver |
| 68 | + - **Given** default factory method used, **when** resolving a type, **then** use "TypeAlias" ResourceBundle by convention |
| 69 | + - **Given** null or empty alias values in ResourceBundle, **when** resolving, **then** safely handle and delegate |
| 70 | + - Spec-style naming: `shouldReturnAliasWhenFoundInResourceBundle()`, `shouldDelegateWhenNoAliasFound()`, etc. |
| 71 | +- Tests written first (TDD), implementation follows |
| 72 | +- All tests must pass with 100% code coverage for `AliasableMessageTypeResolver` |
| 73 | + |
| 74 | +**Step 1.4** package-info.java pointing to `AliasableMessageTypeResolver#aliasThroughResourceBundle` |
| 75 | + |
| 76 | +### Phase 2: Integration test — type-alias-axon-5-serializer-integration-test |
| 77 | + |
| 78 | +**Step 2.1** Create `type-alias-axon-5-serializer-integration-test/pom.xml` |
| 79 | +- Parent: `alias-parent 2.0.1-SNAPSHOT` |
| 80 | +- Java 17 source/target |
| 81 | +- Dependencies: |
| 82 | + - `type-alias-axon-5-serializer` (compile) |
| 83 | + - `type-alias` (provided) — annotation processor |
| 84 | + - `axon-messaging` 5.1.0 |
| 85 | + - `axon-modelling` 5.1.0 |
| 86 | + - `axon-eventsourcing` 5.1.0 |
| 87 | + - `axon-test` 5.1.0 (test scope) |
| 88 | + - `axon-conversion` 5.1.0 (test scope) — JacksonConverter |
| 89 | + - `jackson-databind` (test scope) — JSON serialization |
| 90 | + - `junit-jupiter` (test scope) |
| 91 | + - `hamcrest-core` (test scope) |
| 92 | +- NO arquillian, NO wildfly, NO reactor, NO CDI, NO JEE APIs |
| 93 | +- Build: maven-compiler-plugin Java 17, maven-surefire-plugin |
| 94 | + |
| 95 | +**Step 2.2** Domain model — minimal test aggregate |
| 96 | +- `messages/` package with `@TypeAlias`-annotated events: |
| 97 | + - `SomethingHappenedEvent` with `@TypeAlias("SomethingHappened")` |
| 98 | +- `@TypeAliases` + `@TypeAliasGeneratedFile` on package-info.java → generates ResourceBundle |
| 99 | +- Aggregate: `SomethingAggregate` with @CommandHandler and @EventSourcingHandler |
| 100 | + |
| 101 | +**Step 2.3** Integration tests (plain JUnit 5, BDD style, **MUST HAVE**) |
| 102 | +- `TypeAliasMessageTypeResolverIT.java` (Behavior Driven Development): |
| 103 | + 1. **Given** an alias defined via `@TypeAlias("SomethingHappened")`, **when** resolving `SomethingHappenedEvent.class`, **then** return `MessageType("SomethingHappened", ...)` |
| 104 | + 2. **Given** an `AggregateTestFixture` configured with `AliasableMessageTypeResolver` and domain event with alias, **when** command dispatched and event published, **then** stored event's `MessageType.name()` equals "SomethingHappened" (not FQCN) |
| 105 | + 3. **Given** events stored under alias, **when** aggregate re-sourced from event store, **then** state correctly reconstructed |
| 106 | +- Test names follow spec style: `shouldResolveAliasFromAnnotation()`, `shouldStoreEventUnderAliasName()`, `shouldRestoreAggregateFromAliasedEvents()` |
| 107 | +- All integration tests must pass |
| 108 | + |
| 109 | +### Phase 3: Root POM update |
| 110 | + |
| 111 | +**Step 3.1** Add `type-alias-axon-5-serializer` to root `pom.xml` modules list |
| 112 | +- `type-alias-axon-5-serializer-integration-test` should NOT be in root modules (same as v4 pattern) |
| 113 | + |
| 114 | +### Relevant files |
| 115 | +- `pom.xml` (root) — add module entry for `type-alias-axon-5-serializer` |
| 116 | +- `type-alias-axon-serializer/pom.xml` — reference for v4 structure/patterns |
| 117 | +- `type-alias-axon-serializer/src/main/java/org/alias/axon/serializer/TypeDecoratableSerializer.java` — reference API pattern |
| 118 | +- `type-alias-axon-serializer/src/main/java/org/alias/axon/serializer/aliasable/AliasableTypeResourceBundleRegister.java` — reference ResourceBundle logic (DO NOT depend on; re-implement simply) |
| 119 | +- `type-alias-axon-serializer-integration-test/src/main/java/.../messages/` — reference for @TypeAlias annotations pattern |
| 120 | + |
| 121 | +### Verification steps |
| 122 | +1. `mvn install -pl type-alias-axon-5-serializer` — library builds without errors |
| 123 | +2. `mvn test -pl type-alias-axon-5-serializer` — unit tests pass |
| 124 | +3. `mvn test -pl type-alias-axon-5-serializer-integration-test` — integration tests pass |
| 125 | +4. Verify no compile dependency on `type-alias-axon-serializer` (v4 library) |
| 126 | +5. Verify `axon-messaging` is `optional` scope in library pom |
| 127 | +6. `mvn install` from root — root build includes new library module |
| 128 | + |
| 129 | +### Decisions |
| 130 | +- Single artifact impossible: binary incompatibility between Axon 4/5 Serializer packages |
| 131 | +- Axon 5 hook: `MessageTypeResolver` (simpler than v4's two-hook Serializer approach) |
| 132 | +- Alias format: flat `MessageType(alias, DEFAULT_VERSION)` — same as Axon 5's FQCN-based default, just alias string replaces FQCN |
| 133 | +- No Axon-internal aliases (GapAwareTrackingToken etc.) — only user domain types |
| 134 | +- No WildFly/CDI: removed entirely for v5 integration test |
| 135 | +- Java 17+ for v5 module (Axon 5 requirement) |
| 136 | + |
| 137 | +### Out of scope |
| 138 | +- Backward compatibility reading events stored with v4 alias names (upcaster territory) |
| 139 | +- Spring Boot auto-configuration bean for AliasableMessageTypeResolver |
| 140 | +- Support for @Event/@Command native Axon 5 annotations (users can chain AnnotationMessageTypeResolver before our resolver) |
| 141 | + |
| 142 | +### Guidelines |
| 143 | + |
| 144 | +#### Think Before Coding |
| 145 | + |
| 146 | +**Don't assume. Don't hide confusion. Surface tradeoffs.** |
| 147 | + |
| 148 | +Before implementing: |
| 149 | +- State your assumptions explicitly. If uncertain, ask. |
| 150 | +- If multiple interpretations exist, present them - don't pick silently. |
| 151 | +- If a simpler approach exists, say so. Push back when warranted. |
| 152 | +- If something is unclear, stop. Name what's confusing. Ask. |
| 153 | + |
| 154 | +#### Simplicity First |
| 155 | + |
| 156 | +**Minimum code that solves the problem. Nothing speculative.** |
| 157 | + |
| 158 | +- No features beyond what was asked. |
| 159 | +- No abstractions for single-use code. |
| 160 | +- No "flexibility" or "configurability" that wasn't requested. |
| 161 | +- No error handling for impossible scenarios. |
| 162 | +- If you write 200 lines and it could be 50, rewrite it. |
| 163 | + |
| 164 | +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. |
| 165 | + |
| 166 | +#### Goal-Driven Execution |
| 167 | + |
| 168 | +**Define success criteria. Loop until verified.** |
| 169 | + |
| 170 | +Transform tasks into verifiable goals: |
| 171 | +- "Add validation" → "Write tests for invalid inputs, then make them pass" |
| 172 | +- "Fix the bug" → "Write a test that reproduces it, then make it pass" |
| 173 | +- "Refactor X" → "Ensure tests pass before and after" |
| 174 | + |
| 175 | +For multi-step tasks, state a brief plan: |
| 176 | +``` |
| 177 | +1. [Step] → verify: [check] |
| 178 | +2. [Step] → verify: [check] |
| 179 | +3. [Step] → verify: [check] |
| 180 | +``` |
| 181 | + |
| 182 | +#### Unit Tests are Non-Negotiable |
| 183 | + |
| 184 | +**BDD/TDD style. Tests first, implementation second. Clear spec.** |
| 185 | + |
| 186 | +- **Every new class MUST have unit tests** — no exceptions. |
| 187 | +- Tests written in **Behavior Driven Development (BDD) style**: |
| 188 | + - Use `Given/When/Then` structure in test names and comments |
| 189 | + - Spec-style test names: `shouldReturnAliasWhenFoundInResourceBundle()`, not `testResolveAlias()` |
| 190 | + - Each test verifies ONE behavior, not multiple assertions on different concerns |
| 191 | + - Red → Green → Refactor cycle (TDD) |
| 192 | +- **100% code coverage** for production code |
| 193 | +- Use meaningful assertion messages (not default Hamcrest/JUnit messages) |
| 194 | +- Mock external dependencies; test behavior, not implementation details |
| 195 | +- No empty test files or placeholder tests |
0 commit comments