Django项目中业务逻辑代码应该写在哪里
什么是业务逻辑?#
业务逻辑就是程序中那些真正用于赚钱或省钱的业务逻辑与过程。 – 《Clean Architecture》
例如:
账户的余额不能为负数
用户选择激活后向用户发送一封激活邮件
业务逻辑应该写在哪里?#
Django框架只提供了View和Model两种对象,没有专门的业务逻辑层(类似Spring的Service层)。
The view layer#
Django has the concept of “views” to encapsulate the logic responsible for processing a user’s request and for returning the response.
View是用来处理用户的请求并返回结果的,业务逻辑代码写在View中是不合适的。
因为这样业务逻辑就与请求返回的协议耦合在一起,同时也不方便业务逻辑之间相互调用(只能通过HTTP请求来调用了)。
The model layer#
Django provides an abstraction layer (the “models”) for structuring and manipulating the data of your web application.
Django中Model采用的是 Active Record 模式(我非常喜欢Active Record)。
如果业务逻辑只影响某一个业务对象,可以考虑将业务逻辑放到Model中,像上面提到的:账户的余额不能为负,是可以考虑放在Model类中进行检查的。
值得一提的是网上有不少建议不要把业务逻辑检查的代码放在model.save()函数中,Django框架为model的自定义校验提供了专门的函数:https://docs.djangoproject.com/en/4.1/ref/models/instances/#validating-objects
Model的业务逻辑校验代码放在model.clean()函数中是比较合适的,需要注意的是model.clean()函数不会在model.save()中自动被调用,需要自己写代码调用。
如果在创建业务对象时需要使用的业务逻辑,可以放在Manager对象中。
一些业务逻辑中的查询(例如:获取全部未激活的用户),可以在Manager中增加对应的查询函数,或者使用QuerySet method。
在这篇文章中,作者强烈推荐在Model中编写业务逻辑代码,反对使用Service:https://www.b-list.org/weblog/2020/mar/16/no-service/
但我认为,那些跨多个业务实体或系统的逻辑,写在Model中显然不合适。
Service layer#
跨多个业务实体或系统的逻辑应该放在Service层,例如上面用户激活的例子:
services.py
def activate_user(user_id):
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
上面的代码来自于这个问题的回答:https://stackoverflow.com/questions/12578908/separation-of-business-logic-and-data-access-in-django
代码展示了用Service层实现业务逻辑的例子。由于Django中是没有Service对象的,所以只能自己来写。
好在自己写也不复杂,只需要在django的app中用一个services.py文件来实现service的业务逻辑即可。
实现风格#
网上有些建议在services.py文件中直接使用函数来组织业务逻辑代码,我认为这种面向过程风格的代码在需要进行依赖反转时会遇到问题。
在进行依赖反转时,需要在调用的模块定义接口,在另外的模块实现接口,使用面向对象风格的代码在进行依赖反转改造时会更简单。
在Spring的Service层中,通常是用一个类来实现一个模块中的所有业务处理,不过《Clean Architecture》的风格更偏向于每个业务处理用一个类实现。
依赖注入#
提到依赖反转,就会想到Spring中方便的依赖注入,确实能够很大程度上简化系统构造的代码。
Python也有一些依赖注入的库,例如:python-inject和injector。
边界隔离#
引入了Service层后,还有一个问题需要考虑:各个模块(或子系统)之间隔离到什么程度。
参考资料:#
https://stackoverflow.com/questions/12578908/separation-of-business-logic-and-data-access-in-django