Step 2: Add Users microservice with API Gateway routing

- Created Users service with full CRUD operations
- Updated Console to act as API Gateway for Users service
- Implemented service-to-service communication
- Added service health monitoring in Console
- Docker Compose now manages both services

Services running:
- Console (API Gateway): http://localhost:8011
- Users service: Internal network only

Test endpoints:
- Status: curl http://localhost:8011/api/status
- Users: curl http://localhost:8011/api/users/users

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jungwoo choi
2025-09-10 16:09:36 +09:00
parent 52b76d0e77
commit 7559c4c5a8
6 changed files with 237 additions and 11 deletions

View File

@ -1,7 +1,10 @@
from fastapi import FastAPI
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from datetime import datetime
import httpx
import os
from typing import Any
app = FastAPI(
title="Console API Gateway",
@ -9,6 +12,9 @@ app = FastAPI(
version="0.1.0"
)
# Service URLs from environment
USERS_SERVICE_URL = os.getenv("USERS_SERVICE_URL", "http://users-backend:8000")
# CORS middleware
app.add_middleware(
CORSMiddleware,
@ -36,19 +42,66 @@ async def health_check():
@app.get("/api/status")
async def system_status():
services_status = {}
# Check Users service
try:
async with httpx.AsyncClient() as client:
response = await client.get(f"{USERS_SERVICE_URL}/health", timeout=2.0)
services_status["users"] = "online" if response.status_code == 200 else "error"
except:
services_status["users"] = "offline"
# Other services (not yet implemented)
services_status["oauth"] = "pending"
services_status["images"] = "pending"
services_status["applications"] = "pending"
services_status["data"] = "pending"
services_status["statistics"] = "pending"
return {
"console": "online",
"services": {
"users": "pending",
"oauth": "pending",
"images": "pending",
"applications": "pending",
"data": "pending",
"statistics": "pending"
},
"services": services_status,
"timestamp": datetime.now().isoformat()
}
# API Gateway - Route to Users service
@app.api_route("/api/users/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
async def proxy_to_users(path: str, request: Request):
"""Proxy requests to Users service"""
try:
async with httpx.AsyncClient() as client:
# Build the target URL
url = f"{USERS_SERVICE_URL}/{path}"
# Get request body if exists
body = None
if request.method in ["POST", "PUT", "PATCH"]:
body = await request.body()
# Forward the request
response = await client.request(
method=request.method,
url=url,
headers={
key: value for key, value in request.headers.items()
if key.lower() not in ["host", "content-length"]
},
content=body,
params=request.query_params
)
# Return the response
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers)
)
except httpx.ConnectError:
raise HTTPException(status_code=503, detail="Users service unavailable")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
uvicorn.run(
"main:app",

View File

@ -11,11 +11,28 @@ services:
environment:
- ENV=development
- PORT=8000
- USERS_SERVICE_URL=http://users-backend:8000
volumes:
- ./console/backend:/app
networks:
- site11_network
restart: unless-stopped
depends_on:
- users-backend
users-backend:
build:
context: ./services/users/backend
dockerfile: Dockerfile
container_name: site11_users_backend
environment:
- ENV=development
- PORT=8000
volumes:
- ./services/users/backend:/app
networks:
- site11_network
restart: unless-stopped
networks:
site11_network:

View File

@ -5,8 +5,8 @@
## Current Status
- **Date Started**: 2025-09-09
- **Current Phase**: Step 1 Complete ✅
- **Next Action**: Step 2 - Add First Service (Users)
- **Current Phase**: Step 2 Complete ✅
- **Next Action**: Step 3 - Database Integration (MongoDB)
## Completed Checkpoints
✅ Project structure planning (CLAUDE.md)
@ -16,6 +16,11 @@
- docker-compose.yml created
- console/backend with FastAPI
- Running on port 8011
✅ Step 2: Add First Service (Users)
- Users service with CRUD operations
- Console API Gateway routing to Users
- Service communication verified
- Test: curl http://localhost:8011/api/users/users
## Active Working Files
```

View File

@ -0,0 +1,21 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first for better caching
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Run the application
CMD ["python", "main.py"]

View File

@ -0,0 +1,127 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
import uvicorn
app = FastAPI(
title="Users Service",
description="User management microservice",
version="0.1.0"
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# In-memory storage for now (will be replaced with MongoDB later)
users_db = {}
user_id_counter = 1
# Pydantic models
class UserCreate(BaseModel):
username: str
email: str
full_name: Optional[str] = None
class UserUpdate(BaseModel):
username: Optional[str] = None
email: Optional[str] = None
full_name: Optional[str] = None
class User(BaseModel):
id: int
username: str
email: str
full_name: Optional[str] = None
created_at: datetime
updated_at: datetime
# Health check
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"service": "users",
"timestamp": datetime.now().isoformat()
}
# CRUD Operations
@app.get("/users", response_model=List[User])
async def get_users():
return list(users_db.values())
@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: int):
if user_id not in users_db:
raise HTTPException(status_code=404, detail="User not found")
return users_db[user_id]
@app.post("/users", response_model=User, status_code=201)
async def create_user(user: UserCreate):
global user_id_counter
# Check if username already exists
for existing_user in users_db.values():
if existing_user["username"] == user.username:
raise HTTPException(status_code=400, detail="Username already exists")
new_user = {
"id": user_id_counter,
"username": user.username,
"email": user.email,
"full_name": user.full_name,
"created_at": datetime.now(),
"updated_at": datetime.now()
}
users_db[user_id_counter] = new_user
user_id_counter += 1
return new_user
@app.put("/users/{user_id}", response_model=User)
async def update_user(user_id: int, user_update: UserUpdate):
if user_id not in users_db:
raise HTTPException(status_code=404, detail="User not found")
user = users_db[user_id]
if user_update.username is not None:
# Check if new username already exists
for uid, existing_user in users_db.items():
if uid != user_id and existing_user["username"] == user_update.username:
raise HTTPException(status_code=400, detail="Username already exists")
user["username"] = user_update.username
if user_update.email is not None:
user["email"] = user_update.email
if user_update.full_name is not None:
user["full_name"] = user_update.full_name
user["updated_at"] = datetime.now()
return user
@app.delete("/users/{user_id}")
async def delete_user(user_id: int):
if user_id not in users_db:
raise HTTPException(status_code=404, detail="User not found")
del users_db[user_id]
return {"message": "User deleted successfully"}
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True
)

View File

@ -0,0 +1,3 @@
fastapi==0.109.0
uvicorn[standard]==0.27.0
pydantic==2.5.3