Skip to content

[Port] Align QualifiedName#(Class) outcome with annotated approach for nested classes#4685

Merged
smcvb merged 3 commits into
mainfrom
port/qualified-name-includes-inner-classes
Jun 23, 2026
Merged

[Port] Align QualifiedName#(Class) outcome with annotated approach for nested classes#4685
smcvb merged 3 commits into
mainfrom
port/qualified-name-includes-inner-classes

Conversation

@smcvb

@smcvb smcvb commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

This pull request is a port of #4630.

MateuszNaKodach and others added 3 commits June 23, 2026 11:55
…aults for nested classes

The framework derived two different qualified names for the same nested class, depending on the path:

- QualifiedName(Class) - and thus MessageType(Class) - used Class.getName(), producing
  "pkg.Outer$Inner" for nested classes.
- Annotation-based resolution (@Command/@event defaults via AnnotationMessageTypeResolver) uses
  getPackageName() + getSimpleName(), producing "pkg.Inner".
- ClassBasedMessageTypeResolver documented getPackageName() + getSimpleName(), but implemented
  Class.getName(), contradicting its own javadoc for nested classes.

As a result, a MessageType built from a nested payload class could never match messages dispatched
through annotation-based naming - a silent mismatch, e.g. a QualifiedName-keyed interceptor or handler
lookup that simply never matches. Nested payload classes are idiomatic in Kotlin (sealed command/event
hierarchies), making this especially easy to hit there.

Changes:
- QualifiedName(Class) now combines getPackageName() and getSimpleName(), aligning with the annotation
  defaults. Primitive wrapper resolution is preserved. Classes without a simple name (e.g. anonymous
  classes) fall back to Class.getName(). Javadoc updated accordingly.
- ClassBasedMessageTypeResolver now routes through the Class-based MessageType constructor, making the
  implementation match its documented behavior.
- MessageTypeTest gains nested-class tests (written first, TDD) pinning that MessageType(Class) equals
  the package + simple name spelling for nested and top-level classes.
- AsyncMessageHandlerTest's declarative registrations used the raw Class.getName() String and are
  updated to the Class-based QualifiedName constructor.

Verified: messaging (3530 tests), eventsourcing (489), modelling (370), and conversion (174) suites pass.

Notes for review:
- Behavioral change: nested, un-annotated payload classes resolved through ClassBasedMessageTypeResolver
  change their name from "pkg.Outer$Inner" to "pkg.Inner". Events already stored under the old spelling
  will no longer match handlers/criteria without a mapping - needs a release note or migration guidance.
- Nested classes with the same simple name in the same package now collide, mirroring the trade-off the
  annotation defaults already made; explicit @Command/@event name/namespace attributes remain the escape
  hatch.
- String-based constructors (QualifiedName(String), MessageType(String, ...)) remain verbatim by design.
  Kotlin users should pass ::class.java rather than KClass.qualifiedName, which renders nested classes
  with a dotted spelling that does not match either.
…nnotationMessageTypeResolver

The annotation-based resolver spelled its class-derived defaults (getPackageName() / getSimpleName())
itself, duplicating the naming policy that QualifiedName(Class) owns. Derive the defaults from
QualifiedName(Class) instead, so the policy lives in exactly one place and partial annotation overrides
(namespace without name, and vice versa) overlay the same class-derived name as every other path.

Behavior is unchanged for regular classes, pinned by the new default-name tests in
AnnotationMessageTypeResolverTest. For the exotic edge of an annotated type without a simple name, the
local name now falls back to the binary-name-derived local name instead of failing on an empty local name.
- Drop @Command/@query reference from JavaDoc, as it has no business on
this level
- Rename simpleQualifiedNameOf deriveNameOf for clarity
- Add test for anonymous class name resolution
- Adjust test to use String as a "top level class" i.o. itself for
testing

#4630
@smcvb smcvb added this to the Release 5.2.0 milestone Jun 23, 2026
@smcvb smcvb self-assigned this Jun 23, 2026
@smcvb smcvb requested a review from a team as a code owner June 23, 2026 09:56
@smcvb smcvb requested review from jangalinski and zambrovski and removed request for a team June 23, 2026 09:56
@smcvb smcvb added the Type: Bug Use to signal issues that describe a bug within the system. label Jun 23, 2026
@smcvb smcvb added the Priority 2: Should High priority. Ideally, these issues are part of the release they’re assigned to. label Jun 23, 2026

@hatzlj hatzlj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@smcvb smcvb merged commit 6538728 into main Jun 23, 2026
9 checks passed
@smcvb smcvb deleted the port/qualified-name-includes-inner-classes branch June 23, 2026 10:44
@smcvb smcvb added the Type: Port Use to refer to an issue or pull request that ports logic from one branch to another. label Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority 2: Should High priority. Ideally, these issues are part of the release they’re assigned to. Type: Bug Use to signal issues that describe a bug within the system. Type: Port Use to refer to an issue or pull request that ports logic from one branch to another.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants