Clean Architecture - Injection of app services into another app services

1.9k Views Asked by At

I'm working in a PHP API trying to follow a Clean Architecture pattern in order to be able to extract modules of the app to micro-services in the future.

My question is how application services should use each other without getting coupled. Even if I'm injecting a bound abstract (interface), injected service's methods are handling entities outside the host service's domain. So in the future i would have coupled services and i will not be able to externalize them.

<?php

/* Domain: INJECTED */
class InjectedService implements InjectedServiceInterface 
{
    public function get(int $id): InjectedServiceDomainEntity 
    {
        return $this->repo->findById($id);
    }
}

/* Domain: HOST */
class HostService implements HostServiceInterface 
{
    /** @var InjectedServiceInterface $injectedService */
    private $injectedService;

    public function __construct(InjectedServiceInterface $injectedService)
    {
        $this->injectedService = $injectedService;
    }

    public function someMethod($someId)
    {
        /** @var InjectedServiceDomainEntity $injectedServiceEntity */
        $injectedServiceEntity = $this->injectedService->get($someId);
        // here I'm managing an outsider entity
    }
}

at someMethod am i not coupling the services? managing an entity from another service/domain? what happens when i want to move the HostService to a micro-service?

Thanks a lot in advance for your ideas.

3

There are 3 best solutions below

0
Eben Roux On

Even though you are using PHP the following may provide useful on a conceptual level:

Probably the simplest would be to implement some generic mediator that you depend on in your application layer (integration concern).

I have a simple open-source implementation with another option being Jimmy Bogard's mediatr.

In my implementation a particular participant would depend on the relevant service(s) and act on the relevant message being passed. Any given participant can respond to various messages. In your case there may be two participants with each relying on the relevant service.

This mechanism also works well when trying to cut down on the number of dependencies injected into, or used by, a class.

In the past I have also used an application Task (not to be confused with .Net Task) that represents a use-case and then it would accept both OrderService as well as PaymentService (using your example here) and interact with the two in the relevant way. The code dependency of one on the other could be extracted into meaningful methods.

The mediator is a more implicit implementation of this concept whereas some use-case specific class would be more explicit.

0
Ali Soltani On

If I could correctly understand your question, there are two services for example, OrderService along with PaymentService. OrderService is needed the PaymentService.

There are two scenarios

First scenario: PaymentService is third party for OrderService, which could be independently developed. If it's true, it's better to put PaymentService in Infrastructure layer and inject it in application layer services like OrderService.

Second scenario: PaymentService isn't third party. In terms of clean code, there is an issue in this case. Single Responsibility Principle would be violated. There is more than one reason to change methods like someMethod. Calling a method from another service inside another one makes more than one reason to change. It would be better to call PaymentService in presentation layer and fetch data needed for OrderService and after that someMethod in OrderService could be invoked by passing the data without any need to inject PaymentService.

Discussing this sort of questions is a bit hard. Hope to say my meaning properly. ;)

0
winson On

You will always have coupling if service X uses service Y, BUT:

Clean Architecture and mostly its dependency inversion principle gives you the tools necessary to make the coupling or depedency unidirectional.

In your case DI and interfaces allow you to have the Host module depend on the Injected module but never the other way round. You could even make the Host module depend on nothing if you move InjectedServiceInterface and an interface for InjectedServiceDomainEntity into the Host module. If you call get now you will only deal with source code from the Host module.

With DI those interfaces are implemented in a different module. With Microservices those interfaces are implemented by a remote calling infrastructure component.

There is no point in trying to reduces coupling to zero because then your modules do not interact at all. At least it appears that way in the source code

One more thought: in a dynamically types language like PHP you propbably do have the opportunity to have more decoupling if you want by using function types instead of declared interfaces as parameters and duck typing for entities/objects.