I started a few months ago looking at a fresh way of architecting web applications. Since writing the first article in this series I’ve extended the codebase considerably and have a few more thoughts on how to take these ideas further. If you’ve not read it already, you probably want to go back and read the previous article first.
This time round, I discuss how I reduced the codebase dramatically by separating the full updating of the domain from the generation of change information. This refactor stemmed from two problems I had spotted in my code: an observation that some of my methods were doing too much, and the need for wrapper methods to preserve immutability.
Problem 1: methods doing too much
In Sol Trader: Online, each player has a
Position within the game. I’m using the word “position” in the same way you’d talk about a chess position: it’s the state of the players involvement in the game.
In the previous article I was doing two things within each method in the domain that performed an action for game
- Returning a new copy of the domain object modified to reflect the change
- Pushing a new object onto the
For example, this particular method on a player’s
Position allowed them to attack a target:
As well as calling
with_order to create and return a new
Position with an
AttackOrder, I’m creating a
CharacterOrderedToAttack change, adding the new position to it, and adding that to the
Problem 2: container objects needing wrapper methods
This was problematic enough, but it was exacerbated by the fact that the position was only one of a list of positions within a
Game object, so in order to keep the
Game immutable, I had to have a
Game#attack method to wrap the new position returned within a new game, using code something like this:
I abstracted much of this out to helper methods, but I was deeply unhappy about having to have a method on the
Game object for each action I could apply to
A mistaken assumption: that I actually need this object
The reason for writing all this code to return a new copy of the object with the changes applied rested on the assumption that I was actually going to need this object to finish processing the request.
After a close look at the calling code in the web logic, it turns out that I didn’t need it at all - I only ever operated on the changes that were returned in the array. Therefore, why was I bothering to generate it?
If I don’t bother to return the updated game object, I can reduce the code for the
attack method to a one-liner which simply returns a change. Now my domain logic was back to just doing one thing:
I didn’t then need
Game#attack at all, so that method could be safely deleted.
This one insight, when propagated through the system for all the existing actions, reduced the size of the codebase by about 10%.
In making this change, I’ve moved entirely to a version of the Command Query Reponsibility Segregation (CQRS) pattern. My regular domain is entirely read only, and only used for querying the data, and my domain for updating the model is based on lightweight change objects, which are passed to services to update persistence and handle web requests.
Changes can cause other changes: Next time, I plan to talk about how I’m feeding changes back into other services, to allow reactions to certain behaviour.
Ealdorlight is now on Kickstarter! Use your connections to win the throne with intrigue or force. RPG with realistic damage and procedural storytelling for PC, Mac and Linux.