Chuyển đến nội dung chính

Dependency Injection trong Python: Xây dựng mã mô-đun và dễ kiểm thử

Tìm hiểu cách làm cho mã của bạn mô-đun, dễ kiểm thử và dễ bảo trì bằng cách hiểu và triển khai các framework dependency injection trong Python.
Đã cập nhật 5 thg 6, 2026  · 14 phút đọc

Dependency injection là một mẫu thiết kế trong đó các đối tượng hoặc dịch vụ (tức là các dependency) được cung cấp cho một lớp từ bên ngoài, thay vì lớp đó tự tạo bên trong. Điều này giúp tách rời các thành phần.

Việc sử dụng dependency injection trong Python mang lại nhiều lợi ích, chẳng hạn như:

  • Tính mô-đun: Mã của bạn được chia thành các phần nhỏ hơn, có thể tái sử dụng.
  • Dễ kiểm thử: Dễ dàng kiểm thử vì bạn có thể thay thế phần thực bằng các mock.
  • Dễ bảo trì: Bạn có thể cập nhật hoặc chỉnh sửa từng phần mã mà không làm hỏng phần còn lại của hệ thống. 

Trong hướng dẫn này, tôi sẽ minh họa cách sử dụng dependency injection trong Python thông qua các ví dụ đơn giản, thực tế.

TL;DR

  • Dependency injection là một mẫu thiết kế trong đó một lớp nhận các dependency từ bên ngoài thay vì tự tạo. Nó giúp mã của bạn mô-đun hơn.

  • Bạn có thể inject dependency thông qua constructor, phương thức setter, hoặc trực tiếp vào phương thức, và Python hỗ trợ việc này thủ công hoặc thông qua các framework như dependency-injector, injector, và punq.

  • Các web framework phổ biến xử lý khác nhau: FastAPI có hỗ trợ sẵn qua Depends(). Flask và Django cần thư viện như dependency-injector.

Tìm hiểu Dependency Injection trong Python

Để hiểu cách triển khai dependency injection, điều cốt yếu là bạn nắm vững các nguyên tắc chính chi phối nó.

Dependency injection dựa trên nguyên tắc Inversion of Control (IoC), nghĩa là thay vì một lớp tự tạo và quản lý các dependency, nó nhận chúng từ nguồn bên ngoài. Điều này giúp tập trung vào một nhiệm vụ duy nhất và làm mã của bạn gọn gàng.

Inject dependency trong Python bao gồm những cách phổ biến sau, ngoài ra còn có các cách khác:

  • Constructor injection: Truyền dependency qua constructor của lớp.
  • Setter injection: Thiết lập dependency bằng phương thức setter sau khi đối tượng được tạo.
  • Method injection: Truyền dependency trực tiếp vào phương thức cần dùng.

Vì sao nên dùng Dependency Injection trong Python?

Là một nhà phát triển, hiểu và triển khai dependency injection có thể tạo ra khác biệt lớn trong thiết kế mã của bạn. Hãy xem một số lợi ích của dependency injection.

Tính mô-đun, tái sử dụng và linh hoạt

Dependency injection cho phép bạn chia nhỏ mã thành các thành phần tập trung hơn, giúp tăng tính linh hoạt và mô-đun. 

Mỗi phần hoặc thành phần xử lý một tác vụ cụ thể và dựa vào các dependency bên ngoài, giúp dễ dàng tái sử dụng ở những phần khác trong ứng dụng của bạn hoặc của người khác

Ví dụ, nếu bạn có một lớp gửi tin nhắn. Thay vì xây dựng logic email hoặc SMS trực tiếp trong dịch vụ của bạn, bạn có thể inject một lớp gửi email hoặc SMS. 

Khi đó, lớp gửi tin nhắn của bạn không cần quan tâm tin nhắn được gửi như thế nào, mà chỉ sử dụng bộ gửi được cung cấp. Điều này ngụ ý rằng bạn có thể dùng bộ gửi tin nhắn với các bộ gửi email hoặc SMS khác nhau. 

Dễ bảo trì và kiểm thử

Khi dùng dependency injection, bạn có thể dễ dàng thay thế dependency thật bằng dữ liệu giả (mock) khi kiểm thử. Điều này giúp viết unit test đơn giản hơn mà không phụ thuộc vào API hoặc cơ sở dữ liệu. 

Giả sử, như một bài tập suy nghĩ, bạn có một lớp lưu dữ liệu vào cơ sở dữ liệu. Khi kiểm thử, bạn có thể inject một cơ sở dữ liệu giả thay vì cơ sở dữ liệu thật để tránh ràng buộc bài kiểm thử với cơ sở dữ liệu sản xuất.

Dependency injection cho phép bạn chỉnh sửa các phần cụ thể của mã mà không làm vỡ toàn bộ hệ thống. Điều này đặc biệt hữu ích khi cần chuyển nhà cung cấp thanh toán trong ứng dụng, ví dụ từ Stripe sang PayPal, mà không sửa đổi phần mã còn lại.

Đảo ngược kiểm soát và dependency có thể hoán đổi

Ý tưởng về đảo ngược kiểm soát là: dependency injection sẽ chuyển trách nhiệm tạo và quản lý dependency sang một nguồn bên ngoài. 

Điều này có nghĩa bạn có thể dễ dàng hoán đổi một triển khai cho triển khai khác; ví dụ, nếu bạn có lớp ReportGenerator định dạng và xuất báo cáo. 

Thay vì lớp tự quyết định định dạng xuất, bạn có thể inject một lớp xuất, như PDFExporter, ExcelExporter, hoặc CSVExporter, vào nó.

Lớp ReportGenerator không quan tâm việc xuất diễn ra thế nào, nên để đổi định dạng, bạn chỉ cần hoán đổi bộ xuất mà bạn inject, không cần chỉnh sửa ReportGenerator

Triển khai Dependency Injection trong Python

Giờ bạn đã hiểu dependency injection là gì và vì sao hữu ích, hãy xem cách triển khai nó trong Python. Bắt đầu với phương pháp đơn giản nhất: dependency injection thủ công.

Dependency injection thủ công

Với dependency injection thủ công, bạn tự tạo các dependency và truyền chúng vào lớp hoặc hàm cần dùng. 

Phương pháp này hoạt động tốt với ứng dụng nhỏ, nhưng khi ứng dụng mở rộng, bạn cần dùng một framework DI để tránh lỗi.

Dưới đây là ví dụ mã không dùng dependency injection

class EmailService:
    def send_email(self, message):
        print(f"Sending email: {message}")


class UserNotifier:
    def __init__(self):
        self.email_service = EmailService()  # Creates its own dependency

    def notify(self, message):
        self.email_service.send_email(message)


notifier = UserNotifier()
notifier.notify("Welcome!")

Trong ví dụ trên, UserNotifier gắn chặt với EmailService. Bạn không thể dễ dàng hoán đổi hoặc mock EmailService để kiểm thử. 

Dưới đây là một phiên bản khác, nhưng với dependency injection thủ công.

class EmailService:
    def send_email(self, message):
        print(f"Sending email: {message}")


class UserNotifier:
    def __init__(self, email_service):
        self.email_service = email_service  # Dependency is injected

    def notify(self, message):
        self.email_service.send_email(message)


email_service = EmailService()
notifier = UserNotifier(email_service)
notifier.notify("Welcome!")

Phiên bản trên linh hoạt và cho phép bạn inject các dịch vụ nhắn tin khác nhau; bạn cũng có thể thay EmailService bằng mock trong kiểm thử.

Sử dụng framework dependency-injector

Khi ứng dụng phát triển, việc quản lý dependency trở nên phức tạp và lộn xộn. 

Framework dependency-injector cung cấp một cách tiếp cận có cấu trúc.

Kiến trúc container-provider

Framework dependency-injector hoạt động dựa trên kiến trúc container-provider. 

  • Container: Sổ đăng ký trung tâm cho các dependency của ứng dụng.
  • Providers: Định nghĩa cách các dependency được tạo ra.
  • Configuration: Cho phép bạn chỉnh sửa thiết lập và inject vào các đối tượng. 
  • Overriding: Cho phép bạn thay thế dependency mà không đổi mã ứng dụng, đặc biệt khi kiểm thử. 
from dependency_injector import containers, providers


# Services
class EmailService:
    def send_email(self, message):
        print(f"Sending email: {message}")


class UserNotifier:
    def __init__(self, email_service):
        self.email_service = email_service

    def notify(self, message):
        self.email_service.send_email(message)


# Container
class Container(containers.DeclarativeContainer):
    email_service = providers.Singleton(EmailService)
    user_notifier = providers.Factory(UserNotifier, email_service=email_service)


# Usage
container = Container()
notifier = container.user_notifier()
notifier.notify("Hello!")

Trong ví dụ trên:

  • Container định nghĩa cách tạo các instance

  • Singleton đảm bảo chỉ có một instance của EmailService

  • Factory tạo một UserNotifier mới với EmailService được inject tự động.

Cơ chế wiring

Bạn có thể dùng decorator như @inject để giảm nhu cầu truyền dependency thủ công. 

Dưới đây là một phiên bản khác của ví dụ trước:

from dependency_injector import containers, providers


# Define services
class EmailService:
    def send_email(self, message):
        print(f"Sending email: {message}")


class UserNotifier:

    def __init__(self, email_service: EmailService):
        self.email_service = email_service

    def notify(self, message):
        self.email_service.send_email(message)


# Create container
class Container(containers.DeclarativeContainer):
    email_service = providers.Singleton(EmailService)
    user_notifier = providers.Factory(UserNotifier, email_service=email_service)


from dependency_injector.wiring import inject, Provide


@inject
def main(notifier: UserNotifier = Provide[Container.user_notifier]):
    notifier.notify("Hello!")


if __name__ == "__main__":
    container = Container()
    container.wire(modules=[__name__])  # This wires the current module
    main()

Trong phiên bản hiện tại;

  • Không cần thêm dependency thủ công, decorator @inject tự động inject dependency của hàm hoặc lớp.

  • Lớp Provide cho dependency-injector biết cần inject dependency nào.

Cách tiếp cận này loại bỏ nhu cầu wiring dependency thủ công từ container. 

Nó hữu ích trong các ứng dụng lớn nơi dịch vụ có thể phụ thuộc vào nhiều thành phần (do đó cần khởi tạo tự động).

Sử dụng Dependency Injection trong các Framework Python phổ biến

Hầu hết các framework Python hiện đại giúp bạn làm việc với dependency injection dễ dàng, cho phép viết mã gọn gàng và dễ kiểm thử hơn. 

Trong phần này, bạn sẽ học cách áp dụng dependency injection trong Flask, Django và FastAPI.

Dependency injection trong Flask

Flask không có hỗ trợ sẵn cho dependency injection, nhưng bạn có thể triển khai bằng thư viện dependency-injector theo các bước sau.

Bước 1: Định nghĩa dịch vụ của bạn

class GreetingService:
    def get_greeting(self, name):
        return f"Hello, {name}!"

Bước 2: Thiết lập container dependency injection

from dependency_injector import containers, providers

class Container(containers.DeclarativeContainer):
    greeting_service = providers.Factory(GreetingService)

Bước 3: Tạo ứng dụng Flask và inject dependency.

from flask import Flask, request
from dependency_injector.wiring import inject, Provide

container = Container()

app = Flask(__name__)

@app.route("/greet")
@inject
def greet(greeting_service: GreetingService = Provide[Container.greeting_service]):
    name = request.args.get("name", "World")
    return greeting_service.get_greeting(name)

container.wire(modules=[__name__])

if __name__ == "__main__":
    app.run(debug=True)

Từ đoạn mã trên:

  • Hàm name = request.args.get(“name”, “World”) trích xuất name từ query string và mặc định là ”World” nếu không được cung cấp. 

  • return greeting_service.get_greeting(name) gọi phương thức trên dịch vụ được inject và trả về thông điệp chào. 

  • container.wire(modules=[__name__]) yêu cầu dependency injector quét module này để tìm các decorator @inject và marker Provide[...] nhằm kết nối chúng với provider thực tế. 

Bước 4: Kiểm thử ứng dụng

Chạy ứng dụng trên terminal bằng lệnh python <name_of_file>.py, và truy cập URL http://localhost:5000/greet?name=Jacob

Bạn sẽ thấy như sau.

Hình ảnh hiển thị URL của một ứng dụng Flask đang chạy

Dependency injection trong Django

Tương tự Flask, Django không cung cấp dependency injection tích hợp sẵn. Tuy nhiên, bạn có thể dùng class-based view, middleware và cấu trúc app để tích hợp DI thủ công hoặc dùng thư viện dependency-injector

Dưới đây là quy trình từng bước để triển khai dependency injection trong Django. 

Bước 1: Định nghĩa dịch vụ

Tạo một dịch vụ đơn giản chứa business logic trong file mới services.py dưới thư mục app của bạn. 

# myapp/services.py
class GreetingService:
    def get_greeting(self, name):
        return f"Hello, {name}!"

Bước 2: Thiết lập container dependency injector

Bên trong thư mục ứng dụng của bạn, tạo một container bằng thư viện dependency-injector trong file mới containers.py.

# myapp/containers.py
from dependency_injector import containers, providers
from .services import GreetingService

class Container(containers.DeclarativeContainer):
    greeting_service = providers.Factory(GreetingService)

Bước 3: Tạo View với dependency được inject

Dùng decorator @inject trong view để nhận dependency trong views.py.

# myapp/views.py
from django.http import HttpResponse
from dependency_injector.wiring import inject, Provide
from .containers import Container
from .services import GreetingService

@inject
def greet_view(
    request, greeting_service: GreetingService = Provide[Container.greeting_service]
):
    name = request.GET.get("name", "World")
    message = greeting_service.get_greeting(name)
    return HttpResponse(message)

Bước 4: Cập nhật urls.py

Trong thư mục dự án, vào file urls.py và kết nối dependency injector vào hệ thống định tuyến URL của Django.

from django.contrib import admin
from django.urls import path
from myapp.views import greet_view

urlpatterns = [
    path("admin/", admin.site.urls),
    path("greet/", greet_view),
]

Bước 5: Wire container trong apps.py 

Vào apps.py và wire container vào ứng dụng. 

 # myapp/apps.py
from django.apps import AppConfig
from .containers import Container

class MyAppConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "myapp"

    def ready(self):
        container = Container()
        container.wire(modules=["myapp.views"])

Sau đó vào cấu hình app trong INSTALLED_APPS và thêm ứng dụng của bạn.

# settings.py
INSTALLED_APPS = [
    'myapp.apps.MyAppConfig',
    ...
]

Bước 6: Kiểm thử View

Khởi động máy chủ Django và truy cập view qua URL http://localhost:8000/greet/?name=Django

Bạn sẽ thấy như sau.

Hình ảnh hiển thị ứng dụng Django đang chạy.

Dependency Injection trong FastAPI

FastAPI có hỗ trợ sẵn cho dependency injection thông qua marker đặc biệt Depends() để inject bất cứ thứ gì một hàm trả về. 

Dưới đây là hướng dẫn từng bước về dependency injection trong FastAPI.

Bước 1: Định nghĩa một dịch vụ

class GreetingService:
    def get_greeting(self, name: str) -> str:
        return f"Hello, {name}!"

Bước 2: Tạo hàm dependency

def get_greeting_service():
    return GreetingService()

Bước 3: Thiết lập ứng dụng FastAPI và dùng Dependency Injection

app = FastAPI()

@app.get("/greet")
def greet(
    name: str = "World", service: GreetingService = Depends(get_greeting_service)
):
    return {"message": service.get_greeting(name)}

Bước 4: Chạy và kiểm thử ứng dụng

Khởi động máy chủ bằng lệnh uvicorn main:app –reload và truy cập URL http://localhost:8000/greet?name=FastAPI. Bạn sẽ thấy phản hồi sau.

Hình ảnh hiển thị một ứng dụng FastAPI đang chạy.

So sánh các Framework Dependency Injector cho Python

Có nhiều framework Dependency cho Python, mỗi cái có thế mạnh, cú pháp và trường hợp sử dụng riêng. Lựa chọn framework phù hợp phụ thuộc vào quy mô và cấu trúc dự án của bạn. 

Dưới đây là so sánh một số framework dependency injection phổ biến cho Python.

Framework

Phù hợp nhất cho

Thế mạnh

Hạn chế

dependency-injector

Ứng dụng phức tạp trong môi trường sản xuất

Đầy đủ tính năng, nhanh và có thể cấu hình

Dài dòng

injector

Đơn giản và phù hợp cho ứng dụng tối giản

Cú pháp gọn gàng

Ít nâng cao hơn các lựa chọn khác.

pinject

Logic binding nâng cao

Tự động wiring

Chậm hơn do introspection

punq

Ứng dụng đơn giản

Tối giản và nhanh

Tính năng hạn chế so với các lựa chọn khác

FastAPI Depends()

Ứng dụng FastAPI

Hỗ trợ sẵn cho async và dễ kiểm thử

Không thể tái sử dụng ngoài FastAPI

Flask-injector

Ứng dụng Flask

Dễ tích hợp với ứng dụng Flask

Phụ thuộc vào injector

django-injector

Dự án Django

Cung cấp dependency injection cho view và middleware

Phụ thuộc vào injector

Các mẫu triển khai nâng cao

Khi codebase của bạn trở nên lớn, dependency injection trở nên khó duy trì. 

Bạn có thể dùng DI nâng cao để xử lý các tác vụ như tài nguyên theo phạm vi request, dọn dẹp, và tách biệt mối quan tâm. 

Dependency theo phạm vi (scoped)

Scoped dependency cho phép bạn kiểm soát thời gian tồn tại của một dependency, ví dụ khi bạn muốn một đối tượng mới cho mỗi request, một đối tượng dùng chung trong vòng đời ứng dụng, hoặc tự động dọn dẹp tài nguyên như kết nối cơ sở dữ liệu. 

Hầu hết các framework DI hỗ trợ các scope như:

  • Singleton: Một instance cho toàn bộ ứng dụng.
  • Factory: Tạo instance mới mỗi lần được inject.
  • Thread-local hoặc theo request: Một instance cho mỗi thread/request.
  • Resource: Được quản lý với logic khởi tạo và hủy (setup/teardown).

Dưới đây là ví dụ về scoped dependency với container lồng nhau dùng dependency-injector.

from dependency_injector import containers, providers, resources

# A simple resource that needs setup and cleanup
class MyResource:
    def __enter__(self):
        print("Setting up the resource")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Cleaning up the resource")

# Create a container to manage the resource
class MyContainer(containers.DeclarativeContainer):
    resource = providers.Resource(MyResource)

# Using the resource with a context manager
container = MyContainer()

with container.resource() as r:
    print("Using the resource")

Trong ví dụ trên, MyResource mô phỏng một lớp cần setup và teardown, như mở/đóng file hoặc cơ sở dữ liệu.

container cung cấp resource bằng providers.Resource, và khi bạn dùng with container.resource(), nó vào và thoát ngữ cảnh đúng cách.

Mẫu này đảm bảo các tài nguyên như handle file, kết nối cơ sở dữ liệu, hoặc phiên mạng được quản lý sạch sẽ.

Hãy hình dung một endpoint API cần một phiên cơ sở dữ liệu cho request hiện tại hoặc một logger có ngữ cảnh theo request. 

Với scoped dependency, framework tạo một container theo phạm vi request khi request bắt đầu và resolve mọi dependency từ scope đó. 

Khi request kết thúc, mọi việc dọn dẹp cần thiết, như đóng phiên cơ sở dữ liệu, sẽ diễn ra tự động. Điều này đảm bảo trạng thái không bị chia sẻ giữa các request và tránh rò rỉ tài nguyên.

Injection bất đồng bộ (asynchronous)

Để xử lý hiệu quả các tác vụ I/O-bound, bạn cần thực thi bất đồng bộ. Hệ thống DI trong các framework như FastAPI và dependency-injector hỗ trợ injection bất đồng bộ dùng async/await.

Asynchronous injection là việc inject các dependency là hàm async def hoặc quản lý async resources như kết nối DB async, API bên ngoài, hoặc tác vụ nền. 

Thay vì resolve một cách đồng bộ, hệ thống DI sẽ chờ chúng được resolve trước khi inject. 

Dưới đây là ví dụ về dependency async trong FastAPI:

from fastapi import FastAPI, Depends

app = FastAPI()

# Async dependency
async def get_user():
    # Simulate async I/O
    await some_async_db_query()
    return {"name": "Philip Jones"}

@app.get("/profile")
async def read_profile(user: dict = Depends(get_user)):
    return {"user": user}

FastAPI tự động xử lý việc resolve bất đồng bộ của get_user và inject kết quả.

dependency-injector cũng hỗ trợ quản lý vòng đời async bằng provider AsyncResource.

from dependency_injector import containers, providers, resources

class AsyncDB:
    async def connect(self):
        print("Connected")
        return self

    async def disconnect(self):
        print("Disconnected")

class AsyncDBResource(resources.AsyncResource):
    async def init(self) -> AsyncDB:
        db = AsyncDB()
        return await db.connect()

    async def shutdown(self, db: AsyncDB):
        await db.disconnect()

class Container(containers.DeclarativeContainer):
    db = providers.Resource(AsyncDBResource)

# Usage
async def main():
    container = Container()
    await container.init_resources()

    db = await container.db()  # Get the AsyncDB instance

    # Use db here...

    await container.shutdown_resources()

Trong đoạn mã trên:

  • resources.AsyncResource được dùng cho logic setup/teardown async.

  • await container.init_resources() khởi tạo tất cả resource và gọi init.

  • await container.shutdown_resources() dọn dẹp tất cả resource.

  • await container.db() lấy instance resource thực tế, là AsyncDB.

Sử dụng async DI giúp ứng dụng vận hành hiệu quả bằng cách tránh I/O đồng bộ trong các route bất đồng bộ, và loại bỏ nhu cầu tự await dependency trong logic của bạn. 

Hệ quả bảo mật

Mặc dù DI cải thiện tính mô-đun và khả năng kiểm thử, nó có thể mang đến rủi ro bảo mật nếu không được quản lý cẩn thận.

Một rủi ro đáng chú ý là dependency confusion, khi các gói độc hại giả mạo gói hợp pháp. Ví dụ, kẻ tấn công đã tải lên các gói lên PyPI với tên được dùng trong hệ thống nội bộ của doanh nghiệp để lừa ứng dụng sử dụng phiên bản độc hại.

Để an toàn, hãy đảm bảo bạn không bao giờ inject các thành phần không tin cậy hoặc do người dùng kiểm soát và xác thực nguồn cấu hình, như biến môi trường, trước khi inject.

Sử dụng file requirements.txt hoặc poetry.lock để khóa phiên bản chính xác, và tự động quét bảo mật khi thay đổi dependency. Ngoài ra, đảm bảo thường xuyên kiểm toán các dependency để phát hiện lỗ hổng.

Tập trung hóa việc kiểm soát dependency cũng rất quan trọng, vì nó đảm bảo tính nhất quán giữa các ứng dụng và giúp áp dụng bản vá bảo mật hoặc cập nhật phiên bản dễ dàng hơn. Nó giúp tránh các "shadow dependency" được inject theo nhiều cách khác nhau trong codebase. 

Một số công cụ có thể phát hiện rủi ro và cải thiện tư thế bảo mật của bạn:

  • Snyk: Quét các lỗ hổng đã biết trong gói Python và đề xuất cách khắc phục.
  • Xygeni: Cung cấp bảo vệ cho pipeline CI/CD.
  • Safety: Kiểm tra các gói không an toàn và lỗ hổng trong mã.
  • Bandit: Trình phân tích mã tĩnh cho Python tập trung vào vấn đề bảo mật.

Ứng dụng thực tế

Dependency injection không chỉ là lý thuyết; nó được dùng trong nhiều hệ thống sản xuất để quản lý độ phức tạp, cải thiện tính linh hoạt và đơn giản hóa kiểm thử. 

Cấu hình dịch vụ web

Trong các ứng dụng web hiện đại, DI đóng vai trò quan trọng trong quản lý các dịch vụ chuẩn như xác thực, logging và truy cập cơ sở dữ liệu. 

Các framework như FastAPI đóng vai trò thiết yếu trong việc resolve route và dependency.

Ví dụ dưới đây minh họa một triển khai xác thực tập trung trong FastAPI.

from fastapi import Depends, HTTPException

def get_current_user(token: str = Depends(oauth2_scheme)):
    user = verify_token(token)
    if not user:
        raise HTTPException(status_code=401)
    return user

@app.get("/protected")
def protected_route(user: dict = Depends(get_current_user)):
    return {"message": f"Hello {user['name']}"}

Trong đoạn mã trên, get_current_user() được inject làm dependency của route, nhưng logic kiểm tra token được tập trung và có thể tái sử dụng.

Bạn cũng có thể làm tương tự cho truy cập cơ sở dữ liệu, để các phiên làm việc được tự động quản lý và dọn dẹp. 

Dưới đây là ví dụ cách dùng DI để tập trung hóa truy cập cơ sở dữ liệu.

from sqlalchemy.ext.asyncio import AsyncSession

async def get_db_session() -> AsyncSession:
    async with async_session() as session:
        yield session

@app.get("/items")
async def read_items(db: AsyncSession = Depends(get_db_session)):
    return await db.execute("SELECT * FROM items")

Trong đoạn mã trên, get_db_session là một nhà cung cấp dependency. Khi route read_items được gọi, FastAPI tự động inject tham số db, cho phép route truy cập cơ sở dữ liệu mà không cần tự thiết lập hay dọn dẹp phiên. 

Phát triển hướng kiểm thử (TDD)

Dependency injection giúp bạn dễ dàng thay đổi các dependency mà thành phần nhận, điều này rất quan trọng khi kiểm thử. 

Bạn có thể thay dịch vụ thật bằng mock trong khi kiểm thử, khiến mã của bạn mô-đun hơn và dễ kiểm thử hơn. 

FastAPI và dependency-injector hỗ trợ ghi đè dependency tại thời điểm kiểm thử. 

Dưới đây là ví dụ trong FastAPI.

from fastapi.testclient import TestClient
from main import app, get_db_session

def override_db():
    return TestDBSession()

app.dependency_overrides[get_db_session] = override_db

client = TestClient(app)
response = client.get("/items")
assert response.status_code == 200

Trong ví dụ trên, get_db_session được ghi đè bằng cơ sở dữ liệu giả trong quá trình kiểm thử; do đó không cần vá (patch) hoặc thay đổi mã sản xuất.

Tương tự trong depedency-injector, bạn có thể ghi đè cơ sở dữ liệu thật bằng cơ sở dữ liệu giả để kiểm thử, như ví dụ dưới đây.

from containers import Container

def test_service_behavior():
    container = Container()
    container.db.override(providers.Factory(TestDB))

    service = container.service()
    assert service.get_data() == "mocked"

Phương thức override() thay thế dependency thật bằng mock, một cách sạch sẽ, có kiểm soát và có thể đảo ngược. 

Dưới đây là một số chiến lược bạn nên dùng để ghi đè dependency:

  • Constructor injection: Đảm bảo bạn truyền mock trực tiếp vào constructor.
  • Ghi đè trong framework: Dùng công cụ ghi đè tích hợp như FastAPI.dependency_overrides trong FastAPI hoặc dependency_injector.override() trong dependency-injector.
  • Fixture: Dùng framework kiểm thử như pytest để tạo mock dùng lại và inject qua fixture.

Thực hành tốt và các phản mẫu (anti-pattern)

Mặc dù DI nhằm giúp mã mô-đun và dễ kiểm thử, nó có thể dẫn đến lỗi hoặc tạo độ phức tạp trong mã. 

Khuyến nghị thực hành

Dưới đây là một số thực hành tốt bạn nên tuân thủ khi tạo dependency:

  • Lập trình hướng giao diện, không hướng triển khai: Định nghĩa dependency bằng abstract base class hoặc interface thay vì các triển khai phức tạp. Điều này giúp mã dễ kiểm thử và khuyến khích dùng mock trong unit test.
  • Ưu tiên composition hơn kế thừa: Thay vì xây dựng hệ phân cấp lớp phức tạp, hãy cấu thành đối tượng bằng các dependency được inject để giữ thành phần nhỏ gọn và tập trung.
  • Cấu hình dependency tập trung: Sử dụng một container hoặc module duy nhất để định nghĩa và quản lý dependency, giúp việc ghi đè và kiểm thử dễ dàng hơn.
  • Tránh dependency vòng tròn: Thiết kế cẩn thận đồ thị dependency để ngăn tham chiếu vòng tròn bằng cách sử dụng factory function hoặc provider để trì hoãn khởi tạo.

Những lỗi thường gặp

Dưới đây là một số lỗi thường gặp bạn nên tránh khi dùng DI.

  • Phản mẫu service locator: Tránh dùng container toàn cục để lấy dependency động, vì có thể che giấu dependency và làm mã khó hiểu, khó kiểm thử.
  • Over-injection: Tránh inject quá nhiều dịch vụ vào một lớp hoặc hàm, để không dẫn đến constructor cồng kềnh và logic khó kiểm thử.
  • Quản lý scope kém và rò rỉ trạng thái: Đảm bảo luôn dùng đúng scope để tránh chia sẻ trạng thái giữa người dùng hoặc bài kiểm thử.

Kiểm thử Dependency Injection trong Python

Dependency injection giúp dễ dàng inject các đối tượng mock vào thành phần của bạn, từ đó đơn giản hóa unit test. Điều này cho phép bạn tách các thành phần cốt lõi khỏi các thành phần dành riêng cho kiểm thử.

Với DI, các bài test có tính quyết định, không có tác dụng phụ và chạy nhanh hơn. 

Dưới đây là ví dụ cho thấy cách bạn có thể dùng DI để inject các phiên bản mock, stub, hoặc fake của những dependency trong bài kiểm thử. 

class EmailService:
    def send_email(self, to: str, message: str): ...

class Notifier:
    def __init__(self, email_service: EmailService):
        self.email_service = email_service

    def alert(self, user_email):
        self.email_service.send_email(user_email, "Alert!")

Trong bài kiểm thử, thay EmailService bằng mock:

class TestEmailService:
    def send_email(self, to, message):
        self.sent_to = to

def test_notifier_sends_email():
    test_email = TestEmailService()
    notifier = Notifier(test_email)

    notifier.alert("test@example.com")
    assert test_email.sent_to == "test@example.com"

Hãy dùng DI thường xuyên và tránh monkey patching để dễ bảo trì và tránh thay đổi phá vỡ khi tên nội bộ thay đổi.

Kết luận

Mặc dù ban đầu bạn có thể chưa cần DI cho các dự án nhỏ, nhưng khi dự án lớn lên và mở rộng, bạn sẽ hưởng lợi từ cấu trúc và tính mô-đun mà DI mang lại. 

Nhìn về phía trước, tương lai của DI trong Python rất hứa hẹn, và chúng ta có thể kỳ vọng tiếp tục có hệ thống kiểu (type system) được cải thiện và quản lý vòng đời async tốt hơn.

Thành thạo DI là điều thiết yếu để trở thành một lập trình viên Python thuần thục. Nhưng chưa dừng lại ở đó; còn nhiều khái niệm nâng cao khác có thể nâng tầm kỹ năng Python của bạn. 

Để giúp bạn tăng tốc lộ trình học tập, dưới đây là một số tài nguyên bạn nên tham khảo.

Khóa học:

Bài viết blog:


Adejumo Ridwan Suleiman's photo
Author
Adejumo Ridwan Suleiman
LinkedIn

Giảng viên khoa học dữ liệu giàu kinh nghiệm và Nhà thống kê sinh học, có chuyên môn về Python, R và học máy.

FAQs

Dependency Injection là gì?

Dependency injection là một mẫu thiết kế giúp bạn viết mã sạch hơn bằng cách tạo các dependency trong một lớp, có thể được truyền từ bên ngoài, khiến mã của bạn dễ quản lý và chỉnh sửa hơn.

Lợi ích chính của Dependency Injection là gì?

Dependency injection giúp mã của bạn mô-đun, dễ kiểm thử và dễ bảo trì.

Làm thế nào để triển khai dependency injection trong Python?

Bạn có thể triển khai DI trong Python thủ công hoặc bằng các thư viện như dependency_injector.

Asynchronous Injection là gì?

Asynchronous injection là việc inject các dependency là hàm async def hoặc quản lý async resources như kết nối DB async, API bên ngoài, hoặc tác vụ nền.

Các rủi ro bảo mật của dependency injection là gì và tôi xử lý chúng như thế nào?

Một rủi ro đáng kể là dependency confusion, khi các gói độc hại giả mạo gói hợp pháp. Để ngăn chặn, bạn có thể dùng các công cụ như Snyk, Xygeni, Safety hoặc Bandit để quét mã và gói của bạn nhằm phát hiện lỗ hổng.

Chủ đề

Học Python với DataCamp

Courses

Nhập môn Python

4 giờ
6.9M
Nắm vững phân tích dữ liệu với Python chỉ trong 4 giờ. Khóa học online này giúp bạn làm quen với giao diện Python và các thư viện phổ biến.
Xem chi tiếtRight Arrow
Bắt đầu khóa học
Xem thêmRight Arrow