从领域逻辑的组织看单体应用与微服务架构

2015-07-17

马丁福勒在《企业应用架构模式》一书中阐述了领域逻辑的几种组织形式,事务脚本、表模块、领域模型和服务层。服务层虽然指的是抽象的代码组织形式,如果选择的服务层采用了 RPC 技术,它和微服务也基本没什么区别。

一个真实的例子

在微信朋友圈中,用户可以发布一些图片和短文分享给他的好友们。这些图片和短文我们称之为帖子。

原始需求:删除的帖子不能被好友们看到

需求

用户可以删除他发表的帖子,帖子被删除后,他的好友们将看不到这个帖子。

设计

我们设计一个『帖子』实体,命名为「Post」。帖子表有个字段 is_deleted 用来标识当前帖子的是否已被删除,譬如:

Table 1. 帖子的删除状态 is_deleted
含义 可见

1

已删除

0

正常

代码

我们分别采用事务脚本、表模块、领域模型等三种领域逻辑模式来实现这个需求:

  1. 事务脚本

    事务脚本的数据源模式一般会采用表入口模式,示例代码如下:

    post_gateway = BeanFinder.get('PostGateway')
    row = post_gateway.get(post_id)
    is_deleted = row['is_deleted']
    return is_visible(is_deleted)
  2. 表模块

    表模块一般会采用行入口模式,示例代码如下:

    finder = BeanFinder.get('PostFinder')
    post = finder.get(post_id)
    return is_visible(post)
  3. 领域模型

    领域模型自然会采用活动记录这种模式,示例代码如下:

    unitofwork = BeanFinder.get('UnitOfWork')
    post = unitofwork.get(Post, post_id)
    return post.is_visible()
Table 2. 三种领域逻辑组织方式的对比
领域逻辑模式 数据源模式 载体 输入

事务脚本

表入口

函数

字段

表模块

行入口

函数

一条记录

领域模型

活动记录

方法

无(对象本身/整个上下文)

自从 2004 年 Rails 诞生以后,『活动记录』这种模式横扫天下,几乎所有的 FullStack 框架都支持基于 ActiveRecord 的 ORM 特性,这也从侧面反映出以上三种领域逻辑组织方式的优劣。

新需求:手机号认证

需求

只有进行了手机号认证的用户发的帖子才对外可见。

设计

我们设计一个『用户』实体,名为 UserUser 实体使用字段 is_verified 来标识用户是否进行过认证。

Table 3. 用户的认证状态 is_verified
含义 可见

1

已认证

0

正常

代码

根据以上设计,我们的示例代码如下:

领域逻辑模式 代码 变化

事务脚本

示例
post_gateway = BeanFinder.get('PostGateway')
user_gateway = BeanFinder.get('UserGateway')

post_row = post_gateway.get(post_id)
user_id = post_row['user_id']
is_deleted = post_row['is_deleted']

user_row = user_gateway.get(user_id)
is_verified = user_row['is_verified']

return is_visible(is_deleted, is_verified)

is_visible 参数增加到两个参数

表模块

示例
post_finder = BeanFinder.get('PostFinder')
post = post_finder.get(post_id)
user_finder = BeanFinder.get('UserFinder')
user = user_finder.get(post.user_id)
return is_visible(post, user)

is_visible 参数增加到两个参数

领域模型

示例
unitofwork = BeanFinder.get('UnitOfWork')
post = unitofwork.get(Post, post_id)
return post.is_visible()

is_visible 没有变化

纳尼?领域模型不需要做任何的调整么?当然不是,只是它在领域实体 Post 类内部把需求消化掉了!

怎么做到的?我们看一下 is_visible 内部的实现就明白了。

def is_visible(self):
    user = self.user
    is_verified = user.is_verified
    is_deleted = self.is_deleted
    return not is_deleted and is_verified
Note
self.user 是 ORM 的提供的加载特定外键关系对象的一种方法。

看起来领域模型这种组织领域逻辑的方式真是不错,不知道它能否搞定后面的需求。

新需求:软文帖子

需求

随着朋友圈越做越大,用户越来越多,我们推出了一项增值服务,让用户可以付费推广他们的帖子:每个付费的帖子可以展示100次!

设计

现在公司又来了一个团队,叫商业化研发团队,帖子的付费和展示统计都是由他们来完成,他们为帖子提供了一个 RPC 接口,可以查询付费帖子剩余的次数。

代码

根据以上设计,我们的示例代码如下:

领域逻辑模式 代码

事务脚本

示例一
post_gateway = BeanFinder.get('PostGateway')
user_gateway = BeanFinder.get('UserGateway')

post_row = post_gateway.get(post_id)
user_id = post_row['user_id']
is_deleted = post_row['is_deleted']

user_row = user_gateway.get(user_id)
is_verified = user_row['is_verified']

client = BeanFinder.get('PostCounterClient')
left_count = client.get(post_id)

return is_visible(is_deleted, is_verified, left_count)

表模块

示例二
post_finder = BeanFinder.get('PostFinder')
post = post_finder.get(post_id)
user_finder = BeanFinder.get('UserFinder')
user = user_finder.get(post.user_id)

client = BeanFinder.get('PostCounterClient')
left_count = client.get(post_id)

return is_visible(post, user, left_count)

领域模型

示例三
unitofwork = BeanFinder.get('UnitOfWork')
post = unitofwork.get(Post, post_id)
return post.is_visible()

等等,你确定这次领域实体 Post 类也能把 PostCounterClient 内部消化掉么?当然,只是这样会有一些代价,引入更多的依赖。

def get_left_count(self):
    client = BeanFinder.get('PostCounterClient')
    return client.get(self.post_id)

一大波需求正在袭来

  1. 分享超过 1W 次的帖子不再可见

  2. 分享来自 Uber 的帖子不可见

  3. 用户举报超过 100 次的帖子不可见

  4. 被运营人员标记的帖子不可见

  5. 被识别为微商的帖子不可见

  6. …​…​

已不忍直视,我们的帖子实体 Post 类,至此已经依赖了统计系统、竞品系统、举报系统、运营系统、反垃圾系统等其他系统。

大家可以脑补一下 Post 类里面代码的样子。

再来一个压轴需求:自发自看

需求

自己发的帖子只能自己看到

之所以称它为压轴需求,是因为这个需求引入了一个新的实体对象:当前用户。该实体的引入导致之前的接口都需要变,譬如:post.is_visible() 要改为 post.is_visible(current_user_id)

results matching ""

    No results matching ""