window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster already exists') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Symfony: Microservices Communication part I - The Codest
The Codest
  • About us
  • Services
    • Software Development
      • Frontend Development
      • Backend Development
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Data Engineers
      • Cloud Engineers
      • QA Engineers
      • Other
    • It Advisory
      • Audit & Consulting
  • Industries
    • Fintech & Banking
    • E-commerce
    • Adtech
    • Healthtech
    • Manufacturing
    • Logistics
    • Automotive
    • IOT
  • Value for
    • CEO
    • CTO
    • Delivery Manager
  • Our team
  • Case Studies
  • Know How
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
  • About us
  • Services
    • Software Development
      • Frontend Development
      • Backend Development
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Data Engineers
      • Cloud Engineers
      • QA Engineers
      • Other
    • It Advisory
      • Audit & Consulting
  • Value for
    • CEO
    • CTO
    • Delivery Manager
  • Our team
  • Case Studies
  • Know How
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
Back arrow GO BACK
2022-06-28
Software Development

Symfony: Microservices Communication part I

The Codest

Sebastian Luczak

PHP Unit Leader

Read the first part of our PHP series devoted to microservices communication in Symfony framework and the most popular way – AMQP communication using RabbitMQ.

Modern application architecture has forced developers to change the way of thinking about the communication between different components of IT systems. Once the matter was simpler – most systems were created as monolithic structures connected with each other by a network of business logic connections. Maintaining such dependencies in a PHP project was a huge challenge for PHP developers, and the growing popularity of SaaS solutions and the huge increase in the popularity of cloud services caused that today we hear more and more about microservices and application modularity.

Just how, by creating independent microservices, can we make them exchange information with each other?

This article is the first in a series of posts on microservices communication in Symfony framework and it covers the most popular way – AMQP communication using RabbitMQ.

Goal

To create two independent applications and achieve communication between them using only Message Bus.

The Concept

We have two, imaginary, independent applications:
* app1: which sends E-Mail and SMS notifications to employees
* app2: which allows you to manage employee’s work and assign them tasks.

We want to create a modern and simple system whereby the assignment of work to an employee in app2 will send a notification to the customer using app1. Despite appearances, this is very simple!

Preparation

For the purpose of this article, we will use the latest Symfony(version 6.1 at the time of writing) and the latest version of PHP (8.1). In a few very simple steps we will create a working local Docker environment with two microservices. All you need is:
* a working computer,
* installed Docker + Docker Compose environment
* and a locally configured Symfony CLI and some free time.

Runtime environment

We will use Docker’s capabilities as an application virtualization and containerization tool. Let’s start by creating a directory tree, a framework for two Symfony applications, and describe the infrastructure of our environments using the docker-compose.yml file.

 cd ~
 mkdir microservices-in-symfony
 cd microservices-in-symfony
 symfony new app1
 symfony new app2
 touch docker-compose.yml

We have created two directories for two separate Symfony applications and created an empty docker-compose.yml file to launch our environment.

Let’s add the following sections to the docker-compose.yml file:

version: '3.8'

services:
app1:
containername: app1
build: app1/.
restart: on-failure
envfile: app1/.env
environment:
APPNAME: app1
tty: true
stdinopen: true

app2:
containername: app2
build: app2/.
restart: on-failure
envfile: app2/.env
environment:
APPNAME: app2
tty: true
stdinopen: true

rabbitmq:
containername: rabbitmq
image: rabbitmq:management
ports:
- 15672:15672
- 5672:5672
environment:
- RABBITMQDEFAULTUSER=user
- RABBITMQDEFAULT_PASS=password

Source code available directly: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml

But wait, what happened here? For those unfamiliar with Docker, the above configuration file may seem enigmatic, however its purpose is very simple. Using Docker Compose we are building three “services”:

  • app1: which is a container for the first Symfony application
  • app2: which is the container for the second Symfony application
  • rabbitmq: the RabbitMQ application image as a communication middleware layer

For proper operation, we still need Dockerfile files which are the source to build the images. So let’s create them:

 touch app1/Dockerfile
 touch app2/Dockerfile

Both files have exactly the same structure and look as follows:

FROM php:8.1

COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
COPY . /app/
WORKDIR /app/

ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/

RUN chmod +x /usr/local/bin/install-php-extensions && sync &&
install-php-extensions amqp

RUN apt-get update
&& apt-get install -y libzip-dev wget --no-install-recommends
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN docker-php-ext-install zip;

CMD bash -c "cd /app && composer install && php -a"

Source code available directly: /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile

The above file is used by Docker Compose to build a container from a PHP 8.1 image with Composer and the AMQP extension installed. Additionally, it runs the PHP intepreter in append mode to keep the container running in the background.

Your directory and file tree should now look as follows:

 .
 ├── app1
 │   └── Dockerfile
 |       (...) # Symfony App Structure
 ├── app2
 │   └── Dockerfile
 |       (...) # Symfony App Structure
 └── docker-compose.yml

The first Symfony microservice

Let’s start with the app1 directory and the first application.
In our example, it is an application that listens and consumes messages from the queue sent by app2 as described in the requirements:

assigning a job to a worker in app2 will send a notification to the client

Let’s start by adding the required libraries. AMQP is natively supported for the symfony/messenger extension. We will additionally install monolog/monolog to keep track of system logs for easier application behavior analysis.

 cd app1/
 symfony composer req amqp ampq-messenger monolog

After the installation, an additional file was added under config/packages/messenger.yaml. It is a configuration file for the Symfony Messenger component and we don’t need its full configuration.
Replace it with the YAML file below:

framework:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
# failure_transport: failed

    transports:
        # https://symfony.com/doc/current/messenger.html#transport-configuration
        external_messages:
            dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
            options:
                auto_setup: false
                exchange:
                    name: messages
                    type: direct
                    default_publish_routing_key: from_external
                queues:
                    messages:
                        binding_keys: [from_external]

Source code available directly: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml

Symfony Messenger is used for synchronous and asynchronous communication in Symfony applications. It supports a variety of transports, or sources of truth of the transport layer. In our example, we use the AMQP extension that supports the RabbitMQ event queue system.

The above configuration defines a new transport named external_messages, which references the MESSENGER_TRANSPORT_DSN environment variable and defines direct listening on the messages channel in Message Bus. At this point, also edit the app1/.env file and add the appropriate transport address.

“`env

(…)
MESSENGERTRANSPORTDSN=amqp://user:password@rabbitmq:5672/%2f/messages
(…)
“
After preparing the application framework and configuring the libraries, it is time to implement the business logic. We know that our application must respond to the assignment of a job to a worker. We also know that assigning a job in theapp2system changes the status of the job. So let's create a model that mimics the status change and save it in theapp1/Message/StatusUpdate.php` path:
image
 {
public function __construct(protected string $status){}

public function getStatus(): string
{
    return $this->status;
}

}

Source code available directly: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php

We still need a class that will implement the business logic when our microservice receives the above event from the queue. So let’s create a Message Handler in the app1/Handler/StatusUpdateHandler.php path:

image
 use PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;

[AsMessageHandler]

class StatusUpdateHandler
{
public function __construct(
protected LoggerInterface $logger,
) {}

public function __invoke(StatusUpdate $statusUpdate): void
{
    $statusDescription = $statusUpdate->getStatus();

    $this->logger->warning('APP1: {STATUS_UPDATE} - '.$statusDescription);

    // the rest of business logic, i.e. sending email to user
    // $this->emailService->email()
}

}

Source code available directly: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php

PHP attributes make things much easier and mean that in this particular case we don’t have to worry about autowiring or service declaration. Our microservice for handling domain events is ready, it’s time to get down to the second application.

Second Symfony microservice

We will take a look at the app2 directory and the second Symfony application. Our idea is to send a message to the queue when a worker is assigned a task in the system. So let’s do a quick configuration of AMQP and get our second microservice to start publishing StatusUpdate events to the Message Bus.

Installing the libraries is exactly the same as for the first application.

 cd ..
 cd app2/
 symfony composer req amqp ampq-messenger monolog

Let’s make sure that the app2/.env file contains a valid DSN entry for RabbitMQ:

(...)
 MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672/%2f/messages
 (...)

All that remains is to configure Symfony Messenger in the app2/config/packages/messenger.yaml file:

framework:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
# failure_transport: failed

    transports:
        # https://symfony.com/doc/current/messenger.html#transport-configuration
        async:
            dsn: '%env(MESSENGER_TRANSPORT_DSN)%'

    routing:
        # Route your messages to the transports
        'AppMessageStatusUpdate': async

As you can see, this time the transport definition points directly to async and defines routing in the form of sending our StatusUpdate message to the configured DSN. This is the only area of configuration, all that is left is to create the logic and implementation layer of the AMQP queue. For this we will create the twin StatusUpdateHandler and StatusUpdate classes in app2.

image
 use PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;

[AsMessageHandler]

class StatusUpdateHandler
{
public function __construct(
private readonly LoggerInterface $logger,
) {}

public function __invoke(StatusUpdate $statusUpdate): void
{
    $statusDescription = $statusUpdate->getStatus();

    $this->logger->warning('APP2: {STATUS_UPDATE} - '.$statusDescription);

    ## business logic, i.e. sending internal notification or queueing some other systems
}

}
image
 {
public function __construct(protected string $status){}

public function getStatus(): string
{
    return $this->status;
}

}


Source code available directly: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php

Finally, all that has to be done is to create a way to send a message to the Message Bus. We will create a simple Symfony Command for this:

image
use SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentMessengerMessageBusInterface;

[AsCommand(

name: "app:send"

)]
class SendStatusCommand extends Command
{
public function construct(private readonly MessageBusInterface $messageBus, string $name = null)
{
parent::construct($name);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $status = "Worker X assigned to Y";

    $this->messageBus->dispatch(
        message: new StatusUpdate($status)
    );

    return Command::SUCCESS;
}

}

Thanks to Dependency Injection we can use an instance of MessageBusInterface in our Command and send a StatusUpdate message via the dispatch() method to our queue. Additionally, we also use PHP Attributes here.

That’s it – all that’s left is to run our Docker Compose environment and see how our applications behave.

Running the environment and testing

With Docker Compose the containers with our two applications will be built and run as separate instances, the only middleware layer will be the rabbitmq container and our Message Bus implementation.

From the root directory of the project, let’s run the following commands:

cd ../ # make sure you're in main directory
 docker-compose up --build -d

This command can take some time, as it builts two separate containers with PHP 8.1 + AMQP and pulls RabbitMQ image. Be patient. After images are built you can fire our Command from app2 and send some messages on a queue.

 docker exec -it app2 php bin/console app:send

You can do it as many times as you can. As long as there’s no consumer your messages will not be processed. As soon as you fire up the app1 and consume all the messages they’ll show on your screen.

docker exec -it app1 php bin/console messenger:consume -vv external_messages
image

The complete source code along with the README can be found in our public repository The Codest Github

Summary

Symfony with its libraries and tools allows for a fast and efficient approach to developing modern web applications. With a few commands and a few lines of code we are able to create a modern communication system between applications. Symfony, like PHP, is ideal for developing web applications and thanks to its ecosystem and ease of implementation this ecosystem achieves some of the best time-to-market indicators.

However, fast does not always mean good – in the example above we presented the simplest and fastest way of communication. What the more inquisitive will certainly notice that there is a lack of disconnection of domain events outside the application layer – in the current version they are duplicated, and there is no full support for Envelope, among others there is no Stamps. For those and others, I invite you to read Part II, where we’ll cover the topic of unifying the domain structure of Symfony applications in a microservices environment, and discuss the second popular microservices communication method – this time synchronous, based on the REST API.

cooperation banner

Related articles

Software Development

PHP 8.2: What’s new?

The new version of PHP is just around the corner. What are the new implementations you should know about? Check this article to find out!

The Codest
Sebastian Luczak PHP Unit Leader
Software Development

PHP Development. Symfony Console Component – Tips & Tricks

This article was created with the aim to show you the most useful and retrieving tips and tricks about Symfony Console Development.

The Codest
Sebastian Luczak PHP Unit Leader

Subscribe to our knowledge base and stay up to date on the expertise from the IT sector.

    About us

    The Codest – International software development company with tech hubs in Poland.

    United Kingdom - Headquarters

    • Office 303B, 182-184 High Street North E6 2JA
      London, England

    Poland - Local Tech Hubs

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warsaw, Poland

      The Codest

    • Home
    • About us
    • Services
    • Case Studies
    • Know How
    • Careers
    • Dictionary

      Services

    • It Advisory
    • Software Development
    • Backend Development
    • Frontend Development
    • Staff Augmentation
    • Backend Developers
    • Cloud Engineers
    • Data Engineers
    • Other
    • QA Engineers

      Resources

    • Facts and Myths about Cooperating with External Software Development Partner
    • From the USA to Europe: Why do American startups decide to relocate to Europe
    • Tech Offshore Development Hubs Comparison: Tech Offshore Europe (Poland), ASEAN (Philippines), Eurasia (Turkey)
    • What are the top CTOs and CIOs Challenges?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Copyright © 2025 by The Codest. All rights reserved.

    en_USEnglish
    de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek en_USEnglish