Posted on July 30, 2017
Today I am going to tell you a little secret. It must be a secret since almost no programmers seems to know about it. Modeling your code based on your domain is the key to maintainable codebase!
It actually is not secret, it is a well known anti-pattern to use primitive types to model your domain. I would like to advocate in favor of domain objects.
When coding we should always aim at creating domain specific language based on the software use cases (aka domain). This is called abstraction. Do not confuse abstraction with indirection. Abstraction is process of hiding complexity and providing tightly targeted objects (functions). Indirection on the other hand is a way to achieve low coupling of components. Those two terms are often tangled together, usually loosing benefits of both in the process. In java/spring world word domain object often refers to ORM entities in anemic architecture. This is obviously not correct since entities represent structure of the database not your business models and use cases.
What are domain objects? They are small objects describing your business. An example from finance software would be classes InteresetRate, LoanAmount, Anuity, DaysPastDue, etc. This seems like a boilerplate code since we can represent these concepts by a language primitives like double, int, BigDecimal and others. I would like to explain how using non-primitive objects even for the simplest concepts is beneficial.
Most of our programming time is spent in already existing code. We have to fix bugs, change behavior or just reuse older code in new components.
Most common anti-pattern among Developers: making choices based on speed of initial development rather than ease of debugging. — Mikeal Rogers (@mikeal) [24 Jun 2017]
By spending a bit more time using domain object instead of a primitive will save you many times more in the future.
Naming variables is hard. If you do not use domain object you feel the tendency to use the object name as variable name or at least to include it to the name. This is then repeated throughout the codebase again and again. Lets omit that and encode that name directly to the variable type!
double offeredInterestRate ....; // vs InterestRate offered = ...;
I would be even tempted to create OfferedInterestRate in this case :) Ok, shorter variable names are not a big deal. But whole method using domain concepts instead of primitives, that is something! Such code could be read by non-programmers too. Try describing double and InterestRate to your business team, which one will be easier?
This is the most important consequence of using domain objects in typed systems. Imagine a simple method double calcualteInterest(...); Does it return a percents in 4.99 % or 0.0499 form? We can only hope that the author of the code included that in the javadoc of that method. Even if he did, you have to remember that every time when you call it and that it will not change unexpectedly in newer versions. Use a domain object instead and encapsulate that fact inside that object InterestRate calcualteInterest(...);. Now I don't care which form it does have. And there is more!
// consider a method signature double calcualteInterest(double amount, double annuity); // and its usage double amount = ...; double anuity = ...; double interest = calcualteInterest(annuity, amount);
Can you spot a bug? Of course you do, its three lines of code! But can you spot that in a large codebase and long pull requests? I know a guy who can spot such bugs with ease. It's in fact his job! He is called type system and together with a compiler he will yell at you every time you make such a mistake. He has one condition though: you have to use types for your domain.
Lets take the same method as in previous example calcualteInterest(double amount, double annuity) again. Is negative amount a valid loan amount or even negative annuity? This method looks like they are because it will accept them as parameters. We do not have Dependent types in java and I think we wont have them for a very long time so we should explain our constraints with ... surprise ... domain objects. Objects maintain their own always valid state (OOP lesson number one). Is complete work of Shakespeare a valid email address? No, it is not, so why you have a String email; Represent your domain with always-valid objects and you can get rid off validity checks that spread across all your code base. Another point for better maintenance!
You cannot put javadoc on top of primitive objects and library classes but you can do that for your domain objects! Thank you captain obvious, right? :) This is such a trifle, but it makes a huge difference. When I see int daysPastDue = ...; what does it mean? I have to search some internal wiki or ask someone. Instead I could just navigate to that class and read
/** * Represents number of days that user is due with a payment with an annuity. It is calculated as .... * Other very useful info. * Domain objects rulez! */ public class DaysPastDue { ... }
Not everything needs to be a domain object but the more the better.