<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 9985字,需瀏覽 20分鐘

           ·

          2021-08-13 22:36


          來(lái)源:juejin.cn/post/6894109393173315597

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


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

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

          領(lǐng)域接口化

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

          public interface User {
              // ...
          }

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

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

          user-object-uml

          這時(shí)候看起來(lái)有點(diǎn)東西,因?yàn)槲覀優(yōu)榱诉m配不同的數(shù)據(jù)源 ,提供了不同的實(shí)現(xiàn)類。

          最開(kāi)始要把領(lǐng)域?qū)ο?/strong> 設(shè)計(jì)成接口,確實(shí)是為了在不同的 ORM 框架之間實(shí)現(xiàn)無(wú)縫切換 。因?yàn)?JPA 對(duì)面向?qū)ο蟮闹С肿詈茫?Mybatis 因?yàn)楹?jiǎn)單在大環(huán)境下比較流行。在解決這個(gè)問(wèn)題時(shí),通常使用層內(nèi)包裹 或者叫對(duì)象轉(zhuǎn)換 的方式來(lái)解決。具體來(lái)說(shuō)是在持久層使用持久化對(duì)象(PO)與領(lǐng)域?qū)ο螅―O)的之間進(jìn)行轉(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 對(duì)象基本上是對(duì)數(shù)據(jù)庫(kù)表的映射,然后將數(shù)據(jù)與 User 對(duì)象進(jìn)行交換。對(duì)于這種需要交換的方式既有性能的損失又比較繁瑣,將 User 設(shè)計(jì)成接口后,這個(gè)交換的問(wèn)題就比較簡(jiǎn)單地解決了,如下:

          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);
              }
              // ...
          }

          補(bǔ)充 JpaUser.of() 方法的實(shí)現(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;
              }
          }

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

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

          為了解決這個(gè)問(wèn)題,我們需要使用委托的方式,如下:

          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)系上來(lái)討論。

          對(duì)于需要持久化的實(shí)體來(lái)說(shuō),我們不可能直接在成員屬性上使用接口類型,因?yàn)槌志没蚣軣o(wú)法通過(guò)接口來(lái)判定具體實(shí)現(xiàn)類。如下:

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

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

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

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

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

          @Getter
          @Setter
          @NoArgsConstructor
          @Document(indexName = "user")
          public class ElasticsearchOrder implements Order {
              // ...
              // 使用具體特化類型進(jìn)行解決。
              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 中進(jìn)行配置來(lái)解決:

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

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

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

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

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

          系統(tǒng)接口化

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

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

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

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

          user-modules

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

          開(kāi)源電商案例

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

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

          項(xiàng)目地址:https://gitee.com/mallfoundry/mall

          推薦 3 個(gè)開(kāi)源項(xiàng)目:

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

          總結(jié)

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

          - END -


          推薦閱讀:

          世界的真實(shí)格局分析,地球人類社會(huì)底層運(yùn)行原理

          不是你需要中臺(tái),而是一名合格的架構(gòu)師(附各大廠中臺(tái)建設(shè)PPT)

          企業(yè)IT技術(shù)架構(gòu)規(guī)劃方案

          論數(shù)字化轉(zhuǎn)型——轉(zhuǎn)什么,如何轉(zhuǎn)?

          華為干部與人才發(fā)展手冊(cè)(附PPT)

          企業(yè)10大管理流程圖,數(shù)字化轉(zhuǎn)型從業(yè)者必備!

          【中臺(tái)實(shí)踐】華為大數(shù)據(jù)中臺(tái)架構(gòu)分享.pdf

          華為的數(shù)字化轉(zhuǎn)型方法論

          華為如何實(shí)施數(shù)字化轉(zhuǎn)型(附PPT)

          超詳細(xì)280頁(yè)Docker實(shí)戰(zhàn)文檔!開(kāi)放下載

          華為大數(shù)據(jù)解決方案(PPT)


          瀏覽 87
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产无码免费在线观看 | 国产精品无人区 | 操鸡巴网 | 中文字幕av一区二区三区 | 19p内射 |