A
A
Alexey Verkhovtsev2019-02-13 14:04:20
symfony
Alexey Verkhovtsev, 2019-02-13 14:04:20

Can services communicate with each other?

Hello!
In short, the architecture is as follows:
entity -> repository -> service -> controller
entity - represented by an anemic model, so all the logic is in services
An example of
an entity is CheckoutFlow, EmailTemplate. CheckoutFlow contains EmailTemplate
services CheckoutFlowService (CRUD), EmailTemplateService (CRUD)
Services work with entities through repositories
The task of duplicating CheckoutFlow arises, we add a method to the CheckoutFlowService::duplicate service
We duplicate the entity and save
But we also need to duplicate and EmailTemplate entities, respectively, and here are 2 options there is
1) Our services communicate only with repositories and therefore we duplicate our essence in CheckoutFlowService through the CheckoutFlowRepository repository, and through the EmailTemplateRepository repository in the same method our own (EmailTemplate)
Cons of the approach - what if we just need to duplicate EmailTemplate and we will have to duplicate code
2 ) We can create a method in the EmailTemplateService::duplicate service that would duplicate just EmailTemplate
Pros - we can use separate duplication
Cons - then you have to inject the EmailTemplateService service into the CheckoutFlowService and I don't know how true this is. But another minus in cross-references, when CheckoutFlowService contains EmailTemplateService, and EmailTemplateService in turn CheckoutFlowService and the solution for this is lazy loading, which somehow does not at all inspire confidence that we are moving in the right direction.
CheckoutFlowController

class CheckoutFlowController
{
    private $checkoutFlowService;

    public function __construct(
        CheckoutFlowService $checkoutFlowService
    ) {
        $this->checkoutFlowService = $checkoutFlowService;
    }
    
    public function duplicate(
        Request $request,
        int $id
    ): Response {
        $body = $request->request->all();

        $checkoutFlow = $this->checkoutFlowService->getCheckoutFlowById($id);

        $newCheckoutFlow = $this->checkoutFlowService->duplicateCheckoutFlow(
            $checkoutFlow,
            $body['name'],
            $body['slug_name']
        );

        $data = [
            'checkout_flow' => $newCheckoutFlow
        ];
        $response = new JSendResponse(JSendResponse::SUCCESS, $data);

        return new JsonResponse($response, Response::HTTP_OK);
    }
}

CheckoutFlowService
class CheckoutFlowService
{
    private $checkoutFlowRepository;
    
    private $emailTemplateService;

    public function __construct(
        CheckoutFlowRepositoryInterface $checkoutFlowRepository,
        EmailTemplateService $emailTemplateService

    ) {
        $this->checkoutFlowRepository = $checkoutFlowRepository;
        $this->emailTemplateService = $emailTemplateService;
    }

    public function duplicateCheckoutFlow(CheckoutFlow $checkoutFlow, string $name, string $slugName): CheckoutFlow
    {
        $campaign = $checkoutFlow->getCampaign();

        $newCheckoutFlow = null;
        $newEmailsData = null;

        $currency = $checkoutFlow->getCurrency();
        $currencyId = ($currency !== null) ? $currency->getId() : null;

        $shippingPreset = $checkoutFlow->getShippingPreset();
        $shippingPresetId = ($shippingPreset !== null) ? $shippingPreset->getId() : null;

        $countriesPrices = $checkoutFlow->getCountriesPrices();
        $countriesPrices = ($countriesPrices !== null) ? json_decode($countriesPrices) : null;

        $metaData = $checkoutFlow->getMetaData();
        $metaData = ($metaData !== null) ? json_decode($metaData) : null;

        $cfTemplate = $checkoutFlow->getCfTemplate();

        $emailsData = json_decode($checkoutFlow->getEmailsData());

        if ($emailsData !== null) {
            $newEmailsData = [];

            foreach ($emailsData as $emailData) {
                $emailTemplate = $this->emailTemplateService->getEmailTemplateById($emailData->email_template_id);
                $newEmailTemplate = $this->emailTemplateService->duplicateEmailTemplate($emailTemplate);
                $emailData->email_template_id = $newEmailTemplate->getId();
                $newEmailsData[] = (array) $emailData;
            }
        }

        $newCheckoutFlowArray = [
            'name' => $name,
            'slug_name' => $slugName,
            'flat_rate_shipping_enable' => $checkoutFlow->getFlatRateShippingEnable(),
            'currency_by_country' => $checkoutFlow->getCurrencyByCountry(),
            'countries_prices' => $countriesPrices,
            'campaign_id' => $campaign->getId(),
            'currency_id' => $currencyId,
            'checkout_flow_template_id' => $cfTemplate->getId(),
            'flat_rate_shipping_price' => $checkoutFlow->getFlatRateShippingPrice(),
            'meta_data' => $metaData,
            'emails_data' => $newEmailsData,
            'shipping_preset_id' => $shippingPresetId
        ];

        $newCheckoutFlow = $this->checkoutFlowRepository->save(new CheckoutFlow($newCheckoutFlowArray));

        return $newCheckoutFlow;
    }
}

EmailTemplateService
class EmailTemplateService
{
    private $emailTemplateRepository;

    private $checkoutFlowService;
    
    public function __construct(
        EmailTemplateRepositoryInterface $emailTemplateRepository,
        CheckoutFlowService $checkoutFlowService
    ) {
        $this->emailTemplateRepository = $emailTemplateRepository;
        $this->checkoutFlowService = $checkoutFlowService;
    }

    public function addEmailTemplate(array $fields): EmailTemplate
    {
        $emailTemplate = new EmailTemplate();

        $emailTemplate->setSubject($fields['subject']);
        $emailTemplate->setData($fields['data']);

        return $this->emailTemplateRepository->save($emailTemplate);
    }

    public function duplicateEmailTemplate(EmailTemplate $emailTemplate): EmailTemplate
    {
        $newEmailTemplateArray = [
            'subject' => $emailTemplate->getSubject(),
            'data' => $emailTemplate->getData()
        ];

        return $this->addEmailTemplate($newEmailTemplateArray);
    }
}

The code is presented just as an example, something is omitted, something does not work.
What are the ways to implement the plan?

Answer the question

In order to leave comments, you need to log in

2 answer(s)
I
Iloveski, 2019-02-13
@Iloveski

As described, this is the norm. The main rule is that only its own service calls the methods of the repository. But where the service is called from, from another service or from a higher level, is not so important.

D
developer007, 2019-02-16
@developer007

Services should not be cross-referenced. Change business logic.
If you have cross references then you are doing something wrong.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question