168 lines
5.9 KiB
Python
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() |