1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        領(lǐng)域驅(qū)動設(shè)計(DDD):領(lǐng)域接口化設(shè)計

        共 10464字,需瀏覽 21分鐘

         ·

        2021-08-10 20:25


        點擊上方 藍字 關(guān)注我們!



        Java,Python,C/C++,Linux,PHP,Go,C#,QT,大數(shù)據(jù),算法,軟件教程,前端,簡歷,畢業(yè)設(shè)計等分類,資源在不斷更新中... 點擊領(lǐng)取!

        每天 11 點更新文章,餓了點外賣,點擊 ??《無門檻外賣優(yōu)惠券,每天免費領(lǐng)!》

        來源:juejin.cn/post/6894109393173315597

        • 領(lǐng)域接口化設(shè)計
        • 領(lǐng)域接口化
        • 關(guān)聯(lián)接口化
        • 系統(tǒng)接口化
        • 開源電商
        • 總結(jié)


        領(lǐng)域接口化設(shè)計

        把服務(wù)對象(service)和資源庫對象(repository)設(shè)計成接口是最常見的。但是這對接口化的認識還遠遠不夠,我們需要更深入地去分析接口化設(shè)計和更全面地應(yīng)用接口化編程。所以我們要討論的是全面接口化,尤其是對領(lǐng)域模型 接口化的認識。

        領(lǐng)域接口化

        通常的情況下我們會把領(lǐng)域模型設(shè)計成類(class) ,但是你有沒有想過把領(lǐng)域模型設(shè)計成接口(interface) ?比如:

        public interface User {
            // ...
        }

        public class UserImpl implements User {
            // ...
        }

        這樣的設(shè)計似乎沒有任何價值,那么繼續(xù)深入地看看。比如:

        user-object-uml

        這時候看起來有點東西,因為我們?yōu)榱诉m配不同的數(shù)據(jù)源 ,提供了不同的實現(xiàn)類。

        最開始要把領(lǐng)域?qū)ο?/strong> 設(shè)計成接口,確實是為了在不同的 ORM 框架之間實現(xiàn)無縫切換 。因為 JPA 對面向?qū)ο蟮闹С肿詈茫?Mybatis 因為簡單在大環(huán)境下比較流行。在解決這個問題時,通常使用層內(nèi)包裹 或者叫對象轉(zhuǎn)換 的方式來解決。具體來說是在持久層使用持久化對象(PO)與領(lǐng)域?qū)ο螅―O)的之間進行轉(zhuǎn)換。例如:

        public class JpaUserRepository implements UserRepository {
            // ...
            @Override
            public Optional<User> findById(String id) {
                UserPO userPO = this.entityManager.find(UserPO.classid);
                return Optional.ofNullable(userPO).map(UserPO::toUser);
            }

            @Override
            public User save(User user) {
                UserPO userPO = this.entityManager.find(UserPO.classuser.getId());
                userPO.setNickname(user.getNickname());
                // ...
                return this.entityManager.merge(userPO).toUser();
            }
        }

        其中 UserPO 對象基本上是對數(shù)據(jù)庫表的映射,然后將數(shù)據(jù)與 User 對象進行交換。對于這種需要交換的方式既有性能的損失又比較繁瑣,將 User 設(shè)計成接口后,這個交換的問題就比較簡單地解決了,如下:

        public class JpaUserRepository implements UserRepository {
            // ...
            @Override
            public User create(String id) {
                return new JpaUser(id);
            }

            @Override
            public Optional<User> findById(String id) {
                JpaUser user = this.entityManager.find(JpaUser.classid);
                return Optional.ofNullable(user);
            }

            @Override
            public User save(User user) {
                JpaUser target = JpaUser.of(user);
                return this.entityManager.merge(target);
            }
            // ...
        }

        補充 JpaUser.of() 方法的實現(xiàn):

        public class JpaUser extends UserSupport {
            // ...
            public static JpaUser of(User user) {
                if (user instanceof JpaUser) {
                    return (JpaUser) user;
                }
                var target = new JpaUser();
                BeanUtils.copyProperties(user, target);
                // ...
                return target;
            }
        }

        對于使用 JPA 或者 Elasticsearch 等等各種不同的數(shù)據(jù)源,Spring data 都為此做了全面的支持。但由于 User 是接口,Spring data 提供的 Repository 接口泛型 只支持具體類型 ,比如:

        public interface ElasticsearchUserRepository
                extends ElasticsearchRepository<ElasticsearchUserString
        {
             // extends ElasticsearchRepository<User, String> // Not supported
        }

        為了解決這個問題,我們需要使用委托的方式,如下:

        public class DelegatingElasticsearchUserRepository implements UserRepository {

            private final ElasticsearchUserRepository elasticsearchUserRepository;

            public DelegatingElasticsearchUserRepository(ElasticsearchUserRepository elasticsearchUserRepository) {
                this.elasticsearchUserRepository = elasticsearchUserRepository;
            }

            @Override
            public User create(String id) {
                return new ElasticsearchUser(id);
            }

            @Override
            public Optional<User> findById(String id) {
                return CastUtils.cast(this.elasticsearchUserRepository.findById(id));
            }

            @Override
            public User save(User user) {
                return this.elasticsearchUserRepository.save(ElasticsearchUser.of(user));
            }
            // ...
        }

        關(guān)聯(lián)接口化

        order-association

        接口之間的關(guān)聯(lián)關(guān)系依然需要具體到子類的關(guān)聯(lián)關(guān)系上來討論。

        對于需要持久化的實體來說,我們不可能直接在成員屬性上使用接口類型,因為持久化框架無法通過接口來判定具體實現(xiàn)類。如下:

        @Getter
        @Setter
        @NoArgsConstructor
        @Entity
        @Table(name = "mf_order")
        public class JpaOrder implements Order {
            // ...
            // OrderItem 是一個接口類型,不能持久化。
            private List<OrderItem> items = new ArrayList<>();
            // ...
        }

        對于泛化 關(guān)聯(lián)關(guān)系問題,我們可以使用 JPA 注解提供的 targetEntity 屬性來解決:

        // ...
        public class JpaOrder implements Order {
            // ...
            // 通過指定具體的 targetEntity 類型,來解決泛化與特化的問題。
            @OneToMany(targetEntity = JpaOrderItem.class)
            private List<OrderItemitems 
        new ArrayList<>();
            // ...
        }

        • 支持 targetEntity 屬性的注解包括:@OneToMany@OneToOne、@ManyToOne、@ManyToMany。

        對于不支持類似 targetEntity 屬性的框架或者其它持久化技術(shù),我們可以使用封裝 來解決。如下:

        @Getter
        @Setter
        @NoArgsConstructor
        @Document(indexName = "user")
        public class ElasticsearchOrder implements Order {
            // ...
            // 使用具體特化類型進行解決。
            private List<ElasticsearchOrderItem> items = new ArrayList<>();

            @Override
            public void setItems(List<OrderItem> items) {
                this.items = Objects.requireNonNullElseGet(items, (Supplier<List<OrderItem>>) ArrayList::new)
                        .stream().map(ElasticsearchOrderItem::of).collect(Collectors.toList());
            }
            // ...
        }

        如果使用的是 Mybatis 作為持久化框架,依然可以在 OrderMapper.xml 中進行配置來解決:

        <resultMap id="Order" type="org.mallfoundry.order.repository.mybatis.MybatisOrder">
            <!-- ... -->
            <collection property="items" ofType="org.mallfoundry.order.repository.mybatis.MybatisOrderItem">
                <!-- ... -->
            </collection>
            <!-- ... -->
        </resultMap>

        在解決掉不同數(shù)據(jù)源無縫切換和關(guān)聯(lián)關(guān)系特化的問題后,在創(chuàng)建 User 對象上就和以往使用 new 的方式有所不同了,如下:

        @Test
        public void testCreateUser() {
            User user = this.userService.createUser(null); // new User()
            user.setNickname("Nickname");
            user.setGender(Gender.MALE);
            this.userService.addUser(user);
        }

        再過去創(chuàng)建對象都是使用 new 關(guān)鍵字,然而現(xiàn)在要使用 UserService 提供的 createUser(String id) 來創(chuàng)建。

        這種思維的轉(zhuǎn)變可能讓你初次不太很適應(yīng),但在考慮另一個問題。

        系統(tǒng)接口化

        對于一個產(chǎn)品我們要考慮的不只是產(chǎn)品本身能解決的業(yè)務(wù)需求,還需要在部署上有所追求。如果項目初期的并發(fā)量很小,客戶可能采用單進程的方式部署,慢慢地單進程扛不住了會升級到集群的方式,最終還要升級到微服務(wù)的方式。如何在單進程、集群和微服務(wù)之間進行無縫切換呢?

        再過去單機和集群項目與微服務(wù)項目是不能兼容的,因為領(lǐng)域模型都是類(class)而不是接口(interface)。具體來說:服務(wù)提供者(provider)的 User 對象與服務(wù)消費者(Consumer)的 User 對象是不兼容,不兼容將導(dǎo)致在單機項目中使用的是服務(wù)提供方的內(nèi)部 User 對象,而一旦遷移到微服務(wù)項目后,需要大量的修改工作。要把以前調(diào)用方使用內(nèi)部 User 對象替換為服務(wù)消費者提供的 User 對象。這樣的工作也是不可以逆的,一旦遷移成功就不能降級到單機環(huán)境了。

        再過去我們確實把服務(wù)(service)設(shè)計成了接口,這種接口的設(shè)計對于內(nèi)部的開發(fā)看似會有幫助,但是從實戰(zhàn)的經(jīng)驗來看卻不像大家想象的那樣可以為 Service 提供不同的實現(xiàn)。因為現(xiàn)在都是迭代開發(fā),都是一個版本一個版本的去不斷完善應(yīng)用服務(wù)代碼,而不是替換應(yīng)用服務(wù)代碼,所以在 IDDD 中把應(yīng)用服務(wù)(Application Service)類型由接口(Interface)改為了類(Class)。

        如果我們把領(lǐng)域?qū)ο笤O(shè)計成接口類型,并與服務(wù)接口以及其它接口一起組織在一個新的模塊內(nèi),形成一個新的接口(API)模塊。然后為各種不同地端口提供適配此端口的實現(xiàn),這樣的設(shè)計是不是可以解決在運行環(huán)境中無縫切換的問題,如下:

        user-modules

        這樣的設(shè)計使得調(diào)用者 只需要使用 User 接口(user-api)開發(fā)業(yè)務(wù),并且在單進程(Standalone)環(huán)境中只需要依賴 user 模塊,在微服務(wù)環(huán)境中只需要依賴 user-openfeign-client 模塊,在外部環(huán)境中只需要依賴 user-rest-client 模塊。調(diào)用者通過依賴不同地實現(xiàn)模塊 來解決不同環(huán)境的無縫切換,并且調(diào)用者使用的代碼是不需要改變的。

        開源電商

        Mallfoundry 是一個完全開源的使用 Spring Boot 開發(fā)的多商戶電商平臺。它可以嵌入到已有的 Java 程序中,或者作為服務(wù)器、集群、云中的服務(wù)運行。

        • 領(lǐng)域模型采用領(lǐng)域驅(qū)動設(shè)計(DDD)、接口化以及面向?qū)ο笤O(shè)計。

        項目地址:https://gitee.com/mallfoundry/mall

        推薦 3 個開源項目:

        • https://github.com/YunaiV/ruoyi-vue-pro
        • https://github.com/YunaiV/SpringBoot-Labs
        • https://github.com/YunaiV/onemall

        總結(jié)

        領(lǐng)域?qū)ο蠼涌诨沟梦覀冊趦?nèi)部實現(xiàn)了一套統(tǒng)一的接口,并將領(lǐng)域?qū)ο蠼涌诨瘮U展到系統(tǒng)級別時,我們又在系統(tǒng)層次上設(shè)計出一套統(tǒng)一地全局接口來開發(fā)業(yè)務(wù)和應(yīng)對未來變化的環(huán)境。這樣的設(shè)計雖然非常好,但對軟件設(shè)計人員、軟件架構(gòu)師以及開發(fā)人員的專業(yè)性也有了一定的要求,但是它所帶來的好處是可見的。

        - END -


        往期推薦

        編程5分鐘,命名2小時!聊聊命名規(guī)則!

        比 requests 更強大 Python 庫

        Tomcat 組成與工作原理

        常用的限流框架都在這里了!


        看完文章,餓了點外賣,點擊 ??《無門檻外賣優(yōu)惠券,每天免費領(lǐng)!》

        END



        若覺得文章對你有幫助,隨手轉(zhuǎn)發(fā)分享,也是我們繼續(xù)更新的動力。


        長按二維碼,掃掃關(guān)注哦

        ?「C語言中文網(wǎng)」官方公眾號,關(guān)注手機閱讀教程 ?


        必備編程學習資料


        目前收集的資料包括: Java,Python,C/C++,Linux,PHP,go,C#,QT,git/svn,人工智能,大數(shù)據(jù),單片機,算法,小程序,易語言,安卓,ios,PPT,軟件教程,前端,軟件測試,簡歷,畢業(yè)設(shè)計,公開課 等分類,資源在不斷更新中...


        點擊“閱讀原文”,立即免費領(lǐng)取最新資料!
        ??????
        瀏覽 55
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            娇妻被领导抱进卧室3p视频 | 边吃奶边爱爱好爽 | 欧美AAA在线观看 | 国产网红福利网站 | 骚货无码| 成人片爱爱爱激情视频 | 9热在线| 女技师三级做爰按摩 | 成年人天堂 | 日本japanese高潮尖叫 |