上面的问题貌似通过postgresql来解决了,但其实问题在微服务中依旧存在.只不过数据库专注于读而服务专注于写.
还是上面的问题,当后请员工的各项操作和老师领域的相关操作分到两个微服务的时候怎么办.毕竟老师很多时候是和教学等等紧密相关的.这样的话数据库的问题就变成了微服务的问题.微服务也可以分为一个微服务(方案一),两个微服务(方案二),三个微服务(方案三).就算是多个微服务使用同一个数据库,借助postgresql解决了数据一致性的问题,但还是没法解决操作的继承关系.
类似的情况当面对继承时微服务也有三种划分方式(对应于数据库的三种)
A 一个微服务搞定父类及其子类的所有操作(最好不要有多重继承)
B 每一个子类有自己的单独微服务
C 有N+1个微服务
如果把查询考虑进来,第二种会比较麻烦(除非使用CQRS来单独处理Query)
A 情况如下:
interface StaffService{
leave(String id);
teach(String id,String classId);
}
这样的话StaffService的操作将非常多,可以考虑将一些子类操作分出来.
class StaffService{
void leave(String id){
Staff staff=repository.retrieve(id);
staff.leave();
repository.save(staff);
}
}
class Staff{
void leave(){}
}
class TeacherService{
void teach(String id,String classId){
Teacher teacher=repository.retrieve(id);
teacher.teach(classId);
repository.save(teacher);
}
//我觉得这里teacherService不需要有leave,应为外界可以直接调用StaffService即可,即使实现了也与 父类完全一致
}
class Teacher extends Staff{
void teach(String classId){}
void leave(){
//这里又有几种情况
//情景a,子类的该方法实现和父类相同,则可以省略该方法
//情景b,子类的该方法与父类完全不同,则需要重写该方法
//情景c,比较麻烦的是这种情况.子类需要调用父类的方法super.leave(),然后再做一些其他操作
}
}
为了考虑到Teacher.leave()和Staff.leave()的不同,这就要求StaffService中repository.retrieve得到的是一个Teacher而不是Staff,可问题是repository如何通过id知道这是个什么子对象呢?
一种方案是查两次
StaffRepository{
@Autowired
private teacherRepository;
@Autowired
private StaffDAO dao;
public Staff retrieve(String id){
StaffPO staff=dao.selectById(id);
if("Teacher".equals(staff.getDiscriminator())){
return teacheRepository.retrieve(id);
}
return toDomainEntity(staff);
}
}
另一种方案是通过url上的小技巧来完成
class StaffService{
@PostMapping("/{discriminator}/{id}")
public void leave(@PathVeriable("discriminator") String discriminator,
@PathVeriable("id") String id){
if("Teacher".equals(discriminator)){
//
}else{
//
}
}
}
当然也可以把这个url分给每个子类的url这样对外来看都是类型+id作为url的一部分,实际上是路由到每个子类的Service上.显然上面的if判断并不好,但另一方面由调用方来告知类型是否加重了调用方的负担?当然这个可以通过hateoas的加持来实现
并且这样文档和实际的接口不一致(如果我们使用swagger这样的工具的话)并且这样的一个方案将类型这个字段的含义变多了.应为类型本省是用来区分业务含义的,如果以后类型之间出现了嵌套,但是为了和原系统兼容,那么类型的处理会比较困难
对于目前讨论的方案A是可以用url来区分,也可以不用,如果是后面的方案B则必须有这样的区分
方案B和方案C的差别在于子类重写父类方法的处理
参见前面的a,b,c三种情况
对于场景a,则使用方案C比较好,否则这些代码要在B类的所有子类都重写一遍,当然,最简单就是使用A方案
对于场景b,则使用方案B最好,毫无疑问
对于比较麻烦的场景c,方案B和C都可以解决.差别在于方案B通过打入父类的jar然后各自写子类来继承,而方案C则可能通过远程调用来完成了
这种远程调用可能在Service层
class TeacherService{
private StaffService staffService;
void leave(String id){
staffService.leave(id);
Teacher teacher=repository.retrieve(id);
teacher.leave();
repository.save(teacher);
}
}
可以看出来这样的话Teacher.leave只需要处理与父类有差别的那一部分,但是却没有使用super来调用,整个感觉一次业务操作被肢解了并且难以联系.另一种远程调用由Domain来完成
class Staff{
private StaffService service;
public leave(){
service.leave(getId());
}
}
class Teacher extends Staff{
public leave(){
super.leave();
//do other thing
}
}
这样的一种方式在语义上是完整的(但可能是虚假的),并且在单个应用拆分时改动也比较小.但这样做的坏处是事务的一致性难以得到保证.因为这对于外在来说是一个操作.
虽然标准的微服务划分是分库的.但对于这种继承的特殊情况,配合数据库之前的表继承,也许会合库会比较方法,特别是有些公共信息可以放在一个表(例如各种订单都会有支付记录这个表)这相当于从另一方式实现了CQRS