那在操作层面上如何处理application service和domain service呢?
首先application service既然是入口,在一个模块中,它必定是存在的。与之相反,domain service则不一定需要。
因此,再做类设计时可以先假定domain service不存在。直接写application service,在application service中对其他domain object进行操作。
理想情况下,application service存在的代码基本上就是它调用其他domain object的方法,具体的业务逻辑都会在domain object的方法中。所以当application service中出现if/else之类的语句,或者application service的一个方法变得很长时,我们就该警惕是不是把业务逻辑写到了application service中。这个时候我们该考虑是否需要重构,比如把逻辑放进domain object,或者增加一个domain service。
按上面一个注册账号的例子,这个时候我们并不需要domain service。
但如果我们首先要确认email是否被注册,那这时候代码就会变成下面那样
public class AccountApplicationService {
@Autowired
private IAccountRepository accountRepository;
public void register(AccountDTO accountDTO){
Account account = accountRepository.find(new AccountSpecificationByEmail(accountDTO.getEmail()));
if (account != null) {
throw new EmailAlreadyRegisteredException();
}
account = Account.createAccount(accountDTO.getEmail(), accountDTO.getPassword());
accountRepository.save(account);
}
}
尽管方法不是很长,但判断账号能否被注册的逻辑(业务逻辑)写在了application service中。我们必须考虑将这个逻辑移到其他地方。
当然理想情况下是把它放进Account中,但查询账号是否存在的逻辑使用到AccountRepository,这个很难放进Account中,所以AccountService自然会是一个选择。
public class AccountService {
@Autowired
private IAccountRepository accountRepository;
public void register(String email, String password){
Account account = accountRepository.find(new AccountSpecificationByEmail(email));
if (account != null) {
throw new EmailAlreadyRegisteredException();
}
account = Account.createAccount(email, password);
accountRepository.save(account);
}
}
因为不想让domain层的东西依赖于application层的form,dto类,所以方法的参数没有用dto。
那application service会变成
public class AccountApplicationService {
@Autowired
private AccountService accountService;
public void register(AccountDTO accountDTO){
accountService.register(accountDTO.getEmail(), accountDTO.getPassword());
}
}
另外,有了AccountService这个类,并不是所有关于Account的逻辑都必须放进那里。
比如AccountApplicationService里的changePassword(),没有必要放进AccountService中。
public class AccountApplicationService {
@Autowired
private IAccountRepository accountRepository;
@Autowired
private AccountService accountService;
public void register(AccountDTO accountDTO){
accountService.register(accountDTO.getEmail(), accountDTO.getPassword());
}
public void changePassword(String email, String oldPassword, String newPasssord) {
Account account = accountRepository.findById(email);
account.changePassword(oldPassword, newPasssord);
accountRepository.save(account);
}
}
总结
ddd中把service类分成三种。
application service, domain service, infrastructure service。
domain service中可以写业务逻辑,但同时理想情况下我们尽量不实用domain service。
另外,我们要注意不要将业务逻辑写到application service中。
关于Account的逻辑尽量使放在Account类中。在Account类实现不了的情况下考虑把逻辑写入AccountService中。
这样的做法源于DDD的思想。将业务的模型尽量反映到代码上。
现实的业务中,一般会存在Account这种模型,不太会有AccountService这种模型存在。比如我们在产品经理或者用户讨论功能时,不太会说AccountService如何如何,一般会说Account如何如何,存在什么样的功能吧?
所以理想情况下,关于Account的逻辑看Account这个类就行了。比如更换密码的逻辑,看Account的changePassword的实现就行。
但是一些比较难实现的逻辑,比如需要Repository的逻辑,没办法放入Account,只能借助AccountService来实现这部分逻辑。