業務中繼承關係研究(微服務)
上面的問題貌似通過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判斷並不好,但另一方面由呼叫方來告知型別是否加重了呼叫方的負擔?並且這樣文件和實際的介面不一致(如果我們使用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