Of Oracles and Djinns
A few years ago, I was working on a team for a client in the logistics industry.
We build a rather typical Java-based backend following DDD practices and using a Ports-and-Adapter architecture style, with a PostgreSQL database for persistance.
At some point during the project, we had to calculate the total value of all pending orders in a specific time frame.
This would be an easy task for our relational database: A simple SQL query that sums up everything we need with a non-trivial, but certainly understandable
where clause to accounted for some special cases in the data model.
However, there were some members on the team who argued that this calculation followed certain business rules and that ALL business rules should be implemented in the domain core of our application. Therefore, we should define a service class that loads all the orders in question from the database, calculates the sum and returns it to the caller of the service (a REST controller, in this case).
I was against taking this path because of the horrific performance of this service. As the repository class we had defined with Spring Data JDBC returned a list of the whole order objects, the amount of data that was being loaded into memory was huge and most of it (like item name) wasn’t even relevant to the use case at hand. However, as I was fairly new in the project and the other team members had been at my company longer than I have (and I tend to be a very agreeable person), I was unable to convince the other programmers. For them, it was more important to uphold the integrity of the domain logic than the performance benefit of doing the calculation in the database. The end users of the software were also used to a very slow legacy system, so even this mediocre performance was acceptable to them. And so, we build the service class and sure enough, the request took quite a while to execute, even in our little test data set.
Please do not misinterpret my writing: This blog post is not a rant against my former colleagues. I was not able to express my concerns vividly enough to them and I was not able to offer an alternative solution. This blog post is an attempt to do both.
Why keep the domain logic clean?
First of all, I am a loud advocate for the Ports-and-Adapter architecture style (which some people call it by the horribly imprecise name “Hexagonal Architecture” or the slightly, but not much, better name “Onion Architecture”). We as software developers often deal with complex business domains were even the client has only a vague idea of how the business actually works and what the business needs. There is often a lot of ambiguity and blind spots in the view of domain experts and we as developers have the burden of merging all of it into a clean and unambiguous bundle we call software. Doing all of this AND also dealing with technical concerns can be daunting and too much cognitive load for mere mortals. A nice boundary between the technical side and the domain side can increase the understandability of code. And an understandable codebase means that features can be produced more quickly. Everybody wins!
Do not fall into dogma
A clean domain logic is great for developers, but at the end, we need to write useful software for end users.
I do not think that the famous quote that “Premature optimization is the root of all evil” applies in the story above.
This quote does not mean that there should be no thoughts of optimization at the beginning of a new feature nor does it say that you should neglect performance altogether.
Premature optimization is arguing about whether or not you should use
Premature optimization is comparing different sorting algorithms when you know that the list to be sorted will never have more than 30 elements.
Premature optimization is NOT questioning a solution that requires loading a gigantic amount of data from the database.
This is good engineering.
An Oracle to the rescue
Let me present to you a solution that I hope would satisfy both camps: The “clean domain logic” programmers and the “performance is everything” programmers. From a caller or frontend perspective, the service we’re building looks the same: Given a time frame, it returns a little data structure that contains the aggregated information for our use case. Inside, however, we do things a little differently. Instead of invoking a repository method and loading all order objects into memory or building a SQL-statement directly, we ask ourselves the following question:
How would the code look like if we had an oracle that could answer the question that we’re trying to answer?
Well, it would look rather simple: Ask the oracle what you want to know, then wrap it in the right return type and give it back to the caller. Easy. The interface of the oracle can be defined in the core domain logic, and can be implemented outside in a database adapter module. Inside the database adapter, we can use all the benefits that the DBMS can offer us, even if that means that we put logic that shouldn’t reside here into SQL statements. By introducing the concept of an oracle, we gave ourselves an escape hatch from the “clean domain logic” idea to do things that would otherwise be too slow.
Ask Djinns for wishes
What the oracle is for reading, the djinn is for writing. The question we’re asking ourselves here during the development of the service class is slightly different:
How would the code look like if we had a djinn that we could ask to do the thing we’re trying to do for us?
The solution is also simple: Formulate your wish to the djinn, and if he says he is done, inform the caller. Like the oracle, the interface of the djinn can be defined in the core domain of the project, while the implementation resides outside in a database adapter module.
How would you name the interface and implementation of an oracle or a djinn in code?
I haven’t found a good convention yet and I would not like to call them
CalculateOrderSumOracle or something similar.
Let me know in the comments if you can think of a good name for these components.
Don’t depend on too much magic
I did not choose the names ‘oracle’ and ‘djinns’ for this pattern by random: They introduce magic to your codebase. They are neither good nor bad, but you should really ask yourself if you want to rely on them. Because if you use them too much, you loose the ability to have a rich and fool-proof domain model that embeds all the business rules and that can be used with different database technologies.
Hopefully, you found this blog post useful. Please let me know in the comments if you have any thoughts (especially for a good naming convention).