Bonne pratique générale
Asynchronie et Multiprocessing - comment donner le tournis au GIL
VII. Bonnes Pratiques de Conception d’APIs
La conception d’une API est un élément fondamental qui détermine sa facilité d’utilisation, sa performance et sa maintenabilité. Voici quelques bonnes pratiques à considérer lors de la conception d’une API.
1. Conception RESTful
RESTful fait référence à une API qui suit les contraintes de l’architecture REST (Representational State Transfer). Cela signifie que l’API utilise des méthodes HTTP standard sans maintenir l’état du client entre les requêtes, ce qui la rend sans état. Les données sont représentées de manière standard (comme JSON ou XML), et les opérations sur les données sont effectuées à l’aide de méthodes HTTP (comme GET, POST, PUT, DELETE).
Un exemple d’API RESTful est une API qui gère les utilisateurs. Les URIs sont structurées de manière logique, par exemple :
- GET /users pour récupérer une liste d’utilisateurs.
- POST /users pour créer un nouvel utilisateur.
- Utilisation correcte des méthodes HTTP : GET pour récupérer des données, POST pour créer des données, PUT/PATCH pour mettre à jour et DELETE pour supprimer.
- Des URIs bien structurées : Les URIs doivent être intuitives et hiérarchiques, reflétant les relations entre les ressources.
- Sans état : Chaque requête doit contenir toutes les informations nécessaires pour être traitée, sans dépendre d’un état stocké sur le serveur.
- Cacheable : Les réponses doivent être définies comme cacheables ou non, pour améliorer les performances.
- Système de couche : L’API doit être structurée en couches, chaque couche ayant une fonction spécifique.
2. Gestion des erreurs
Une bonne gestion des erreurs est essentielle pour aider les développeurs à comprendre ce qui ne va pas lorsqu’une requête échoue. Voici quelques recommandations :
- Utiliser des codes d’état HTTP appropriés : Par exemple, 404 pour “Non trouvé”, 400 pour “Mauvaise requête”, 500 pour “Erreur interne du serveur”.
- Fournir des messages d’erreur descriptifs : Inclure des messages d’erreur clairs et, si possible, des indications sur la façon de résoudre le problème.
- Standardiser les réponses d’erreur : Utiliser un format cohérent pour toutes les réponses d’erreur.
Voici un exemple de gestion des erreurs dans Flask :
from flask import Flask, jsonify, make_response
= Flask(__name__)
app
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
= find_user_by_id(user_id)
user if user is None:
return not_found('Not found')
return jsonify({'user': user})
Dans cet exemple, @app.errorhandler(404) est un décorateur qui définit une fonction personnalisée pour gérer les erreurs 404, renvoyant un message JSON et le code d’état approprié.
3. Pagination, Filtrage et Tri des données
Lorsque vous travaillez avec de grandes collections de données, il est important de fournir des mécanismes pour la pagination, le filtrage et le tri :
- Pagination : Limiter le nombre d’éléments renvoyés dans une seule réponse et fournir des liens pour accéder aux pages suivantes.
- Filtrage : Permettre aux utilisateurs de spécifier des critères pour limiter les données renvoyées.
- Tri : Offrir la possibilité de trier les données selon différents attributs.
a. Exemple de pagination dans Django Rest Framework
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import User
from .serializers import UserSerializer
class UserListView(APIView):
= PageNumberPagination
pagination_class
def get(self, request, format=None):
= User.objects.all()
users = self.pagination_class()
paginator = paginator.paginate_queryset(users, request)
page
if page is not None:
= UserSerializer(page, many=True)
serializer return paginator.get_paginated_response(serializer.data)
= UserSerializer(users, many=True)
serializer return Response(serializer.data)
Ce code montre comment implémenter la pagination dans une vue qui renvoie une liste d’utilisateurs. Le PageNumberPagination est utilisé pour diviser les résultats en pages.
b. Exemple de filtrage et de tri pour une API utilisant Django Rest Framework
Nous allons utiliser le modèle User et le serializer UserSerializer pour illustrer comment filtrer et trier les données renvoyées par une requête GET.
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics
from .models import User
from .serializers import UserSerializer
class UserList(generics.ListAPIView):
= User.objects.all()
queryset = UserSerializer
serializer_class = [DjangoFilterBackend]
filter_backends = ['username', 'email'] # Fields to filter by
filterset_fields = ['username', 'date_joined'] # Fields to order by
ordering_fields
def get_queryset(self):
"""
Optionally restricts the returned users to a given user,
by filtering against a username query parameter in the URL.
"""
= User.objects.all()
queryset = self.request.query_params.get('username')
username if username is not None:
= queryset.filter(username=username)
queryset return queryset
Dans cet exemple, DjangoFilterBackend est utilisé pour ajouter des capacités de filtrage à la vue. Les utilisateurs peuvent filtrer les résultats en ajoutant des paramètres de requête à l’URL, comme ?username=johndoe pour filtrer par nom d’utilisateur, ou ?email=johndoe@example.com pour filtrer par e-mail.
Le tri est géré par ordering_fields, qui spécifie les champs sur lesquels les utilisateurs peuvent trier les résultats. Les utilisateurs peuvent ajouter un paramètre de requête ordering à l’URL, comme ?ordering=username pour trier par nom d’utilisateur ou ?ordering=-date_joined pour trier par date d’inscription en ordre décroissant (le signe moins indique un tri décroissant).
Ces fonctionnalités permettent aux utilisateurs de l’API de récupérer des données de manière plus précise et organisée, améliorant ainsi l’expérience utilisateur et la performance de l’API.
4. Rate Limiting et Caching
Pour protéger votre API contre les abus et gérer les ressources serveur de manière efficace :
- Rate Limiting : Imposer des limites sur le nombre de requêtes qu’un utilisateur peut faire dans un certain intervalle de temps.
- Caching : Utiliser des caches pour stocker les réponses des requêtes fréquemment faites afin de réduire la charge sur le serveur et d’améliorer les temps de réponse.
Pour le rate limiting dans FastAPI, vous pourriez utiliser un middleware personnalisé :
from fastapi import FastAPI, Request, HTTPException
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
= Limiter(key_func=get_remote_address)
limiter = FastAPI()
app
= limiter
app.state.limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
= await call_next(request)
response return response
@app.get("/users")
@limiter.limit("5/minute")
async def get_users():
return [{"user": "user1"}, {"user": "user2"}]
Dans cet exemple, le décorateur @limiter.limit(“5/minute”) est utilisé pour limiter les requêtes à 5 par minute pour l’endpoint /users.
Pour le caching, vous pourriez utiliser Flask-Caching dans une application Flask :
from flask import Flask
from flask_caching import Cache
= Flask(__name__)
app = Cache(app, config={'CACHE_TYPE': 'simple'})
cache
@app.route('/')
@cache.cached(timeout=50)
def index():
return "Ceci est une page mise en cache"
Ici, @cache.cached(timeout=50) est utilisé pour mettre en cache la réponse de l’endpoint racine pendant 50 secondes.
Ces bonnes pratiques sont essentielles pour créer une API robuste, performante et sécurisée. Elles contribuent également à une meilleure expérience pour les développeurs qui utilisent votre API, en leur fournissant des mécanismes clairs et des directives pour intégrer l’API dans leurs applications.