Vì vậy, bạn muốn tạo một ứng dụng web đầy đủ tính năng và bạn đang tự hỏi liệu mình nên sử dụng một framework lớn như Django hay thứ gì đó tối giản hơn như Flask?
Bài viết này mô tả cách chúng tôi xây dựng loại dịch vụ phụ trợ này theo các nguyên tắc sau
- Kiến trúc dễ bảo trì
- Sẵn sàng cho sản xuất
- Tận dụng tối đa asyncio cho API, logic nghiệp vụ và tương tác với các hệ thống của bên thứ ba
Điều kiện tiên quyết . đã cài đặt Python gần đây [≥ 3. 9] với pip là đủ
1. Thiết kế hướng tên miền
Đầu tiên, hãy nói về kiến trúc
Có rất nhiều điều để học hỏi từ thiết kế kiến trúc khi bạn muốn xây dựng các ứng dụng trong thế giới thực. Khi bạn xem hầu hết các ví dụ mã do Flask hoặc FastAPI cung cấp, bạn sẽ có một ứng dụng rất đơn giản với API REST chỉ với một trình xử lý đơn giản cho mỗi điểm cuối. Trong các ứng dụng thực tế, bạn muốn tách logic nghiệp vụ của mình khỏi lệnh gọi API để có thể tương tác với ứng dụng thông qua các kênh khác như API GraphQL hoặc tin nhắn RabbitMQ. Bạn cũng cần xử lý một hoặc nhiều hệ thống lưu trữ, cơ sở dữ liệu, lớp bộ đệm, dịch vụ lưu trữ đối tượng, kho lưu trữ bí mật và các hệ thống phức tạp hơn như API của nhà cung cấp đám mây, Kubernetes, v.v.
Để thực hiện đúng cách tách các mối quan tâm và tương tác trừu tượng với các hệ thống khác, các khái niệm Thiết kế hướng miền [DDD] cung cấp một hộp công cụ tuyệt vời để xem xét. Sách Các mẫu kiến trúc với Python [có sẵn trực tuyến tại đây ], là một mỏ vàng để hiểu cách triển khai kiến trúc DDD trong Python. Nó cung cấp rất nhiều ví dụ từng bước cho mọi khái niệm để bạn có thể hiểu tại sao bạn nên hoặc không nên áp dụng chúng. Đây là cuốn sách phải đọc và hầu hết những gì được trình bày ở đây đều dựa trên cuốn sách này.
Vì vậy, chúng tôi sẽ hướng dẫn bạn một kiến trúc bao gồm 3 lớp. Tên miền, ứng dụng và cơ sở hạ tầng. Lớp Miền xác định cấu trúc Dữ liệu trong các đối tượng Python đơn giản. các đối tượng kinh doanh. Lớp Ứng dụng nắm giữ bộ não của Ứng dụng. logic kinh doanh. Cuối cùng, lớp Cơ sở hạ tầng là "tay chân" của Ứng dụng của chúng tôi. phần tương tác với thế giới bên ngoài [API HTTP, cơ sở dữ liệu, hệ thống tệp, động cơ servo, v.v.]
Vì vậy, hãy tạo khung ứng dụng của chúng ta với
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml1 tuyệt vời
mkdir myapp cd myapp pip install poetry poetry init mkdir -p myapp/application mkdir myapp/domain mkdir myapp/infrastructure
Bạn nên có một cái gì đó như
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml
1. 1 miền
Lớp miền là một đại diện mô hình của các dịch vụ. Nó thực sự là cốt lõi của các dịch vụ của chúng tôi và nó phải có khả năng phát triển nhanh chóng. Lớp này không phụ thuộc vào bất kỳ lớp nào khác [tuân theo nguyên tắc đảo ngược phụ thuộc] và không nhập các thư viện bên ngoài [trừ khi có các ngoại lệ hợp lý, nó chỉ bao gồm mã python thô]
Miền là lớp dữ liệu xác định đối tượng nghiệp vụ. Hầu hết các phương thức của các lớp dữ liệu này bao gồm các trình trợ giúp thao túng trạng thái của lớp dữ liệu. Một số lớp này là lớp trừu tượng, được triển khai bởi các lớp khác từ lớp cơ sở hạ tầng.
Các phương thức của các lớp này có thể trả về các đối tượng Miền, trạng thái ["đã xảy ra lỗi", "không có vấn đề gì ở đây", "chỉ có bước 1 và 3 hoạt động"…] hoặc không có gì
Nguyên tắc chung là đặt càng nhiều thứ càng tốt ở đó
Ví dụ: đây là một đối tượng đại diện cho một mục trong ứng dụng việc cần làm của chúng tôi. Và vâng, ví dụ của chúng ta sẽ là một ứng dụng việc cần làm. [như tất cả chúng ta ^^]
import uuid from datetime import datetime from dataclasses import dataclass, field @dataclass class TodoEntry: id: str created_at: datetime content: str tags: set[str] = field[default_factory=set] @classmethod def create_from_dict[cls, content:str] -> "TodoEntry": return cls[id=str[uuid.uuid4[]], created_at=datetime.utcnow[], content=content] def set_tag[self, tag: str] -> None: self.tags.add[tag]
Bạn có nhận thấy rằng chúng tôi sử dụng nhiều loại Python không? . Chúng tôi thực sự khuyên bạn nên sử dụng chúng và thực thi nó trong CI để bạn không gặp bất ngờ khi thực hiện
1. 2 Cơ sở hạ tầng
Để quản lý tất cả các tương tác với các hệ thống bên ngoài như cơ sở dữ liệu, hệ thống tệp, mạng, API, v.v.
Các dịch vụ này đóng vai trò là "trình bao bọc" xung quanh các phụ thuộc bên ngoài để chúng có thể được sử dụng trong lớp Ứng dụng
1. 2. 1 Mẫu kho lưu trữ
Đây cũng là nơi chúng ta có thể tìm thấy Kho lưu trữ. Mẫu kho lưu trữ chỉ đơn giản là một lớp trừu tượng hóa tính bền vững của đối tượng. Nó cung cấp ít nhất
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml2 và
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml3 chức năng, cung cấp một cách duy nhất để lưu trữ và truy xuất dữ liệu từ hệ thống lưu trữ. Chúng tôi có thể bắt đầu với bộ lưu trữ tệp Pickle cho đến khi chúng tôi đạt đến giới hạn hiệu suất cho biết chúng tôi chuyển sang cơ sở dữ liệu SQL hoặc thứ gì đó khác. Quá trình này giúp chúng tôi không phải thay đổi bất kỳ dòng mã nào trong lớp Ứng dụng hoặc Miền của chúng tôi
Ví dụ: đây là kho lưu trữ 'Các mục cần làm' sử dụng thư viện Pickle để tuần tự hóa các đối tượng thành các tệp
import pickle from dataclasses import dataclass from pathlib import Path from myapp.domain.todo import TodoEntry from myapp.domain.todo_entry_repository import ITodoEntryRepository class TodoEntryNotFound[Exception]: pass @dataclass class TodoEntryPickleRepository[ITodoEntryRepository]: storage_dir: str def get[self, entry_id: str] -> TodoEntry: try: entry: TodoEntry with open[Path[self.storage_dir] / entry_id] as entry_file: entry = pickle.load[entry_file] return entry except Exception: raise TodoEntryNotFound[] def add[self, entry: TodoEntry] -> None: with open[Path[self.storage_dir] / entry.id] as entry_file: pickle.dump[entry, entry_file
Lưu ý rằng chúng tôi triển khai một lớp trừu tượng trong lớp Miền. Điều này cho phép chúng tôi nhập giao diện kho lưu trữ từ lớp Ứng dụng mà không cần biết triển khai thực tế là gì
1. 3 ứng dụng
Bây giờ chúng ta có Miền chứa đối tượng nghiệp vụ cũng như Kho lưu trữ của chúng ta để quản lý tính bền vững của đối tượng này trong lớp Cơ sở hạ tầng, chúng ta cần gắn chúng lại với nhau bằng logic nghiệp vụ của mình
Lớp Ứng dụng chứa tất cả các dịch vụ do ứng dụng cung cấp, sử dụng cấu trúc Miền và Cơ sở hạ tầng làm phụ trợ
Các dịch vụ Ứng dụng này "sắp xếp" các cấu trúc của Miền và các dịch vụ Cơ sở hạ tầng để chúng hoạt động hài hòa với nhau
Dữ liệu ứng dụng không được sửa đổi ở đây; . Như đã đề cập trước đây, không có dữ liệu nào được sửa đổi trực tiếp ở đây. Tuy nhiên, chúng tôi nắm bắt các ngoại lệ và sử dụng các phương thức đối tượng để áp dụng đúng quy tắc kinh doanh
Ví dụ: chúng ta có thể có một TodoService như thế này
from dataclasses import dataclass from typing import Optional from myapp.domain.todo import TodoEntry from myapp.domain.todo_entry_repository import ITodoEntryRepository @dataclass class TodoService: todo_repository: ITodoEntryRepository def add_entry[self, content: str] -> str: entry = TodoEntry.create_from_content[content] self.todo_repository.add[entry] return entry.id def add_tag[self, entry_id: str, tag: str] -> None: entry = self.todo_repository.get[entry_id] entry.set_tag[tag] def get_all[self, search: Optional[str] = None] -> list[TodoEntry]: return self.todo_repository.get_all[search]
Chờ đợi. todo_repository này được tạo ra khi nào và bởi ai?
Muốn tăng tốc
phát triển phụ trợ?
Bắt đầu chương trình phụ trợ của bạn
1. 4 Tiêm phụ thuộc
Mục tiêu của việc tiêm phụ thuộc là để tránh tạo các đối tượng ở mọi nơi hoặc chuyển chúng vào tất cả các chức năng trong một số loại
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml4 nồi nấu chảy. Để làm như vậy, chúng tôi sẽ xác định nơi tất cả các dịch vụ Cơ sở hạ tầng được tạo, ở một nơi duy nhất. Sau đó, chúng tôi có thể dễ dàng đưa các dịch vụ này làm phần phụ thuộc của các dịch vụ Ứng dụng bằng cách sử dụng giá trị mặc định là một đơn vị [e. g. cho kết nối cơ sở dữ liệu] hoặc đối tượng dùng một lần từ nhà máy [e. g. cho trình xử lý yêu cầu HTTP]
Thư viện Dép phụ thuộc được thiết kế tốt và cung cấp mọi thứ bạn cần để xác định tất cả dịch vụ của mình, đưa chúng vào và thậm chí tải cấu hình.
Hãy cài đặt nó
poetry add dependency_injector
Giải thích cấu hình và sự phụ thuộc trong vùng chứa. mã py
from dependency_injector import providers, containers from myapp.application.todo_service import TodoService from myapp.infrastructure.database.todo_entry_repository import TodoEntryPickleRepository class ApplicationContainer[containers.DeclarativeContainer]: configuration = providers.Configuration[] todo_entry_repository = providers.Singleton[ TodoEntryPickleRepository, storage_dir=configuration.storage_dir ] todo_service = providers.Factory[ TodoService, todo_entry_repository ]
2. API web
Bây giờ chúng ta đã có ứng dụng cơ bản, chúng ta cần tạo một API. FastAPI là một thư viện thực sự tuyệt vời giúp bạn tạo các điểm cuối API của mình, định tuyến chúng, tuần tự hóa và giải tuần tự hóa các đối tượng API [được gọi là mô hình] và thậm chí tạo tương tác .
Hãy thêm nó vào phần phụ thuộc của chúng tôi với
poetry add fastapi
Một cách thích hợp để thêm API của bạn là tách chúng theo bộ điều khiển, từng bộ theo nhóm điểm cuối. Vì vậy, hãy tạo một tệp thiết lập tập trung để tổng hợp cấu hình chung và nội dung phụ thuộc cho tất cả các bộ điều khiển trong
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml5
from fastapi import FastAPI from myapp.container import ApplicationContainer from myapp.infrastructure.api import todo_controller def setup[app: FastAPI, container: ApplicationContainer] -> None: # Add other controllers here app.include_router[todo_controller.router] # Inject dependencies container.wire[ modules=[ todo_controller, ] ]
Và bộ điều khiển cho
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml6 điểm cuối
from dataclasses import asdict from typing import Optional from dependency_injector.wiring import Provide from fastapi import APIRouter from myapp.application.todo_service import TodoService from myapp.container import ApplicationContainer from myapp.infrastructure.api.todo_schema import TodoEntrySchema todo_service: TodoService = Provide[ApplicationContainer.todo_service] router = APIRouter[ prefix="/todo", tags=["Todo"], responses={404: {"description": "Not found"}}, ] @router.get["/", response_model=list[TodoEntrySchema]] async def list_todos[search: Optional[str] = None] -> list[TodoEntrySchema]: todo_entries = todo_service.get_all[search] return [TodoEntrySchema[**asdict[todo_entry]] for todo_entry in todo_entries] @router.post["/"] async def add_todo[content: str] -> str: return todo_service.add_entry[content]
Đây là lược đồ việc cần làm được sử dụng để tuần tự hóa. Lưu ý rằng chúng tôi sử dụng một đối tượng khác với
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml7 nội bộ từ miền vì chúng tôi muốn giải mã nó khỏi API bên ngoài. Do đó, bạn có thể thay đổi cách diễn đạt API của mình và ẩn các giá trị bên trong không hữu ích cho người dùng. Lược đồ dựa trên mô hình Pydantic sử dụng kiểu gõ tích hợp sẵn của Python. Như được quảng cáo trong tài liệu FastAPI, nó có rất nhiều ưu điểm như phân tích tĩnh với Mypy, tự động hoàn thành IDE hữu ích, gỡ lỗi dễ dàng, v.v.
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml0
3. kiểm tra nó
Lưu ý rằng để đơn giản, chúng tôi đã giữ phần thử nghiệm cho đến nay. xấu hổ về chúng tôi. Đây là một trong những lý do chính khiến chúng tôi phân chia tất cả mã của mình theo cách này. Hãy nhớ rằng tất cả các thành phần có thể được kiểm tra dễ dàng riêng biệt hoặc trong ngữ cảnh. Tôi sẽ cho bạn một ví dụ. API của chúng tôi gọi dịch vụ ứng dụng, sau đó gọi kho lưu trữ và sau đó trả về danh sách việc cần làm được chuyển đổi từ đối tượng miền
Đây là một thử nghiệm đơn giản cho kho lưu trữ của chúng tôi trong
├── myapp │ ├── application │ ├── domain │ └── infrastructure └── pyproject.toml8
...
>> Trang tiếp theo >>
Trang tiếp theo
Michael Mercier
Kỹ sư phần mềm hàng đầu tại Ryax Technologies. Michael là Tiến sĩ Khoa học Máy tính và kỹ sư R&D, với kiến thức chuyên môn rộng về cơ sở hạ tầng CNTT trong nhiều bối cảnh. Đám mây, Điện toán hiệu năng cao và Dữ liệu lớn