Triển khai Hệ thống Nhiều Cơ sở Dữ liệu trong Django qua Router

Giới thiệu về việc phân tách cơ sở dữ liệu

Trong quá trình phát triển các hệ thống Django phức tạp, việc một dự án chứa nhiều ứng con (apps) là điều phổ biến. Tuy nhiên, yêu cầu về hiệu năng và bảo mật đôi khi đòi hỏi mỗi ứng con phải lưu trữ dữ liệu trên một cơ sở dữ liệu riêng biệt. Để giải quyết vấn đề này, Django cung cấp cơ chế Database Router cho phép định tuyến các thao tác đọc/ghi đến đúng cơ sở dữ liệu mục tiêu.

1. Cấu hình kết nối trong settings.py

Bước đầu tiên là khai báo các kết nối cơ sở dữ liệu trong file cấu hình chính. Thay vì chỉ có một kết nối mặc định, chúng ta sẽ định nghĩa thêm các kết nối phụ.

DATABASES = {
    'primary': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db_primary.sqlite3',
    },
    'analytics': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db_analytics.sqlite3',
    },
    'legacy': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db_legacy.sqlite3',
    },
}

Trong ví dụ này, hệ thống sử dụng 3 cơ sở dữ liệu: primary cho dữ liệu chính, analytics cho dữ liệu phân tích và legacy cho dữ liệu cũ.

2. Đăng ký Database Router

Để Django biết được cần sử dụng logic định tuyến nào, bạn cần thêm đường dẫn đến class Router vào danh sách DATABASE_ROUTERS trong settings.py.

DATABASE_ROUTERS = ['core.routers.MultiDBRouter']

Giả sử project của bạn tên là core, file chứa logic định tuyến là routers.py và class xử lý là MultiDBRouter.

3. Thiết lập bản đồ ánh xạ ứng dụng

Chúng ta cần một cấu hình để quy định rõ ứng con nào sẽ tương tác với cơ sở dữ liệu nào. Bạn có thể tạo một biến cấu hình tùy chỉnh trong settings.py:

APP_DB_ROUTE_MAP = {
    'account': 'primary',
    'reporting': 'analytics',
    'old_system': 'legacy',
    # Các app hệ thống của Django
    'auth': 'primary',
    'contenttypes': 'primary',
}

Cấu hình này giúp việc quản lý trở nên linh hoạt hơn, dễ dàng thay đổi mà không cần sửa sâu vào code logic của Router.

4. Implement logic định tuyến

Tại file core/routers.py, chúng ta sẽ viết class chịu trách nhiệm điều hướng các thao tác. Class này sẽ dựa vào biến APP_DB_ROUTE_MAP đã khai báo ở trên.

from django.conf import settings

class MultiDBRouter:
    """
    Điều hướng các thao tác database dựa trên app_label của model.
    """
    
    def __init__(self):
        self.route_map = getattr(settings, 'APP_DB_ROUTE_MAP', {})

    def db_for_read(self, model, **hints):
        """Xác định database cho thao tác đọc."""
        app_label = model._meta.app_label
        return self.route_map.get(app_label)

    def db_for_write(self, model, **hints):
        """Xác định database cho thao tác ghi."""
        app_label = model._meta.app_label
        return self.route_map.get(app_label)

    def allow_relation(self, obj1, obj2, **hints):
        """
        Cho phép tạo quan hệ giữa các model chỉ khi chúng cùng nằm trên một database.
        """
        db1 = self.route_map.get(obj1._meta.app_label)
        db2 = self.route_map.get(obj2._meta.app_label)
        
        if db1 is not None and db2 is not None:
            return db1 == db2
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Đảm bảo migration chỉ chạy trên database được chỉ định cho app đó.
        """
        if app_label in self.route_map:
            return self.route_map[app_label] == db
        elif db == 'primary':
            return None
        return False

Logic này đảm bảo rằng các thao tác đọc/ghi sẽ tự động chuyển hướng, và quá trình migrate chỉ tạo bảng ở đúng nơi quy định.

5. Định nghĩa Model với app_label

Khi tạo các model trong mỗi ứng con, việc khai báo app_label trong class Meta là bắt buộc để Router có thể nhận diện chính xác. Nếu thiếu, Django có thể hiểu sai và đưa vào database mặc định.

Ví dụ trong ứng account:

class Member(models.Model):
    email = models.EmailField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        app_label = 'account'

Ví dụ trong ứng reporting:

class DailyLog(models.Model):
    member = models.ForeignKey('account.Member', on_delete=models.CASCADE)
    action = models.CharField(max_length=50)
    timestamp = models.DateTimeField(auto_now_add=True)

    class Meta:
        app_label = 'reporting'

Lưu ý rằng quan hệ ForeignKey vẫn có thể hoạt động giữa các app nếu Router cho phép (cùng database hoặc cấu hình cho phép quan hệ chéo).

6. Thực thi Migration

Khi chạy lệnh tạo bảng, bạn cần chỉ rõ database mục tiêu thông qua tham số --database. Nếu không chỉ định, Django sẽ cố gắng tạo trên database mặc định.

Tạo bảng cho ứng account trên database primary:

python manage.py migrate --database=primary

Tạo bảng cho ứng reporting trên database analytics:

python manage.py migrate --database=analytics

Sau khi hoàn tất quá trình khởi tạo bảng, các thao tác truy vấn dữ liệu thông qua ORM sẽ tự động diễn ra trên đúng cơ sở dữ liệu đã cấu hình mà không cần gọi thêm phương thức using() thủ công.

Thẻ: Django database-router multi-database orm backend-architecture

Đăng vào ngày 21 tháng 5 lúc 19:59