Blueprints와 라우터를 사용한 확장 가능한 Python 웹 애플리케이션 구축
Wenhao Wang
Dev Intern · Leapcell

확장 가능한 Python 웹 애플리케이션 구축
웹 개발의 세계에서 유지보수, 이해 및 확장이 용이한 애플리케이션을 만드는 것은 무엇보다 중요합니다. 프로젝트가 몇 가지 간단한 엔드포인트에서 다양한 기능을 처리하는 복잡한 시스템으로 성장함에 따라, 지능적인 조직 구조가 없어서는 안 될 것이 됩니다. 코드베이스를 구성하는 것에 대한 신중한 접근 방식 없이는, 가장 세련된 솔루션조차도 빠르게 관리 불가능한 혼란으로 전락할 수 있습니다. 이 글에서는 Flask의 Blueprints와 FastAPI의 APIRouters가 Python 웹 애플리케이션의 모듈성과 확장성을 달성하기 위한 훌륭한 메커니즘을 제공하여, 잠재적인 혼란을 구조화된 세련됨으로 변화시키는 방법을 살펴봅니다.
모듈성의 핵심 개념 이해
Flask Blueprints와 FastAPI Routers의 구체적인 내용으로 들어가기 전에, 그 유용성을 뒷받침하는 핵심 개념에 대한 일반적인 이해를 확립해 보겠습니다.
- 모듈성(Modularity): 이는 시스템의 구성 요소를 분리하고 재결합할 수 있는 정도를 의미합니다. 소프트웨어에서는 대규모 애플리케이션을 작고, 독립적이며, 상호 교환 가능한 모듈로 분해하는 것을 의미합니다. 그런 다음 각 모듈은 특정 기능 조각에 집중할 수 있어 전체 시스템을 더 쉽게 이해하고, 테스트하고, 디버깅할 수 있습니다.
- 확장성(Scalability): 소프트웨어의 맥락에서 확장성은 종종 애플리케이션이 증가하는 양의 작업을 처리하거나 기능 또는 사용자 기반 측면에서 쉽게 확장될 수 있는 능력을 의미합니다. 모듈식 설계는 개발자가 상당한 충돌 없이 애플리케이션의 다른 부분에서 동시에 작업할 수 있도록 하고, 관련 없는 구성 요소에 영향을 주지 않고 새 기능을 추가하거나 기존 기능을 수정하기 쉽게 함으로써 확장성에 직접적으로 기여합니다.
- 관심사의 분리(Separation of Concerns): 이는 컴퓨터 프로그램을 별개의 섹션으로 분리하는 것을 옹호하는 설계 원칙으로, 각 섹션은 별도의 관심사를 다룹니다. 웹 애플리케이션의 경우, 사용자 관리, 제품 카탈로그, 결제 처리 및 관리 기능을 서로 다른, 독립적으로 유지되는 단위로 분리하는 것을 의미할 수 있습니다.
Flask의 Blueprints와 FastAPI의 APIRouters는 이러한 원칙을 구현하는 강력한 도구로, 개발자가 잘 구성되고 확장 가능한 애플리케이션을 구축할 수 있도록 합니다.
Flask Blueprints: 애플리케이션 모듈화
가벼운 마이크로 프레임워크인 Flask는 모듈성 달성을 위한 주요 메커니즘으로 Blueprints를 제공합니다. Blueprint는 본질적으로 Flask 애플리케이션에 등록될 수 있는 일련의 작업(라우트, 정적 파일, 템플릿, 오류 핸들러 등)을 정의할 수 있게 해줍니다. 이는 더 큰 Flask 애플리케이션에 플러그인으로 연결할 수 있는 미니 애플리케이션과 같습니다.
실제 예시로 이를 설명해 보겠습니다. users
와 products
에 대한 별도의 모듈이 필요한 전자 상거래 애플리케이션을 상상해 보세요.
먼저 app/users/routes.py
에 users
blueprint를 정의합니다.
# app/users/routes.py from flask import Blueprint, jsonify, request users_bp = Blueprint('users', __name__, url_prefix='/users') @users_bp.route('/', methods=['GET']) def get_users(): # 실제 앱에서는 데이터베이스에서 사용자를 가져올 것입니다 return jsonify({"message": "List of users"}), 200 @users_bp.route('/<int:user_id>', methods=['GET']) def get_user(user_id): return jsonify({"message": f"Details for user {user_id}"}), 200 @users_bp.route('/', methods=['POST']) def create_user(): data = request.get_json() return jsonify({"message": "User created", "data": data}), 201
다음으로 app/products/routes.py
에 products
blueprint를 정의합니다.
# app/products/routes.py from flask import Blueprint, jsonify, request products_bp = Blueprint('products', __name__, url_prefix='/products') @products_bp.route('/', methods=['GET']) def get_products(): return jsonify({"message": "List of products"}), 200 @products_bp.route('/<int:product_id>', methods=['GET']) def get_product(product_id): return jsonify({"message": f"Details for product {product_id}"}), 200
마지막으로 app.py
에서 메인 Flask 애플리케이션에 이러한 blueprint를 등록합니다.
# app.py from flask import Flask from app.users.routes import users_bp from app.products.routes import products_bp def create_app(): app = Flask(__name__) # Blueprints 등록 app.register_blueprint(users_bp) app.register_blueprint(products_bp) @app.route('/') def index(): return "Welcome to the E-commerce API!" return app if __name__ == '__main__': app = create_app() app.run(debug=True)
작동 방식:
- 각 Blueprint는 자체
url_prefix
를 기준으로 하는 일련의 라우트를 정의합니다. 예를 들어,users_bp
는/users/
및/users/<user_id>
요청을 처리합니다. __name__
인수는 Flask가 blueprint와 관련된 리소스(템플릿 또는 정적 파일 등, 이 간단한 예에서는 표시되지 않음)를 찾는 데 도움이 됩니다.- 메인
app
에users_bp
와products_bp
를 등록함으로써, 정의된 모든 라우트는 메인 애플리케이션의 라우팅 테이블의 일부가 됩니다.
이 구조는 다른 팀이나 개발자가 users
모듈과 products
모듈에서 서로의 코드와 간섭 없이 독립적으로 작업할 수 있도록 합니다.
FastAPI Routers: 현대적인 모듈식 디자인
Starlette와 Pydantic를 기반으로 구축된 FastAPI는 내장된 데이터 유효성 검사 및 문서화를 통해 비동기 웹 개발에 대한 보다 현대적인 접근 방식을 제공합니다. Flask의 Blueprints에 해당하는 것은 APIRouter입니다. FastAPI의 라우터는 엔드포인트를 구성하고 여러 라우트에 걸쳐 공통 구성을 공유하는 동일한 목적을 수행하여 애플리케이션을 모듈식으로 유지보수하기 쉽게 만듭니다.
FastAPI의 APIRouters를 사용하여 전자 상거래 예제를 리팩토링해 보겠습니다.
먼저 app/users/routes.py
에 users
라우터를 정의합니다.
# app/users/routes.py from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import List router = APIRouter( prefix="/users", tags=["users"], responses={404: {"description": "Not found"}}, ) class User(BaseModel): id: int name: str email: str # 실제 앱에서는 이것이 데이터베이스 호출일 것입니다 fake_users_db = { 1: {"name": "Alice", "email": "alice@example.com"}, 2: {"name": "Bob", "email": "bob@example.com"}, } @router.get("/", response_model=List[User]) async def read_users(): return [{"id": k, **v} for k, v in fake_users_db.items()] @router.get("/{user_id}", response_model=User) async def read_user(user_id: int): if user_id not in fake_users_db: raise HTTPException(status_code=404, detail="User not found") return {"id": user_id, **fake_users_db[user_id]} @router.post("/", response_model=User, status_code=201) async def create_user(user: User): if user.id in fake_users_db: raise HTTPException(status_code=409, detail="User with this ID already exists") fake_users_db[user.id] = {"name": user.name, "email": user.email} return user
다음으로 app/products/routes.py
에 products
라우터를 정의합니다.
# app/products/routes.py from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import List router = APIRouter( prefix="/products", tags=["products"], responses={404: {"description": "Not found"}}, ) class Product(BaseModel): id: int name: str price: float # 실제 앱에서는 이것이 데이터베이스 호출일 것입니다 fake_products_db = { 101: {"name": "Laptop", "price": 1200.0}, 102: {"name": "Mouse", "price": 25.0}, } @router.get("/", response_model=List[Product]) async def read_products(): return [{"id": k, **v} for k, v in fake_products_db.items()] @router.get("/{product_id}", response_model=Product) async def read_product(product_id: int): if product_id not in fake_products_db: raise HTTPException(status_code=404, detail="Product not found") return {"id": product_id, **fake_products_db[product_id]}
마지막으로 main.py
의 메인 FastAPI 애플리케이션에 이러한 라우터를 포함합니다.
# main.py from fastapi import FastAPI from app.users.routes import router as users_router from app.products.routes import router as products_router app = FastAPI( title="E-commerce API", description="A simple e-commerce API built with FastAPI.", version="0.0.1", ) # 라우터 포함 app.include_router(users_router) app.include_router(products_router) @app.get("/") async def read_root(): return {"message": "Welcome to the E-commerce API!"} if __name__ == '__main__': import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
작동 방식:
- 각
APIRouter
인스턴스는 경로 작업 모음을 정의합니다. prefix
인수는 Flask의url_prefix
와 유사하게 해당 라우터 내에 정의된 모든 라우트에 URL 접두사를 자동으로 적용합니다.tags
는 자동 API 문서화(Swagger/OpenAPI UI)에 사용되어 관련 엔드포인트를 그룹화하기 쉽게 만듭니다.responses
는 라우터의 모든 라우트에 대한 공통 응답 스키마를 정의할 수 있습니다.app.include_router()
는 정의된 라우터를 메인 FastAPI 애플리케이션으로 통합합니다.
FastAPI의 설계는 요청 및 응답 유효성 검사를 위한 Pydantic
모델, 더 명확한 코드를 위한 유형 힌트, 효율적인 I/O 작업을 위한 async/await
의 사용을 장려하며, 이 모든 것이 라우터 개념과 자연스럽게 통합됩니다.
이것이 확장성과 유지보수성에 중요한 이유
Flask Blueprints와 FastAPI Routers 모두 애플리케이션 개발의 중요한 측면을 다룹니다.
- 코드 구성: 메인 애플리케이션 파일이 단일 거대한 파일이 되는 것을 방지합니다. 각 기능 또는 도메인은 자체 디렉터리에 자체 blueprint/router, 모델 및 비즈니스 로직과 함께 존재할 수 있습니다.
- 충돌 감소: 여러 개발자나 팀이 애플리케이션의 다른 부분에서 작업할 때, 모듈화는 버전 제어 병합 및 개발 중에 충돌을 최소화합니다.
- 재사용성: Blueprint와 Router는 재사용 가능한 기능을 캡슐화할 수 있습니다. 예를 들어,
admin
blueprint 또는 router는 한 번 개발한 다음 여러 애플리케이션에 등록할 수 있습니다. - 테스트 용이성: 작고 독립적인 모듈은 격리하여 테스트하기가 더 쉬워 더 강력한 테스트 스위트를 생성합니다.
- 쉬운 온보딩: 새로운 팀 구성원은 전체 애플리케이션의 복잡성을 한 번에 파악할 필요 없이 코드베이스의 특정 부분을 더 빠르게 이해하고 기여할 수 있습니다.
- 성능 및 배포(간접적): 런타임 성능에 직접적인 영향을 미치지는 않지만, 모듈식 구조는 병목 현상을 식별하고, 특정 구성 요소를 최적화하고, 향후 필요에 따라 특정 모듈을 마이크로 서비스로 배포하는 것을 더 쉽게 만듭니다.
결론
확장 가능한 Flask 또는 FastAPI 애플리케이션을 효율적으로 구성하는 것은 각기 해당하는 모듈화 도구(Blueprints와 APIRouters)를 활용하는 데 달려 있습니다. 이러한 기능은 개발자가 복잡한 애플리케이션을 더 작고, 관리 가능하며, 독립적으로 배포 또는 유지보수할 수 있는 단위로 분해할 수 있도록 하여, 궁극적으로 더 강력하고, 확장 가능하며, 협업적인 개발 환경을 조성합니다. 이러한 패턴을 채택함으로써, 우아하게 성장하고 발전할 수 있는 애플리케이션을 위한 견고한 기초를 마련하게 됩니다.