Sometimes, when modelling your domain, security comes up. Which users are allowed to do this? What role is required to perform this operation? Often, access is restricted to parts of the application, or to specific operations. For example, spring security allows us to to annotate our request mappings so that we have security at the API level. But what if you want to explicitly include ownership in your domain model? What if you require that only the creator of certain resources should be able to modify it?
In this blog post, we will explore just that: including ownership of an aggregate in the domain model, and, more importantly, have a concise way of verifying this ownership, which will be the responsibility of the domain as well. We will be using axon framework and spring boot to achieve this. 🚀
👨💻 Establishing ownership
First things first, we will need a domain model. Let's assume we have a system that allows people to arrange meetings. Only the organizer of a meeting should for example be able to cancel their meeting.
Part of creating the meeting is figuring out who is creating it, and keeping track of that user. We will rely on spring security to authenticate the user for us, so we will need a way to get the user into the domain model. We could include the userId in each command and event, but that's would be very cumbersome. We would rather have a transparent way of obtaining the userId. For us, putting the userId on the meta data of each command will be suitable. Axon allows us to do this using a MessageDispatchInterceptor: 🕸️
While it looks a bit complicated, all we are doing is adding a meta data entry to the command message containing the username from the authentication. We will be using this meta data entry to establish ownership:
Here, when handling the PlanMeeting command, we require the USER_ID meta data value to be present. It's then included in the MeetingPlanned event so we can store it in the aggregate for future verification. We could carry over the userId from the command meta data to the event meta data, but we like to explicitly add it to the properties of the event instead, that way it is clear that the ownership is an important part of the domain. 👌
👀 Checking ownership
Sometimes, meetings get cancelled. We can model this pretty simply with a CancelMeeting / MeetingCancelled command and event, and we just need the meetingId to identify which meeting to cancel. But, in our case, we only want the organizer of the meeting to be able to cancel it! We could inject the meta data value of the user id in the cancel command handler, but would require us to do that for all other commands we might be having! Also, we would have to check it in each command handler. It would be better if we could have one place where we could check the ownership, and then either cancel or proceed the handling of the command. 🤔
Axon has something for this: the @CommandHandlerInterceptor annotation. This will allow us to intercept each command in our aggregate and decide to either proceed or not. The interceptor also allows us to access the meta data, so it's a perfect fit for our situation: 🥰
Here we will proceed if the user that is sending the command has the same id as the organizerId that we saved earlier. Otherwise, we will throw an AccessDenied exception, which we can later map to, for example, a 403 Forbidden response. ⛔
This makes the command and event handling for cancelling a meeting pretty simple:As you can see, there is no need to check anything in the command handler: we can just apply the event and trust that it was the organizer that wanted to cancel this meeting, because the command handler interceptor already did it for us! 🎉
This also means that new commands that we might introduce in the future will also get verified for ownership before getting handled. Combined with the dispatcher we wrote earlier, the programmer does not even have to think about! 💭💭
🧪 Testing it!
For completeness, let's verify the Meeting aggregate is checking the ownership by writing some unit tests. We will use Axon's aggregate testing facilities here in order to get control of the meta data being used with each command. Let's first test for unknown and missing users: ❌
And of course: let's test for the proper user: ✅
While we could've written a custom spring security @PreAuthorize expression, or maybe load the meeting from a database and checking the ownership, making it an explicit part of the domain allows us to really embed this in the core of the application, rather then hoping to catch it at the "front door" that is the API.
By making the aggregate responsible for verifying the commands, security and ownership can be made an integral part of the the domain, and would enable you to include that in the unit tests for that aggregate. That way, you can really validate that publishing certain commands will result in the right events or exceptions rather than trusting the API will get it right. 🙏🙏
We've made an example project available to you in GitHub, where you are able to plan and cancel meetings using two users, Alice and Bob. Please refer to the README.md for instructions. 📖
Thanks for reading! If you have any questions or comments you can do so down below. There you can also subscribe to our awesome newsletter! 🗞️🎉🔥