Intermédiaire

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

Facile

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 :

  1. Lister tous les projets d'un utilisateur
  2. Créer un nouveau projet pour un utilisateur
  3. Récupérer les détails d'un projet
  4. Lister toutes les tâches d'un projet
  5. Créer une tâche dans un projet
  6. Modifier le statut d'une tâche
  7. 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

Moyen

Le code suivant contient plusieurs erreurs de conception et de bonnes pratiques. Identifiez-les et proposez des corrections.

python
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 :

  1. Quels problèmes de structure identifiez-vous ?
  2. Quels codes de statut manquent ou sont incorrects ?
  3. Quelle validation est absente ?
  4. Quel problème de sécurité ou de robustesse voyez-vous ?
  5. 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é

Facile

Pour chaque situation, indiquez le code de statut HTTP le plus approprié :

  1. Un utilisateur envoie un POST pour créer un compte et tout se passe bien
  2. Un utilisateur non connecté tente d'accéder à son profil
  3. Un utilisateur connecté (role: "user") tente de supprimer le compte d'un autre utilisateur
  4. Un utilisateur envoie un GET pour récupérer un livre qui n'existe pas
  5. Un utilisateur envoie un POST avec un body JSON invalide (champ email manquant)
  6. Un utilisateur supprime son propre commentaire avec succès (pas de body en réponse)
  7. Le serveur plante à cause d'une exception non gérée dans le code Python
  8. Un utilisateur envoie 200 requêtes en 1 minute alors que la limite est de 100
  9. Un utilisateur tente de faire un DELETE sur un endpoint qui n'accepte que GET et POST
  10. 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

Difficile

É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 :

  1. Créer un utilisateur (CreateUserDTO)
  2. Modifier partiellement un utilisateur (UpdateUserDTO — tous les champs optionnels)
  3. Service avec la méthode create qui 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 :

python
# dtos/user_dto.py
class CreateUserDTO(BaseModel):
    # ...

class UpdateUserDTO(BaseModel):
    # ...

# services/user_service.py
class UserService:
    def create(self, dto: CreateUserDTO) -> User:
        # ...