Backend 8 min de lecture

Le Starter Pack Microservices Laravel 2026 : Architecture, Docker & RabbitMQ

#laravel#microservices#docker#rabbitmq#architecture#devops

Tutoriel radical pour monter une architecture microservices Laravel propre : Monorepo, Docker Compose, RabbitMQ, Gateway JWT et Events asynchrones. Code copy/paste 'production-ready'.

Tu es développeur Laravel et tu entends parler de microservices. Tu veux voir du concret, pas de la théorie fumeuse. Tu veux savoir comment on connecte vraiment 4 applications Laravel entre elles sans que ça devienne un plat de spaghettis.

Ce guide est un Starter Pack. Il te donne une base saine, testée et reproductible pour démarrer un projet distribué. Nous allons construire une application "E-commerce" minimaliste (MVP) mais architecturée correctement.

1. Pourquoi (et quand pas) Microservices ?

Pourquoi ? Pour l'indépendance de déploiement et l'organisation des équipes. Si tu as 30 développeurs qui se marchent dessus sur le même repo, découper le monolithe permet à la "Team Checkout" de déployer sans attendre la "Team Catalogue".

Quand NE PAS le faire ? Si tu es seul ou une petite équipe (< 5). La complexité opérationnelle (logging distribué, cohérence éventuelle, latence réseau) va te tuer. Si ton problème est la performance, fais un Monolithe Modulaire bien cachée. Si ton problème est l'organisation, lis la suite.

2. Architecture Cible

On part sur un pattern classique : API Gateway + Backend for Frontend (BFF).

                    ┌──────────────┐
                    │  Client Web  │
                    └──────┬───────┘
                           │ HTTPS (JSON + JWT)
                           ▼
                  ┌──────────────────┐
                  │   API GATEWAY    │ (Port 8000)
                  │ Auth & Routing   │
                  └────┬────┬────┬───┘
                       │    │    │
       ┌───────────────┘    │    └────────────────┐
       │ HTTP               │ HTTP                │ HTTP
       ▼                    ▼                     ▼
┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│   SERVICE   │      │   SERVICE   │      │   SERVICE   │
│    USERS    │      │   CATALOG   │      │   ORDERS    │
│ (Port 8001) │      │ (Port 8002) │      │ (Port 8003) │
└──────┬──────┘      └──────┬──────┘      └──────┬──────┘
       │ DB                 │ DB                 │ DB + Publie
       ▼                    ▼                    ▼
   (db_users)          (db_catalog)          (db_orders)
                                                 │
                                                 │ "OrderCreated" (Async)
                                                 ▼
                                          ┌──────────────┐
                                          │   RABBITMQ   │
                                          └──────┬───────┘
                                                 │ Consomme
                                                 ▼
                                          ┌──────────────┐
                                          │   SERVICE    │
                                          │ NOTIFICATION │
                                          └──────────────┘

Les Flux :

  1. Synchrone (Bleu) : Le client parle uniquement à la Gateway. La Gateway vérifie le JWT, puis proxyfie la requête vers le service adéquat via HTTP interne.
  2. Asynchrone (Rouge) : Quand une commande est créée, orders-service ne parle PAS à notification-service. Il publie un message sur RabbitMQ. notification-service se réveille, consomme le message et envoie un email.

3. Arborescence & Conventions (Monorepo)

Un monorepo simplifie la gestion de l'infra locale et le partage de code (DTOs, Enums).

my-microservices/
├── apps/               # Le code de tes services
│   ├── gateway/        # Laravel
│   ├── users/          # Laravel
│   ├── catalog/        # Laravel
│   ├── orders/         # Laravel
│   └── notifications/  # Laravel
├── docker/             # Config Docker spécifique (Nginx, PHP...)
├── docker-compose.yml  # L'orchestration locale
├── Makefile            # Tes raccourcis vitaux
└── README.md

Convention de nommage réseau : Dans Docker Compose, les hostnames seront les noms des services : http://users, http__catalog, etc.

4. Docker Compose : L'Infra complète

C'est la pièce maîtresse. Copie-colle ça dans docker-compose.yml à la racine. On utilise une image Postgres unique pour l'économie de ressources, mais avec 3 bases de données distinctes créées au démarrage.

version: '3.8'

services:
  # --- INFRASTRUCTURE ---
  
  postgres:
    image: postgres:15-alpine
    container_name: ms_postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: root
      # Script d'init pour créer les multiples DBs
      POSTGRES_MULTIPLE_DATABASES: "users_db,catalog_db,orders_db,notifications_db"
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./docker/postgres/init:/docker-entrypoint-initdb.d
    networks:
      - backend

  rabbitmq:
    image: rabbitmq:3-management-alpine
    container_name: ms_rabbitmq
    ports:
      - "5672:5672"   # AMQP protocol
      - "15672:15672" # Dashboard Management (User: guest/guest)
    networks:
      - backend
    healthcheck:
        test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
        interval: 10s
        timeout: 10s
        retries: 5

  # --- SERVICES MÉTIERS ---

  gateway:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
      args:
        PATH_TO_APP: apps/gateway
    container_name: ms_gateway
    ports:
      - "8000:8000"
    volumes:
      - ./apps/gateway:/var/www/html
    environment:
      APP_PORT: 8000
      DB_HOST: postgres
      RABBITMQ_HOST: rabbitmq
    depends_on:
      - postgres
      - rabbitmq
    command: php artisan serve --host=0.0.0.0 --port=8000
    networks:
      - backend

  users:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
      args:
        PATH_TO_APP: apps/users
    container_name: ms_users
    volumes:
      - ./apps/users:/var/www/html
    environment:
      APP_PORT: 8001
      DB_HOST: postgres
      DB_DATABASE: users_db
    command: php artisan serve --host=0.0.0.0 --port=8001
    networks:
      - backend

  catalog:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
      args:
        PATH_TO_APP: apps/catalog
    container_name: ms_catalog
    volumes:
      - ./apps/catalog:/var/www/html
    environment:
      APP_PORT: 8002
      DB_HOST: postgres
      DB_DATABASE: catalog_db
    command: php artisan serve --host=0.0.0.0 --port=8002
    networks:
      - backend

  orders:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
      args:
        PATH_TO_APP: apps/orders
    container_name: ms_orders
    volumes:
      - ./apps/orders:/var/www/html
    environment:
      APP_PORT: 8003
      DB_HOST: postgres
      DB_DATABASE: orders_db
      RABBITMQ_HOST: rabbitmq
    depends_on:
      - rabbitmq
    command: php artisan serve --host=0.0.0.0 --port=8003
    networks:
      - backend

  notifications:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
      args:
        PATH_TO_APP: apps/notifications
    container_name: ms_notifications
    volumes:
      - ./apps/notifications:/var/www/html
    environment:
      DB_HOST: postgres
      DB_DATABASE: notifications_db
      RABBITMQ_HOST: rabbitmq
    depends_on:
      - rabbitmq
    # Ce service ne sert pas de HTTP, il consomme des queues
    command: php artisan rabbitmq:consume orders.created --tries=3
    networks:
      - backend

steps:
  backend:
    driver: bridge

volumes:
  pg_data:

Tip Infra : Pour que PostgreSQL crée automatiquement les bases, ajoutez ce script bash docker/postgres/init/create-multiple-postgresql-databases.sh (Google it: "Postgres docker multiple databases") dans votre repo. Sans ça, seule la DB par défaut est créée.

5. Création des Apps Laravel

Pas de magie, on utilise Composer.

mkdir apps
cd apps

# Création des 5 projets
for service in gateway users catalog orders notifications; do
    composer create-project laravel/laravel $service
    # Tip: Installez tout de suite le driver RabbitMQ
    cd $service
    composer require vladimir-yuldashev/laravel-queue-rabbitmq
    cd ..
done

Configuration des `.env` (Exemple pour `orders`)

Dans apps/orders/.env :

APP_NAME=OrdersService
APP_URL=http://ms_orders:8003

DB_CONNECTION=pgsql
DB_HOST=ms_postgres # Nom du container ou 'postgres' selon ton network
DB_PORT=5432
DB_DATABASE=orders_db
DB_USERNAME=postgres
DB_PASSWORD=root

QUEUE_CONNECTION=rabbitmq
RABBITMQ_HOST=ms_rabbitmq
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_QUEUE=orders.created

6. Implémenter la Gateway (HTTP Proxy)

La Gateway est "bête". Elle ne contient pas de logique métier. Elle route. Dans apps/gateway, on utilise le client HTTP de Laravel.

`routes/api.php` de la Gateway

use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Http;

Route::middleware('api')->group(function () {
    
    // Auth Proxy (vers Users Service qui gère l'auth pour de vrai, ou géré ici)
    // Pour ce tuto, on proxyfie tout.
    
    // USERS Service
    Route::get('/users/{id}', function ($id) {
        return Http::get("http://ms_users:8001/api/users/{$id}")->json();
    });

    // CATALOG Service
    Route::get('/products', function () {
        return Http::get("http://ms_catalog:8002/api/products")->json();
    });

    // ORDERS Service
    Route::post('/orders', function () {
        // Validation basique d'entrée ici
        $data = request()->all();
        
        // Appel au service
        $response = Http::post("http://ms_orders:8003/api/orders", $data);
        
        return response()->json($response->json(), $response->status());
    });
});

Bonus Sécurité : Ajoute un middleware CheckJwt global sur la Gateway. C'est le seul point d'entrée public. Les services internes (Users, Catalog...) ne doivent PAS être exposés sur des ports publics en prod (en Docker Compose, ils ne le sont pas, seul 8000 est mappé).

7. L'Événement Asynchrone : OrderCreated

Quand une commande est créée dans orders, on veut notifier notifications SANS bloquer la réponse HTTP.

Dans `apps/orders`

  1. Créer la commande via Controller.
  2. Publier un event sur la queue.
// apps/orders/app/Http/Controllers/OrderController.php
public function store(Request $request)
{
    $order = Order::create($request->all());

    // On publie le message brut ou via une classe Job/Event
    // Ici version simple avec le package RabbitMQ
    Queue::pushRaw(json_encode([
        'event' => 'order.created',
        'data' => $order->toArray()
    ]), 'orders.created');

    return response()->json($order, 201);
}

Dans `apps/notifications` (Le Consommateur)

Ce service n'a pas besoin de controllers complexes. Il a besoin d'un Job qui traite le message.

  1. Créer un Job ProcessOrderCreated.
  2. Configurer le worker pour écouter la queue orders.created.
// apps/notifications/app/Jobs/ProcessOrderCreated.php
public function handle()
{
    // $this->job->getRawBody() contient le JSON
    $payload = json_decode($this->job->getRawBody(), true);
    
    Log::info("📧 Email envoyé pour la commande #" . $payload['data']['id']);
    
    // Ici: Mail::to($payload['data']['email'])...
}

Commande de lancement : C'est la commande définie dans le docker-compose.yml -> php artisan rabbitmq:consume orders.created.

8. Le Makefile : Ton meilleur ami

Ne tape jamais docker-compose exec gateway php artisan.... Utilise un Makefile.

# Makefile
setup:
	@echo "Installation des dépendances..."
	@docker run --rm -v $(PWD)/apps/gateway:/app composer install
	@docker run --rm -v $(PWD)/apps/users:/app composer install
	@docker run --rm -v $(PWD)/apps/catalog:/app composer install
	@docker run --rm -v $(PWD)/apps/orders:/app composer install
	@docker run --rm -v $(PWD)/apps/notifications:/app composer install
	@echo "Démarrage..."
	@make up

up:
	docker-compose up -d

down:
	docker-compose down

logs:
	docker-compose logs -f

test:
	docker-compose exec gateway php artisan test
	docker-compose exec users php artisan test

9. Pièges Classiques & Checklist de Prod

  1. Latence réseau : Chaque appel HTTP interne ajoute 20-50ms. N'enchaîne pas 10 appels (N+1 problem distribué). Utilise RabbitMQ pour tout ce qui n'est pas nécessaire à l'affichage immédiat.
  2. Transaction distribuée : Si Order est créé mais que le paiement plante dans PaymentService, tu as une commande fantôme. Solution : Saga Pattern (ou simplement, ne sépare pas Commandes et Paiements au début).
  3. Logs Illisibles : Utilise un Correlation-ID.
    • La Gateway génère un UUID X-Correlation-ID.
    • Elle le passe dans les headers à Users, Orders.
    • Chaque service loggue cet ID.
    • Poussez les logs vers une stack ELK ou Loki.

10. Bonus : Observabilité (OpenTelemetry)

Pour savoir ça plante, installe OpenTelemetry. En PHP, ça s'installe via PECL ou des packages composer.

Ajoute à tes Services : composer require open-telemetry/opentelemetry

Et configure tes traces pour être envoyées vers Jaeger (ajoute un container Jaeger à ton docker-compose).


Ce starter pack est minimaliste mais fonctionnel. Il respecte la séparation des concerns et introduit l'asynchronisme dès le début, indispensable pour scaler. À toi de jouer : clone, make setup, et code tes features !