Modeling in Tracepaper: Commands
We continue the Modeling in Tracepaper series by focusing on the interaction with our write domain. Last week we left off with the wish to trigger our behavior flow from the GraphQL API. This write interaction is modeled using commands. The scope of this post is visualized in our concept overview.
In IT, a command is a message just like an event. The two differ subtly.
1. An event is a message that represents that "something" has happened.
2. A command is a message signaling that we want "something" to happen.
A command is a wish, and an event is a fact.
We made a conscious choice to use events only to trigger behavior-flows or notifiers. The reason is quite crude, if we only have to handle events we can keep the cognitive complexity of our modeling tool (Tracepaper) in check.
If you are unfamiliar with GraphQL, this post describes the key concepts in a nutshell. It also features a 5 minute video if you prefere an audio visual medium.
We gave the write side of the API (GraphQL Mutations) 2 responsibilities:
1. Convert a command into an event. "OpenAccount" becomes "OpenAccountRequested", it does sound silly but it does effectively convert a wish into a fact. Of course, it may happen that the domain is not capable of processing the event. But that does not change the fact that somebody requested it.
2. Convert the synchronous API call into an asynchronous message.
The client issued a command however, this means they are probably interested in knowing if the processing was successful or not. To facilitate this, the API will return a correlation ID in case the command is accepted. The client can use this identifier to query or subscribe to the track and trace functionality. You may have noticed in our previous post that after every form submission we see a stream of events that have happened in the Tracepaper domain. This is a subscription to track & trace.
It is not necessary perse to show these trace events in our user interface, we do show them however and the reason is two-fold.
1. It gives insight into how our generated applications cope with asynchronicity and how we model our domain.
2. I like to see that something is happening after I press a button.
Modeling a Command
We need to model two things when modeling a command.
1. The message structure.
2. The API structure and access management.
Commands are a core concept and therefore you can navigate to them from the main project screen. A command is thus not bound to another domain concept, and it is perfectly possible to let multiple aggregates and notifiers react to a single command. Remember we convert the command to an event as soon as possible.
You can see that the first fields are meant for modeling the API structure. The GraphQL Namespaces are used to logically group commands together to increase the readability of the API contract. Namespaces can be nested by separating them with dots.
The GraphQL method name is the actual method in that namespace, those values should be unique together. We generate the command name from those two fields. And in the backend we generate the Event name from the command name:
GraphQL Namespace: Subdomain.Aggregate
GraphQL Method name: create
Command Name: CreateSubdomainAggregate
Event Name: CreateSubdomainAggregateRequested
We can select an authentication method, the default is authenticated, meaning that the user just needs to be signed in. We have 2 other options (there is secret option 3, anonymous access, but this is not yet enabled):
Role-based requires the user to have a specific role, this one is used a lot in Tracepaper.
User-based: requires a specific message field to be equal to the signed-in user name. For example, to make sure that only the user itself can mutate, let's say, their profile aggregate instance. The engine has another mechanism to ensure this, but unfortunately, this is not yet supported by Tracepaper (at the moment of writing, check this refinement task for the actual state).
The command input fields are used to model the message structure, they represent a JSON structure. We do allow 1 level nesting (Hence the 'NestedString' type), and they are always used for collections. The message structure for this command (naturally, this form is backed by a command that uses the same semantics) looks something like this:
If we would submit this form, the actual JSON structure that is sent to the API looks like this:
This is simplified actually because I don't want to bore you with the nitty-gritty details. But here is a screenshot from the actual API structure:
We now know how commands are used and modeled, and we know that they are not necessarily bound to one behavior flow or notifier. But you can imagine that most of the time we model specific commands to trigger specific behavior. With that in mind, there is a more convenient way for modeling commands using the best-effort autofill feature. And to utilize this we initialize the command modeling from the context of modeling a trigger for the behavior flow.
Let's look at it step by step:
We are done, we created a trigger for our behavior flow that is accessible via the generated API. Before we can show the API, we have to build the code, and we can't build the code before we have tests, a repository, and a pipeline.
One of the curses of writing is I'm bound to provide a linear flow of information while the subject is more of a graph. So for now, to protect my linear flow, let's focus on the modeling. We will look at pipelines and delivery after this series is done.
Just like the Aggregate, the Command Concept has more features than are available in Tracepaper at this moment. Here is a filter on our backlog to get a feeling of what to expect in the coming months.
This is a good time for a break, next time we will model a test for an Aggregate Behavior-Flow. This post was particularly hard to write, I think because the Command concept is a pretty hefty abstraction, it does quite a lot. A piece of advice I give to all interns and fresh architects.
Just because it was hard to write doesn't mean it should be hard to read. If your audience doesn't understand it, you failed.
And with that in mind, looking at this blog post, I may have failed this time. Thank you for reading, if you have feedback let me know.