什么是领域服务?
领域中的服务(service)表示一个无状态的操作,它用于实现特定于某个领域的任务。当某个操作不适合放在实体和值对象上时,最好的方式便是使用领域服务了。
看完上面一段比较官方的领域服务的描述,感觉到像是领悟到了些、又好像是啥都没有领悟到。
领域服务,到底是什么?
实体、值对象、领域事件,都是真实世界的映照,偏向于业务的视角。领域服务是属于DDD的技术模式,与资源库一样,偏向于技术的视角,很难找到真实世界的映射。
想象一下,一个图书系统,有一个用户(用户实体是现实世界用户的真实映照)正在登陆系统,系统则需要对用户登陆身份校验。那么,对用户进行身份校验,这个业务流程能力,放在用户实体中,是否合适?在现实世界中,用户自身是不具备身份校验的能力的。在没有软件系统的时候,图书馆需要安排管理员对用户进行身份进行人工校验。
如果,我们可以图书馆软件系统当成是那个图书馆的管理员呢?那就可以由软件系统来对用户进行身份校验了!而软件系统分布在整个领域之中,已然是一个最大的实体,不适合再定义一个实体来与软件系统映射。
因此,DDD提出了领域服务这个概念,用来对能力进行抽象。
因此,当某个操作不适合放在实体和值对象上时,最好的方式便是使用领域服务了。
领域服务的一些疑问
1、领域服务与实体、值对象的行为方法的区别
实体、值对象的行为方法存在于实体、值对象之中,行为方法可以访问到实体、值对象内部的任意属性、方法。比如,UserEntity中有一个私有属性password、一个私有方法login(),行为方法可以访问到password与login()。
实体、值对象的行为方法随着实体、值对象的生灭而生灭。要调用行为方法,必须要先构建出实体(值对象),通过实体(值对象)的实例,才能调用行为方法。
如果构建一个实体(值对象)的成本很大,而行为方法只是需要其中一些简单的参数,那么使用行为方法的成本是巨大的,不划算的。比如,如果把行为方法doLogin(account,password)放到UserEntity中,那么就必须要new出UserEntity的实例才能执行doLogin方法。
领域服务在内存中的实例,往往是单例的,只有一个实例对象。而实体、值对象在内存中是有着多个实例。
调用领域服务的方法时,往往不需要创建领域服务实例,因为在系统启动的时候,领域服务实例已经被初始化了。
领域服务中没有属性,只有行为方法。而实体、值对象存在行为方法,还有属性。
领域服务是一个轻量经的,而实体、值对象是一个重量级的。
2、领域服务与应用服务的区别
领域服务(domain service)是处于领域层,在代码中的表现是放在xxx.domain.service包中。应用服务(application service)是处于应用层,在代码中的表现是放在xxx.application.service中。
领域服务可以是只在领域层定义一个简单的接口,和资源库一样,领域服务的实现可以放到基础设施层(领域服务的设计思想和资源库的设计思想有着相通之处)。
应用服务的具体实现,一定是放在应用层。
应用层依赖领域层,应用服务会依赖领域服务,领域服务中的方法被应用服务所调用。
3、领域服务与资源库的区别
在概念上,领域服务的定义是用来处理领域业务的,资源库是用来存储领域对象的。
以下举一两个场景,透过场景来看领域服务与资源库。
场景一:业务服务A需要获取用户数据,用户数据存储在SOA服务中
“获取数据”是一个很明显的资源操作语义词,因此使用资源库(IUserRepository)会比领域服务更加贴切。
场景二:订单服务在下单时,需要调用商品库存服务扣除商品库存
“扣除商品库存”是一个很显示的业务动作语义词,因此使用领域服务(ProductStorageService)会比资源库更加贴切。
哪些场景下使用领域服务
1、在对象第三方系统API的时候,请使用领域服务
任何的第三方系统API,都是不可靠的,因此DDD提出了防腐层。
领域服务,就是一个很好的防腐层。使用领域服务,将外部API的数据转化成己方领域模块的数据,将己方领域模型的数据转化成外部API所需的参数。
2、应该由计算机完成的功能,如身份验证之类的功能,请使用领域服务
因为由计算机完成的功能,一般都是不容易在实现世界中找不到具体物品与之映射的,因此,很适合放到领域服务中。
3、当一个功能涉及多个实体的时候,请使用领域服务
因为一个功能涉及到多个实体时,就明显是某个操作不适合放在实体和值对象上的了,最好的方式便是使用领域服务了。
举个例子,银行转账。账户A转钱到账户B,账户A要扣款,账户要入款。如果将操作方法放到账户这个实体上,那么就破坏了实体的本义,那么其伪代码会上这样子:
public class Account {
// 转账
void transfer(Account to,Long money){
this.pay(money);
to.recieve(money);
}
// 扣款
void pay(Long pay);
// 入款
void recieve(Long recieve);
}
因为,适合放到领域服务中
public interface AccountDomainService {
// 转账
void transfer(Account from, Account to);
}