پروژه عملی پایانی
در این فصل پایانی، همه آموختههای دوره را در یک پروژه عملی بهکار میبریم: یک سیستم e-commerce میکروسرویس کامل از صفر تا production.
۱۵.۱ مقدمه
در این فصل پایانی، همه آموختههای دوره را در یک پروژه عملی بهکار میبریم: یک سیستم e-commerce میکروسرویس کامل از صفر تا production.
پروژه: سیستم فروشگاه آنلاین با ۶ سرویس، API Gateway، Message Queue، Docker، Kubernetes و CI/CD.
۱۵.۲ معماری کلی
┌──────────────────┐
│ Customer (Web) │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ API Gateway │ ← Traefik
│ (Auth, Rate) │
└────────┬─────────┘
│
┌──────────────────────────┼──────────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Auth Service │ │ Catalog │ │ Order Svc │
│ (Django) │ │ (FastAPI) │ │ (Django) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
PostgreSQL PostgreSQL PostgreSQL
(auth_db) (catalog_db) (orders_db)
+ Elasticsearch
┌──────────────────────────┬──────────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Inventory │ │ Payment │ │ Notification │
│ (FastAPI) │ │ (FastAPI) │ │ (Celery) │
└──────┬───────┘ └──────┬───────┘ └──────────────┘
│ │
▼ ▼
PostgreSQL PostgreSQL
╔═══════════════════════════════════════════════════════╗
║ RabbitMQ (Event Bus) ║
╠═══════════════════════════════════════════════════════╣
║ - order.placed → Inventory, Payment, Notification ║
║ - payment.completed → Order ║
║ - inventory.reserved → Order ║
╚═══════════════════════════════════════════════════════╝
┌─────────────────────────────────────────────────────┐
│ Observability: Prometheus + Grafana + Jaeger + ELK │
└─────────────────────────────────────────────────────┘
Bounded Contexts
- Auth: ثبتنام، login، JWT issuance
- Catalog: محصولات، دستهبندی، جستجو
- Order: سفارش، Saga orchestrator
- Inventory: موجودی، رزرو
- Payment: پرداخت، wallet
- Notification: ایمیل، SMS، push
۱۵.۳ ساختار Repository (Monorepo)
shop/
├── README.md
├── docker-compose.yml
├── docker-compose.dev.yml
├── .github/workflows/
│ ├── auth-ci.yml
│ ├── catalog-ci.yml
│ └── ...
├── services/
│ ├── auth/
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ ├── manage.py
│ │ └── apps/
│ │ └── users/
│ ├── catalog/
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ ├── main.py (FastAPI)
│ │ └── app/
│ ├── order/
│ ├── inventory/
│ ├── payment/
│ └── notification/
├── shared/
│ └── events/ # event schemas (proto یا JSON Schema)
├── k8s/
│ ├── base/
│ ├── overlays/
│ │ ├── staging/
│ │ └── production/
│ └── istio/
├── infra/
│ ├── prometheus/
│ ├── grafana/dashboards/
│ ├── jaeger/
│ └── traefik/
└── docs/
├── architecture.md
├── api/
└── runbooks/
۱۵.۴ Auth Service
# services/auth/apps/users/views.py
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import get_user_model
from .serializers import RegisterSerializer, LoginSerializer
from .tokens import create_token_pair
User = get_user_model()
class RegisterView(APIView):
permission_classes = []
def post(self, request):
serializer = RegisterSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
# publish event
from .events import publish_event
publish_event("user.registered", {
"user_id": user.id,
"email": user.email,
"name": user.full_name,
})
tokens = create_token_pair(user)
return Response({
"user": {"id": user.id, "email": user.email},
**tokens
}, status=201)
class LoginView(APIView):
permission_classes = []
def post(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data["user"]
tokens = create_token_pair(user)
return Response({
"user": {"id": user.id, "email": user.email, "role": user.role},
**tokens
})
# tokens.py
import jwt
from datetime import datetime, timedelta
from django.conf import settings
def create_token_pair(user):
now = datetime.utcnow()
access = jwt.encode({
"sub": str(user.id),
"role": user.role,
"type": "access",
"iat": now,
"exp": now + timedelta(minutes=15),
}, settings.JWT_PRIVATE_KEY, algorithm="RS256")
refresh = jwt.encode({
"sub": str(user.id),
"type": "refresh",
"iat": now,
"exp": now + timedelta(days=30),
"jti": str(uuid.uuid4()),
}, settings.JWT_PRIVATE_KEY, algorithm="RS256")
return {"access_token": access, "refresh_token": refresh}
۱۵.۵ Catalog Service (FastAPI)
# services/catalog/main.py
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from elasticsearch import AsyncElasticsearch
from typing import List
from .database import get_db
from .models import Product
from .schemas import ProductCreate, ProductResponse
from .events import publish_event
app = FastAPI(title="Catalog Service", version="1.0.0")
es = AsyncElasticsearch(["http://elasticsearch:9200"])
@app.get("/products", response_model=List[ProductResponse])
async def list_products(
skip: int = 0,
limit: int = 20,
category_id: int = None,
db: AsyncSession = Depends(get_db),
):
query = db.query(Product).filter(Product.active == True)
if category_id:
query = query.filter(Product.category_id == category_id)
return await query.offset(skip).limit(limit).all()
@app.get("/products/search")
async def search_products(q: str, limit: int = 20):
"""جستجو با Elasticsearch"""
result = await es.search(
index="products",
body={
"query": {
"multi_match": {
"query": q,
"fields": ["name^3", "description", "tags"],
"fuzziness": "AUTO",
}
},
"size": limit,
}
)
return [hit["_source"] for hit in result["hits"]["hits"]]
@app.get("/products/{product_id}", response_model=ProductResponse)
async def get_product(product_id: int, db: AsyncSession = Depends(get_db)):
product = await db.get(Product, product_id)
if not product:
raise HTTPException(404, "Product not found")
return product
@app.post("/products", response_model=ProductResponse, status_code=201)
async def create_product(
data: ProductCreate,
db: AsyncSession = Depends(get_db),
user = Depends(require_admin),
):
product = Product(**data.dict())
db.add(product)
await db.commit()
await db.refresh(product)
# publish event for ES indexing
await publish_event("product.created", {
"id": product.id,
"name": product.name,
"price": product.price,
"category_id": product.category_id,
})
return product
۱۵.۶ Order Service با Saga
# services/order/saga.py
from enum import Enum
import uuid
from .clients import InventoryClient, PaymentClient
from .events import publish_event
class OrderSaga:
"""Orchestrator-based Saga برای ثبت سفارش"""
def __init__(self):
self.inventory = InventoryClient()
self.payment = PaymentClient()
async def execute(self, order_data):
saga_id = str(uuid.uuid4())
completed_steps = []
try:
# Step 1: Create Order (PENDING)
order = await self._create_order_pending(order_data, saga_id)
completed_steps.append("create_order")
# Step 2: Reserve Inventory
await self.inventory.reserve(
items=order.items,
saga_id=saga_id,
)
completed_steps.append("reserve_inventory")
# Step 3: Charge Payment
payment = await self.payment.charge(
user_id=order.user_id,
amount=order.total,
saga_id=saga_id,
)
completed_steps.append("charge_payment")
# Step 4: Confirm Order
await self._confirm_order(order.id, payment.id)
# Publish success event
await publish_event("order.placed", {
"order_id": order.id,
"user_id": order.user_id,
"total": order.total,
"items": [i.dict() for i in order.items],
"payment_id": payment.id,
})
return order
except Exception as e:
# Compensation
await self._compensate(completed_steps, saga_id, str(e))
raise
async def _compensate(self, completed_steps, saga_id, reason):
"""rollback مراحل کامل شده"""
if "charge_payment" in completed_steps:
try:
await self.payment.refund(saga_id=saga_id)
except Exception as e:
logger.error(f"Refund failed: {e}", extra={"saga_id": saga_id})
if "reserve_inventory" in completed_steps:
try:
await self.inventory.release(saga_id=saga_id)
except Exception as e:
logger.error(f"Inventory release failed: {e}")
if "create_order" in completed_steps:
await self._cancel_order(saga_id, reason)
# views.py
@router.post("/orders")
async def place_order(
data: OrderCreate,
user = Depends(verify_token),
):
saga = OrderSaga()
try:
order = await saga.execute(OrderData(
user_id=user["user_id"],
items=data.items,
total=calculate_total(data.items),
))
return order
except SagaException as e:
raise HTTPException(400, str(e))
۱۵.۷ Docker Compose کامل
# docker-compose.yml
version: "3.9"
services:
# Infrastructure
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
- ./scripts/init-multiple-dbs.sh:/docker-entrypoint-initdb.d/init.sh
networks: [backend]
redis:
image: redis:7-alpine
volumes: [redis_data:/data]
networks: [backend]
rabbitmq:
image: rabbitmq:3.12-management-alpine
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
ports: ["15672:15672"]
volumes: [rabbitmq_data:/var/lib/rabbitmq]
networks: [backend]
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
volumes: [es_data:/usr/share/elasticsearch/data]
networks: [backend]
# Services
auth:
build: ./services/auth
environment:
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@postgres/auth_db
REDIS_URL: redis://redis:6379/0
RABBITMQ_URL: amqp://admin:${RABBITMQ_PASSWORD}@rabbitmq
depends_on: [postgres, redis, rabbitmq]
networks: [backend, frontend]
labels:
- "traefik.enable=true"
- "traefik.http.routers.auth.rule=PathPrefix(`/api/auth`)"
- "traefik.http.services.auth.loadbalancer.server.port=8000"
catalog:
build: ./services/catalog
environment:
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@postgres/catalog_db
ELASTICSEARCH_URL: http://elasticsearch:9200
RABBITMQ_URL: amqp://admin:${RABBITMQ_PASSWORD}@rabbitmq
depends_on: [postgres, elasticsearch, rabbitmq]
networks: [backend, frontend]
labels:
- "traefik.enable=true"
- "traefik.http.routers.catalog.rule=PathPrefix(`/api/catalog`)"
order:
build: ./services/order
environment:
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@postgres/orders_db
RABBITMQ_URL: amqp://admin:${RABBITMQ_PASSWORD}@rabbitmq
INVENTORY_URL: http://inventory:8000
PAYMENT_URL: http://payment:8000
depends_on: [postgres, rabbitmq]
networks: [backend, frontend]
inventory:
build: ./services/inventory
environment:
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@postgres/inventory_db
RABBITMQ_URL: amqp://admin:${RABBITMQ_PASSWORD}@rabbitmq
networks: [backend]
payment:
build: ./services/payment
environment:
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@postgres/payment_db
RABBITMQ_URL: amqp://admin:${RABBITMQ_PASSWORD}@rabbitmq
networks: [backend]
notification-worker:
build: ./services/notification
command: celery -A app worker --loglevel=info
environment:
RABBITMQ_URL: amqp://admin:${RABBITMQ_PASSWORD}@rabbitmq
REDIS_URL: redis://redis:6379/2
deploy:
replicas: 2
networks: [backend]
# Gateway
gateway:
image: traefik:v3.0
command:
- "--providers.docker=true"
- "--providers.docker.exposedByDefault=false"
- "--entrypoints.web.address=:80"
- "--api.dashboard=true"
- "--api.insecure=true"
ports: ["80:80", "8080:8080"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks: [frontend]
# Observability
prometheus:
image: prom/prometheus
volumes:
- ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prom_data:/prometheus
networks: [backend]
grafana:
image: grafana/grafana
ports: ["3000:3000"]
volumes:
- grafana_data:/var/lib/grafana
- ./infra/grafana/dashboards:/etc/grafana/provisioning/dashboards
networks: [backend]
jaeger:
image: jaegertracing/all-in-one
ports: ["16686:16686"]
environment:
COLLECTOR_OTLP_ENABLED: "true"
networks: [backend]
volumes:
pg_data:
redis_data:
rabbitmq_data:
es_data:
prom_data:
grafana_data:
networks:
frontend:
backend:
internal: false # برای dev راحتتر
اجرا
# اولین بار
cp .env.example .env
# ویرایش .env با مقادیر مناسب
docker-compose up -d --build
# Migrations
docker-compose exec auth python manage.py migrate
docker-compose exec catalog alembic upgrade head
docker-compose exec order python manage.py migrate
# مشاهده logs
docker-compose logs -f order
# تست
curl http://localhost/api/auth/register
-H "Content-Type: application/json"
-d "{"email": "test@shop.com", "password": "secret", "name": "Test"}"
۱۵.۸ Deploy به Kubernetes
با استفاده از Kustomize برای multi-environment:
k8s/
├── base/
│ ├── auth/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── kustomization.yaml
│ ├── catalog/
│ ├── order/
│ ├── infra/
│ │ ├── postgres-statefulset.yaml
│ │ ├── rabbitmq-statefulset.yaml
│ │ └── ...
│ └── kustomization.yaml
└── overlays/
├── staging/
│ ├── kustomization.yaml
│ └── replica-patch.yaml
└── production/
├── kustomization.yaml
├── replica-patch.yaml
└── resources-patch.yaml
# Apply
kubectl apply -k k8s/overlays/production
# Check
kubectl get pods -n shop
kubectl get svc -n shop
# با ArgoCD
argocd app create shop
--repo https://github.com/shop/gitops
--path k8s/overlays/production
--dest-namespace shop
--dest-server https://kubernetes.default.svc
--sync-policy automated
۱۵.۹ تست End-to-End
# tests/e2e/test_order_flow.py
import pytest
import httpx
BASE = "http://localhost"
@pytest.mark.asyncio
async def test_complete_order_flow():
async with httpx.AsyncClient(base_url=BASE) as client:
# 1. Register
r = await client.post("/api/auth/register", json={
"email": "test@shop.com",
"password": "Test1234!",
"name": "Test User"
})
assert r.status_code == 201
token = r.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
# 2. Browse products
r = await client.get("/api/catalog/products?limit=10")
assert r.status_code == 200
products = r.json()
assert len(products) > 0
# 3. Top up wallet
r = await client.post(
"/api/payment/wallet/topup",
json={"amount": 5000000},
headers=headers
)
assert r.status_code == 200
# 4. Place order
r = await client.post(
"/api/orders",
json={
"items": [
{"product_id": products[0]["id"], "qty": 2}
]
},
headers=headers
)
assert r.status_code == 201
order = r.json()
order_id = order["id"]
# 5. Check order status
r = await client.get(f"/api/orders/{order_id}", headers=headers)
assert r.json()["status"] == "confirmed"
# 6. Inventory should be reduced
r = await client.get(f"/api/inventory/{products[0]['id']}")
# (chک کاهش)
۱۵.۱۰ گامهای بعدی
این فقط شروع است. برای production-ready کردن این پروژه:
- 📊 پیادهسازی کامل Observability (Prometheus، Grafana، Jaeger، ELK)
- 🔐 mTLS با Istio
- 🔄 GitOps با ArgoCD
- 🧪 Chaos Engineering با Chaos Mesh
- 📈 Auto-scaling با HPA و KEDA
- 💾 Backup و Disaster Recovery
- 🌍 Multi-region deployment
- 📱 Mobile API (Flutter)
- 🛒 شخصیسازی با ML
- 🤖 ChatBot برای support
۱۵.۱۱ جمعبندی دوره
🎓 آنچه در این دوره آموختید:
- ✅ مفاهیم پایه میکروسرویس و مقایسه با Monolith
- ✅ Domain-Driven Design و Bounded Context
- ✅ الگوهای ارتباطی Sync (REST، gRPC) و Async (RabbitMQ، Kafka)
- ✅ API Gateway و Service Discovery
- ✅ پیامرسانی پیشرفته با Schema Registry
- ✅ Saga Pattern برای distributed transactions
- ✅ Event-Driven Architecture، Event Sourcing، CQRS
- ✅ الگوهای Resilience: Circuit Breaker، Retry، Bulkhead
- ✅ Containerization با Docker
- ✅ Orchestration با Kubernetes
- ✅ Observability کامل (Logs، Metrics، Traces)
- ✅ امنیت پیشرفته با mTLS و Service Mesh
- ✅ CI/CD حرفهای و GitOps
- ✅ پیادهسازی پروژه کامل end-to-end
🎉 تبریک!
شما حالا دانش لازم برای طراحی، پیادهسازی و نگهداری یک سیستم میکروسرویس production-grade را دارید.
یادتان باشد: میکروسرویس یک silver bullet نیست. هر تصمیم معماری trade-off دارد. عاقلانه استفاده کنید — گاهی Monolith بهتر است!
منابع برای ادامه یادگیری:
- کتاب “Building Microservices” — Sam Newman
- کتاب “Microservices Patterns” — Chris Richardson
- microservices.io — مرجع الگوها
- CNCF Landscape — ابزارهای cloud-native
- Martin Fowler’s blog — اصول طراحی