FastAPI:从 App.py 到模块化架构
吧 使用 FastAPI 构建后端时,通常从单个app.py
文件开始。虽然这种方法适用于小型项目,但随着应用程序的增长,维护和扩展很快就会变得具有挑战性。
在这篇博文中,我们将探讨如何将 FastAPI 应用程序从单片app.py
文件重构为由路由器、控制器、服务和存储库组成的更结构化的架构。
介绍:我们的 Todo API
在深入重构过程之前,让我们先看一下要使用的 API。我们正在构建一个简单的 Todo 应用程序,其中包含以下端点:
这些 API 将允许用户对待办事项执行 CRUD(创建、读取、更新、删除)操作。每个待办事项将具有以下属性:
**id**
:待办事项的唯一标识符
**title**
:待办事项的标题或描述
**completed**
:布尔值,表示待办事项是否已完成
现在我们了解了我们正在使用的 API,让我们看看一些先决条件以及如何实现它们。
先决条件
在我们开始初始方法和重构之前,让我们先设置我们的 FastAPI 项目。
安装 Python
设置并激活虚拟环境
python3 -m venv venv
source env /bin/activate # 在 Windows 上使用 `env\Scripts\activate`
创建
**requirements.txt**
FastAPI 和 Uvicorn 并将其添加到依赖项列表
fastapi
uvicorn
安装依赖项
pip3 安装 -r 要求.txt
初步方法:app.py 中的所有内容
app.py
让我们从一个完全在根级别实现的简单 Todo API 开始:
从fastapi导入FastAPI、HTTPException
从pydantic导入BaseModel
app = FastAPI()
# 用于创建新待办事项的 Pydantic 模型
class TodoCreate ( BaseModel ):
title: str
# 待办事项的 Pydantic 模型,继承自 TodoCreate 并添加 id 和 done 字段
class Todo ( TodoCreate ):
id : int
done: bool = False
# 待办事项的内存存储(模拟数据库)
todos = []
# 创建新待办事项的端点
@app.post( "/todos" , response_model=Todo )
def create_todo ( todo: TodoCreate ):
# 创建一个带有递增 id 的新待办事项
new_todo = Todo( id = len (todos) + 1 , **todo.model_dump())
todos.append(new_todo) # 将新的待办事项添加到列表中
return new_todo # 将创建的待办事项作为响应返回
# 端点获取所有待办事项
@app.get( "/todos" , response_model= list [Todo] )
def get_todos ():
return todos # 返回待办事项列表作为响应
# 通过 id 获取特定待办事项的端点
@app.get( "/todos/{todo_id}" , response_model=Todo )
def get_todo ( todo_id: int ):
for todo in todos:
if todo. id == todo_id:
返回todo # 如果找到则返回 todo
# 如果未找到 todo,则引发带有 404 状态代码和消息的 HTTPException
raise HTTPException(status_code= 404,detail = “Todo not found”)
# 通过 id 更新 todo 的端点
@app.put(“/todos/{todo_id}”,response_model=Todo)
def update_todo(todo_id:int,updated_todo:TodoCreate):
for todo in todos:
if todo.id == todo_id:
todo.title = updated_todo.title # 更新 todo 的标题
返回todo # 返回更新后的 todo
# 如果未找到 todo,则引发带有 404 状态代码和消息的 HTTPException raise HTTPException
( status_code= 404 , detail= "Todo not found" )
# 通过 id 删除 todo 的端点
@app.delete( "/todos/{todo_id}" )
def delete_todo ( todo_id: int ):
for index, todo in enumerate (todos):
if todo. id == todo_id:
del todos[index] # 从列表中删除待办事项
return { "message" : "待办事项已成功删除" } # 返回成功消息
# 如果未找到待办事项,则引发带有 404 状态代码和消息的 HTTPException
raise HTTPException(status_code= 404 , detail= "未找到待办事项" )
# 使用 Uvicorn 服务器运行应用程序的主块
if __name__ == "__main__" :
import uvicorn
uvicorn.run( "app:app" , port= 3000 , host= "0.0.0.0" , reload= True )
Pydantic 是一个 Python 数据验证和解析库,它使用 Python 类型注释来定义和验证数据结构。它通过提供自动类型检查和解析来确保数据完整性,从而更轻松地处理结构化数据。
要启动 API,我们使用命令***,******python3 app.py\***
唔…
虽然这种方法适用于小型应用程序,但它有几个缺点:
- 所有路由、业务逻辑和数据存储都混合在一个文件中。
- 随着应用程序的增长,维护和扩展变得困难。
- 测试单个组件变得具有挑战性
- 代码可重用性有限
让我们开始重构之旅……
路由器简介
构建我们的应用程序的第一步是引入路由器。
API 中的路由器将相关端点(路由)组织和分组到单独的模块或文件中,从而增强代码模块化和结构化。
创建一个名为的新目录routers
并添加一个名为todo_router.py
从fastapi导入APIRouter
router = APIRouter()
@router.post( "/todos" )
def create_todo ():
pass
@router.get( "/todos" )
def get_todos ():
pass
@router.get( "/todos/{todo_id}" )
def get_todo ( todo_id: int ):
pass
@router.put( "/todos/{todo_id}" )
def update_todo ( todo_id: int ):
pass
@router.delete( "/todos/{todo_id}" )
def delete_todo ( todo_id: int ):
pass
现在更新app.py
以使用路由器,
从fastapi导入FastAPI
从路由器导入todo_router
app = FastAPI()
app.include_router(todo_router.router)
如果__name__ == "__main__" :
导入uvicorn
uvicorn.run( "app:app" , port= 3000 , host= "0.0.0.0" , reload= True )
通过引入路由器,我们将待办事项相关的路由与主app.py
文件分开,使其更清晰、更集中。
添加控制器
接下来,我们将引入控制器来处理请求处理逻辑。
控制器处理传入的请求,协调应用程序的逻辑,并管理 API 路由(端点)和服务层之间的数据流。
创建一个名为的新目录controllers
并添加一个名为todo_controller.py
从fastapi导入HTTPException
从pydantic导入BaseModel
类 TodoCreate(BaseModel):
title:str
类 Todo(TodoCreate):
id:int
已完成:bool = False
类 TodoController:
def __init__(self):
self.todos = []
def create_todo(self,todo:TodoCreate):
new_todo = Todo(id = len(self.todos)+ 1,**todo.model_dump())
self.todos.append(new_todo)
返回new_todo
def get_todos(self):
返回self.todos
def get_todo(self,todo_id:int):
对于self.todos中的todo :
如果todo。id == todo_id:
返回todo
引发HTTPException(status_code= 404 , detail= "未找到 Todo" )
def update_todo ( self, todo_id: int , updated_todo: TodoCreate ):
for todo in self.todos:
如果todo. id == todo_id:
todo.title = updated_todo.title
返回todo
引发HTTPException(status_code= 404 , detail= "未找到 Todo" )
def delete_todo ( self, todo_id: int ):
for index, todo in enumerate (self.todos):
如果todo. id == todo_id:
del self.todos[index]
返回{ "message" : "已成功删除 Todo" }
引发HTTPException(status_code= 404 , detail= "未找到 Todo" )
更新todo_router.py
以使用控制器,
from fastapi import APIRouter
fromcontrollers.todo_controller import TodoController, TodoCreate, Todo
router = APIRouter()
todo_controller = TodoController()
@router.post( " / todos" , response_model=Todo )
def create_todo ( todo: TodoCreate ):
return todo_controller.create_todo (todo)
@router.get( "/todos" , response_model= list [Todo] )
def get_todos ():
return todo_controller.get_todos()
@router.get( "/todos/{todo_id}" , response_model=Todo )
def get_todo ( todo_id: int ):
return todo_controller.get_todo(todo_id)
@router.put( "/todos/{todo_id}" , response_model=Todo )
def update_todo ( todo_id:int,updated_todo:TodoCreate):
返回todo_controller.update_todo(todo_id,updated_todo)@router.delete
(“/todos/{todo_id}”)
defdelete_todo (todo_id :int):
返回todo_controller.delete_todo(todo_id)
实现服务层
现在,让我们引入一个服务层来处理业务逻辑。
服务包含 API 的核心业务逻辑,实现应用程序所需的特定功能和操作。它们从 API 端点抽象出复杂的业务规则。
创建一个名为的新目录services
并添加一个名为todo_service.py
from pydantic import BaseModel
class TodoCreate ( BaseModel ):
title: str
class Todo ( TodoCreate ):
id : int
Completed: bool = False
class TodoService :
def __init__ ( self ):
self.todos = []
def create_todo ( self, todo: TodoCreate ) ) -> Todo:
new_todo = Todo( id = len (self.todos) + 1 , **todo.model_dump())
self.todos.append(new_todo)
return new_todo
def get_todos ( self ) ->列表[Todo]:
返回self.todos
def get_todo ( self, todo_id: int ) -> Todo |无:
用于self.todos中的待办事项:
如果待办事项。id == todo_id:
返回todo
返回 None
def update_todo ( self,todo_id:int,updated_todo:TodoCreate ) -> Todo | None:
for todo in self.todos:
如果todo.id == todo_id:
todo.title = updated_todo.title
返回todo
返回 None
def delete_todo ( self, todo_id: int ) -> bool :
for index, todo in enumerate (self.todos):
if todo. id == todo_id:
del self.todos[index]
返回 True
返回 False
更新todo_controller.py
以使用该服务,
from fastapi import HTTPException
from services.todo_service import TodoService, TodoCreate, Todo
class TodoController :
def __init__ ( self ):
self.todo_service = TodoService()
def create_todo ( self, todo: TodoCreate ):
return self.todo_service.create_todo(todo)
def get_todos ( self ):
return self.todo_service.get_todos()
def get_todo ( self, todo_id: int ):
todo = self.todo_service.get_todo(todo_id)
如果todo为 None :
raise HTTPException(status_code= 404 ,detail= "Todo not find" )
return todo
def update_todo ( self, todo_id: int , Updated_todo: TodoCreate ):
todo = self.todo_service.update_todo(todo_id, Updated_todo)
如果todo为 None :
raise HTTPException(status_code = 404,detail = “未找到Todo”)
返回todo
def delete_todo(self,todo_id:int):
if self.todo_service.delete_todo(todo_id):
return { “message”:“Todo已成功删除” }
raise HTTPException (状态码 = 404,详细信息 = “未找到待办事项”)
创建存储库层
最后,让我们引入一个存储库层来处理数据持久性。
存储库为数据持久性操作提供了一个抽象层,封装了与数据库或外部数据源的交互。它们提供了存储、检索、更新和删除数据的标准化方法。
创建一个名为的新目录repositories
并添加一个名为todo_repository.py
from pydantic import BaseModel
class TodoCreate ( BaseModel ):
title: str
class Todo ( TodoCreate ):
id : int
Completed: bool = False
class TodoRepository :
def __init__ ( self ):
self.todos = []
def create_todo ( self, todo: TodoCreate ) ) -> Todo:
new_todo = Todo( id = len (self.todos) + 1 , **todo.model_dump())
self.todos.append(new_todo)
return new_todo
def get_todos ( self ) ->列表[Todo]:
返回self.todos
def get_todo ( self, todo_id: int ) -> Todo |无:
用于self.todos中的待办事项:
如果待办事项。id == todo_id:
返回todo
返回 None
def update_todo ( self,todo_id:int,updated_todo:TodoCreate ) -> Todo | None:
for todo in self.todos:
如果todo.id == todo_id:
todo.title = updated_todo.title
返回todo
返回 None
def delete_todo ( self, todo_id: int ) -> bool :
for index, todo in enumerate (self.todos):
if todo. id == todo_id:
del self.todos[index]
返回 True
返回 False
更新todo_service.py
以使用存储库,
from repositories.todo_repository import TodoRepository, TodoCreate, Todo
class TodoService :
def __init__ ( self ):
self.todo_repository = TodoRepository()
def create_todo ( self, todo: TodoCreate ) -> Todo:
return self.todo_repository.create_todo(todo)
def get_todos ( self ) ->列表[Todo]:
return self.todo_repository.get_todos()
def get_todo ( self, todo_id: int ) -> Todo |无:
返回self.todo_repository.get_todo(todo_id)
def update_todo ( self, todo_id: int , Updated_todo: TodoCreate ) -> Todo | None :
返回self.todo_repository.update_todo(todo_id, Updated_todo)
def delete_todo ( self, todo_id: int ) -> bool :
返回self.todo_repository.delete_todo(todo_id)
我们的重构之旅到此结束…
在从单体架构转变为使用 FastAPI 的结构化模块化架构的过程中app.py
,我们将 Todo API 转变为更具可扩展性和可维护性的应用程序。通过采用路由器、控制器、服务和存储库,我们实现了更清晰的关注点分离,并增强了我们在项目发展过程中管理复杂性的能力。
模块化架构的主要优点:
- **提高可维护性:**每个组件(路由器、控制器、服务和存储库)现在都处理特定的职责,从而降低了进行更改时产生意外副作用的风险。
- **增强的可测试性:**有了不同的层次,单元测试变得更加简单。我们可以单独测试每个组件,确保整个应用程序的稳健性和可靠性。
- **可扩展性和灵活性:**模块化设计更易于扩展。无需对整个代码库进行大量重新设计,即可添加新功能或修改现有功能。这种灵活性可以无缝切换数据库或更新业务逻辑。
我们的存储库现在如下所示…
通过将我们的 FastAPI 应用程序重构为模块化架构,我们为持续增长和灵活性奠定了坚实的基础。这种方法不仅增强了我们当前的开发工作,还为我们应对未来的挑战和机遇做好了准备。
采用模块化不仅仅意味着组织代码——它还意味着培育一种高效、协作和持续改进的文化。
性的能力。
模块化架构的主要优点:
- **提高可维护性:**每个组件(路由器、控制器、服务和存储库)现在都处理特定的职责,从而降低了进行更改时产生意外副作用的风险。
- **增强的可测试性:**有了不同的层次,单元测试变得更加简单。我们可以单独测试每个组件,确保整个应用程序的稳健性和可靠性。
- **可扩展性和灵活性:**模块化设计更易于扩展。无需对整个代码库进行大量重新设计,即可添加新功能或修改现有功能。这种灵活性可以无缝切换数据库或更新业务逻辑。
我们的存储库现在如下所示…
[外链图片转存中…(img-DNEVyXCt-1722244113558)]
通过将我们的 FastAPI 应用程序重构为模块化架构,我们为持续增长和灵活性奠定了坚实的基础。这种方法不仅增强了我们当前的开发工作,还为我们应对未来的挑战和机遇做好了准备。
采用模块化不仅仅意味着组织代码——它还意味着培育一种高效、协作和持续改进的文化。
再见!!
博客原文:专业人工智能社区