1
Current Location:
>
Microservices
Practical Microservices Architecture with Python: Building a Highly Available User Service System from Scratch
Release time:2024-11-14 00:06:02 read 35
Copyright Statement: This article is an original work of the website and follows the CC 4.0 BY-SA copyright agreement. Please include the original source link and this statement when reprinting.

Article link: https://yigebao.com/en/content/aid/1840

Opening Message

Do you often hear the term microservices but feel it's somewhat abstract? Today, I'll guide you step-by-step to implement a real microservices system in the simplest way. As a developer who has been coding in Python for many years, I know the pitfalls you'll encounter transitioning from monolithic applications to microservices. Let's embark on this learning journey.

Core Concepts

Microservices sound grand, but they really just involve breaking a large application into multiple smaller services. It's like going to a restaurant where the kitchen is divided into different work areas: cold dishes, hot dishes, pastry, etc., each focusing on its own tasks. This not only increases efficiency but also ensures that even if one section encounters an issue, it doesn't affect the entire restaurant's operation.

In our practical project, we'll implement a user service system. You might ask, why choose user services? Because almost all systems require user management, and its functionality is relatively independent, making it an ideal introductory example for microservices.

Technology Selection

Before we start coding, we need to decide on the technology stack. Based on years of practice, I especially recommend the following combination:

  1. FastAPI as the web framework
  2. SQLAlchemy for database operations
  3. Redis for caching
  4. Docker for containerized deployment

Why choose FastAPI over Django or Flask? Mainly for the following reasons:

  • Exceptional performance: FastAPI is based on an asynchronous framework, offering better performance than Flask and Django.
  • Type hints: Built-in type checking helps us avoid many potential bugs.
  • Automatic documentation: Generates beautiful API documentation without extra configuration.

Code Implementation

Let's start with the most basic user service:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional
import uvicorn
import asyncio
import redis.asyncio as redis
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker, declarative_base


app = FastAPI(title="User Service")


redis_pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
redis_client = redis.Redis(connection_pool=redis_pool)


DATABASE_URL = "postgresql+asyncpg://user:password@localhost/db"
engine = create_async_engine(DATABASE_URL, echo=True)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)


Base = declarative_base()

class UserModel(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    username = Column(String, unique=True)
    email = Column(String, unique=True)
    hashed_password = Column(String)
    is_active = Column(Boolean, default=True)


class UserCreate(BaseModel):
    username: str
    email: str
    password: str

class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    is_active: bool

@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate):
    async with async_session() as session:
        # Check if user already exists
        existing_user = await session.execute(
            select(UserModel).where(
                or_(
                    UserModel.username == user.username,
                    UserModel.email == user.email
                )
            )
        )
        if existing_user.scalar_one_or_none():
            raise HTTPException(status_code=400, detail="User already exists")

        # Create a new user
        db_user = UserModel(
            username=user.username,
            email=user.email,
            hashed_password=get_password_hash(user.password)
        )
        session.add(db_user)
        await session.commit()
        await session.refresh(db_user)

        # Update cache
        user_cache = UserResponse(
            id=db_user.id,
            username=db_user.username,
            email=db_user.email,
            is_active=db_user.is_active
        )
        await redis_client.setex(
            f"user:{db_user.id}",
            3600,  # Expires in 1 hour
            user_cache.json()
        )

        return user_cache

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Performance Optimization

In actual development, I've found several areas that particularly impact performance:

  1. Database query optimization
  2. Caching strategy
  3. Concurrency handling

Let's look at how to optimize each one.

Database Query Optimization

@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    # Check cache first
    cached_user = await redis_client.get(f"user:{user_id}")
    if cached_user:
        return UserResponse.parse_raw(cached_user)

    # Cache miss, query database
    async with async_session() as session:
        result = await session.execute(
            select(UserModel)
            .options(selectinload(UserModel.roles))  # Preload related data
            .where(UserModel.id == user_id)
        )
        user = result.scalar_one_or_none()
        if not user:
            raise HTTPException(status_code=404, detail="User not found")

        # Update cache
        user_response = UserResponse.from_orm(user)
        await redis_client.setex(
            f"user:{user_id}",
            3600,
            user_response.json()
        )
        return user_response

Practical Experience

In real projects, I've encountered an issue where the user service experiences a surge in database connections, causing the system to slow down. The solutions are:

  1. Use a connection pool to manage database connections
  2. Implement circuit breakers
  3. Add monitoring alerts

These are practical experiences you can directly apply.

Future Outlook

Microservices architecture is continuously evolving. I believe several future directions are worth attention:

  1. Application of Service Mesh
  2. Integration with Serverless architecture
  3. Further simplification of containerized deployment

What do you think? Feel free to share your thoughts in the comments.

Summary

Today, we built a basic user service system from scratch. Remember, microservices are not the goal but a means. Choosing a microservices architecture should be based on actual needs, not just for the sake of using microservices.

Do you now have a clearer understanding of microservices architecture? If you have any questions, feel free to leave a comment. Next time, we'll delve into the communication mechanisms between services, so stay tuned.

Python Microservices Architecture: A Practical Guide from Basics to Mastery
Previous
2024-11-13 05:06:01
Python Microservices in Action: Building a Highly Available E-commerce Payment System from Scratch
2024-11-25 09:15:54
Next
Related articles