Redis练习

代码评审

1. 正确性:

  • 基本结构: 代码基本上实现了使用 SQLAlchemy 进行数据库操作,FastAPI 提供 API 端点,并使用 Redis 进行缓存。
  • 依赖管理: 使用了依赖注入 (Depends) 来管理数据库会话,这是一个好的实践。

2. 存在的问题和不足:

  • 数据库选择不符合需求: 需求中要求使用 MySQL,但你使用了 SQLite。需要更改为 MySQL。
  • 模型与 Pydantic 之间的转换:
    • create_order 函数中,你尝试将 Pydantic 的 OrderItem 直接添加到 SQLAlchemy 的 session 中,这是不正确的。应该将 OrderItem 转换为 Order 实例。
  • Redis 缓存逻辑不正确:
    • create_order 中,你使用 product_id 作为 Redis 的 key,并只缓存了 quantity。根据需求,应该缓存订单 ID 以及相关的订单信息。
  • get_order 函数不完整:
    • 代码中 get_order 函数未完成,实现不完整。应该根据 order_id 来获取订单,而不是 product_id
  • API 端点的问题:
    • /put_order 使用了 PUT 方法,通常用于更新操作。创建订单更适合使用 POST 方法。
    • /get_order 端点未完成,缺少具体实现。
  • 数据库会话管理不当:
    • 你在全局范围内创建了一个 session,这可能导致并发问题。应该通过依赖注入来管理数据库会话。
  • 表名使用单数形式:
    • 一般来说,数据库表名使用复数形式,例如 orders,以保持一致性和可读性。
  • 缺少日志记录和错误处理:
    • 代码中缺少日志记录和错误处理,这对于生产环境中的调试和维护非常重要。
  • Redis 客户端的同步使用:
    • 使用了同步的 Redis 客户端,可能会阻塞 FastAPI 的异步操作。建议使用异步的 Redis 客户端如 aioredis

改进建议

  1. 使用 MySQL 作为数据库: 修改数据库连接字符串以使用 MySQL,并确保数据库已正确配置。
  2. 模型转换:create_order 中,将 OrderItem 转换为 Order 实例再添加到数据库。
  3. 完善 Redis 缓存逻辑: 缓存完整的订单信息,使用 order_id 作为 key。
  4. 完成 get_order 函数: 根据 order_id 获取订单信息,先查询 Redis,如果缓存中没有,再查询数据库并缓存结果。
  5. 调整 API 端点: 使用 POST 方法创建订单,GET 方法获取订单。
  6. 优化数据库会话管理: 通过依赖注入管理数据库会话,避免使用全局 session。
  7. 添加日志记录和错误处理: 使用 logging 模块记录关键操作日志,并在适当的位置添加错误处理。
  8. 考虑使用异步 Redis 客户端: 提高性能,避免阻塞 FastAPI 的异步操作。

修正后的代码

以下是修正后的代码,使用 MySQL 作为数据库,并改进了其他问题:

from sqlalchemy import Column, Integer, DateTime
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base, Session
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
import aioredis
import json
import logging
from datetime import datetime

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

Base = declarative_base()

# 使用 MySQL 连接
DATABASE_URL = 'mysql+pymysql://username:password@localhost:3306/test_db'

engine = create_engine(DATABASE_URL, echo=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

app = FastAPI()

# 连接到Redis服务器(使用异步客户端)
redis_client = aioredis.from_url("redis://localhost:6379/0", decode_responses=True)

class Order(Base):
    __tablename__ = 'orders'

    id = Column(Integer, primary_key=True, index=True)
    user_id = Column(Integer, nullable=False)
    product_id = Column(Integer, nullable=False)
    quantity = Column(Integer, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)

# 创建数据库表
Base.metadata.create_all(bind=engine)

def get_db():
    """
    依赖函数,用于获取数据库会话
    """
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

class OrderCreate(BaseModel):
    user_id: int
    product_id: int
    quantity: int

class OrderResponse(BaseModel):
    id: int
    user_id: int
    product_id: int
    quantity: int
    created_at: datetime

    class Config:
        orm_mode = True

def create_order(db: Session, order_item: OrderCreate):
    order = Order(
        user_id=order_item.user_id,
        product_id=order_item.product_id,
        quantity=order_item.quantity
    )
    db.add(order)
    db.commit()
    db.refresh(order)
    # 将订单ID和订单信息存储到Redis中,使用 JSON 格式
    order_data = {
        "id": order.id,
        "user_id": order.user_id,
        "product_id": order.product_id,
        "quantity": order.quantity,
        "created_at": order.created_at.isoformat()
    }
    redis_client.set(f'order:{order.id}', json.dumps(order_data))
    logger.info(f'Order created and cached in Redis: {order.id}')
    return order

async def get_order_cache(order_id: int):
    order_json = await redis_client.get(f'order:{order_id}')
    if order_json:
        logger.info(f'Order {order_id} retrieved from Redis cache')
        return json.loads(order_json)
    return None

def get_order_db(db: Session, order_id: int):
    order = db.query(Order).filter(Order.id == order_id).first()
    if not order:
        raise HTTPException(status_code=404, detail="Order not found")
    return order

async def cache_order(order: Order):
    order_data = {
        "id": order.id,
        "user_id": order.user_id,
        "product_id": order.product_id,
        "quantity": order.quantity,
        "created_at": order.created_at.isoformat()
    }
    await redis_client.set(f'order:{order.id}', json.dumps(order_data))
    logger.info(f'Order {order.id} cached in Redis')

@app.post("/orders", response_model=OrderResponse)
def create_order_endpoint(order_item: OrderCreate, db: Session = Depends(get_db)):
    try:
        order = create_order(db, order_item)
        return order
    except Exception as e:
        logger.error(f'Error creating order: {e}')
        raise HTTPException(status_code=500, detail="Internal Server Error")

@app.get("/orders/{order_id}", response_model=OrderResponse)
async def get_order_endpoint(order_id: int, db: Session = Depends(get_db)):
    try:
        # 尝试从Redis获取
        order_data = await get_order_cache(order_id)
        if order_data:
            return OrderResponse(**order_data)
        # 如果Redis中没有,查询数据库
        order = get_order_db(db, order_id)
        # 缓存到Redis
        await cache_order(order)
        return OrderResponse.from_orm(order)
    except HTTPException as he:
        raise he
    except Exception as e:
        logger.error(f'Error retrieving order: {e}')
        raise HTTPException(status_code=500, detail="Internal Server Error")

主要改动和改进点

  1. 使用 MySQL:
    • 更改了数据库连接字符串为 MySQL (mysql+pymysql://username:password@localhost:3306/test_db)。请确保安装了 pymysql 库并替换 usernamepassword 为实际的数据库用户名和密码。
  2. 使用异步 Redis 客户端:
    • 使用了 aioredis 作为 Redis 客户端,避免阻塞 FastAPI 的异步操作。
  3. 模型和 Pydantic:
    • 定义了 OrderCreateOrderResponse 两个 Pydantic 模型,分别用于请求和响应。
    • OrderResponse 中添加了 Config 类以支持 ORM 模式。
  4. 会话管理:
    • 通过依赖注入 get_db 获取数据库会话,避免使用全局 session。
  5. 创建订单:
    • create_order 函数现在创建 Order 实例并添加到数据库,之后将订单数据以 JSON 格式缓存到 Redis。
  6. 获取订单:
    • get_order_endpoint 首先尝试从 Redis 获取订单数据,如果不存在则从数据库查询并缓存。
  7. 错误处理和日志记录:
    • 使用 logging 模块记录重要操作和错误,使用 HTTPException 返回适当的 HTTP 状态码。
  8. API 端点改进:
    • 使用 POST 方法创建订单,GET 方法获取订单,路径更符合 RESTful 风格。
  9. 数据序列化:
    • 订单数据在缓存时使用 JSON 序列化,方便存储和读取。

其他建议

  • 环境配置:
    • 将数据库和 Redis 的配置信息放入环境变量或配置文件中,避免在代码中硬编码。这可以通过使用 python-dotenv 或其他配置管理工具实现。
  • 异步数据库操作:
    • 考虑使用异步的数据库客户端(如 Databases 库)来进一步提升性能,尤其是在高并发场景下。
  • 测试:
    • 添加单元测试和集成测试,确保各个部分功能正确。可以使用 pytesthttpx 进行测试。
  • 安全性:
    • 考虑用户认证和授权,确保只有合法用户能够创建和获取订单。可以集成 OAuth2 或 JWT 认证机制。
  • 优化 Redis 缓存策略:
    • 设置缓存过期时间(TTL),以防止缓存数据长期占用内存。例如,可以在 set 操作中指定 ex 参数设置过期时间。
  • 性能监控:
    • 集成性能监控工具(如 Prometheus 和 Grafana),监控应用的性能和资源使用情况。

当然,下面我将详细解释你提到的两种依赖注入方式的区别,并说明为什么助手提供的方式在许多方面更为优越。

1. 函数定义类型(同步 vs 异步)

  • 你的代码:

    async def put_order(user_id, product_id, quantity, db = Depends(get_db)):
        ...
    

    你使用了 async def 定义异步函数。

  • 助手的代码:

    def create_order_endpoint(order_item: OrderCreate, db: Session = Depends(get_db)):
        ...
    

    助手使用了 def 定义同步函数。

区别与建议:

  • 同步函数 (def) vs 异步函数 (async def):
    • 同步函数适用于使用同步库(如同步版本的 SQLAlchemy 和 Redis 客户端)的情况。它们更简单,适用于大多数常见应用场景。
    • 异步函数在处理高并发 I/O 操作时更高效,适用于需要处理大量并发请求的应用。然而,使用异步函数需要确保所有依赖的库也支持异步操作(例如,使用 aioredis 代替同步的 redis 库,使用异步版本的 SQLAlchemy)。
  • 在你的情况下: 由于你使用的是同步的 SQLAlchemy 和 Redis 客户端,使用同步函数 (def) 更为合适。如果你希望利用异步的优势,可以考虑使用支持异步的库,如 async SQLAlchemyaioredis

2. 参数传递方式(单个参数 vs Pydantic 模型)

  • 你的代码:

    async def put_order(user_id, product_id, quantity, db = Depends(get_db)):
        ...
    

    你通过函数参数分别传递 user_idproduct_idquantity

  • 助手的代码:

    def create_order_endpoint(order_item: OrderCreate, db: Session = Depends(get_db)):
        ...
    

    助手通过 Pydantic 模型 OrderCreate 传递所有相关参数。

区别与建议:

  • 使用 Pydantic 模型:
    • 优点:
      • 数据验证: Pydantic 自动验证传入的数据,确保数据类型和必填字段的正确性,减少手动验证的工作。
      • 代码简洁: 使用模型可以减少函数参数的数量,使代码更清晰易读。
      • 文档生成: FastAPI 可以自动生成基于 Pydantic 模型的 API 文档,提升开发效率和可维护性。
    • 缺点:
      • 需要定义额外的模型类,但这种开销通常是值得的。
  • 单个参数传递:
    • 优点:
      • 简单直接,适用于参数较少且简单的情况。
    • 缺点:
      • 缺乏结构和自动验证,可能导致代码中需要额外的验证逻辑,增加复杂性。

推荐: 使用 Pydantic 模型来封装请求数据。这不仅提高了代码的结构性和可维护性,还利用了 FastAPI 强大的数据验证和文档生成功能。

3. 依赖注入的使用细节(类型注解)

  • 你的代码:

    async def put_order(user_id, product_id, quantity, db = Depends(get_db)):
        ...
    

    你在依赖注入中直接使用 Depends(get_db),但没有为 db 参数添加类型注解。

  • 助手的代码:

    def create_order_endpoint(order_item: OrderCreate, db: Session = Depends(get_db)):
        ...
    

    助手为 db 参数添加了类型注解 Session

区别与建议:

  • 类型注解的重要性:
    • 可读性: 类型注解使代码更具可读性,其他开发者可以更容易理解 db 是一个 Session 对象。
    • 静态类型检查: 使用类型注解可以利用静态类型检查工具(如 mypy)提前发现潜在的类型错误,提升代码质量。
    • 编辑器支持: 类型注解可以增强 IDE 和编辑器的自动补全和类型提示功能,提高开发效率。

推荐: 始终为依赖注入的参数添加类型注解。这不仅提升了代码的可读性和可维护性,还能利用工具提升代码质量。

4. 整体代码结构和最佳实践

助手的代码在以下方面进行了改进:

  • 使用 Pydantic 模型进行请求和响应的定义:
    • OrderCreate 用于创建订单的请求数据。
    • OrderResponse 用于响应订单数据,支持 ORM 模式(Config.orm_mode = True),方便将 SQLAlchemy 对象转换为 Pydantic 模型。
  • 优化数据库会话管理: 通过依赖注入 get_db 获取数据库会话,避免使用全局会话,防止并发问题。
  • 完善 Redis 缓存逻辑: 缓存完整的订单信息,并使用订单 ID 作为 key,方便后续的获取和管理。
  • 错误处理和日志记录: 使用 logging 模块记录关键操作和错误,提升代码的可维护性和可调试性。
  • RESTful API 设计: 使用 POST 方法创建订单,GET 方法获取订单,路径设计符合 RESTful 风格。

总结

主要区别:

  1. 函数类型:
    • 你的代码使用异步函数 (async def),助手的代码使用同步函数 (def)。对于同步的库,建议使用同步函数。
  2. 参数传递方式:
    • 你的代码通过单独的参数传递,助手的代码使用 Pydantic 模型进行封装,提升了数据验证和代码结构。
  3. 类型注解:
    • 你的代码缺乏类型注解,助手的代码为依赖注入的参数添加了类型注解,增强了代码的可读性和可维护性。
  4. 附加改进:
    • 助手的代码包含错误处理、日志记录和更完善的缓存逻辑,提升了代码的健壮性和可维护性。

建议:

  • 选择合适的函数类型:
    • 对于使用同步库的情况,使用同步函数 (def) 更为合适。
    • 如果需要高并发性能,可以考虑使用异步库并编写异步函数 (async def)。
  • 使用 Pydantic 模型:
    • 封装请求数据,利用 Pydantic 的数据验证和自动文档生成功能,提升代码的结构性和可靠性。
  • 添加类型注解:
    • 为依赖注入的参数添加类型注解,提高代码的可读性和静态类型检查的效果。
  • 遵循最佳实践:
    • 实现完善的错误处理和日志记录。
    • 设计符合 RESTful 风格的 API 端点。
    • 优化数据库和缓存的使用逻辑,确保高效和可靠。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值