microservices example

This commit is contained in:
2026-01-04 23:01:46 +03:00
parent a0c17162ce
commit f76982bb57
6 changed files with 619 additions and 2 deletions
+20
View File
@@ -0,0 +1,20 @@
# Используем official Python runtime как базовый образ
FROM python:3.11-slim
# Устанавливаем рабочую директорию в контейнере
WORKDIR /app
# Копируем файл зависимостей
COPY requirements.txt .
# Устанавливаем зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Копируем код приложения
COPY service-1-main.py main.py
# Экспонируем порт
EXPOSE 8001
# Команда для запуска приложения
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
+20
View File
@@ -0,0 +1,20 @@
# Используем official Python runtime как базовый образ
FROM python:3.11-slim
# Устанавливаем рабочую директорию в контейнере
WORKDIR /app
# Копируем файл зависимостей
COPY requirements.txt .
# Устанавливаем зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Копируем код приложения
COPY service-2-main.py main.py
# Экспонируем порт
EXPOSE 8002
# Команда для запуска приложения
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8002"]
+312 -2
View File
@@ -1,3 +1,313 @@
# ApiGatewayTest
# Примеры тестирования микросервисов через API Gateway
Минимально жизнеспособный пример работы api gateway
## Структура проекта
```
.
├── service-1-main.py # User Service
├── service-2-main.py # Order Service
├── requirements.txt # Зависимости
├── Dockerfile.service1 # Docker образ для Service 1
├── Dockerfile.service2 # Docker образ для Service 2
└── docker-compose.yaml # Конфигурация Docker Compose
```
## Запуск контейнеров
```bash
# Построить образы и запустить контейнеры
docker-compose up --build
# Запустить в фоновом режиме
docker-compose up -d --build
# Просмотреть логи
docker-compose logs -f
# Остановить контейнеры
docker-compose down
```
## Service 1 - User Service (порт 8001)
### Health Check
```bash
curl http://localhost:8001/
curl http://localhost:8001/health
```
### Получить список пользователей
```bash
curl http://localhost:8001/users
```
### Получить пользователя по ID
```bash
curl http://localhost:8001/users/1
```
### Создать нового пользователя
```bash
curl -X POST http://localhost:8001/users \
-H "Content-Type: application/json" \
-d '{
"name": "Петр Иванов",
"email": "petr@example.com",
"age": 30
}'
```
### Обновить пользователя
```bash
curl -X PUT http://localhost:8001/users/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Иван Петров (обновлено)",
"email": "ivan.updated@example.com",
"age": 29
}'
```
### Удалить пользователя
```bash
curl -X DELETE http://localhost:8001/users/1
```
### Получить статистику
```bash
curl http://localhost:8001/stats
```
## Service 2 - Order Service (порт 8002)
### Health Check
```bash
curl http://localhost:8002/
curl http://localhost:8002/health
```
### Получить список продуктов
```bash
curl http://localhost:8002/products
```
### Получить продукт по ID
```bash
curl http://localhost:8002/products/1
```
### Создать новый продукт
```bash
curl -X POST http://localhost:8002/products \
-H "Content-Type: application/json" \
-d '{
"name": "Мышка беспроводная",
"price": 1500.00,
"quantity": 20
}'
```
### Обновить продукт
```bash
curl -X PUT http://localhost:8002/products/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Ноутбук (обновлено)",
"price": 55000.00,
"quantity": 3
}'
```
### Удалить продукт
```bash
curl -X DELETE http://localhost:8002/products/1
```
### Получить список заказов
```bash
curl http://localhost:8002/orders
```
### Получить заказ по ID
```bash
curl http://localhost:8002/orders/1
```
### Создать новый заказ
```bash
curl -X POST http://localhost:8002/orders \
-H "Content-Type: application/json" \
-d '{
"user_id": 2,
"product_id": 2,
"quantity": 2
}'
```
### Получить статистику
```bash
curl http://localhost:8002/stats
```
## Тестирование через Python requests
```python
import requests
import json
BASE_URL_SERVICE1 = "http://localhost:8001"
BASE_URL_SERVICE2 = "http://localhost:8002"
# Проверка здоровья Service 1
response = requests.get(f"{BASE_URL_SERVICE1}/health")
print(f"Service 1 Health: {response.json()}")
# Получение всех пользователей
response = requests.get(f"{BASE_URL_SERVICE1}/users")
print(f"Users: {response.json()}")
# Создание пользователя
user_data = {
"name": "Тест Пользователь",
"email": "test@example.com",
"age": 25
}
response = requests.post(f"{BASE_URL_SERVICE1}/users", json=user_data)
print(f"Created User: {response.json()}")
# Проверка здоровья Service 2
response = requests.get(f"{BASE_URL_SERVICE2}/health")
print(f"Service 2 Health: {response.json()}")
# Получение всех продуктов
response = requests.get(f"{BASE_URL_SERVICE2}/products")
print(f"Products: {response.json()}")
# Создание заказа
order_data = {
"user_id": 1,
"product_id": 3,
"quantity": 5
}
response = requests.post(f"{BASE_URL_SERVICE2}/orders", json=order_data)
print(f"Created Order: {response.json()}")
```
## Конфигурация API Gateway
Для проверки работы API Gateway, вы можете использовать:
1. **Kong** - популярный API Gateway
2. **NGINX** - простой и быстрый
3. **Traefik** - автоматическое обнаружение сервисов
4. **AWS API Gateway** - облачное решение
### Пример конфигурации NGINX
```nginx
upstream service1 {
server service-1:8001;
}
upstream service2 {
server service-2:8002;
}
server {
listen 8000;
server_name localhost;
location /api/users {
proxy_pass http://service1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/products {
proxy_pass http://service2;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/orders {
proxy_pass http://service2;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
## Основные характеристики микросервисов
### Service 1 - User Service
- **Порт**: 8001
- **Функционал**: Управление пользователями (CRUD)
- **Endpoints**:
- `GET /` - Проверка статуса
- `GET /health` - Health check
- `GET /users` - Список пользователей
- `GET /users/{id}` - Получить пользователя
- `POST /users` - Создать пользователя
- `PUT /users/{id}` - Обновить пользователя
- `DELETE /users/{id}` - Удалить пользователя
- `GET /stats` - Статистика
### Service 2 - Order Service
- **Порт**: 8002
- **Функционал**: Управление продуктами и заказами (CRUD)
- **Endpoints**:
- `GET /` - Проверка статуса
- `GET /health` - Health check
- `GET /products` - Список продуктов
- `GET /products/{id}` - Получить продукт
- `POST /products` - Создать продукт
- `PUT /products/{id}` - Обновить продукт
- `DELETE /products/{id}` - Удалить продукт
- `GET /orders` - Список заказов
- `GET /orders/{id}` - Получить заказ
- `POST /orders` - Создать заказ
- `GET /stats` - Статистика
## Логирование и мониторинг
Оба сервиса включают:
- Встроенное логирование (INFO уровень)
- Health check endpoints
- Метрики статистики
- Документацию Swagger (автоматическая генерация)
### Доступ к Swagger документации
- Service 1: `http://localhost:8001/docs`
- Service 2: `http://localhost:8002/docs`
## Отладка
```bash
# Проверить статус контейнеров
docker-compose ps
# Просмотреть логи конкретного сервиса
docker-compose logs service-1
docker-compose logs service-2
# Подключиться к контейнеру
docker-compose exec service-1 /bin/bash
# Проверить сетевое подключение между сервисами
docker-compose exec service-1 curl http://service-2:8002/health
```
## Требования
- Docker 20.10+
- Docker Compose 2.0+
- Python 3.11+ (для локального запуска без Docker)
- curl или PostMan для тестирования API
## Лицензия
MIT
+57
View File
@@ -0,0 +1,57 @@
version: '3.8'
services:
# Service 1 - User Service (порт 8001)
service-1:
build:
context: .
dockerfile: Dockerfile.service1
container_name: user-service
ports:
- "8001:8001"
environment:
- SERVICE_NAME=user-service
- SERVICE_PORT=8001
networks:
- microservices-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
labels:
service.type: "microservice"
service.name: "user-service"
service.port: "8001"
# Service 2 - Order Service (порт 8002)
service-2:
build:
context: .
dockerfile: Dockerfile.service2
container_name: order-service
ports:
- "8002:8002"
environment:
- SERVICE_NAME=order-service
- SERVICE_PORT=8002
networks:
- microservices-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8002/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
labels:
service.type: "microservice"
service.name: "order-service"
service.port: "8002"
networks:
microservices-network:
driver: bridge
name: microservices-network
+4
View File
@@ -0,0 +1,4 @@
fastapi==0.109.1
uvicorn[standard]==0.27.0
pydantic==2.5.3
python-multipart==0.0.6
+206
View File
@@ -0,0 +1,206 @@
"""
Второй микросервис - Service 2
Предоставляет API для управления заказами и продуктами
"""
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
import logging
app = FastAPI(
title="Service 2 - Order Service",
description="Микросервис для управления заказами и продуктами",
version="1.0.0"
)
# Логирование
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Модели данных
class Product(BaseModel):
id: int
name: str
price: float
quantity: int
class ProductCreate(BaseModel):
name: str
price: float
quantity: int
class Order(BaseModel):
id: int
user_id: int
product_id: int
quantity: int
total_price: float
created_at: str
class OrderCreate(BaseModel):
user_id: int
product_id: int
quantity: int
# Имитация базы данных
products_db = {
1: {"id": 1, "name": "Ноутбук", "price": 50000.00, "quantity": 5},
2: {"id": 2, "name": "Монитор", "price": 15000.00, "quantity": 10},
3: {"id": 3, "name": "Клавиатура", "price": 3000.00, "quantity": 25},
}
orders_db = {
1: {
"id": 1,
"user_id": 1,
"product_id": 1,
"quantity": 1,
"total_price": 50000.00,
"created_at": "2025-01-01T10:00:00"
},
}
next_product_id = 4
next_order_id = 2
# Маршруты
@app.get("/", tags=["Health"])
async def root():
"""Проверка работоспособности Service 2"""
logger.info("Health check для Service 2")
return {
"service": "Service 2 - Order Service",
"status": "operational",
"version": "1.0.0"
}
@app.get("/health", tags=["Health"])
async def health_check():
"""Health check endpoint"""
return {"status": "healthy", "service": "order-service"}
# Endpoints для продуктов
@app.get("/products", response_model=List[Product], tags=["Products"])
async def list_products(skip: int = 0, limit: int = 10):
"""Получить список всех продуктов"""
logger.info(f"Получение списка продуктов (skip={skip}, limit={limit})")
products_list = list(products_db.values())
return products_list[skip:skip + limit]
@app.get("/products/{product_id}", response_model=Product, tags=["Products"])
async def get_product(product_id: int):
"""Получить продукт по ID"""
logger.info(f"Получение продукта с ID: {product_id}")
if product_id not in products_db:
logger.warning(f"Продукт с ID {product_id} не найден")
raise HTTPException(status_code=404, detail="Продукт не найден")
return products_db[product_id]
@app.post("/products", response_model=Product, tags=["Products"])
async def create_product(product: ProductCreate):
"""Создать новый продукт"""
global next_product_id
logger.info(f"Создание нового продукта: {product.name}")
new_product = {
"id": next_product_id,
"name": product.name,
"price": product.price,
"quantity": product.quantity
}
products_db[next_product_id] = new_product
next_product_id += 1
logger.info(f"Продукт создан с ID: {new_product['id']}")
return new_product
@app.put("/products/{product_id}", response_model=Product, tags=["Products"])
async def update_product(product_id: int, product: ProductCreate):
"""Обновить продукт"""
logger.info(f"Обновление продукта с ID: {product_id}")
if product_id not in products_db:
raise HTTPException(status_code=404, detail="Продукт не найден")
updated_product = {
"id": product_id,
"name": product.name,
"price": product.price,
"quantity": product.quantity
}
products_db[product_id] = updated_product
return updated_product
@app.delete("/products/{product_id}", tags=["Products"])
async def delete_product(product_id: int):
"""Удалить продукт"""
logger.info(f"Удаление продукта с ID: {product_id}")
if product_id not in products_db:
raise HTTPException(status_code=404, detail="Продукт не найден")
del products_db[product_id]
return {"message": f"Продукт {product_id} удален"}
# Endpoints для заказов
@app.get("/orders", response_model=List[Order], tags=["Orders"])
async def list_orders(skip: int = 0, limit: int = 10):
"""Получить список всех заказов"""
logger.info(f"Получение списка заказов (skip={skip}, limit={limit})")
orders_list = list(orders_db.values())
return orders_list[skip:skip + limit]
@app.get("/orders/{order_id}", response_model=Order, tags=["Orders"])
async def get_order(order_id: int):
"""Получить заказ по ID"""
logger.info(f"Получение заказа с ID: {order_id}")
if order_id not in orders_db:
logger.warning(f"Заказ с ID {order_id} не найден")
raise HTTPException(status_code=404, detail="Заказ не найден")
return orders_db[order_id]
@app.post("/orders", response_model=Order, tags=["Orders"])
async def create_order(order: OrderCreate):
"""Создать новый заказ"""
global next_order_id
logger.info(f"Создание нового заказа для пользователя {order.user_id}")
if order.product_id not in products_db:
raise HTTPException(status_code=404, detail="Продукт не найден")
product = products_db[order.product_id]
if product["quantity"] < order.quantity:
raise HTTPException(status_code=400, detail="Недостаточно товара на складе")
total_price = product["price"] * order.quantity
new_order = {
"id": next_order_id,
"user_id": order.user_id,
"product_id": order.product_id,
"quantity": order.quantity,
"total_price": total_price,
"created_at": datetime.utcnow().isoformat()
}
orders_db[next_order_id] = new_order
product["quantity"] -= order.quantity
next_order_id += 1
logger.info(f"Заказ создан с ID: {new_order['id']}")
return new_order
@app.get("/stats", tags=["Stats"])
async def get_stats():
"""Получить статистику по заказам"""
logger.info("Получение статистики")
total_revenue = sum(order["total_price"] for order in orders_db.values())
return {
"total_orders": len(orders_db),
"total_products": len(products_db),
"total_revenue": total_revenue,
"service": "order-service"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8002)