TL;DR
Construisez des API multi-protocoles en séparant la logique métier des couches de protocole. Créez une couche de domaine partagée, puis ajoutez des adaptateurs REST, GraphQL et gRPC par-dessus. Modern PetstoreAPI démontre cette architecture avec des modèles de données cohérents sur les trois protocoles.
Introduction
Votre API sert les clients web, les applications mobiles et les microservices internes. Les clients web veulent REST pour la simplicité. Les applications mobiles veulent GraphQL pour réduire le transfert de données. Les microservices veulent gRPC pour la performance. Devez-vous construire trois API distinctes ?
Non. Vous construisez une API avec trois couches de protocole. La logique métier reste la même. Seuls les adaptateurs de protocole changent. C'est l'architecture d'API multi-protocole.
Modern PetstoreAPI implémente REST, GraphQL et gRPC à partir d'un cœur partagé. La même logique de magasin d'animaux de compagnie sert les trois protocoles avec un comportement cohérent.
Dans ce guide, vous apprendrez à architecturer des API multi-protocoles, à implémenter des couches de protocole et à assurer la cohérence entre les protocoles en utilisant Modern PetstoreAPI comme référence.
Architecture multi-protocole
Les API multi-protocoles séparent les préoccupations en couches.
Architecture en couches
┌─────────────────────────────────────────┐
│ Couche de protocole (REST/GraphQL/gRPC) │
├─────────────────────────────────────────┤
│ Couche d'application (Cas d'utilisation) │
├─────────────────────────────────────────┤
│ Couche de domaine (Logique métier) │
├─────────────────────────────────────────┤
│ Couche de données (Base de données, Cache) │
└─────────────────────────────────────────┘
Couche de protocole : Gère les requêtes HTTP, GraphQL, les appels gRPC Couche d'application : Orchestre les cas d'utilisation (créer un animal, passer une commande) Couche de domaine : Règles métier (validation, calculs) Couche de données : Persistance (base de données, cache)
Principes clés
1. Cœur agnostique du protocole
La logique métier ne connaît pas HTTP, GraphQL ou gRPC. Elle fonctionne avec des objets de domaine.
2. Adaptateurs de protocole minces
Les couches de protocole traduisent entre les formats de protocole et les objets de domaine. Elles ne contiennent pas de logique métier.
3. Modèles de données partagés
Tous les protocoles utilisent les mêmes modèles de domaine en interne, garantissant la cohérence.
4. Déploiement indépendant
Chaque protocole peut être déployé séparément si nécessaire.
Couche de domaine partagée
La couche de domaine contient la logique métier partagée par tous les protocoles.
Modèles de domaine
// Modèle de domaine (agnostique du protocole)
class Pet {
id: string;
name: string;
species: Species;
status: PetStatus;
price: number;
constructor(data: PetData) {
this.validate(data);
Object.assign(this, data);
}
validate(data: PetData): void {
if (!data.name || data.name.length < 2) {
throw new ValidationError('Name must be at least 2 characters');
}
if (data.price < 0) {
throw new ValidationError('Price cannot be negative');
}
}
adopt(userId: string): Order {
if (this.status !== PetStatus.AVAILABLE) {
throw new BusinessError('Pet is not available for adoption');
}
this.status = PetStatus.ADOPTED;
return new Order({
petId: this.id,
userId,
total: this.price
});
}
}
Ce modèle fonctionne pour REST, GraphQL et gRPC. La validation et les règles métier sont les mêmes.
Cas d'utilisation
// Cas d'utilisation (agnostique du protocole)
class AdoptPetUseCase {
constructor(
private petRepository: PetRepository,
private orderRepository: OrderRepository
) {}
async execute(petId: string, userId: string): Promise<Order> {
const pet = await this.petRepository.findById(petId);
if (!pet) {
throw new NotFoundError('Pet not found');
}
const order = pet.adopt(userId);
await this.petRepository.save(pet);
await this.orderRepository.save(order);
return order;
}
}
Ce cas d'utilisation fonctionne qu'il soit appelé depuis REST, GraphQL ou gRPC.
Couche de protocole REST
La couche REST traduit les requêtes HTTP en opérations de domaine.
Contrôleur REST
// Adaptateur REST
class PetsController {
constructor(private adoptPetUseCase: AdoptPetUseCase) {}
async adoptPet(req: Request, res: Response): Promise<void> {
try {
const { petId } = req.params;
const { userId } = req.body;
const order = await this.adoptPetUseCase.execute(petId, userId);
res.status(201).json({
id: order.id,
petId: order.petId,
userId: order.userId,
total: order.total,
status: order.status
});
} catch (error) {
this.handleError(error, res);
}
}
private handleError(error: Error, res: Response): void {
if (error instanceof NotFoundError) {
res.status(404).json({
type: 'https://petstoreapi.com/errors/not-found',
title: 'Not Found',
status: 404,
detail: error.message
});
} else if (error instanceof ValidationError) {
res.status(400).json({
type: 'https://petstoreapi.com/errors/validation-error',
title: 'Validation Error',
status: 400,
detail: error.message
});
} else {
res.status(500).json({
type: 'https://petstoreapi.com/errors/internal-error',
title: 'Internal Server Error',
status: 500
});
}
}
}
Routes REST
app.post('/v1/pets/:petId/adopt', (req, res) =>
petsController.adoptPet(req, res)
);
Points de terminaison REST de Modern PetstoreAPI
Couche de protocole GraphQL
La couche GraphQL traduit les requêtes GraphQL en opérations de domaine.
Schéma GraphQL
type Pet {
id: ID!
name: String!
species: Species!
status: PetStatus!
price: Float!
}
type Order {
id: ID!
petId: ID!
userId: ID!
total: Float!
status: OrderStatus!
}
type Mutation {
adoptPet(petId: ID!, userId: ID!): Order!
}
Résolveur GraphQL
// Adaptateur GraphQL
const resolvers = {
Mutation: {
adoptPet: async (
_parent: any,
args: { petId: string; userId: string },
context: Context
): Promise<Order> => {
try {
return await context.adoptPetUseCase.execute(
args.petId,
args.userId
);
} catch (error) {
throw new GraphQLError(error.message, {
extensions: {
code: error instanceof NotFoundError ? 'NOT_FOUND' :
error instanceof ValidationError ? 'BAD_USER_INPUT' :
'INTERNAL_SERVER_ERROR'
}
});
}
}
}
};
Requête GraphQL
mutation {
adoptPet(
petId: "019b4132-70aa-764f-b315-e2803d882a24"
userId: "user-123"
) {
id
total
status
}
}
Schéma GraphQL de Modern PetstoreAPI
Couche de protocole gRPC
La couche gRPC traduit les messages Protocol Buffer en opérations de domaine.
Définition du tampon de protocole
syntax = "proto3";
package petstore.v1;
service PetService {
rpc AdoptPet(AdoptPetRequest) returns (AdoptPetResponse);
}
message AdoptPetRequest {
string pet_id = 1;
string user_id = 2;
}
message AdoptPetResponse {
string order_id = 1;
string pet_id = 2;
string user_id = 3;
double total = 4;
string status = 5;
}
Implémentation du service gRPC
// Adaptateur gRPC
class PetServiceImpl implements IPetService {
constructor(private adoptPetUseCase: AdoptPetUseCase) {}
async adoptPet(
call: ServerUnaryCall<AdoptPetRequest, AdoptPetResponse>,
callback: sendUnaryData<AdoptPetResponse>
): Promise<void> {
try {
const { petId, userId } = call.request;
const order = await this.adoptPetUseCase.execute(petId, userId);
callback(null, {
orderId: order.id,
petId: order.petId,
userId: order.userId,
total: order.total,
status: order.status
});
} catch (error) {
callback({
code: error instanceof NotFoundError ? status.NOT_FOUND :
error instanceof ValidationError ? status.INVALID_ARGUMENT :
status.INTERNAL,
message: error.message
});
}
}
}
Service gRPC de Modern PetstoreAPI
Comment Modern PetstoreAPI implémente le multi-protocole
Modern PetstoreAPI démontre l'architecture multi-protocole avec des exemples concrets.
Vue d'ensemble de l'architecture
Modern PetstoreAPI
├── Couche de domaine
│ ├── Animal de compagnie (entité)
│ ├── Commande (entité)
│ └── Cas d'utilisation
│ ├── CréerAnimal
│ ├── AdopterAnimal
│ └── PasserCommande
├── Couche REST
│ ├── /v1/pets
│ ├── /v1/orders
│ └── Spécification OpenAPI 3.2
├── Couche GraphQL
│ ├── Résolveurs de requêtes
│ ├── Résolveurs de mutations
│ └── Schéma GraphQL
└── Couche gRPC
├── ServiceAnimal
├── ServiceCommande
└── Définitions .proto
Modèles de données cohérents
Tous les protocoles renvoient les mêmes données :
REST :
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"species": "CAT"
}
GraphQL :
{
"data": {
"pet": {
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"species": "CAT"
}
}
}
gRPC :
{
pet_id: "019b4132-70aa-764f-b315-e2803d882a24"
name: "Fluffy"
species: CAT
}
Mêmes données, formats différents.
Validation partagée
La validation a lieu dans la couche de domaine, de sorte que tous les protocoles appliquent les mêmes règles :
// Validation de domaine (partagée)
if (name.length < 2) {
throw new ValidationError('Name must be at least 2 characters');
}
REST renvoie :
{
"type": "https://petstoreapi.com/errors/validation-error",
"status": 400,
"detail": "Name must be at least 2 characters"
}
GraphQL renvoie :
{
"errors": [{
"message": "Name must be at least 2 characters",
"extensions": {"code": "BAD_USER_INPUT"}
}]
}
gRPC renvoie :
code: INVALID_ARGUMENT
message: "Name must be at least 2 characters"
Même validation, formats d'erreur spécifiques au protocole.
Tester les API multi-protocoles avec Apidog
Apidog prend en charge le test des trois protocoles.
Tester le point de terminaison REST
POST https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/adopt
Content-Type: application/json
{
"userId": "user-123"
}
Tester la mutation GraphQL
mutation {
adoptPet(
petId: "019b4132-70aa-764f-b315-e2803d882a24"
userId: "user-123"
) {
id
total
}
}
Tester le service gRPC
grpc.petstore.v1.PetService/AdoptPet
{
"pet_id": "019b4132-70aa-764f-b315-e2803d882a24",
"user_id": "user-123"
}
Vérifier la cohérence
Vérifiez que les trois protocoles renvoient les mêmes données :
- Appeler le point de terminaison REST
- Appeler la mutation GraphQL
- Appeler le service gRPC
- Comparer les résultats
Apidog peut automatiser ce test multi-protocoles.
Stratégies de déploiement
Stratégie 1 : Service unique
Déployer tous les protocoles dans un seul service :
┌─────────────────────────┐
│ Service PetstoreAPI │
│ ├── REST (port 8080) │
│ ├── GraphQL (port 8081)│
│ └── gRPC (port 50051) │
└─────────────────────────┘
Avantages : Déploiement simple, ressources partagées Inconvénients : Tous les protocoles évoluent ensemble
Stratégie 2 : Services séparés
Déployer chaque protocole séparément :
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Service REST │ │ Service GraphQL │ │ Service gRPC │
│ (port 8080) │ │ (port 8081) │ │ (port 50051) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└─────────────────┴──────────────────┘
│
┌──────────────┐
│ Noyau partagé │
│ Bibliothèque │
└──────────────┘
Avantages : Mise à l'échelle indépendante, isolation des protocoles Inconvénients : Déploiement plus complexe
Stratégie 3 : Passerelle API
Utilisez une passerelle API pour acheminer vers des backends spécifiques au protocole :
┌─────────────┐
│ Passerelle API │
└─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌───────▼──────┐ ┌────▼─────┐ ┌──────▼──────┐
│ Backend REST │ │ GraphQL │ │ Backend gRPC│
└──────────────┘ └──────────┘ └─────────────┘
Avantages : Routage centralisé, limitation de débit, authentification Inconvénients : Latence supplémentaire, complexité de la passerelle
Modern PetstoreAPI utilise la Stratégie 1 pour la simplicité.
Conclusion
Les API multi-protocoles offrent aux clients une flexibilité sans dupliquer la logique métier. En séparant les couches de protocole de la logique de domaine, vous pouvez prendre en charge REST, GraphQL et gRPC à partir d'un noyau partagé.
Modern PetstoreAPI démontre cette architecture avec des modèles de données cohérents, une validation partagée et des adaptateurs spécifiques au protocole. Que les clients utilisent REST pour la simplicité, GraphQL pour la flexibilité ou gRPC pour la performance, ils accèdent au même magasin d'animaux de compagnie avec les mêmes règles métier.
Testez vos API multi-protocoles avec Apidog pour assurer la cohérence entre les protocoles. Explorez Modern PetstoreAPI pour voir l'architecture multi-protocole en action.
FAQ
Dois-je prendre en charge les trois protocoles ?
Non. Commencez par REST pour les API publiques. Ajoutez GraphQL si les clients ont besoin d'une récupération de données flexible. Ajoutez gRPC pour les microservices internes. N'ajoutez des protocoles que lorsque vous avez un cas d'utilisation clair.
Comment puis-je maintenir la cohérence des protocoles ?
Partagez la logique métier dans une couche de domaine. Les adaptateurs de protocole ne devraient que traduire les formats, et non contenir de règles métier. Testez tous les protocoles pour vérifier qu'ils renvoient les mêmes données.
Puis-je versionner les protocoles indépendamment ?
Oui. REST peut être en v2 tandis que GraphQL est en v1. Mais cela crée de la complexité. Essayez de maintenir les protocoles synchronisés si possible.
Comment gérer l'authentification entre les protocoles ?
Utilisez la même authentification dans tous les protocoles. REST utilise des jetons Bearer dans les en-têtes. GraphQL utilise les mêmes jetons. gRPC utilise des métadonnées. La couche de domaine valide les jetons de la même manière.
Qu'en est-il de WebSocket et SSE ?
WebSocket et SSE sont des protocoles de transport, pas des protocoles d'API. Vous pouvez les ajouter aux côtés de REST/GraphQL/gRPC pour des mises à jour en temps réel. Modern PetstoreAPI inclut les deux.
Comment documenter les API multi-protocoles ?
Utilisez OpenAPI pour REST, le schéma GraphQL pour GraphQL et les fichiers .proto pour gRPC. Modern PetstoreAPI fournit les trois à l'adresse https://docs.petstoreapi.com/
Puis-je utiliser des bases de données différentes pour différents protocoles ?
Oui, mais cela crée des problèmes de cohérence. Il est préférable d'utiliser la même couche de données pour tous les protocoles. Laissez les adaptateurs de protocole gérer les différences de format.
Comment tester les API multi-protocoles ?
Utilisez Apidog pour tester tous les protocoles dans un seul outil. Créez des suites de tests qui vérifient la cohérence entre les protocoles. Vérifiez que REST, GraphQL et gRPC renvoient les mêmes données pour les mêmes opérations.
