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 from fastapi.middleware.cors import CORSMiddleware
import uvicorn import uvicorn
from datetime import datetime from datetime import datetime
import httpx
import os
from typing import Any
app = FastAPI( app = FastAPI(
title="Console API Gateway", title="Console API Gateway",
@ -9,6 +12,9 @@ app = FastAPI(
version="0.1.0" version="0.1.0"
) )
# Service URLs from environment
USERS_SERVICE_URL = os.getenv("USERS_SERVICE_URL", "http://users-backend:8000")
# CORS middleware # CORS middleware
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@ -36,19 +42,66 @@ async def health_check():
@app.get("/api/status") @app.get("/api/status")
async def system_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 { return {
"console": "online", "console": "online",
"services": { "services": services_status,
"users": "pending",
"oauth": "pending",
"images": "pending",
"applications": "pending",
"data": "pending",
"statistics": "pending"
},
"timestamp": datetime.now().isoformat() "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__": if __name__ == "__main__":
uvicorn.run( uvicorn.run(
"main:app", "main:app",

View File

@ -11,11 +11,28 @@ services:
environment: environment:
- ENV=development - ENV=development
- PORT=8000 - PORT=8000
- USERS_SERVICE_URL=http://users-backend:8000
volumes: volumes:
- ./console/backend:/app - ./console/backend:/app
networks: networks:
- site11_network - site11_network
restart: unless-stopped 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: networks:
site11_network: site11_network:

View File

@ -5,8 +5,8 @@
## Current Status ## Current Status
- **Date Started**: 2025-09-09 - **Date Started**: 2025-09-09
- **Current Phase**: Step 1 Complete ✅ - **Current Phase**: Step 2 Complete ✅
- **Next Action**: Step 2 - Add First Service (Users) - **Next Action**: Step 3 - Database Integration (MongoDB)
## Completed Checkpoints ## Completed Checkpoints
✅ Project structure planning (CLAUDE.md) ✅ Project structure planning (CLAUDE.md)
@ -16,6 +16,11 @@
- docker-compose.yml created - docker-compose.yml created
- console/backend with FastAPI - console/backend with FastAPI
- Running on port 8011 - 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 ## 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