Frameworks are awesome, they can save us a lot of time and protect us from ourselves. I believe however that it’s important to keep the separation from frameworks and make sure that our core business value is delivered in a framework agnostic manner.
Uncle Bob Martin illustrates this concept very well with his clean architecture approach. Frameworks help us take care of the details, but should not dictate how we structure our application as a whole.
Umm but why rock the boat? Why not just use MVC? In my experience, not every application fits nicely into the M, V or C buckets and trying to make it adhere to the framework structure introduces accidental complexity and rigidity.
Not every domain concept is worthy of a database table or a model. Having worked on mid to large size apps, i’ve seen first hand the mayhem of trying to have models for and controllers for everything only to appease the framework.
One particular example that comes to mind was a travel reservation system. When i got on the team there were at least 50 different models and 20 different controllers dealing with different reservation types for different types of passengers etc. When i asked about the architecture I was told that it was a Laravel app, so they did what you do in Laravel.
It took me a few days to figure out which model did what and what the purpose was. What was even scarier is that all of the models inherited from an abstract model which then inherited from Laravel’s Eloquent model class.
While inheritance may have seemed like a good way to reuse code, it is also a very strong relationship. When class A inherits from B it get’s all of the B’s dependencies.
Such hierarchy can work in some cases, however, more often than not it really contributes to system fragility and rigidity. It is also a nightmare to test. If we want to access class A we need to bootstrap class B and all of it’s dependencies.
For the project above I had to implement a way to communicate reservation data to a third party service. The use case was simple, gather data from the application format it accordingly and send it to the 3rd party service.
Isolate the use case
I started by taking a look at what are the knowns and unknowns. Initially I didn’t know about the models and how data is stored in the application. I did know the format that the data should be in to be consumed by the 3rd party service so i started there.
I’ve introduced a POPO (Plain Old PHP Object) that generated the appropriate xml format. Because it had no dependency on Laravel, I had the freedom to test it in complete isolation. I took care to ensure I don’t introduce side effects. Basically the whole object was a pure transformer.
I was able to test drive the whole thing. I know, I know TDD is dead, but it can be a really friendly ghost.
Encapsulate necessary data manipulation behind repositories
My XML transformer needed passenger information, but i didn’t want to have to figure out existing models just yet. Instead i pretended my repositories already existed and gave the data in the format that was required. In essence i added a couple POPOs and codified the communication behind an interface.
For instance i’ve added a getAddress() method which returned the address in a format that my transformer expected. Note that these interfaces were specific to my use case. There may well have been another getAddress method somewhere that formatted the address differently all together
Make the framework dependent code implement my interfaces
After i’ve defined the communication patterns i’ve simply made the existing eloquent models implement the interfaces so that the code became a plugin to my transformer. This is an application of the Dependency Inversion Principle (SOLID) We’ll cover it in more detail soon.
Is it Worth It ?
I believe so, the code finally had a bit that was responsible for one thing. It was easier to demo, easier to debug and easier to test. It was also more resilient to changes.
The design for this use case grew organically from a monolith to a hexagonal architecture. I’ve managed to keep existing technical debt at bay by making it a plugin to the new design. Also I was able to run my tests in a fraction of the time of the entire test suite.
Your milage may vary based on your use cases. However look for ways to limit dependencies on a framework. A good framework can help you with the general stuff, however your business specific rules should not be exposed to it. We’ll talk more about it when talking about Domain Driven Design.