Django 实战项目业务逻辑规划

理论

大型公司如谷歌、Facebook 在进行业务逻辑规划时,一般会遵循以下原则:

  • 分层:业务逻辑应分为多个层次,每个层次都有其特定的职责。常见的分层包括数据访问层(DAO)、业务逻辑层(Service)、表示层(Controller 或 View)等。

  • 解耦:不同的业务逻辑应尽可能地解耦。解耦可以降低代码的复杂性,提高代码的可读性和可维护性。

  • 可测试:业务逻辑应易于进行单元测试。可测试的代码可以保证代码的质量,降低代码的错误率。

以下是一个简单的示例,这个示例展示了如何将一个博客系统的业务逻辑分解为数据访问层、业务逻辑层和表示层:

# DAO 层
class PostDAO:
    def get(self, post_id):
        # 获取文章
        pass

    def save(self, post):
        # 保存文章
        pass

# Service 层
class PostService:
    def __init__(self, dao):
        self.dao = dao

    def get_post(self, post_id):
        # 获取文章
        return self.dao.get(post_id)

    def create_post(self, title, content, author):
        # 创建文章
        post = Post(title, content, author)
        return self.dao.save(post)

# Controller 层
class PostController:
    def __init__(self, service):
        self.service = service

    def get_post(self, post_id):
        # 获取文章
        post = self.service.get_post(post_id)
        return JsonResponse(post)

    def create_post(self, request):
        # 创建文章
        title = request.POST.get('title')
        content = request.POST.get('content')
        author = request.POST.get('author')
        post = self.service.create_post(title, content, author)
        return JsonResponse(post)

在这个示例中,PostDAO 类是数据访问层,负责与数据库进行交互;PostService 类是业务逻辑层,负责处理与文章相关的业务逻辑;PostController 类是表示层,负责处理 HTTP 请求和响应。

这种分层的设计可以使代码结构更清晰,更易于维护和测试。同时,通过依赖注入(在构造函数中传入依赖的对象)可以使代码更加灵活,更容易进行单元测试。

我们需要明确一些概念。在BaseService类中,我们通常处理更复杂的业务逻辑,而不仅仅是简单地将GenericDAO中的方法封装起来。例如,我们可能需要在创建、更新或删除数据时进行一些额外的操作,如验证数据、记录日志、发送通知等。

封装

谷歌在其 Python 项目中主要使用了一种叫做 "Dependency Injection"(依赖注入)的设计模式,这种模式能够提高代码的模块性和可测试性。

首先我们来看一下 DAO 的改进。我们可以使用一个通用的 DAO,然后通过依赖注入的方式来动态设定它操作的数据模型。

generic_dao.py

from typing import Type, List, Dict, Any, Optional, Union
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import Model, QuerySet

class GenericDAO:
    def __init__(self, model: Type[Model]):
        self.model = model

    def create(self, **kwargs: Any) -> Model:
        return self.model.objects.create(**kwargs)

    def get(self, id: int) -> Optional[Model]:
        try:
            return self.model.objects.get(id=id)
        except self.model.DoesNotExist:
            return None

    def update(self, id: int, **kwargs: Any) -> Optional[Model]:
        try:
            instance = self.model.objects.get(id=id)
            for key, value in kwargs.items():
                setattr(instance, key, value)
            instance.save()
            return instance
        except self.model.DoesNotExist:
            return None

    def delete(self, id: int) -> int:
        return self.model.objects.filter(id=id).delete()[0]

    def list(self, offset: int = 0, limit: int = 10, related_fields: Optional[List[str]] = None) -> Dict[str, Any]:
        query_set = self.model.objects.all()
        if related_fields:
            query_set = query_set.select_related(*related_fields)
        paginator = Paginator(query_set, limit)
        try:
            results = paginator.page(offset)
        except PageNotAnInteger:
            results = paginator.page(1)
        except EmptyPage:
            results = paginator.page(paginator.num_pages)
        return {'total': paginator.count, 'results': list(results)}

    def all(self) -> QuerySet:
        return self.model.objects.all()

然后我们再看一下 Service 的改进。我们可以创建一个基本的 Service 类,然后再创建具体的 Service 类。在具体的 Service 类中,我们通过依赖注入的方式来设定它使用的 DAO。

base_service.py


class BaseService:
    def __init__(self, dao: GenericDAO):
        self.dao = dao

    def create(self, **kwargs: Any) -> Model:
        # 在这里,我们可以添加一些额外的业务逻辑,如验证数据、记录日志、发送通知等。
        # ...
        instance = self.dao.create(**kwargs)
        # ...
        return instance

    def get(self, id: int) -> Optional[Model]:
        # 在这里,我们可以添加一些额外的业务逻辑,如缓存数据、记录日志等。
        # ...
        instance = self.dao.get(id)
        # ...
        return instance

    def update(self, id: int, **kwargs: Any) -> Optional[Model]:
        # 在这里,我们可以添加一些额外的业务逻辑,如验证数据、记录日志、发送通知等。
        # ...
        instance = self.dao.update(id, **kwargs)
        # ...
        return instance

    def delete(self, id: int) -> int:
        # 在这里,我们可以添加一些额外的业务逻辑,如记录日志、发送通知等。
        # ...
        count = self.dao.delete(id)
        # ...
        return count

    def list(self, offset: int = 0, limit: int = 10, related_fields: Optional[List[str]] = None) -> Dict[str, Any]:
        # 在这里,我们可以添加一些额外的业务逻辑,如缓存数据、记录日志等。
        # ...
        result = self.dao.list(offset, limit, related_fields)
        # ...
        return result

    def all(self) -> QuerySet:
        # 在这里,我们可以添加一些额外的业务逻辑,如缓存数据、记录日志等。
        # ...
        result = self.dao.all()
        # ...
        return result

使用:

# 在 blog app 中创建一个具体的 service.py
from blog.models import Post
from core.service import BaseService
from core.dao import GenericDAO

class PostService(BaseService):
    def __init__(self):
        super().__init__(GenericDAO(Post))

在视图中,我们可以像之前一样使用 PostService:

# views.py

from django.http import HttpResponse
from blog.service import PostService

def post_view(request, post_id):
    service = PostService()
    post = service.get(post_id)
    return HttpResponse(post)

def create_post_view(request):
    service = PostService()
    post = service.create_post("title", "content", "author")
    return HttpResponse(post)

通过依赖注入,我们可以更容易地改变 Service 使用的 DAO,或者改变 DAO 操作的数据模型,而不需要修改 Service 或 DAO 的代码。这种设计方式使得代码更加灵活,也更容易进行单元测试。

为什么不可继承?

在面向对象编程中,一般情况下 Service 对象不应该继承 DAO 对象,因为 DAO 对象是对数据库访问的封装,而 Service 对象是对业务逻辑的封装。Service 对象应该使用 DAO 对象来实现数据库访问,而不是成为 DAO 对象的子类。

在基于 Django ORM 的 DAO 和 Service 封装中,BaseService 对象中持有一个 BaseDAO 对象,并使用 BaseDAO 对象来实现数据库访问。这种组合而非继承的方式更符合面向对象设计原则中的“合成复用原则”(Composition Over Inheritance),可以更好地实现代码复用和解耦。

因此,在基于 Django ORM 的 DAO 和 Service 封装中,不建议使用继承的方式将 BaseService 类与 BaseDAO 类耦合起来,而是应该通过组合的方式来使用 BaseDAO 类。

为者常成,行者常至