首页 > Python > Python  在 django 中分离业务逻辑和数据访问

Python  在 django 中分离业务逻辑和数据访问

上一篇 下一篇

我正在用 Django 编写一个项目,我看到 80% 的代码都在文件中。这段代码令人困惑,一段时间后,我不再了解真正发生的事情。models.py

以下是困扰我的地方:

  1. 我觉得很丑陋的是,我的模型级别(应该只
    负责处理数据库中的数据)也在
    发送电子邮件、在 API 上行走到其他服务等。
  2. 此外,我发现将业务逻辑放在视图中是不可接受的,因为
    这样就变得难以控制。例如,在我的
    应用程序中,至少有三种方法可以创建 的新
    实例,但从技术上讲,它应该统一创建它们。
    User
  3. 我并不总是注意到我的模型的方法和
    属性何时变得不确定,以及何时产生
    副作用。

下面是一个简单的示例。起初,模型是这样的:User

class User(db.Models):

    def get_present_name(self):
        return self.name or 'Anonymous'

    def activate(self):
        self.status = 'activated'
        self.save()

随着时间的推移,它变成了这样:

class User(db.Models):

    def get_present_name(self): 
        # property became non-deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

    def activate(self):
        # method now has a side effect (send message to user)
        self.status = 'activated'
        self.save()
        send_mail('Your account is activated!', '…', [self.email])

我想要的是在我的代码中分离实体:

  1. 数据库级实体,即数据库级逻辑:我的应用程序存储什么样的数据?
  2. 应用程序级实体,即业务级逻辑:我的应用程序做什么?

在 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 表单。我更喜欢这种方法,因为它结合了多个密切相关的方面:

  • 命令的执行(它有什么作用?
  • 验证命令参数(它可以做到这一点吗?
  • 命令的演示(我该怎么做?

forms.py

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)

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

自定义标签和过滤器

第一种替代方法对于仅表示性的查询很有用:自定义标记和模板筛选器。

模板.html

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

template_tags.py

@register.filter
def friendly_name(user):
    return remote_api.get_cached_name(user.id)

查询方法

如果您的查询不仅仅是表示性的,则可以将查询添加到 services.py(如果您正在使用它),或者引入 queries.py 模块:

queries.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()

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)

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

forms.py

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 或类似框架卸载到后台任务。

signals.py

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

forms.py

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 文档:信号

架构:领域驱动设计

分割线

网友回答:

我通常在视图和模型之间实现一个服务层。这就像您项目的 API,并为您提供了正在发生的事情的良好直升机视图。我从我的一位同事那里继承了这种做法,他在 Java 项目 (JSF) 中大量使用这种分层技术,例如:

models.py

class Book:
   author = models.ForeignKey(User)
   title = models.CharField(max_length=125)

   class Meta:
       app_label = "library"

services.py

from library.models import Book

def get_books(limit=None, **filters):
    """ simple service function for retrieving books can be widely extended """
    return Book.objects.filter(**filters)[:limit]  # list[:None] will return the entire list

views.py

from library.services import get_books

class BookListView(ListView):
    """ simple view, e.g. implement a _build and _apply filters function """
    queryset = get_books()

请注意,我通常会将模型、视图和服务带到模块级别,并根据
项目的大小进一步分离

分割线

网友回答:

首先,不要重复自己。

然后,请注意不要过度设计,有时这只是浪费时间,使某人失去对重要事情的关注。不时回顾蟒蛇的禅意。

查看活动项目

  • 更多的人=更多的人需要正确组织
  • Django 存储库 他们有一个简单的结构。
  • 的 pip 存储库 他们有一个 straigtforward 目录结构。
  • 结构存储库也是一个很好的参考资料库。
    • 您可以将所有模型放在yourapp/models/logicalgroup.py
  • 例如,相关模型可以下UserGroupyourapp/models/users.py
  • 例如 , , …可以下PollQuestionAnsweryourapp/models/polls.py
  • 在内部加载您需要的内容__all__yourapp/models/__init__.py

更多关于 MVC

  • 模型就是您的数据
    • 这包括您的实际数据
    • 这也包括您的会话/cookie/缓存/fs /索引数据
  • 用户与控制器交互以操作模型
    • 这可以是 API 或保存/更新数据的视图
    • 这可以调整为 / …等request.GETrequest.POST
    • 也考虑分页过滤
  • 数据更新视图
    • 模板获取数据并相应地设置其格式
    • 不带模板的 API 也是视图的一部分;例如 或tastypiepiston
    • 这也应该考虑中间件。

利用中间件/模板标签

  • 如果您需要为每个请求完成一些工作,中间件是一种方法。
    • 例如,添加时间戳
    • 例如,更新有关页面点击量的指标
    • 例如,填充缓存
  • 如果您有代码片段总是重复出现以格式化对象,则模板标记很好。
    • 例如,活动选项卡/URL 痕迹导航

利用模型管理器

  • 创建可以进入 .UserUserManager(models.Manager)
  • 实例的血腥细节应放在 .models.Model
  • 血腥的细节可以放在一个.querysetmodels.Manager
  • 您可能希望一次创建一个对象,因此您可能认为它应该存在于模型本身上,但是在创建对象时,您可能没有所有详细信息:User

例:

class UserManager(models.Manager):
   def create_user(self, username, ...):
      # plain create
   def create_superuser(self, username, ...):
      # may set is_superuser field.
   def activate(self, username):
      # may use save() and send_mail()
   def activate_in_bulk(self, queryset):
      # may use queryset.update() instead of save()
      # may use send_mass_mail() instead of send_mail()

尽可能使用表格

如果您有映射到模型的窗体,则可以消除许多样板代码。这是相当不错的。如果您有很多自定义(或者有时避免循环导入错误以用于更高级的用途),则表单代码与模型代码分开可能会很好。ModelForm documentation

尽可能使用管理命令

  • 例如yourapp/management/commands/createsuperuser.py
  • 例如yourapp/management/commands/activateinbulk.py

如果你有业务逻辑,你可以把它分离出来

  • django.contrib.auth使用后端,就像 DB 有一个后端一样……等。
  • 为您的业务逻辑添加 (例如settingAUTHENTICATION_BACKENDS)
  • 你可以使用django.contrib.auth.backends.RemoteUserBackend
  • 你可以使用yourapp.backends.remote_api.RemoteUserBackend
  • 你可以使用yourapp.backends.memcached.RemoteUserBackend
  • 将困难的业务逻辑委托给后端
  • 确保在输入/输出上设置正确的期望。
  • 更改业务逻辑就像更改设置🙂一样简单

后端示例:

class User(db.Models):
    def get_present_name(self): 
        # property became not deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

可以变成:

class User(db.Models):
   def get_present_name(self):
      for backend in get_backends():
         try:
            return backend.get_present_name(self)
         except: # make pylint happy.
            pass
      return None

有关设计模式的更多信息

  • 关于设计模式已经有一个很好的问题
  • 一个关于实用设计模式的非常好的视频
  • Django 的后端明显使用了委托设计模式。

有关接口边界的更多信息

  • 您要使用的代码真的是模型的一部分吗?->yourapp.models
  • 代码是业务逻辑的一部分吗?->yourapp.vendor
  • 代码是通用工具/库的一部分吗?->yourapp.libs
  • 代码是业务逻辑库的一部分吗?->或yourapp.libs.vendoryourapp.vendor.libs
  • 这里有一个很好的问题:你能独立测试你的代码吗?
    • 是的,很好 🙂
    • 否,您可能遇到接口问题
    • 当有明确的分离时,单元测试应该是轻而易举地使用嘲笑
  • 分离合乎逻辑吗?
    • 是的,很好 🙂
    • 不,您可能难以单独测试这些逻辑概念。
  • 你认为当你获得 10 倍以上的代码时,你需要重构吗?
    • 是的,不好,没有布埃诺,重构可能需要很多工作
    • 不,这太棒了!

简而言之,你可以拥有

  • yourapp/core/backends.py
  • yourapp/core/models/__init__.py
  • yourapp/core/models/users.py
  • yourapp/core/models/questions.py
  • yourapp/core/backends.py
  • yourapp/core/forms.py
  • yourapp/core/handlers.py
  • yourapp/core/management/commands/__init__.py
  • yourapp/core/management/commands/closepolls.py
  • yourapp/core/management/commands/removeduplicates.py
  • yourapp/core/middleware.py
  • yourapp/core/signals.py
  • yourapp/core/templatetags/__init__.py
  • yourapp/core/templatetags/polls_extras.py
  • yourapp/core/views/__init__.py
  • yourapp/core/views/users.py
  • yourapp/core/views/questions.py
  • yourapp/core/signals.py
  • yourapp/lib/utils.py
  • yourapp/lib/textanalysis.py
  • yourapp/lib/ratings.py
  • yourapp/vendor/backends.py
  • yourapp/vendor/morebusinesslogic.py
  • yourapp/vendor/handlers.py
  • yourapp/vendor/middleware.py
  • yourapp/vendor/signals.py
  • yourapp/tests/test_polls.py
  • yourapp/tests/test_questions.py
  • yourapp/tests/test_duplicates.py
  • yourapp/tests/test_ratings.py

或任何其他对您有帮助的东西;找到您需要的接口边界将对您有所帮助。

模板简介:该模板名称为【Python  在 django 中分离业务逻辑和数据访问】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【Python】栏目查找您需要的精美模板。

相关搜索
  • 下载密码 lanrenmb
  • 下载次数 368次
  • 使用软件 Sublime/Dreamweaver/HBuilder
  • 文件格式 编程语言
  • 文件大小 暂无信息
  • 上传时间 04-21
  • 作者 网友投稿
  • 肖像权 人物画像及字体仅供参考
栏目分类 更多 >
热门推荐 更多 >
响应式 微信文章 微信模板 企业网站 html5 微信图片 自适应 微信素材 单页式简历模板 微信公众平台
您可能会喜欢的其他模板