為何選擇了 FastAPI 和 gRPC贺纲?
我們團(tuán)隊(duì)內(nèi)部早期使用的 Django 開發(fā)的海外金融產(chǎn)品航闺,后續(xù)考慮轉(zhuǎn)型到微服務(wù)架構(gòu),做了一些調(diào)研之后猴誊,決定選擇 FastAPI 和 gRPC潦刃。
FastAPI
完全從異步IO思維整合出來的框架,在 Web 領(lǐng)域異步IO的意義比較大懈叹」愿埽基于 Encode 團(tuán)隊(duì)(開發(fā)過大名鼎鼎的 Django REST Framework
)的新作品:Starlette。
gRPC
在 RPC 之塊项阴,Python 還有一個(gè)有名的框架 nameko
滑黔,團(tuán)隊(duì)之前使用過,由于國內(nèi)使用率不高环揽,遇到問題時(shí)略荡,難以解決方案,雖然 nameko
的 API 非常優(yōu)雅歉胶,最終還是求穩(wěn)地選擇了 gRPC汛兜。gRPC 在云原生時(shí)代也是一個(gè)標(biāo)配,流行度上有保證通今。
落地實(shí)踐
簡單地了解了一些 DDD 思維后粥谬,團(tuán)隊(duì)就拍著腦袋開始拆分服務(wù)啦”杷總共拆分了 8 個(gè)服務(wù)漏策,每個(gè)服務(wù)同時(shí)提供 HTTP/RPC 服務(wù)。
類 Flask 框架的 FastAPI臼氨,擁有微框架的靈活性掺喻,但也是這種靈活性,讓團(tuán)隊(duì)技術(shù)水平并不統(tǒng)一的開發(fā)者寫出了各式各式的項(xiàng)目結(jié)構(gòu),交叉維護(hù)項(xiàng)目的時(shí)候感耙,非常頭痛褂乍。于是團(tuán)隊(duì)內(nèi)部為了統(tǒng)一及簡化 FastAPI & gRPC 的開發(fā),迭代出了一個(gè)整合的框架:bali即硼,業(yè)務(wù)代碼分布為在兩個(gè)層次:Model
層和 Resource
層逃片。Model 層是一個(gè) Fat Model 模型,除了字段定義只酥,還有與 Model 相關(guān)的一些邏輯褥实;Resource 層提供同時(shí)兼容 HTTP/RPC 的資源服務(wù)。遵循 RESTful 及 gRPC 的開發(fā)指南定義了幾個(gè)標(biāo)準(zhǔn)方法层皱。
# 注意 bali 框架在 pipy 倉庫里面的名稱是:bali-core
pip install bali-core
項(xiàng)目結(jié)構(gòu)
這里只涂兩個(gè)服務(wù)作為實(shí)踐例子性锭,用戶服務(wù)(user)和訂單服務(wù)(order)赠潦,每個(gè)服務(wù)都是一個(gè)獨(dú)立的 repo叫胖,有個(gè)獨(dú)立的 proto repo 專門用來存放 gRPC 需要使用的 protobuf。
# user repo
├── user
│ ├── Dockerfile
│ ├── README.md
│ ├── clients
│ │ ├── __init__.py
│ │ ├── _config.py
│ │ ├── _utils.py
│ │ ├── intermediates
│ ├── core
│ │ ├── config.py
│ │ ├── constants
│ │ ├── environment.py
│ │ ├── logging.py
│ ├── main.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── user.py
│ ├── requirements.txt
│ ├── requirements_dev.txt
│ ├── resources
│ │ ├── __init__.py
│ │ ├── user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ ├── user.py
│ ├── services
│ │ ├── __init__.py
│ │ ├── db.py
│ │ ├── http
│ │ │ ├── users.py
│ │ └── rpc
│ │ ├── service.py
│ ├── tests
│ │ ├── __init__.py
│ │ ├── test_models
│ │ ├── test_resources
│ │ └── test_services
# order repo
├── user
│ ├── Dockerfile
│ ├── README.md
│ ├── clients
│ │ ├── __init__.py
│ │ ├── _config.py
│ │ ├── _utils.py
│ │ ├── intermediates
│ ├── core
│ │ ├── config.py
│ │ ├── constants
│ │ ├── environment.py
│ │ ├── logging.py
│ ├── main.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── user.py
│ ├── requirements.txt
│ ├── requirements_dev.txt
│ ├── resources
│ │ ├── __init__.py
│ │ ├── user.py
│ ├── schemas
│ │ ├── __init__.py
│ │ ├── user.py
│ ├── services
│ │ ├── __init__.py
│ │ ├── db.py
│ │ ├── http
│ │ │ ├── users.py
│ │ └── rpc
│ │ ├── service.py
│ ├── tests
│ │ ├── __init__.py
│ │ ├── test_models
│ │ ├── test_resources
│ │ └── test_services
# proto repo
proto
├── order
│ └── order.proto
└── user
└── user.proto
可以看出她奥,微服務(wù)的結(jié)構(gòu)是基本一致的瓮增,user 服務(wù)和 order 服務(wù)的業(yè)務(wù)主要存放在 Models 和 Resources。
HTTP 服務(wù)
# user/services/http/users.py
@router.get("/users/", response_model=UserSchema)
def get_users(
*,
channel : str = Header(None),
) -> Any:
return User.get_users()
這是 FastAPI 的自帶的 route 定義方式哩俭,bali 是兼容的绷跑。
# user/resources/user.py
class UserResource(Resource):
schema = UserSchema
@action()
def custome_users(self, schema_in):
User.get_customer_users()
Resouce 的方式,同時(shí)支持 HTTP凡资、RPC砸捏。通用的 action 比如 GetUsers 不需要在 Resource 里面定義,已經(jīng)在 Resource 基類里面實(shí)現(xiàn)了隙赁。
啟動(dòng) HTTP 服務(wù):
python main.py --http
RPC 服務(wù)
- 定義 protobuf 文件
# user/services/rpc/user.proto
編譯 proto 文件垦藏,按 gRPC 官方提供的方法:
$ python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/user.proto
而使用 bali-cli
(bali 框架配套的 cli 工具),只需要在項(xiàng)目根目錄執(zhí)行:
pip install bali-cli
bali build
再配合 CI/CD伞访,user 服務(wù)的 user.proto 會(huì)自動(dòng)添加到 proto repo 里面掂骏。
- gRPC 實(shí)現(xiàn)服務(wù)
# user/services/rpc/service.py
class UserService(user_pb2_grpc.UserServiceServicer):
def GetUsers(self, request, context):
return UserResource(request, context, pb2.UserResponse).get_users()
def serve():
port = 5000
server = grpc.server(
futures.ThreadPoolExecutor(max_workers=50),
interceptors=[ProcessInterceptor()],
)
user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
server.add_insecure_port(f'[::]:{port}')
server.start()
logger.info("Service started on port: %s (env: %s)", port, ENV)
server.wait_for_termination()
由于 get_users
是一個(gè)標(biāo)準(zhǔn)通用的 action,所以在 Resource 里面都不用自己實(shí)現(xiàn)厚掷,定義好 Model弟灼、Schema 就可以直接使用了。
啟動(dòng) RPC 服務(wù):
python main.py --rpc
- gRPC 實(shí)現(xiàn)服務(wù)
在 order 服務(wù)里面調(diào)用 user 服務(wù)冒黑,只需要使用 bali add {service}
命令即可在 order 項(xiàng)目田绑,創(chuàng)建一個(gè) clients
需要的所有文件。
bali add user
在代碼里面調(diào)用
from clients import UserClient
users = UserClient().get_users()
項(xiàng)目總結(jié)
gRPC 確實(shí)是未來一個(gè)方向抡爹,簡單掩驱、跨語言。而且云原生的 service mesh 都默認(rèn)支持 gRPC,比如 Linkerd昙篙。微服務(wù)場景下的網(wǎng)關(guān)產(chǎn)品也都對(duì) gRPC 默認(rèn)支持腊状。
業(yè)務(wù)代碼放哪里這個(gè)問題在所有項(xiàng)目里面都會(huì)成為一個(gè)話題,bali 框架只有兩層苔可,Model 和 Resource缴挖,開發(fā)微服務(wù)就不用思考太多東西,效率會(huì)高焚辅。一套代碼映屋,同時(shí)兼容 HTTP 和 RPC 服務(wù)。