You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI+Jinja2开发Web应用如何遵循DRY原则避免重复编码

FastAPI 接口与前端路由逻辑复用优化方案

方案1:抽离公共业务处理函数(最易落地,零架构调整)

你当前重复的逻辑是「参数接收→过滤器构造→调用CRUD拿数据」这一段,完全可以把这部分从两个路由里抽出来,放到crud/article.py或者单独的services/article.py里作为公共方法。
举个例子,你可以在crud/article.py里新增公共方法:

# crud/article.py 新增代码
async def fetch_articles_by_filter(tag: str, db: AsyncIOMotorClient) -> DisplayArticles:
    filters = ArticleFilters(tag=tag)
    dbarticles = await get_articles_with_filters(db, filters)
    return DisplayArticles(articles=dbarticles, articles_count=len(dbarticles))

之后API路由可以简化为:

# api/api_v1/endpoints/article.py
@router.get("/articles", response_model=DisplayArticles, tags=["articles"])
async def get_articles(
    tag: str = "",
    db: AsyncIOMotorClient = Depends(get_database),
):
    article_data = await fetch_articles_by_filter(tag, db)
    return create_aliased_response(article_data)

对应的webapp路由直接复用同一个公共方法即可:

# webapp/article/article.py
@router.get("/articles", tags=["webapp_articles"])
async def webapp_get_articles(
    tag: str = "",
    db: AsyncIOMotorClient = Depends(get_database),
    request: Request = Depends(),
):
    article_data = await fetch_articles_by_filter(tag, db)
    return templates.TemplateResponse(
        "article_list.html",
        context={"request": request, "articles": article_data.articles, "count": article_data.articles_count}
    )

这个方案不需要调整现有架构,只要把重复的业务逻辑抽成公共方法就行,后续加新接口只要先写公共方法,再分别写API和webapp的路由返回逻辑,完全不会出现代码重复。

方案2:同一路由兼容多返回类型(适合小项目快速迭代)

如果你不想维护两套路由,可以直接在同一个路由里根据请求头的Accept字段判断返回JSON还是HTML,不需要写两个不同的路由函数:

# 可以放在共享路由文件,或者直接放在API层
@router.get("/articles", tags=["articles"])
async def get_articles(
    tag: str = "",
    db: AsyncIOMotorClient = Depends(get_database),
    request: Request = Depends(),
):
    # 公共业务逻辑和之前一致
    filters = ArticleFilters(tag=tag)
    dbarticles = await get_articles_with_filters(db, filters)
    article_data = DisplayArticles(articles=dbarticles, articles_count=len(dbarticles))
    
    # 根据请求类型返回对应格式
    accept = request.headers.get("Accept", "")
    if "text/html" in accept:
        # 前端页面请求返回模板
        return templates.TemplateResponse(
            "article_list.html",
            context={"request": request, **article_data.dict()}
        )
    # 默认返回JSON格式给API调用
    return create_aliased_response(article_data)

这个方案的好处是你只要维护一套路由,后续加参数、改逻辑只要改一次就行,缺点是如果接口和前端页面的参数差异变大,后期维护成本会升高,适合小项目或者逻辑高度一致的场景。

可选架构优化建议

如果后续项目要长期扩展,可以新增一层services目录专门放业务逻辑,把CRUD层只保留纯数据库读写操作,所有业务组装、参数校验逻辑都放到service层,不管是API路由还是webapp路由都只调用service层的方法,完全避免逻辑重复,调整后的结构参考:

app/
- services/
  -- article.py # 存放文章相关的业务逻辑,比如拼接过滤条件、关联查询标签、计算统计数据等
  -- tag.py

这样架构分层更清晰,不管后续是加小程序接口、管理后台接口还是其他端的入口,都直接复用service层的逻辑,不会出现重复代码。

内容的提问来源于stack exchange,提问作者jonbon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 07:54:03