28 March 2019
How to create a framework-agnostic application in PHP (2/3): connecting with a framework and storage
In the previous article, we’ve prepared a framework-agnostic part of our application. Now it’s time to connect it with a framework and storage. For the purpose of this article, we will use the following tools: Laravel 5.7, Eloquent (MySQL 5.7) and Tactician (command bus implementation). All aforementioned tools are a part of our infrastructure – they are external services we want to use because they will make certain things easier for us. However, business logic shouldn’t care much about them. Instead, we use repository contracts and application layer concepts.
Let’s begin with connecting the framework-agnostic app with storage
I decided that in this specific case I’m gonna use a MySQL database. No specific reasons behind that decision – it’s just very popular, most programmers know it pretty well and use it very often.
For starters, we need to prepare Eloquent representation of our domain models. Let’s take a look at domain Runner model one more time:
And now at its Eloquent equivalent:
Notice that Eloquent model contains relations to other storage models. We’ll get back to them later.
Now there’s one critical question: how to connect these two completely different classes? Since they are not similar at all, extending may not really be a good idea, especially because the storage model already extends ORM’s Model class. Well, we have another option. Let’s write a class that will be able to do both directions translation between these two models. Let’s call it a transformer. Here it is:
For now, let’s omit the additional transformers which are injected to handle the other models needed by our runner. We want to focus on methods entityToDomain and domainToEntity. As you can see, thanks to transformer class we can easily connect our domain models with Eloquent and our storage will work just fine.
One more example of Eloquent model based on RunParticipation can be found below.
And now the corresponding transformation class:
As you can see, we’ve got additional entityToDomainMany method. It’s used to translate a whole collection of RunParticipation for a specific runner.
Now we have the possibility to translate models, so we can finally fulfil repository contract. We can use facedes without worrying about coupling concerns because everything is separated. Let’s take a look at RunnerRepository implementation:
It finds data in MySQL and uses a transformer to translate it to the domain’s Runner model.
We can also check repository which is used to save data instead of retrieving it:
It’s very simple, with just two meaningful lines of code. Thanks to “Single Responsibility Principle” most of these classes are quite small and easy to read.
That’s all when it comes to storage! We have connected our business models to ORM and can read/write real data from an external source.
Framework for the framework-agnostic app
Now let’s connect everything to Laravel. Our point of connection is the application layer. I decided, that I don’t want to dilly-dally with HTTP – it’s so common and boring that wanted to try something different, so we’ll be sending requests through CLI. Fortunately, it’ll be a lot easier, than storage preparation. We’re going to use dependency injection whenever it’s possible.
We need to use the command bus implementation. I chose Tactician. There are ready-made packages for Laravel, I used joselfonseca/laravel-tactician.
Here is our participation console command:
We accept two parameters – runnerId and runId. Handle. The method itself is quite simple:
- line 45 – informing command bus which handler should take care of specified command,
- lines 47-48 – creating Id objects, so they will be handled by application command
- line 50 – instantiating command,
- lines 52-58 – dispatching command through command bus and inform about success. If any business rules won’t be met, we catch DomainException and inform the user what was the issue.
You may ask, why haven’t we validated data earlier by using frameworks validators. The truth is, we should have done it, but we’ve skipped it since this application is simplified. You should always validate your data with framework!. Domain exceptions are the last stand, which we normally shouldn’t reach anyway.
We can’t forget about dependency injection though, so here is provider I used:
We need bindings only for our contracts (interfaces), rest is automatically done by Laravel. After this step and writing some database migrations our application is fully working. Not even a piece of logic is depending on a framework or library.
Framework-agnostic means independence
As you can see, being framework-agnostic is not very difficult, we just need to use patterns consciously and add a bit of out-of-box thinking. To prove that our domain and application layers are really independent, I’m gonna reuse them, this time with Symfony and Doctrine. I will describe the process in the third (and the last) part of this article. In the meantime, please take a look at the repository with a working Laravel application.