Architecture Microservices avec API Platform

Guide pratique pour créer une architecture microservices robuste avec API Platform. Retour d'expérience sur des projets réels à Toulouse.

Architecture Microservices avec API Platform

L'architecture microservices est devenue incontournable pour les applications modernes. Avec API Platform, créer et orchestrer des microservices devient plus accessible. Voici mon retour d'expérience après avoir implémenté cette architecture sur plusieurs projets clients.

Pourquoi API Platform pour les Microservices ?

Avantages Clés

  • Génération automatique d'APIs REST et GraphQL
  • Documentation OpenAPI intégrée
  • Validation native des données
  • Sérialisation avancée
  • Tests automatisés

Architecture Type

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Gateway API   │    │  User Service   │    │ Product Service │
│  (API Platform) │◄──►│ (API Platform) │    │ (API Platform) │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   PostgreSQL    │    │     Redis       │    │     MySQL       │
└─────────────────┘    └─────────────────┘    └─────────────────┘

Configuration d'un Microservice

1. Structure du Projet

microservice-user/
├── config/
│   ├── packages/
│   │   └── api_platform.yaml
│   └── services.yaml
├── src/
│   ├── Entity/
│   │   └── User.php
│   ├── Repository/
│   └── EventSubscriber/
├── docker-compose.yml
└── Dockerfile

2. Configuration API Platform

# config/packages/api_platform.yaml
api_platform:
    title: 'User Microservice API'
    version: '1.0.0'
    description: 'Microservice de gestion des utilisateurs'
    
    mapping:
        paths: ['%kernel.project_dir%/src/Entity']
    
    patch_formats:
        json: ['application/merge-patch+json']
    
    swagger:
        versions: [3]
        api_keys:
            apiKey:
                name: Authorization
                type: header

3. Entité Utilisateur

<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity]
#[ApiResource(
    operations: [
        new GetCollection(),
        new Get(),
        new Post(
            validationContext: ['groups' => ['user:create']]
        ),
        new Put(
            security: "is_granted('ROLE_ADMIN') or object.owner == user"
        ),
        new Delete(
            security: "is_granted('ROLE_ADMIN')"
        )
    ],
    normalizationContext: ['groups' => ['user:read']],
    denormalizationContext: ['groups' => ['user:write']]
)]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 180, unique: true)]
    #[Assert\NotBlank(groups: ['user:create'])]
    #[Assert\Email]
    private ?string $email = null;

    #[ORM\Column]
    private array $roles = [];

    #[ORM\Column]
    #[Assert\NotBlank(groups: ['user:create'])]
    #[Assert\Length(min: 8)]
    private ?string $password = null;

    // Getters et setters...
}

Communication Inter-Services

1. Client HTTP Personnalisé

<?php

namespace App\Service;

use Symfony\Contracts\HttpClient\HttpClientInterface;

class ProductServiceClient
{
    public function __construct(
        private HttpClientInterface $httpClient,
        private string $productServiceUrl
    ) {}

    public function getProduct(int $id): array
    {
        $response = $this->httpClient->request('GET', 
            $this->productServiceUrl . '/api/products/' . $id,
            [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->getToken(),
                    'Content-Type' => 'application/json'
                ]
            ]
        );

        return $response->toArray();
    }

    public function createProduct(array $data): array
    {
        $response = $this->httpClient->request('POST',
            $this->productServiceUrl . '/api/products',
            [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->getToken(),
                    'Content-Type' => 'application/json'
                ],
                'json' => $data
            ]
        );

        return $response->toArray();
    }

    private function getToken(): string
    {
        // Logique d'authentification JWT
        return $this->jwtManager->create($this->getUser());
    }
}

2. Event-Driven Architecture

<?php

namespace App\EventSubscriber;

use ApiPlatform\Symfony\EventListener\EventPriorities;
use App\Entity\User;
use App\Message\UserCreatedMessage;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Messenger\MessageBusInterface;

class UserSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private MessageBusInterface $messageBus
    ) {}

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::VIEW => ['publishUserCreated', EventPriorities::POST_WRITE],
        ];
    }

    public function publishUserCreated(ViewEvent $event): void
    {
        $user = $event->getControllerResult();
        $method = $event->getRequest()->getMethod();

        if (!$user instanceof User || Request::METHOD_POST !== $method) {
            return;
        }

        // Publier un message pour notifier les autres services
        $this->messageBus->dispatch(new UserCreatedMessage(
            $user->getId(),
            $user->getEmail(),
            $user->getRoles()
        ));
    }
}

Déploiement avec Docker

1. Dockerfile

FROM php:8.2-fpm-alpine

# Installation des extensions PHP
RUN apk add --no-cache \
    postgresql-dev \
    zip \
    unzip \
    git

RUN docker-php-ext-install pdo pdo_pgsql opcache

# Installation de Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Configuration de l'application
WORKDIR /var/www/html
COPY . .

RUN composer install --no-dev --optimize-autoloader
RUN php bin/console cache:clear --env=prod

EXPOSE 9000
CMD ["php-fpm"]

2. Docker Compose

version: '3.8'

services:
  user-service:
    build: .
    container_name: user_microservice
    environment:
      DATABASE_URL: postgresql://user:password@postgres:5432/users_db
      MESSENGER_TRANSPORT_DSN: redis://redis:6379/messages
    depends_on:
      - postgres
      - redis
    ports:
      - "8001:9000"

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: users_db
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf

volumes:
  postgres_data:

Monitoring et Observabilité

1. Health Checks

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

class HealthController extends AbstractController
{
    #[Route('/health', methods: ['GET'])]
    public function health(): JsonResponse
    {
        return $this->json([
            'status' => 'UP',
            'service' => 'user-microservice',
            'version' => '1.0.0',
            'timestamp' => time()
        ]);
    }

    #[Route('/health/detailed', methods: ['GET'])]
    public function detailedHealth(): JsonResponse
    {
        // Vérifications détaillées
        $checks = [
            'database' => $this->checkDatabase(),
            'redis' => $this->checkRedis(),
            'external_services' => $this->checkExternalServices()
        ];

        $status = array_reduce($checks, 
            fn($carry, $check) => $carry && $check, true) ? 'UP' : 'DOWN';

        return $this->json([
            'status' => $status,
            'checks' => $checks
        ]);
    }
}

Bonnes Pratiques

1. Sécurité

  • JWT pour l'authentification inter-services
  • HTTPS obligatoire
  • Rate limiting sur les endpoints
  • Validation stricte des données

2. Performance

  • Cache Redis pour les données fréquentes
  • Pagination systématique
  • Compression des réponses
  • CDN pour les assets statiques

3. Resilience

  • Circuit breaker pour les appels externes
  • Retry avec backoff exponentiel
  • Timeout configurables
  • Fallback mechanisms

Conclusion

L'architecture microservices avec API Platform offre une solution robuste et scalable. La courbe d'apprentissage est compensée par les bénéfices en termes de maintenabilité et de performance.

Points clés à retenir :

  • Commencer petit et évoluer progressivement
  • Investir dans le monitoring dès le début
  • Automatiser les déploiements
  • Former l'équipe aux concepts distribués

Vous souhaitez implémenter une architecture microservices ? En tant qu'expert API Platform à Toulouse, je peux vous accompagner dans cette transition. Contactez-moi pour échanger sur votre projet.