Refactoring my app into modern Java with JSON + Rich Domain Model. Getting rid of POJOs, mappers, getters & setters.
The aim of this tutorial is to refactor a simple Phone Catalog App into a cool badass 2021 Java. I’m going to get rid of mappers, POJOs, repository-service pattern, getters, setters and all that unwanted boilerplate stuff that’s (sadly) typical in many of the Java projects out there.
I try to follow a simple philosophy: would I do that like this in JS? And if the answer is no, I go for a different idea. There are no POJOs in JS so this needs to be refactored. I love Java, but I can’t stand those super verbose projects full of unnecessary layers and modules just to implement super simple use cases. Let’s get into this!
⚠️ DISCLAIMER: this is not intended to be a guide on DDD or something like this. I try to be as pragmatic as possible since I don’t like to stick to patterns or architectures if they don’t provide me a clear advantage. I’m not even taking care of my writing. F$ck correctness… I’m just sharing my point of view and ideas about this topic.
⚠️ DISCLAIMER: this refactor should be done with a proper test suite. This app is so small and stupid so I’m not ‘wasting’ time on that… But never refactor without tests.
I’m giving similar tips on @codingsheypol. Make sure you already follow me😁
What stack I’m using?
- Java 11
- Vert.x 4.0.3: I love it! It’s my favourite library and way of building services in Java at the moment.
- RxJava2: I like this reactive flavour 💪
- MongoDB: it’s not my fav product for NoSQL but it’s really simple and extended.
- BAZEL: YES! I also love BAZEL. The learning curve is a bit steep but to me it’s worth it.
- Gradle: I just need it for IntelliJ since the BAZEL plugin is not good at all. It also allows running the app locally in IntelliJ if you don’t want to install BAZEL.
Where’s the project?
The app is super simple. There’s just a single endpoint for retrieving the entire list of phones in the DB. This is the link to the v0.1.0 and the starting point for our current project: https://github.com/sergiosheypol/PhoneAppRichDomainModel/tree/v0.1.0
This is the link to the v0.2.0 version with the final version of our refactor: https://github.com/sergiosheypol/PhoneAppRichDomainModel/tree/v0.2.0
What’s already good and what I hate the most about this project?
I would like to point out that I already like how the repository and handlers are implemented when it comes to functional code. Besides, the code is not violating the Single Responsibility Principle so I’m pretty happy for it since I’ve seen many developers trying to do too much in a single method or class.
What do I hate the most? The f***king mapper and POJO. It’s just too old and outdated to me. Mapping across layers only brings bugs, Null Pointer Exceptions and verbose code.
Current structure
This is how my project looks at the moment:
Let’s get to work, folks!
Moving models from POJOs & Mapper to a simple JSON
Our current POJO model looks like this:
But that’s not the only one. There’s also a PhoneResource for the REST layer that contains EXACTLY the same code… So a mapper layer to transform between both models is required. Check how stupid that mapper is:
See? There’s an almost 1:1 correspondence between fields… 💩 We’re simply wasting time. This isn’t what OOP is intended for since Domain Objects have no control over the business processes but they’re mere property containers.
Let’s refactor this. We’re going to store all those properties into a JsonObject and restrict how objects are created (private constructor), what we expose from them (no getters, but ACTIONS) and remove any chance of mutation (no setters, but ACTIONS again).
New Phone model
Here’s how our new model looks like:
BOOOOOM! Easy! Simple. No boilerplate. Let’s take a look at it:
- The constructor is private. The only way to generate Phone objects is through fromMongo(…) method. Since we only generate initialize objects when they come from the DB, there are no other possible options. Why should we leave the door open for options that aren’t happening? That would only lead to objects whose initial status wouldn’t follow our business rules (inconsistent status).
- There are no getters & setters. By doing this, we’re not allowing mutations or weird actions on the object. Remember, if we need to cover a different business rule, we’ll fully implement it on the object and we’ll trigger the private constructor once we’ve made sure the properties/attributes are in a consistent status.
- This model is now unique for the entire app. There’s just this single representation of ‘Phone’. If you need to have variations on it, you’ll need to add different creation methods.
- It’s all JSON. Fuck those rigid models with fixed properties. Think of this: if I have the control over how to create objects, I have the control over how I expose data from my objects… What’s wrong with JSON? I’m simply wrapping it my (JSON) data with my own business rules. I DO have the control!
- I like to keep the properties as static variables on top of my class so I have visibility. The idea is to only use those variables instead of “hardcoding” weird stuff across the class. Remember, you have the control.
- My old ‘PriceModel’ has disappeared. Same idea, I don’t need to do anything fancy with it and if I did, my JSON tree would be there for me since I’ve created it myself in any of the aforementioned methods. Isn’t it simpler?
- I have a toJson() so I can easily export my model for REST purposes.
With all these transformations, my old model + mapper class have disappeared. Easy, right? Also, those duplicated entities (repo + handler layer entities) are also gone. I just generate my entities fromMongo(…) and expose them through my REST layer by calling .toJson().
Also, my business logic is fully encapsulated in my Phone object. Rest of the classes are loosely coupled.
Refactoring the rest of the project
New Repository layer
Yes, my MongoRepository generates Phone entities so you might think it’s tightly coupled but I don’t think so since I do control how they’re generated (remember fromMongo):
If we wanted this repository to be fully agnostic, we would only need to pass as arguments the name of the collection and a lambda function to transform that JSON into a domain object. See? Refactoring and scaling up has instantly become easy.
New package structure
See how our project looks like now:
I’ve removed all my old packages. They’re just adding nothing. Classes should not be packaged by their likelihood (services with services, models with models…) but by how they’re used together. My business case is a catalog, so here’s all I need to publish that catalog. Easy.
Also, there’s no room for Service layer since I’m not using it at the moment. If I had to, I’d add it afterward.
Note that this is a very simple use case. In the future, when this scales up, we might need to add extra packages but for now, it works
Conclusions
As you can see, we’ve easily escaped from an old type of project to something more flexible and, in my opinion, without sacrificing robustness. This project is now cleaner and can easily be scaled up.
I’ve tried my best to prove that we can combine Java advantages with a more flexible 2021 approach. Many JS developers hate Java because it’s too verbose. I hope this inspires how to write more interesting Java projects and, of course, less verbose.
If you liked this quick guide, follow me on @codingsheypol for more tips like this 😚
Acknowledgements
Shout out to Pablo (gutmox) for the influence in this area! He’s an amazing developer 💪