Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

节点数据模型设计及演进 #2

Open
codetalks-new opened this issue Feb 18, 2019 · 3 comments
Open

节点数据模型设计及演进 #2

codetalks-new opened this issue Feb 18, 2019 · 3 comments

Comments

@codetalks-new
Copy link
Owner

首先我们需要创建的数据模型就是节点数据模型。

这种有层次的数据模型我选择基于 django-mptt 来创建。

from django.db import models
from mptt.models import MPTTModel,TreeForeignKey
from wepost.base.models import BaseModel

class Node(MPTTModel,BaseModel):
  """节点"""
  parent = TreeForeignKey('self', null=True, blank=True, related_name="children", on_delete=models.CASCADE,
                          verbose_name="父节点")
  code = models.CharField("编码", max_length=32, unique=True)
  name = models.CharField("名称", max_length=32, unique=True)
  order = models.IntegerField("排序值", default=0)
  star_count = models.PositiveIntegerField("收藏数",default=0)
  post_count = models.PositiveIntegerField("主题数",default=0)
  brief = models.CharField("简介", max_length=512, blank=True, null=True)

比如数据库创建好之后,我想创建一些初始的数据,后面换数据库重新开始时,也不用再手动填充初始的节点数据 。

  1. 创建一个迁移脚本 ./manage_dev.py makemigrations wepost_posts --empty --name init_nodes
    此时在 wepost_posts 下面会创建一个名为 0002_init_nodes.py 的迁移脚本骨架如下:
# Generated by Django 2.2b1 on 2019-02-18 17:22

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('wepost_posts', '0001_initial'),
    ]

    operations = [
    ]
  1. 修改方面的代码,添加一个迁移的 operations, 如下:
    operations = [
        migrations.RunPython(init_nodes)
    ]
  1. 写代码实现 init_nodes 这个 Python 函数 。部分代码如下:
from wepost.apps.posts.models import Node

def init_nodes(apps, schema_editor):
    p = Node(code="programming",name="编程")
    p.save()
    assert p.pk

写好之后,再继续执行迁移脚本

➜  WePost (master) ✗ ./manage_dev.py migrate wepost_posts
Operations to perform:
  Apply all migrations: wepost_posts
Running migrations:
  Applying wepost_posts.0002_init_nodes… OK
➜  WePost (master) ✗ 

@codetalks-new codetalks-new changed the title Django 创建数据模型时同时提供初始数据 节点数据模型设计及演进 Feb 20, 2019
@codetalks-new
Copy link
Owner Author

节点关注数据模型设计

节点的关注和用户的关注一样也需要支持关注和屏蔽的操作。所以这里也设计一个多对多的关联。

也先定义一个关注类型枚举:

class UserNodeStarState(IntEnum):
  FOLLOWING = 1  # 关注
  BLOCKING = 4  # 屏蔽

  @property
  def label(self):
    return _uer_node_star_state_to_text[self]


_uer_node_star_state_to_text = {
  UserNodeStarState.FOLLOWING: "关注",
  UserNodeStarState.BLOCKING: "屏蔽"
}
class UserNodeStar(BaseModel):
  user = models.ForeignKey(WepostUser, on_delete=models.CASCADE, verbose_name="用户")
  node = models.ForeignKey(Node, on_delete=models.CASCADE, verbose_name="节点")
  order = models.IntegerField("排序", default=0, help_text="用于排序关注显示顺序")
  state = models.PositiveSmallIntegerField("状态", choices=UserNodeStarState.choices(),
                                           default=UserNodeStarState.FOLLOWING)

  class Meta:
    verbose_name = "关注节点"
    verbose_name_plural = verbose_name
    unique_together = ("user", "node")

@codetalks-new
Copy link
Owner Author

codetalks-new commented Feb 21, 2019

节点服务层设计

每一个模型应该对应一个服务层,一个企业开发中比如 JAVA 中是有比如 Dao 层,Service层。
而在 Django 中 Model 加 默认的 models.Manager 已经包含了主要的 Dao 层。 所以我们的代码主要关注 Service 层就可以了。而在 Django 中比如 NodeService 的命名习惯应该是 NodeManager

NodeManager 的功能分析

NodeManager 提供的主要功能如下:

  1. 用户关注节点
  2. 用户取消关注节点
  3. 用户屏蔽节点
  4. 用户取消屏蔽节点

体现在代码上类似如下:

class UserNodeStarManager(models.Manager):

  def update_star_state(self, user: WepostUser, node: Node, star_state: UserNodeStarState):
    """更新关注状态"""
    obj, created = self.update_or_create(user=user, node=node, state=star_state)
    return obj, created

  def star_node(self, user: WepostUser, node: Node):
    """关注节点"""
    return self.update_star_state(user, node, UserNodeStarState.FOLLOWING)

  def unstar_node(self, user: WepostUser, node: Node):
    """取消关注节点"""
    return self.filter(user=user, node=node).delete()

  def block_node(self, user: WepostUser, node: Node):
    """屏蔽节点"""
    return self.update_star_state(user, node, UserNodeStarState.BLOCKING)

  def unblock_node(self, user: WepostUser, node: Node):
    """取消屏蔽节点"""
    return self.filter(user=user, node=node).delete()

在代码模块的组织结构上需要注意两个问题:

  1. 但是按照 Python 的尿性,由于 Python 不支持递归导入,所以这个类是比较适合跟 UserNodeStar 模型类放在一起的,否则在 UserNodeStar 声明 objects = UserNodeStarManager() 会导致模块间的递归引用。

  2. 另一种尝试就是, 不在 在 UserNodeStar 声明 objects = UserNodeStarManager().但是这样的话,直接使用 UserNodeStarManager 的话,会失去跟 UserNodeStar的关联,因为 objects 与所属 Model 的关联是在 Model 对应类这个类在创建的时候处理的。具体的来说是在ModelBasenew` 创建类时如下代码相关:

        # Add all attributes to the class.
        for obj_name, obj in attrs.items():
            new_class.add_to_class(obj_name, obj)
    def add_to_class(cls, name, value):
        if _has_contribute_to_class(value):
            value.contribute_to_class(cls, name)
        else:
            setattr(cls, name, value)

BaseManager 类实现 contribute_to_class 协议方法:

    def contribute_to_class(self, model, name):
        self.name = self.name or name
        self.model = model

        setattr(model, name, ManagerDescriptor(self))

        model._meta.add_manager(self)
  1. 将上面的一些类单独放在一个 user_node_star_service 模块中。上面的方star_node 都作为模块的顶层函数。这里有一个问题时,名字空间的问题,在使用的时候直接使用 star_node 这样会导致名字空间的问题,使用 user_node_star_service.star_node 这样可以。

@codetalks-new
Copy link
Owner Author

最终的服务模块设计

上面对服务模块进行了一些思考。其实从上面的服务的方法的签名来看,其实这些可以算作是用户的服务。
如果支持动态扩展类方法的话(不失去静态类型提示)。可以在 auth 模块创建一个 UserService 然后其他可以为 UserService 添加服务接口的话,就添加服务接口。不过 Python 上这样做不太合适。 Swift 上可以。

我最终选择的一种方式是。创建一个 UserNodeService 即与 用户节点相关的服务接口都放在这上面。
此时得到如下服务类的设计,接口签名也更符合语义,减少了共同的参数的传递。

class UserNodeService:
  """用户与节点相关服务类"""

  def __init__(self, user: WepostUser):
    self.user = user

  def _update_star_state(self, node: Node, star_state: UserNodeStarState):
    """更新关注状态"""
    obj, created = UserNodeStar.objects.update_or_create(user=self.user, node=node, state=star_state)
    return obj, created

  def _delete_node(self, node: Node):
    """移除跟某一节点关系"""
    return UserNodeStar.objects.filter(user=self.user, node=node).delete()

  def star_node(self, node: Node):
    """关注节点"""
    return self._update_star_state(node, UserNodeStarState.FOLLOWING)

  def unstar_node(self, node: Node):
    """取消关注节点"""
    return self._delete_node(node)

  def block_node(self, node: Node):
    """屏蔽节点"""
    return self._update_star_state(node, UserNodeStarState.BLOCKING)

  def unblock_node(self, node: Node):
    """取消屏蔽节点"""
    return self._delete_node(node)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant