ROUTING & HANDLERS FEATURE
Payload Parts
A payload can stay the single trusted boundary even when multiple modules need to extend it and guard their own fields.
Feature Guide
A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.
What this does
A payload can be extended by another module without reopening the original route class, so one transport boundary can stay singular while modules stay additive.
How it works
A base module declares the payload with #[AsPayload]. Another module contributes a trait marked with #[AsPayloadPart(base: ...)]. At runtime PayloadFactory composes a wrapper class that extends the base payload and uses all matching traits, so the added trait can own setters and guards for its own extra fields.
Why it matters
This solves a painful modularity problem: extra request concerns do not force a fork of the original payload and do not leak into untyped arrays. The handler still receives one trusted DTO, and each added concern can validate its own field without a central validate() choke point.
Key concepts
- #[AsPayloadPart]
- Marks a trait as an additive extension of an existing payload class.
- PayloadFactory
- Builds the runtime wrapper class that extends the base payload and mixes in discovered payload-part traits.
- trait composition
- Lets separate modules add typed setters, getters, and local guards to the same request boundary without modifying the base payload source.
Modular Route Contract
One payload, more than one module
The route stays declared once, but extra request fields can be composed in by another module through a typed trait instead of a fork.
The base module owns the route path and the main transport contract.
A second module adds its own request concerns, such as tracking or preview flags, with #[AsPayloadPart] on a trait, and the trait can own the setter-level guards for those fields.
Module A and Module B handlers now receive the same composed payload instance, so both can read the mixed contract without a payload fork.
| Concern | Without payload parts | With #[AsPayloadPart] |
|---|---|---|
| Base route ownership | Fork or reopen the original payload class | Base payload stays untouched |
| Module-specific query fields | Ad-hoc arrays or handler glue | Typed trait methods on the same payload |
| Handler input | Scattered conditionals | One composed payload DTO |
The important part is not the trait itself. The important part is that both modules still work with one payload boundary after composition, while each added field keeps its own normalization and guard logic.
Verified against Semitexa Ultimate 2026.09.19.1020
Payload Parts
A payload can be extended by another module without reopening the original route class, so one transport boundary can stay singular while modules stay additive.
How it works
A base module declares the payload with one of the access attributes (#[AsPublicPayload], #[AsProtectedPayload], or #[AsServicePayload]). Another module contributes a trait marked with #[AsPayloadPart(base: ...)]. At runtime PayloadFactory composes a wrapper class that extends the base payload and uses all matching traits, so the added trait can own setters and guards for its own extra fields.
Why this matters
This solves a painful modularity problem: extra request concerns do not force a fork of the original payload and do not leak into untyped arrays. The handler still receives one trusted DTO, and each added concern can validate its own field without a central validate() choke point.
How it works
A base module declares the payload with #[AsPayload]. Another module contributes a trait marked with #[AsPayloadPart(base: ...)]. At runtime PayloadFactory composes a wrapper class that extends the base payload and uses all matching traits, so the added trait can own setters and guards for its own extra fields.
Why it matters
This solves a painful modularity problem: extra request concerns do not force a fork of the original payload and do not leak into untyped arrays. The handler still receives one trusted DTO, and each added concern can validate its own field without a central validate() choke point.
Key concepts
- #[AsPayloadPart]
- Marks a trait as an additive extension of an existing payload class.
- PayloadFactory
- Builds the runtime wrapper class that extends the base payload and mixes in discovered payload-part traits.
- trait composition
- Lets separate modules add typed setters, getters, and local guards to the same request boundary without modifying the base payload source.