When designing Command/Event classes, are there any sort of guidelines? For example, if I have a command to add a collection of values to an aggregate, should it only deal with basic data types or is it ok to create other classes to handle any sort of complexity? If the command is: AddBarsToFoo(fooId, bars) - Should bars be something like List<Map<String, String>> or could it be List? Furthermore, assuming that List is acceptable, should the aggregate contain a different class that represents the Bar passed to the command? eg. is there a command.Bar and domain.Bar? How does this apply to the events? Maybe the commands take accept the simpler types whereas the events contain the collection of Bar? Let me know if I need to clarify anything. Please advise. Thanks!
A few general remarks:
- Commands, Events (and also Queries) are messages, and when an Axon applications scales out into more than one node, they go over the wire. So one of the most fundamental requirements on these classes is that they’re serializable using the serializer you’re using. Out of the box, that’s the XStreamSerializer, but most folks switch to the JacksonSerializer to serialize in JSON format. The serialized form of the messages is the actual API you’re exposing when writing an aggregate, so that’s even more important than the structure of the Java class.
- It’s a good practice to implement Command, Event and Query classes as immutable objects - in a DDD sense think of them as “value objects”.
- In a real Axon system you’ll be writing quite a lot of these classes, and when doing it in pure Java introduces a lot of boilerplate, so you may want to consider an option to reduce that. I personally love to mix in Kotlin with my Java project, and use Kotlin data classes to write message classes, putting many of them in a single file. Other options I’ve seen are to use Kotlin for everything, or use Java with Lombok annotations, or use Scala case classes.
- One of the most basic things that need to happen when processing a Command is that it has to be validated. So strong typing is a good thing. You may also consider using JSR 303 validation annotations on your Commands.
On to your specific questions:
- It is certainly acceptable to write Commands (and Events, and Queries) in such a way that they contain other classes you define yourself. In your example List is probably preferable to List<Map<String, String>>. It’s good to make Bar immutable.
- You can re-use Bar in the aggregate itself if that works out, but don’t make any changes to Bar just because that’s needed for the aggregate implementation. Aggregate implementation details shouldn’t leak into the API.
- There’s no real difference between Commands, Events and Queries as far as these things are concerned.