Exercices - API & Architecture
Mettez en pratique les concepts abordés dans cette séance. Ces exercices sont orientés conception et lecture de code : pas besoin d'exécuter du code, mais de réfléchir et structurer.
Comment travailler
- Prenez le temps de réfléchir avant de regarder les solutions
- Les exercices de design d'API se font sur papier ou dans un éditeur de texte
- Pour les exercices de lecture de code, identifiez les erreurs ou les améliorations possibles
- L'objectif est de comprendre les conventions, pas de mémoriser du code
Exercice 1 : Design d'URI REST
Exercice : Concevoir les endpoints d'une API de gestion de tâches
Vous devez concevoir les URI pour une API de gestion de tâches (todo list). Chaque tâche appartient à un projet. Un projet appartient à un utilisateur.
Ressources :
User(identifiant :userId)Project(identifiant :projectId)Task(identifiant :taskId)
Fonctionnalités requises :
- Lister tous les projets d'un utilisateur
- Créer un nouveau projet pour un utilisateur
- Récupérer les détails d'un projet
- Lister toutes les tâches d'un projet
- Créer une tâche dans un projet
- Modifier le statut d'une tâche
- Supprimer une tâche
Pour chaque fonctionnalité, indiquez :
- La méthode HTTP
- L'URI
- Le code de statut de succès attendu
Exemple de réponse attendue :
GET /api/users/:userId/projects → 200
POST /api/users/:userId/projects → 201
...
Exercice 2 : Identifier les erreurs dans un controller Flask
Exercice : Trouver les problèmes dans ce controller
Le code suivant contient plusieurs erreurs de conception et de bonnes pratiques. Identifiez-les et proposez des corrections.
from flask import Blueprint, request, jsonify
import sqlite3
tasks_bp = Blueprint('tasks', __name__, url_prefix='/api/tasks')
@tasks_bp.route('', methods=['POST'])
def create_task():
data = request.get_json()
title = data['title']
description = data.get('description', '')
# Connexion directe à la base dans le controller
conn = sqlite3.connect('tasks.db')
cursor = conn.cursor()
# Vérifier si une tâche avec le même titre existe
cursor.execute("SELECT * FROM tasks WHERE title = ?", (title,))
existing = cursor.fetchone()
if existing:
return jsonify({"error": "Une tâche avec ce titre existe déjà"})
# Insérer la tâche
cursor.execute(
"INSERT INTO tasks (title, description) VALUES (?, ?)",
(title, description)
)
conn.commit()
# Récupérer la tâche créée
task_id = cursor.lastrowid
cursor.execute("SELECT * FROM tasks WHERE id = ?", (task_id,))
task = cursor.fetchone()
conn.close()
return jsonify({"id": task[0], "title": task[1], "description": task[2]})
Questions :
- Quels problèmes de structure identifiez-vous ?
- Quels codes de statut manquent ou sont incorrects ?
- Quelle validation est absente ?
- Quel problème de sécurité ou de robustesse voyez-vous ?
- Comment restructureriez-vous ce code en couches (Controller, Service, Repository) ?
Exercice 3 : Choisir le bon code de statut
Exercice : Associer chaque situation au code de statut approprié
Pour chaque situation, indiquez le code de statut HTTP le plus approprié :
- Un utilisateur envoie un POST pour créer un compte et tout se passe bien
- Un utilisateur non connecté tente d'accéder à son profil
- Un utilisateur connecté (role: "user") tente de supprimer le compte d'un autre utilisateur
- Un utilisateur envoie un GET pour récupérer un livre qui n'existe pas
- Un utilisateur envoie un POST avec un body JSON invalide (champ
emailmanquant) - Un utilisateur supprime son propre commentaire avec succès (pas de body en réponse)
- Le serveur plante à cause d'une exception non gérée dans le code Python
- Un utilisateur envoie 200 requêtes en 1 minute alors que la limite est de 100
- Un utilisateur tente de faire un DELETE sur un endpoint qui n'accepte que GET et POST
- Un utilisateur envoie un PUT pour mettre à jour intégralement un article et tout se passe bien
Exercice 4 : Structurer un service avec Pydantic
Exercice : Écrire un service de gestion d'utilisateurs
Écrivez les DTOs Pydantic et la structure d'un service pour les opérations suivantes sur une ressource User :
Champs de User :
id(int, auto-généré)email(str, obligatoire, doit contenir un @)username(str, obligatoire, entre 3 et 20 caractères)role(str, optionnel, par défaut "user", valeurs possibles : "user", "admin")
Opérations :
- Créer un utilisateur (
CreateUserDTO) - Modifier partiellement un utilisateur (
UpdateUserDTO— tous les champs optionnels) - Service avec la méthode
createqui vérifie si l'email est déjà pris avant de créer
Contraintes :
- Utilisez les validateurs Pydantic (
field_validator) pour les règles de validation - Le service ne doit pas connaître HTTP ni renvoyer de codes de statut
- Le service doit utiliser un repository (vous pouvez supposer qu'il existe une interface)
Structure attendue :
# dtos/user_dto.py
class CreateUserDTO(BaseModel):
# ...
class UpdateUserDTO(BaseModel):
# ...
# services/user_service.py
class UserService:
def create(self, dto: CreateUserDTO) -> User:
# ...