DEV Community

Bryan Granado
Bryan Granado

Posted on

Laravel microservices with rabbitMQ and docker.

The proposal this post is understand how you make a simple microservices architecture, without all the drama that goes with it . We start with a point booster could be using for any context. Well, let do it

The first step were knows and is download a laravel repository, recommended from it's official page laravel official. Then will download docker(Im using docker desktop because is very easy for me) if you be using linux, must go to Docker official linux.

Once already with enviroment, were go to prepare our architecture! For this blog we are work with a restaurant request to cousine, thats basically through api-manager(client) we called all microservices that be cousine, grocery-store and recipes; this microservices will communicate among themselves and then return a response to api-manager, it handler all request from any client. Remember create a project(Microservice for each module)

Our architecture show like :

api-manager
cousine
grocery-store
recipes
Enter fullscreen mode Exit fullscreen mode

The next step is download rabbitMq server, for communicate from local enviroment. You can get from or if you has downloaded docker(required) using:
docker run --name rabbitmq bitnami/rabbitmq:latest

Ok almost ready ... Once you get all prepared, its time of create our .yaml file for each project. Here leave how you must do it :

For api-manager and anothers services, we starting with create a docker-compose file with name:
docker-compose.yml
You show something like

Image description

Remember that it, must be in root of your service or microservice

Now, you must get a docker compose document as follows:

services:
  db:
    image: mysql:latest
    container_name: mysql
    ports:
      - "3306:3306"
    volumes:
      - mysql-volumes:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: laravel
    networks:
      - app-network

  laravel-app:
    build:
      context: ./docker/php
    container_name: laravel-app
    volumes:
      - .:/home/source/main
    working_dir: /home/source/main
    networks:
      - app-network

  nginx:
    build:
      context: ./docker/nginx
    container_name: todo-nginx
    ports:
      - "8000:80"
    depends_on:
      - laravel-app
    volumes:
      - .:/home/source/main
    networks:
      - app-network

volumes:
  mysql-volumes:
    driver: local
networks:
  app-network:
    driver: bridge
    name: app-network

Enter fullscreen mode Exit fullscreen mode

Likewise for each services. Here :

Cousine

services:
  db:
    image: mysql:latest
    container_name: mysql-cousine
    ports:
      - "3307:3306"
    volumes:
      - mysql-volumes-cousine:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: laravel_cousine
    networks:
      - app-network

  laravel-app:
    build:
      context: ./docker/php
    container_name: micro-cousine
    volumes:
      - .:/home/source/main
    working_dir: /home/source/main
    networks:
      - app-network

  nginx:
    build:
      context: ./docker/nginx
    container_name: todo-nginx-cousine
    ports:
      - "8001:80"
    depends_on:
      - laravel-app
    volumes:
      - .:/home/source/main
    networks:
      - app-network

volumes:
  mysql-volumes-cousine:
    driver: local
networks:
  app-network:
    driver: bridge
    name: app-network
Enter fullscreen mode Exit fullscreen mode

Recipes

services:
  db:
    image: mysql:latest
    container_name: mysql-recipes
    ports:
      - "3309:3306"
    volumes:
      - mysql-volumes-recipes:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: laravel_recipes
    networks:
      - app-network

  laravel-app:
    build:
      context: ./docker/php
    container_name: micro-recipes
    volumes:
      - .:/home/source/main
    working_dir: /home/source/main
    networks:
      - app-network

  nginx:
    build:
      context: ./docker/nginx
    container_name: todo-nginx-recipes
    ports:
      - "8003:80"
    depends_on:
      - laravel-app
    volumes:
      - .:/home/source/main
    networks:
      - app-network

volumes:
  mysql-volumes-recipes:
    driver: local
networks:
  app-network:
    driver: bridge
    name: app-network
Enter fullscreen mode Exit fullscreen mode

Grocery store

services:
  db:
    image: mysql:latest
    container_name: mysql-grosery-store
    ports:
      - "3308:3306"
    volumes:
      - mysql-volumes-grosery-store:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: laravel_grocery
    networks:
      - app-network

  laravel-app:
    build:
      context: ./docker/php
    container_name: micro-grosery-store
    volumes:
      - .:/home/source/main
    working_dir: /home/source/main
    networks:
      - app-network

  nginx:
    build:
      context: ./docker/nginx
    container_name: todo-nginx-grosery-store
    ports:
      - "8002:80"
    depends_on:
      - laravel-app
    volumes:
      - .:/home/source/main
    networks:
      - app-network

volumes:
  mysql-volumes-grosery-store:
    driver: local
networks:
  app-network:
    driver: bridge
    name: app-network
Enter fullscreen mode Exit fullscreen mode

Then for each services:
docker compose up

Remember that each microservice works with yours own databases settings, you must access to into console and set access for databases.

Important: You must set your databases into your container, allow grant access, create user etc.

In this part is moment lets to code and setting. In this case, api-manager handle all request from clients, then send this request to each microservice(cousine, grocery-store, recipes) some like this

<?php

namespace App\Http\Controllers;

use App\Jobs\Cousine\CreateCousineJob;
use App\Jobs\Cousine\DeleteCousineJob;
use App\Jobs\Cousine\FindOneCousineJob;
use App\Jobs\Cousine\GetCousineJob;
use App\Jobs\Cousine\UpdateCousineJob;
use App\Models\Cousine;
use App\Services\rabbitmq\ConsumeMQ;
use App\Services\rabbitmq\SendMQ;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class CousineController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $response = Http::get('http://nginx/api/cousine');
        if ($response->successful()) {
            return response()->json($response->json(), 200);
        } else {
            return response()->json(['error' => 'No se pudieron obtener los datos'], $response->status());
        }
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        SendMQ::handle('send_store_cousine_queue','send_store_cousine_exchange','cousine_key', $request->all());
        SendMQ::close();

        return response()->json(['message' => 'Mensaje enviado correctamente']);
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        $response = Http::get('http://nginx/api/cousine/'.$id);
        if ($response->successful()) {
            return response()->json($response->json(), 200);
        } else {
            return response()->json(['error' => 'No se pudieron obtener los datos'], $response->status());
        }
    }




    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        $payload = [
            'order_number' => $request->order_number,
            'status_id' => $request->status_id,
            'id' => $id
        ];

        // Despachar el job para actualizar una receta específica
        SendMQ::handle('send_update_cousine_queue','send_update_cousine_exchange','cousine_key_update', $payload);
        SendMQ::close();
    }

    public function updateWithIngredientsUpdated(Request $request, string $id)
    {
        $payload = [
            'order_number' => $request->order_number,
            'status_id' => $request->status_id,
            'recipe_id' => $request->recipe_id,
            'id' => $id
        ];

        // Despachar el job para actualizar una receta específica
        SendMQ::handle('send_update_cousine_ingredient_queue','send_update_cousine_ingredient_exchange','cousine_key_update_ingredient', $payload);
        SendMQ::close();
    }



    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        DeleteCousineJob::dispatch($id);
        return response()->json(['message' => 'Cousine deleted'], 200);
    }
}
Enter fullscreen mode Exit fullscreen mode

Yes I know ... You question is ¿Where I get the SendMQ class?
In this example, I wanna show, how can use strategies when using microservices, doesn't necessary that you implement everything with rabbitMQ or Kafka or sqs, events directs. You can combine depends of you needed. For this example, I was create a service folder and with 2 services : ConsumeMQ and SendMQ, each one handle they responsabilities. Here leave a code:
SendMQ:class

<?php

namespace App\Services\rabbitmq;

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

class SendMQ
{
    protected static $connection;
    protected static $channel;

    /**
     * Establece la conexión a RabbitMQ y declara el canal de conexión.
     *
     * Si ya existe una conexión, no hace nada.
     *
     * @return void
     */
    protected static function connect()
    {
        if (is_null(self::$connection)) {
            self::$connection = new AMQPStreamConnection(
                env('AMQP_HOST', 'localhost'),
                env('AMQP_PORT', 5672),
                env('AMQP_USER', 'bryan'),
                env('AMQP_PASSWORD', '123456'),
                env('AMQP_VHOST', '/')
            );
            self::$channel = self::$connection->channel();
        }
    }

    /**
     * Envia un mensaje a la cola de mensajeria indicada.
     *
     * @param string $queueName   Nombre de la cola de mensajeria.
     * @param string $exchangeName Nombre de la exchange de mensajeria.
     * @param string $routingKey   Clave de routing para la cola especificada.
     * @param mixed  $data         Contenido del mensaje.
     */
    public static function handle($queueName, $exchangeName, $routingKey, $data)
    {
        self::connect();
        self::$channel->exchange_declare($exchangeName, 'direct', false, false, false);
        self::$channel->queue_declare($queueName, false, false, false, false);
        self::$channel->queue_bind($queueName, $exchangeName, $routingKey);
        $msg = new AMQPMessage(json_encode($data));
        self::$channel->basic_publish($msg, $exchangeName, $routingKey);
    }

    /**
     * Cierra la conexi n a RabbitMQ.
     *
     * Este m todo debe ser llamado cuando se haya terminado
     * de enviar mensajes y se desee liberar los recursos
     * relacionados con la conexi n.
     */
    public static function close()
    {
        if (self::$channel) {
            self::$channel->close();
        }
        if (self::$connection) {
            self::$connection->close();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

ConsumeMQ

<?php

namespace App\Services\rabbitmq;

use App\Http\Controllers\CousineController;
use Illuminate\Support\Facades\Log;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

class ConsumeMQ
{
    protected static $connection;
    protected static $channel;
    protected static $receivedData = []; // Variable estática para almacenar datos

    protected static function connect()
    {
        if (is_null(self::$connection)) {
            self::$connection = new AMQPStreamConnection(
                env('AMQP_HOST', 'localhost'),
                env('AMQP_PORT', 5672),
                env('AMQP_USER', 'bryan'),
                env('AMQP_PASSWORD', '123456'),
                env('AMQP_VHOST', '/')
            );
            self::$channel = self::$connection->channel();
        }
    }

    public static function handle($queueName)
    {
        self::connect();

        // Declarar la cola
        self::$channel->queue_declare($queueName, false, false, false, false);

        // Callback para manejar los mensajes recibidos
        $msgCallback = function (AMQPMessage $msg) {
            Log::info(' [x] Recibiendo: ' . $msg->body);

            // Almacenar el mensaje recibido
            $list = json_decode($msg->body, true);
            CousineController::setDataList($list);
        };

        // Configurar el consumidor
        self::$channel->basic_consume($queueName, '', false, true, false, false, $msgCallback);

        // Mantener el consumidor activo hasta que se detenga
        echo " [*] Waiting for messages. To exit press CTRL+C\n";
        while (self::$channel->is_consuming()) {
            self::$channel->wait();
        }

        return self::$receivedData; // Retornar los datos recibidos
    }

    public static function getReceivedData()
{
    return self::$receivedData;
}

    public static function close()
    {
        if (self::$channel) {
            self::$channel->close();
        }
        if (self::$connection) {
            self::$connection->close();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

then let to configure our exchange. On *config * folder you must create a file with a name that identify you connection with rabbitmq. For this case : amqp.php. The context is next


return [

    /*
    |--------------------------------------------------------------------------
    | Define which configuration should be used
    |--------------------------------------------------------------------------
    */

    'use' => env('AMQP_ENV', 'production'),

    /*
    |--------------------------------------------------------------------------
    | AMQP properties separated by key
    |--------------------------------------------------------------------------
    */

    'properties' => [

        'production' => [
            'host'                  => env('AMQP_HOST', 'localhost'),
            'port'                  => env('AMQP_PORT', 5672),
            'username'              => env('AMQP_USER', 'myuser'),
            'password'              => env('AMQP_PASSWORD', 'mypass'),
            'vhost'                 => env('AMQP_VHOST', '/'),
            'connect_options'       => [],
            'ssl_options'           => [],

            'exchange'              => 'amq.topic',
            'exchange_type'         => 'topic',
            'exchange_passive'      => false,
            'exchange_durable'      => true,
            'exchange_auto_delete'  => false,
            'exchange_internal'     => false,
            'exchange_nowait'       => false,
            'exchange_properties'   => [],

            'queue_force_declare'   => false,
            'queue_passive'         => false,
            'queue_durable'         => true,
            'queue_exclusive'       => false,
            'queue_auto_delete'     => false,
            'queue_nowait'          => false,
            'queue_properties'      => ['x-ha-policy' => ['S', 'all']],

            'consumer_tag'          => '',
            'consumer_no_local'     => false,
            'consumer_no_ack'       => false,
            'consumer_exclusive'    => false,
            'consumer_nowait'       => false,
            'consumer_properties'   => [],
            'timeout'               => 0,
            'persistent'            => false,
            'publish_timeout'       => 0, // Only applicable when a publish is marked as mandatory
            'qos'                   => false,
            'qos_prefetch_size'     => 0,
            'qos_prefetch_count'    => 1,
            'qos_a_global'          => false
        ],

    ],

];
Enter fullscreen mode Exit fullscreen mode

With this, everything, we are already for work...

*I wait that being usefull for you .. remember leave you comment or ask. You always are welcome *

Fullcode link Here

Follow me : @brngranado at Instagram, X, LinkedIn, threads.

Image of Datadog

The Future of AI, LLMs, and Observability on Google Cloud

Datadog sat down with Google’s Director of AI to discuss the current and future states of AI, ML, and LLMs on Google Cloud. Discover 7 key insights for technical leaders, covering everything from upskilling teams to observability best practices

Learn More

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay