天天看點

業務中繼承關系研究(微服務)

上面的問題貌似通過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

繼續閱讀