Skip to content

Commit f37fa77

Browse files
JohTclaude
andcommitted
Add type alias serializer for axon 5.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 104920c commit f37fa77

16 files changed

Lines changed: 1312 additions & 2 deletions

File tree

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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

COMMANDS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ Overview of the commands to test, run, build and release this project.
88

99
## Most important commands for development
1010

11-
- `mvn verify` Builds the project and runs all tests including integration tests
11+
- `mvn verify` Builds library modules and runs their tests (integration-test modules are not part of the root reactor)
1212
- `mvn install` Includes `mvn verify` and copies the resulting artifacts into the local maven repository.
13+
- To run integration tests, build the specific integration-test module explicitly (e.g., `mvn test -f type-alias-axon-5-serializer-integration-test/pom.xml`)
1314

1415
## (maintainer) Commands to release a new version
1516

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ that generates (by default) the ResourceBundle `TypeAlias.java` inside the defau
3636

3737
### Building this project
3838

39-
Install maven and use `mvn install` to build this project, run all tests including the integration tests and copy the resulting artifacts into the local maven repository. A list of the most important commands can be found in [COMMANDS.md](COMMANDS.md).
39+
Install maven and use `mvn install` to build this project and copy the resulting artifacts into the local maven repository. Integration-test modules are not part of the root reactor and must be built explicitly. A list of the most important commands can be found in [COMMANDS.md](COMMANDS.md).
4040

4141
### Changes
4242

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
<module>type-alias</module>
8181
<module>type-alias-example</module>
8282
<module>type-alias-axon-serializer</module>
83+
<module>type-alias-axon-5-serializer</module>
8384
<module>type-alias-jsonb-typereference</module>
8485
</modules>
8586

renovate.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@
2525
],
2626
"groupName": "Java Development Kit (JDK)",
2727
"enabled": false
28+
},
29+
{
30+
"description": "Keep Axon 4 modules on the latest 4.x version.",
31+
"matchFileNames": [
32+
"type-alias-axon-serializer/**",
33+
"type-alias-axon-serializer-integration-test/**"
34+
],
35+
"matchPackageNames": [
36+
"org.axonframework:**"
37+
],
38+
"allowedVersions": "<5.0.0"
39+
},
40+
{
41+
"description": "Keep Axon 5 modules on the latest 5.x version.",
42+
"matchFileNames": [
43+
"type-alias-axon-5-serializer/**",
44+
"type-alias-axon-5-serializer-integration-test/**"
45+
],
46+
"matchPackageNames": [
47+
"org.axonframework:**"
48+
],
49+
"allowedVersions": ">=5.0.0 <6.0.0"
2850
}
2951
],
3052
"customManagers": [

0 commit comments

Comments
 (0)