Django中业务逻辑和数据访问的分离

Django中业务逻辑和数据访问的分离,第1张

Django中业务逻辑和数据访问的分离

您似乎在询问 数据模型领域模型 之间的区别–后者是您可以找到最终用户感知的业务逻辑和实体的地方,前者是您实际存储数据的地方。

此外,我将问题的第三部分解释为:如何注意到未能将这些模型分开的问题。

这是两个截然不同的概念,很难将它们分开。但是,有一些通用的模式和工具可用于此目的。

关于领域模型

您需要认识的第一件事是您的域模型并不是真正的数据。它 涉及 诸如“激活此用户”,“停用此用户”,“当前已激活哪些用户”和“此用户的名字是什么”之类的
动作问题 。用经典术语来说:它是关于 查询命令的

指挥思维

让我们从示例中的命令开始:“激活此用户”和“停用此用户”。关于命令的好处是,它们可以很容易地通过小给定的情况来表达:


管理员激活该用户 时, 将其 指定 为非活动用户
该用户将变为活动状态
并向该用户发送确认电子邮件,
并将 条目添加到系统日志
(等)。

这种情况对于查看单个命令如何影响基础结构的不同部分很有帮助,在这种情况下,您的数据库(某种“活动”标志),邮件服务器,系统日志等会受到影响。

这样的场景也确实可以帮助您设置测试驱动开发环境。

最后,思考命令确实可以帮助您创建面向任务的应用程序。您的用户将对此表示赞赏:-)

表达命令

Django提供了两种简单的表达命令的方式:它们都是有效的选择,并且将两种方法混合使用并不罕见。

服务层

服务模块
已经通过@Hedde描述。在这里,您定义了一个单独的模块,每个命令都表示为一个函数。

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
使用表格

另一种方法是为每个命令使用Django表单。我更喜欢这种方法,因为它结合了多个紧密相关的方面:

  • 命令的执行(它做什么?)
  • 验证命令参数(可以执行此 *** 作吗?)
  • 命令演示(如何执行此 *** 作?)

表格

class ActivateUserForm(forms.Form):    user_id = IntegerField(widget = UsernameSelectWidget, verbose_name="Select a user to activate")    # the username select widget is not a standard Django widget, I just made it up    def clean_user_id(self):        user_id = self.cleaned_data['user_id']        if User.objects.get(pk=user_id).active: raise ValidationError("This user cannot be activated")        # you can also check authorizations etc.         return user_id    def execute(self):        """        This is not a standard method in the forms API; it is intended to replace the         'extract-data-from-form-in-view-and-do-stuff' pattern by a more testable pattern.         """        user_id = self.cleaned_data['user_id']        user = User.objects.get(pk=user_id)        # set active flag        user.active = True        user.save()        # mail user        send_mail(...)        # etc etc
查询中思考

您的示例不包含任何查询,因此我自由地编写了一些有用的查询。我更喜欢使用“问题”一词,但是查询是经典的术语。有趣的查询是:“此用户的名称是什么?”,“该用户可以登录吗?”,“向我显示已停用用户的列表”和“已停用用户的地理分布是什么?”。

在着手回答这些问题之前,您应该始终问自己以下问题:

  • 仅针对我的模板的 演示 查询,和/或
  • 与执行我的命令有关的 业务逻辑 查询,和/或
  • 一个 报表 查询。

呈现查询只是为了改善用户界面。业务逻辑查询的答案直接影响命令的执行。报告查询仅用于分析目的,并且具有较宽松的时间限制。这些类别不是互相排斥的。

另一个问题是:“我是否完全控制答案?” 例如,在查询用户名(在这种情况下)时,我们对结果没有任何控制权,因为我们依赖于外部API。

进行查询

Django中最基本的查询是使用Manager对象:

User.objects.filter(active=True)

当然,这仅在数据实际在数据模型中表示时才有效。这并非总是如此。在这种情况下,您可以考虑以下选项。

自定义标签和过滤器

第一种替代方法仅对表示性查询有用:自定义标记和模板过滤器。

template.html

<h1>Welcome, {{ user|friendly_name }}</h1>

template_tags.py

@register.filterdef friendly_name(user):    return remote_api.get_cached_name(user.id)
查询方法

如果您的查询不只是表示形式的查询,则可以将查询添加到您的 services.py中 (如果正在使用的话),或引入 querys.py 模块:

querys.py

def inactive_users():    return User.objects.filter(active=False)def users_called_publysher():    for user in User.objects.all():        if remote_api.get_cached_name(user.id) == "publysher": yield user
代理模型

代理模型在业务逻辑和报告的上下文中非常有用。您基本上可以定义模型的增强子集。您可以通过覆盖

Manager.get_queryset()
方法来覆盖Manager的基础QuerySet 。

models.py

class InactiveUserManager(models.Manager):    def get_queryset(self):        query_set = super(InactiveUserManager, self).get_queryset()        return query_set.filter(active=False)class InactiveUser(User):    """    >>> for user in InactiveUser.objects.all():    …        assert user.active is False     """    objects = InactiveUserManager()    class meta:        proxy = True
查询模型

对于本质上很复杂但经常执行的查询,存在查询模型的可能性。查询模型是非规范化的一种形式,其中单个查询的相关数据存储在单独的模型中。当然,技巧是使非规范化模型与主模型保持同步。如果更改完全在您的控制之下,则只能使用查询模型。

models.py

class InactiveUserDistribution(models.Model):    country = CharField(max_length=200)    inactive_user_count = IntegerField(default=0)

第一种选择是在命令中更新这些模型。如果仅通过一个或两个命令更改这些模型,这将非常有用。

表格

class ActivateUserForm(forms.Form):    # see above    def execute(self):        # see above        query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)        query_model.inactive_user_count -= 1        query_model.save()

更好的选择是使用自定义信号。这些信号当然是由您的命令发出的。信号的优点是您可以使多个查询模型与原始模型保持同步。此外,可以使用Celery或类似框架将信号处理任务转移到后台任务。

signal.py

user_activated = Signal(providing_args = ['user'])user_deactivated = Signal(providing_args = ['user'])

表格

class ActivateUserForm(forms.Form):    # see above    def execute(self):        # see above        user_activated.send_robust(sender=self, user=user)

models.py

class InactiveUserDistribution(models.Model):    # see above@receiver(user_activated)def on_user_activated(sender, **kwargs):        user = kwargs['user']        query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)        query_model.inactive_user_count -= 1        query_model.save()
保持清洁

使用这种方法时,很容易确定代码是否保持干净。只需遵循以下准则:

  • 我的模型中是否包含比管理数据库状态还执行更多功能的方法?您应该提取命令。
  • 我的模型是否包含未映射到数据库字段的属性?您应该提取一个查询。
  • 我的模型是否引用了不是数据库的基础架构(例如邮件)?您应该提取命令。

视图也一样(因为视图经常遭受相同的问题)。

  • 我的视图是否主动管理数据库模型?您应该提取命令。
一些参考

Django文档:代理模型

Django文档:信号

体系结构:域驱动设计



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5632291.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存