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 类。
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)