~/icsd.ir — bash
SYSTEM_ONLINE

پروژه عملی پایانی

در این فصل پایانی، همه آموخته‌های دوره را در یک پروژه عملی به‌کار می‌بریم: یک سیستم 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 — اصول طراحی

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.