Files
live-campus-mcs-p-2027.2/Semaine_08/orderflow/seed.py
T
gauvainboiche 3315cb2336 feat: Semaine 8
2026-05-11 09:25:19 +02:00

168 lines
5.9 KiB
Python

"""
seed.py — Envoie une série de commandes CommandFlow pour observer le flux complet.
Usage :
uv run python seed.py # envoie 10 commandes normales
uv run python seed.py --all # inclut des commandes avec montant invalide (DLQ)
uv run python seed.py --count 5 # envoie 5 commandes
Ce script publie directement sur Kafka (sans passer par l'API FastAPI).
Il utilise OrderProducer, le même producer qu'order-service.
Pour observer le flux :
1. Lancer Kafka : docker compose up -d
2. Lancer les consumers dans des terminaux séparés
3. Lancer ce script
4. Observer les logs de chaque consumer
5. Ouvrir Kafdrop sur http://localhost:9000 pour voir les topics
"""
import sys
import os
import time
import argparse
import random
# Ajoute les dossiers nécessaires au path
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, BASE_DIR)
sys.path.insert(0, os.path.join(BASE_DIR, "order_service"))
# order-service contient un tiret → on ne peut pas faire "import order-service.producer"
# On ajoute le dossier au path et on importe directement producer.py
import importlib.util as _ilu
_spec = _ilu.spec_from_file_location(
"order_producer",
os.path.join(BASE_DIR, "order_service", "producer.py"),
)
_mod = _ilu.module_from_spec(_spec)
_spec.loader.exec_module(_mod)
OrderProducer = _mod.OrderProducer
# ---------------------------------------------------------------------------
# Données de test
# ---------------------------------------------------------------------------
RESTAURANTS = [
{"id": 1, "name": "Pizza Roma"},
{"id": 2, "name": "Sushi Zen"},
{"id": 3, "name": "Burger Lab"},
]
MENUS = {
1: [ # Pizza Roma
{"dish_id": 101, "name": "Margherita", "price": 12.50},
{"dish_id": 102, "name": "Quattro Stagioni", "price": 14.00},
{"dish_id": 103, "name": "Tiramisu", "price": 5.50},
{"dish_id": 104, "name": "Coca-Cola", "price": 3.00},
],
2: [ # Sushi Zen
{"dish_id": 201, "name": "California Roll x8", "price": 11.00},
{"dish_id": 202, "name": "Saumon Tataki", "price": 13.50},
{"dish_id": 203, "name": "Edamame", "price": 4.50},
{"dish_id": 204, "name": "Thé vert", "price": 2.50},
],
3: [ # Burger Lab
{"dish_id": 301, "name": "Classic Smash", "price": 10.00},
{"dish_id": 302, "name": "Double Bacon", "price": 13.00},
{"dish_id": 303, "name": "Frites maison", "price": 4.00},
{"dish_id": 304, "name": "Milkshake", "price": 5.00},
],
}
def random_order(customer_id: int, order_id: int) -> dict:
"""Génère une commande aléatoire réaliste."""
restaurant = random.choice(RESTAURANTS)
menu = MENUS[restaurant["id"]]
# 1 à 3 plats aléatoires du menu
items = random.sample(menu, k=random.randint(1, 3))
total = round(sum(item["price"] for item in items), 2)
return {
"order_id": order_id,
"customer_id": customer_id,
"restaurant_id": restaurant["id"],
"items": items,
"total_amount": total,
}
def invalid_order(customer_id: int, order_id: int) -> dict:
"""Génère une commande avec un montant invalide pour déclencher la DLQ."""
return {
"order_id": order_id,
"customer_id": customer_id,
"restaurant_id": 1,
"items": [{"dish_id": 999, "name": "Plat inexistant", "price": -5.00}],
"total_amount": -5.00, # ← invalide : payment-service lèvera une ValueError
}
# ---------------------------------------------------------------------------
# Script principal
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="Seed CommandFlow avec des commandes de test.")
parser.add_argument("--count", type=int, default=10, help="Nombre de commandes à envoyer (défaut : 10)")
parser.add_argument("--all", action="store_true", help="Inclut 2 commandes invalides (pour tester la DLQ)")
parser.add_argument("--delay", type=float, default=0.5, help="Délai entre chaque message en secondes (défaut : 0.5)")
args = parser.parse_args()
producer = OrderProducer()
print("=" * 60)
print(" CommandFlow — seed")
print("=" * 60)
print(f" Commandes à envoyer : {args.count}")
print(f" Délai entre messages : {args.delay}s")
print(f" Commandes invalides : {'oui (DLQ)' if args.all else 'non'}")
print("=" * 60)
print()
orders = []
# Génère les commandes normales
for i in range(1, args.count + 1):
customer_id = random.randint(1, 50)
orders.append(("normal", random_order(customer_id, order_id=i)))
# Insère 2 commandes invalides si --all
if args.all:
bad_id_1 = args.count + 1
bad_id_2 = args.count + 2
orders.insert(len(orders) // 3, ("invalid", invalid_order(99, bad_id_1)))
orders.insert(2 * len(orders) // 3, ("invalid", invalid_order(99, bad_id_2)))
# Envoi
sent = 0
for kind, order in orders:
tag = "⚠ INVALIDE" if kind == "invalid" else ""
print(
f"[seed] {tag} order_id={order['order_id']:>3} "
f"| customer={order['customer_id']:>2} "
f"| resto_id={order['restaurant_id']} "
f"| total={order['total_amount']:>6.2f}"
f"| items={len(order['items'])}"
)
producer.publish_order_created(order)
sent += 1
time.sleep(args.delay)
print()
print("=" * 60)
print(f" {sent} messages publiés sur le topic 'orders'.")
print()
print(" Pour observer le flux :")
print(" → Kafdrop : http://localhost:9000")
print(" → Topics : orders, payments, notifications")
if args.all:
print(" → DLQ : orders-dlq (2 messages attendus)")
print("=" * 60)
if __name__ == "__main__":
main()