DDD应用服务、领域服务傻傻分不清楚?看这篇就够了
DDD应用服务、领域服务傻傻分不清楚?看这篇就够了
在领域驱动设计(DDD)实践中,应用服务(Application Service)和领域服务(Domain Service)是两个容易混淆的概念。本文通过一个"小帅包子铺"的登录场景,详细解释了两者的区别,包括职责划分、复用性、输入输出等方面的差异。
很多开发者在实践DDD的过程中,一直分不清 应用服务 Application Service(后面简称App) 和 领域服务 Domain Service(后面简称Domain) 。让我们结合一个具体的场景来说明它们的区别。
1. 举一个🌰
假设你第一次访问小帅包子铺
系统(后面简称系统),需要通过手机号登录。登录流程包括:输入手机号、获取验证码,提交登录。
在你提交登录后,系统需完成以下任务:
- 验证短信验证码的正确性。如果验证不通过,则登录失败
- 判断该手机号是否已注册,如果还未注册,自动注册一个新用户,并开通余额账户
- 注册成功将会发送一条短信:欢迎你来到小帅包子铺
- 设置登录状态
我们尝试用时序图画一下这个过程,假设这时只有一个Service层:
这个时序图看起来没什么大毛病,但直觉上Service承担的东西有点多。
2. 职责拆分
这时很容易想到,把职责稍微拆分一下,分给其他的Service。我们忽略掉其他要素,只保留Service,大概是这样:
这时你会发现,Service的层级就显示出来了,LoginService是在组合其他几个Service的能力,这就是 App和Domain Service的第一个区别 :
- App层是在对Domain层的服务进行编排组合
- Domain层提供的是原子能力
3. 复用
现在我们有第二个需求:如果用户是在包子铺门店直接买包子,可以向收银员报一个手机号,假如这个手机号还未注册,则直接注册成为一个新用户。(收银员会在收银机上操作,下单时录入手机号)
咦?在门店买包子和登录,这两件事居然有交集了!它们都有可能发生用户注册。这时就可以把用户注册进行复用了,同时优化一下Service之间的关系:
这就是 App和Domain Service的第二个区别 :
- Domain Service能提供复用能力
- App层尽量不去复用,例如登录也可以分为短信验证码登录、微信登录、邮箱登录,但这几种登录方式逻辑上有本质区别,应在App层提供多种不同的登录接口
4. 输入和输出
App层和Domain层的输入、输出(也就是入参、出参)有区别吗?
当然有区别,例如不同的登录方式, App层接口定义 可能是:
public interface LoginAppService {
// 短信登录,需要手机号、验证码
// 返回通用Response,带user id
Response<String> smsLogin(SMSLoginReq req);
// 邮箱登录,邮箱、密码
// 返回通用Response,带user id
Response<String> emailLogin(EmailLoginReq req);
}
在App层,为了保证与客户端交互上的一致性,会使用通用的返回体Response
,如果发生了异常,需要在Response里设置错误码。
但在Domain层,为保证最大程度可以复用,不能把App层的req对象透传下来,而是需要先转化为领域对象,因此 Domain层的接口定义 是:
public interface UserService {
// Domain层直接以领域对象做为入参,注册是一个写请求,无需返回值
void register(User user);
}
这是 App和Domain Service的第三个区别
5. 其他区别
通常情况下,事务应该是在App层管理,App既然做为编排组合的组织者,需要保证事务性
- 即便业务逻辑非常简单也要拆两层吗,会不会显得太啰嗦?
我的个人习惯来说,如果业务逻辑足够简单,且没有可复用的部分,可以不需要Domain层
都可以的,只是说如果有Domain层进行了一些原子化封装,就可以尽量把对repository的访问下沉到Domain
在架构上新增一个层,不仅是加了一层这么简单,层数的增加意味着架构变得复杂,需要考虑的因素也要更多。我只看是否能解决某些问题,若不能解决问题,加再多层也只是增加负担。