Thanks for the suggestions, Jonathan and Christophe.
However, the Commands and Events only need to worry about the properties involved in state change.
This is certainly true for alterations to an existing aggregate, but for first-time aggregate creation and hydration, the commands/events can be quite large for more complex bounded contexts. I suppose I could artificially break initial creation up by introducing a Saga to generate many commands against the aggregate, but this doesn’t pass my sniff test.
This is the price you pay for a de-coupled, multi-tier application with a typed language like Java
I’ve come to the same realization, Christophe. I thought about generating a ton of abstract classes and then using variable hiding in the concrete class so that the annotations can be placed on the appropriate data members. I think this would work, but will sacrifice code readability/maintainability.
The solution, I tend to lean on, is to generate model objects from schema’s like using the OpenApi specification, and generating at least the REST side of things.
I’m not sure I’m following you 100%, but I’ve definitely used Jackson plenty to serialize/deserialize JSON, and I think it would be a good mapping solution from class to class, if I were always willing to serialize to JSON first, and then deserialize to a different class type.
Here’s what I’ve come up with as a mitigation (not a solution):
-
I noticed the Axon developers obviously recognize this problem, and are using Kotlin to cut down on boilerplate code in these POJOs. I plan to start doing the same, or leveraging Lambok more.
-
Similar to your OpenAPI recommendation, if I understand your suggestion correctly, I’m using the MapStruct library to easily map an object to a different class. By configuring it properly, you can have it ignore members that don’t map across classes, giving me similar characteristics to JSON that protect the code in case some of these classes evolve independently of each other. I like this library because it generates code using the annotations and interface you define for the mapping, so you can see what it’s doing at build time instead of getting surprised in production.
Luckily I’m just evaluating Axon right now, so I can always refactor this code, if I can find a better way before it gets into production.