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.