读后笔记 -- FastAPI 构建Python微服务 Chapter4:构建微服务应用程序

4.3 挂载子模块

# content of main.py
from fastapi import FastAPI
from faculty_mgt import faculty_main
from library_mgt import library_main
from student_mgt import student_main

app = FastAPI()

app.mount("/ch04/student", student_main.student_app)
app.mount("/ch04/faculty", faculty_main.faculty_app)
app.mount("/ch04/library", library_main.library_app)

项目结构如下:

 


4.4 ~ 4.7 主端点+通用网关+利用异常跳转自服务

1. step1+step4: 在顶级应用程序中处理网关端点

# content of main.py
from fastapi import FastAPI

# step1: 顶级应用程序处理处理网关端点
app = FastAPI()
# 通过方法 /ch04/university/{portal_id},指向通用的网关
app.include_router(university.router,
                   dependencies=[Depends(call_api_gateway)], prefix="/ch04")

# step3: 自定义异常处理,重定向到各微服务
@app.exception_handler(RedirectStudentPortalException)
def exception_handler_student(request: Request, exc: RedirectStudentPortalException) -> Response:
    return RedirectResponse(url=f"http://localhost:{port}/ch04/student/index")

@app.exception_handler(RedirectFacultyPortalException)
def exception_handler_faculty(request: Request, exc: RedirectFacultyPortalException) -> Response:
    return RedirectResponse(url=f"http://localhost:{port}/ch04/faculty/docs")

@app.exception_handler(RedirectLibraryPortalException)
def exception_handler_library(request: Request, exc: RedirectLibraryPortalException) -> Response:
    return RedirectResponse(url=f"http://localhost:{port}/ch04/library/index")

2. step2: 实现主端点

# content of \controller\university.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/university/{portal_id}")
def access_portal(portal_id: int):
    return {"message": "University ERP Systems"}

3. step3: 定义通用网关

# content of \gateway\api_router.py
from fastapi import Request

def call_api_gateway(request: Request):
    portal_id = request.path_params['portal_id']
    logger.info(request.path_params)
    if portal_id == str(1):
        raise RedirectStudentPortalException()
    elif portal_id == str(2):
        raise RedirectFacultyPortalException()
    elif portal_id == str(3):
        raise RedirectLibraryPortalException()

class RedirectStudentPortalException(Exception):
    pass

class RedirectFacultyPortalException(Exception):
    pass

class RedirectLibraryPortalException(Exception):
    pass

最终结果:

API 访问 http://localhost:8005/ch04/university/3
1)通过 app.include_router 路由 指向 university.py,
2)根据依赖, gateway.api_router.call_api_gateway,根据后面的 portal_id 指向不同的 Exception
3)main.py 中自定义异常处理,将各异常重定向到 各子服务上 (通过 url 上重定向 跳转到 http://localhost:8005/ch04/library/index)


4.8-4.9 使用 loguru,构建日志中间件

# content of main.py
from uuid import uuid4
from loguru import logger

# 使用 loguru 适合来处理 FastAPI 这种异步和同步都存在的架构,而 Python 自带的 logging 处理会出现异常
# 码配置了一个日志处理器,它会将级别为INFO及以上的日志记录,按照指定的格式,异步地写入到名为info.log的文件中,同时包含额外的上下文信息(如log_id),以增强日志的可追踪性和信息丰富度
logger.add("info.log", format="Log: [{extra[log_id]}: {time} - {level} - {message} ", level="INFO", enqueue=True)

# 构建日志中间件
@app.middleware("http")
async def log_middleware(request: Request, call_next):
    log_id = str(uuid4())
    with logger.contextualize(log_id=log_id):
        logger.info("Request to access " + request.url.path)
        try:
            response = await call_next(request)
        except Exception as ex:
            logger.error(f"Request to {request.url.path } failed: {ex}")
            response = JSONResponse(content={"success": False}, status_code=500)
        finally:
            logger.info("Successfully accessed " + request.url.path)
            return response

 


4.10 实现 httpx 模块 实现微服务之间的跳转

1. 子服务的一端(可视为客户端)

# content of \student_mgmg\controllers\assignment.py
import httpx
from fastapi import APIRouter

router = APIRouter()

"""
url 访问 http://127.0.0.1:8005/ch04/student/docs swagger,
1)运行接口 GET "/assignment/list"
2)通过 httpx.Client() GET 请求 http://localhost:8005/ch04/faculty/assignments/list
3)重定向到 faculty_mgmt.controllers.assignments.py 的 /assignments/list 即 /ch04/faculty/assignments/list
4) 剩下的就一路跳转,最终找到 faculty_mgmt.models.data.facultydb 的 faculty_assignments_tbl
"""
@router.get("/assignment/list")
async def list_assignments():
    async with httpx.AsyncClient() as client:
        response = await client.get(f"http://localhost:{port}/ch04/faculty/assignments/list")
        return response.json()

2. 子服务的另一端(视为服务端)

# content of faculty_mgmt.controllers.assignments.py
from fastapi import APIRouter
from faculty_mgt.services.assignments import AssignmentService

router = APIRouter()

@router.get("/assignments/list")
async def provide_assignments():
    assignment_service: AssignmentService = AssignmentService()
    return assignment_service.list_assignment()

 


4.11 实现 requests 模块实现子服务间的通信

1. 子服务的一端(可视为客户端)

# content of faculty_mgmt.controllers.book.py
import requests
from fastapi import APIRouter
from constant import port

router = APIRouter()

"""
url 访问 http://127.0.0.1:8005/ch04/faculty/docs swagger,
1)运行接口 GET "/books/access/list"
2)将在 session 中 GET 请求 http://localhost:8005/ch04/library/book/list
3)重定向到 library_mgmt.controllers.admin.py 的 /book/list 即 /ch04/library/book/list
4) 剩下的就一路跳转,最终找到 library_mgmt.models.data.librarydb 的 book_tbl
"""
@router.get("/books/access/list")
def list_all_books():
    with requests.Session() as sess:
        response = sess.get(f"http://localhost:{port}/ch04/library/book/list")
        return response.json()

 

2. 子服务的另一端(视为服务端)

# content of library_mgmt.controllers.admin.py
from fastapi import APIRouter
from library_mgt.services.books import BookService

router = APIRouter()

# view books
@router.get("/book/list")
def view_books():
    book_service = BookService()
    return book_service.list_book()

 


4.12 应用领域建模方法

1. 领域建模:当设计在应用程序中建立和组织结构时,了解应用程序的行为并使用其背后的业务逻辑来派生领域模型。

下面是来自于 Chatgpt 的更详细的介绍:

领域建模在设计应用程序时扮演着至关重要的角色,它不仅帮助我们理解应用程序的行为,还指导我们如何根据业务逻辑有效地组织和结构化代码。以下是通过业务逻辑派生领域模型时的一些关键步骤和实践:

1. 需求调研与领域知识获取
  访谈与工作坊:与业务专家、最终用户进行深入交流,了解业务流程、规则、术语以及痛点。
  文档审查:查阅现有业务文档、流程图、政策手册,提取关键信息。
2. 领域分解与识别
  识别子域:将复杂业务领域拆分成若干个小的、可管理的子域,每个子域聚焦特定的业务功能或领域知识。
  核心域识别:确定哪些子域对业务竞争力至关重要,这些核心域将得到最多的关注和投资。
3. 领域概念提炼
  实体(Entity):识别并定义领域中的关键对象,它们通常是持久化的,并具有唯一标识。
  值对象(Value Object):描述实体的属性或描述,它们没有独立的身份,仅通过其属性来区分。
  聚合(Aggregate):定义一组相关实体和值对象,作为数据修改的一致性单元。
  领域事件(Domain Event):捕捉领域内发生的重大事件,触发其他领域的反应或改变领域状态。
4. 行为建模
  领域服务(Domain Service):封装不属于任何特定实体或值对象的业务逻辑。
  领域规则(Domain Rules):明确领域内的业务约束和规则,确保业务逻辑的正确性。
  用例分析:通过用例描述系统如何响应外部事件,映射到领域模型中的行为。
5. 模型验证与迭代
  原型与反馈:创建模型的初步版本,通过原型或简单的实现与业务专家验证。
  持续迭代:根据反馈调整模型,直到它准确反映业务需求和逻辑。
6. 编码实现
  领域驱动设计(DDD):依据领域模型,采用DDD的战术模式(如Repository、Factory、Service Layer等)来指导代码结构。
  分层架构:设计清晰的分层架构,如表现层、应用服务层、领域层、基础设施层,确保领域模型的纯净性。
7. 测试与验证
  单元测试:为领域模型的关键行为编写单元测试,确保业务规则的正确实施。
  集成测试:验证不同子域之间的交互,确保系统作为一个整体的协调工作。
通过以上过程,我们可以确保应用程序的设计和结构紧密围绕业务逻辑展开,提高软件的可维护性、扩展性和对业务变化的适应能力。
# 其中一个微服务的结构

ch04-faculty
├── configuration/          # 服务配置             
│   └── config  
│   └── erp_settings.properties
├── controllers/            # 控制层             
│   └── assignments                       
│   └── ...                           
├── models/                 # 模型层,提供应用程序的上下文框架         
│   └── data                  # 数据模型,在其临时数据存储中捕获和存储数据的模型
│       └── faculty  
│       └── facultydb  
│   └── request               # 请求模型,在 API 服务中使用的 BaseModel 对象
│       └── faculty                        
├── repository/             # 存储库层,1)创建管理数据访问的策略;2)为应用程序提供了一个高级抽象             
│   └── assignments                       
│   └── ...                       
├── services/               # 服务层,定义了应用程序的算法、操作和流程。通常而言,与存储库交互,为应用程序的其他组件(如API服务或控制器)构建必要的业务逻辑、管理和控制                 
│   └── assignments                       
│   └── ...                        
└── main.py
└── README.md    

 

2. 微服务的配置

# content of config.py
import os
from datetime import date
from pydantic_settings import BaseSettings

# 方式一:将设置存储为类属性
class FacultySettings(BaseSettings):
    application: str = 'Faculty Management System'
    webmaster: str = 'sjctrags@university.com'
    created: date = '2021-11-10'

# 方式二: 配置文件 + 继承 BaseSettings 的类 + Config 内部类及 env_file 属性读取配置文件
class ServerSettings(BaseSettings):
    production_server: str
    prod_port: int
    development_server: str
    dev_port: int

    # 定义一个内部类 Config 和 env_file 来读取配置文件
    class Config:
        env_file = os.path.join(os.getcwd(), "configuration", "erp_settings.properties")

 

posted on 2024-05-19 11:47  bruce_he  阅读(62)  评论(0编辑  收藏  举报