Step 3: MongoDB and Redis integration complete

- Added MongoDB and Redis containers to docker-compose
- Integrated Users service with MongoDB using Beanie ODM
- Replaced in-memory storage with persistent MongoDB
- Added proper data models with email validation
- Verified data persistence with MongoDB ObjectIDs

Services running:
- MongoDB: Port 27017 (with health checks)
- Redis: Port 6379 (with health checks)
- Users service: Connected to MongoDB
- Console: API Gateway routing working

Test: Users now stored in MongoDB with persistence

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jungwoo choi
2025-09-10 16:21:32 +09:00
parent 7559c4c5a8
commit 683305918c
6 changed files with 218 additions and 71 deletions

View File

@ -28,13 +28,57 @@ services:
environment: environment:
- ENV=development - ENV=development
- PORT=8000 - PORT=8000
- MONGODB_URL=mongodb://mongodb:27017
- DB_NAME=users_db
volumes: volumes:
- ./services/users/backend:/app - ./services/users/backend:/app
networks: networks:
- site11_network - site11_network
restart: unless-stopped restart: unless-stopped
depends_on:
- mongodb
mongodb:
image: mongo:7.0
container_name: site11_mongodb
environment:
- MONGO_INITDB_DATABASE=site11_db
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
- mongodb_config:/data/configdb
networks:
- site11_network
restart: unless-stopped
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: site11_redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- site11_network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks: networks:
site11_network: site11_network:
driver: bridge driver: bridge
name: site11_network name: site11_network
volumes:
mongodb_data:
mongodb_config:
redis_data:

View File

@ -5,8 +5,8 @@
## Current Status ## Current Status
- **Date Started**: 2025-09-09 - **Date Started**: 2025-09-09
- **Current Phase**: Step 2 Complete ✅ - **Current Phase**: Step 3 Complete ✅
- **Next Action**: Step 3 - Database Integration (MongoDB) - **Next Action**: Step 4 - Frontend Skeleton
## Completed Checkpoints ## Completed Checkpoints
✅ Project structure planning (CLAUDE.md) ✅ Project structure planning (CLAUDE.md)
@ -21,6 +21,11 @@
- Console API Gateway routing to Users - Console API Gateway routing to Users
- Service communication verified - Service communication verified
- Test: curl http://localhost:8011/api/users/users - Test: curl http://localhost:8011/api/users/users
✅ Step 3: Database Integration
- MongoDB and Redis containers added
- Users service using MongoDB with Beanie ODM
- Data persistence verified
- MongoDB IDs: 68c126c0bbbe52be68495933
## Active Working Files ## Active Working Files
``` ```

View File

@ -0,0 +1,22 @@
from motor.motor_asyncio import AsyncIOMotorClient
from beanie import init_beanie
import os
from models import User
async def init_db():
"""Initialize database connection"""
# Get MongoDB URL from environment
mongodb_url = os.getenv("MONGODB_URL", "mongodb://mongodb:27017")
db_name = os.getenv("DB_NAME", "users_db")
# Create Motor client
client = AsyncIOMotorClient(mongodb_url)
# Initialize beanie with the User model
await init_beanie(
database=client[db_name],
document_models=[User]
)
print(f"Connected to MongoDB: {mongodb_url}/{db_name}")

View File

@ -4,11 +4,46 @@ from pydantic import BaseModel
from typing import List, Optional from typing import List, Optional
from datetime import datetime from datetime import datetime
import uvicorn import uvicorn
from contextlib import asynccontextmanager
from database import init_db
from models import User
from beanie import PydanticObjectId
# Pydantic models for requests
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 UserResponse(BaseModel):
id: str
username: str
email: str
full_name: Optional[str] = None
created_at: datetime
updated_at: datetime
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
await init_db()
yield
# Shutdown
pass
app = FastAPI( app = FastAPI(
title="Users Service", title="Users Service",
description="User management microservice", description="User management microservice with MongoDB",
version="0.1.0" version="0.2.0",
lifespan=lifespan
) )
# CORS middleware # CORS middleware
@ -20,29 +55,6 @@ app.add_middleware(
allow_headers=["*"], 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 # Health check
@app.get("/health") @app.get("/health")
async def health_check(): async def health_check():
@ -53,70 +65,107 @@ async def health_check():
} }
# CRUD Operations # CRUD Operations
@app.get("/users", response_model=List[User]) @app.get("/users", response_model=List[UserResponse])
async def get_users(): async def get_users():
return list(users_db.values()) users = await User.find_all().to_list()
return [UserResponse(
id=str(user.id),
username=user.username,
email=user.email,
full_name=user.full_name,
created_at=user.created_at,
updated_at=user.updated_at
) for user in users]
@app.get("/users/{user_id}", response_model=User) @app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int): async def get_user(user_id: str):
if user_id not in users_db: try:
user = await User.get(PydanticObjectId(user_id))
if not user:
raise HTTPException(status_code=404, detail="User not found")
return UserResponse(
id=str(user.id),
username=user.username,
email=user.email,
full_name=user.full_name,
created_at=user.created_at,
updated_at=user.updated_at
)
except Exception:
raise HTTPException(status_code=404, detail="User not found") 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
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(user_data: UserCreate):
# Check if username already exists # Check if username already exists
for existing_user in users_db.values(): existing_user = await User.find_one(User.username == user_data.username)
if existing_user["username"] == user.username: if existing_user:
raise HTTPException(status_code=400, detail="Username already exists") raise HTTPException(status_code=400, detail="Username already exists")
new_user = { # Create new user
"id": user_id_counter, user = User(
"username": user.username, username=user_data.username,
"email": user.email, email=user_data.email,
"full_name": user.full_name, full_name=user_data.full_name
"created_at": datetime.now(), )
"updated_at": datetime.now()
}
users_db[user_id_counter] = new_user await user.create()
user_id_counter += 1
return new_user return UserResponse(
id=str(user.id),
username=user.username,
email=user.email,
full_name=user.full_name,
created_at=user.created_at,
updated_at=user.updated_at
)
@app.put("/users/{user_id}", response_model=User) @app.put("/users/{user_id}", response_model=UserResponse)
async def update_user(user_id: int, user_update: UserUpdate): async def update_user(user_id: str, user_update: UserUpdate):
if user_id not in users_db: try:
user = await User.get(PydanticObjectId(user_id))
if not user:
raise HTTPException(status_code=404, detail="User not found")
except Exception:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
user = users_db[user_id]
if user_update.username is not None: if user_update.username is not None:
# Check if new username already exists # Check if new username already exists
for uid, existing_user in users_db.items(): existing_user = await User.find_one(
if uid != user_id and existing_user["username"] == user_update.username: User.username == user_update.username,
User.id != user.id
)
if existing_user:
raise HTTPException(status_code=400, detail="Username already exists") raise HTTPException(status_code=400, detail="Username already exists")
user["username"] = user_update.username user.username = user_update.username
if user_update.email is not None: if user_update.email is not None:
user["email"] = user_update.email user.email = user_update.email
if user_update.full_name is not None: if user_update.full_name is not None:
user["full_name"] = user_update.full_name user.full_name = user_update.full_name
user["updated_at"] = datetime.now() user.updated_at = datetime.now()
await user.save()
return user return UserResponse(
id=str(user.id),
username=user.username,
email=user.email,
full_name=user.full_name,
created_at=user.created_at,
updated_at=user.updated_at
)
@app.delete("/users/{user_id}") @app.delete("/users/{user_id}")
async def delete_user(user_id: int): async def delete_user(user_id: str):
if user_id not in users_db: try:
user = await User.get(PydanticObjectId(user_id))
if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
await user.delete()
del users_db[user_id]
return {"message": "User deleted successfully"} return {"message": "User deleted successfully"}
except Exception:
raise HTTPException(status_code=404, detail="User not found")
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run( uvicorn.run(

View File

@ -0,0 +1,24 @@
from beanie import Document
from pydantic import EmailStr, Field
from datetime import datetime
from typing import Optional
class User(Document):
username: str = Field(..., unique=True)
email: EmailStr
full_name: Optional[str] = None
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
class Settings:
collection = "users"
class Config:
json_schema_extra = {
"example": {
"username": "john_doe",
"email": "john@example.com",
"full_name": "John Doe"
}
}

View File

@ -1,3 +1,6 @@
fastapi==0.109.0 fastapi==0.109.0
uvicorn[standard]==0.27.0 uvicorn[standard]==0.27.0
pydantic==2.5.3 pydantic[email]==2.5.3
pymongo==4.6.1
motor==3.3.2
beanie==1.23.6