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.