I have some kind of a meta/ architecture question.
TL;DR;:
Having objects (especially services) leads me to the point that these services have to be called in one place in my application. Most of the time they have to be called in a certain order. Maybe they also depend on each other. While they contain the business logic, it nevertheless does not feel right to me, calling this chain of services within a controller method (clean and short controllers). Where do I put these procedural calls?
Currently, I still dive into the topics of OOP and MVC and their testing along with their best practices.
Especially with Lumen and Laravel but also without the help of any framework.
Reading/ searching constantly about best practices leads me every time to the point that I question everything I read or learned before.
At this moment I struggle with a topic described here: TDD: best practices mocking stacks of objects
Please don't get me wrong, I don't want to request help for the other question in a new post but this one here builds on top of the other one.
Seeing tutorials of testing/TDD in Laravel, they always show how to test resource controllers with insert()
update()
and delete()
.
But offside of the behavior as a resource manager it's a bit tricky to find an example where some business logic is performed without having a request from a frontend for example.
Over time some things condensed as seemed to be some kind of "true" for me (only a shortlist):
- having repositories to wrap my models into is a good idea in many cases. Couple them to an interface and depend on them via type hinting (I don't want to hype this pattern but what I read about so far sounds pretty helpful for a prudent use).
- Keeping my controller clean so that it only receives a request and returns a response while separating all business logic into services.
- Avoid building services that are too big and couple them via DI so that they can use each other just because of one service need a piece of information from an area of data another service is responsible for.
Unfortunately especially the third point builds a knot in my head. Trying to build OOP in PHP leads me eventually every time to a place where all the objects (most of them are services or actions) have to be called in a certain order to create the result needed. In this place, all of these objects have to be available which is also often called "bad practice" having too many dependencies in one constructor.
Let me introduce an example for better understanding:
Let's say we have an external service providing documents. These documents consist of the "main" document data and one or more sets of metadata nested within the document object. The file meanwhile can be accessed using another endpoint if one provides some of the "main" document data.
All of the data which belong to a document then have to be restructured to be eventually sent to another API. This application described here would be some kind of a Sync-Middleware.
Assumed that I just want to do THE one path in this application I could have one controller method which lists all the necessary calls to different services. Regardless of the fact that this would be not so nice for testing this controller method. Each of these services does one piece of work to restructure a document object.
#Controller
function __construct(
Service1 $service1,
Service2 $service2,
Service3 $service3
Service4 $service4
) {
$this->service1 = $service1;
$this->service2 = $service2;
$this->service3 = $service3;
$this->service4 = $service4;
}
public function runSync($document) {
// receive the document from source API
$document = $this->service1->doYourStuff($document);
// restructure document data
// calls Service1 to receive the actual file
$document = $this->service2->doYourStuff($document);
// restructure meta data
$document = $this->service3->doYourStuff($document);
// send document to target API
$response = $this->service4->doYourStuff($document);
return $response
}
Please imagine that the actual real-world code would have way more steps to do and for example that Service2
depends on Service1
to receive the file of the document because this is the responsibility of Service1
.
But what if I want to provide just a part of this procedure in the frontend? For example, I want to give the user some kind of test or preview where he or she can check if, for a given document, the restructuring of the metadata is done properly.
I would have to copy most of this procedure into another method with some minor changes to return the result at an earlier point in this chain.
Solutions I can imagine at the moment:
- Building classes called "procedures" which contain all permutations of the chain needed in the application.
Problem: This would lead to a whole bunch of classes where, if there would be any changes in the interfaces of one of these services, a change in all procedure classes calling this service, would be required. This problem also counts, if the chain would remain within the controller.
- Building one chain which is called every time and each service calls the next one as soon as ready. Parameters tunneled through the whole chain could be used to define a custom exit.
Problem: This is a pretty strong coupling between these actually independent services. Each of them would know about at least the next one and probably some more if the functionality is needed.
- I could use some kind of observer or event system to forward the finished workpiece again and again.
Problem: Changing the order or insert a new step would be pretty difficult. Also, I would like to create a solution that I would be able to build even without any framework. But observer and event bus are not those things I feel able to build.
- Using the "Chain of responsibility"-pattern (https://sourcemaking.com/design_patterns/chain_of_responsibility) with a controlling entity outside the chain.
Problem: The same as in point 2 and 3. Changing the behavior of the chain or forcing a custom "exit" wouldn't be that easy.
In a nutshell:
When designing an application with testing in mind, or even because I want to use a specific functionality independently somewhere else I end up with many services that probably depend on each other. To perform the main path of the application or a permutation of that path/ a user-initiated test, they have to be called somewhere in a certain order. I shouldn't do it within a controllers method to keep it clean and short but coupling services not for functionality and single responsibility but for a certain execution order doesn't sound like the right way, too.
Where do I put those calls?
Do I miss something? How can I become better with this metaish kind of problem? Reading about design patterns is not that complex most of the time. It's more that I don't know how to glue these patterns together in a higher-order meaning.
Aucun commentaire:
Enregistrer un commentaire