【DDD】領域驅動設計實踐 —— Application層實現

來源:互聯網
上載者:User

標籤:類型轉換   enum   amp   logs   err   orm   valueof   ges   裁剪   

  本文是DDD架構實現講解的第二篇,主要介紹了DDD的Application層的實現,詳細講解了service、assemble的職責和實現。文末附有github地址。相比於《領域驅動設計》原書中的航運系統例子,社交服務系統的業務情境對於大家更加熟悉,相信更好理解。本文是【DDD】系列文章的其中一篇,其他可參考:使用領域驅動設計思想實現業務系統

Application層

  在DDD設計思想中,Application層主要職責為組裝domain層各個組件及基礎設施層的公用組件,完成具體的商務服務。Application層可以理解為粘合各個組件的膠水,使得零散的組件組合在一起提供完整的商務服務。在複雜的業務情境下,一個業務case通常需要多個domain實體參與進來,所以Application的粘合效用正好有了用武之地。

  Application層主要由:service、assembler組成,下面分別對其做講解。

Serviceservice是組件粘合劑

  這裡的Service區別於domain層的domain service,是應用服務。它是組件的粘合劑,組合domain層的各個組件和 infrastructure層的持久化組件、訊息組件等等,完成具體的商務邏輯,提供完整的商務服務。

  通過不斷的實踐,我們發現:通過DDD實現商務服務時,檢驗業務模型的品質的一個標準便是 —— service方法中不要有if/else。如果存在if/else,要麼就是系統用例存在耦合,要麼就是業務模型不夠友好,導致部分商務邏輯泄漏到service了。

  通常意義上,一個業務case在service層便會對應一個service方法,這樣確保case實現的獨立性。拿社區服務中的“文章”模組來講,我們有如下幾個明顯的case:發帖(posting)、刪帖(deletePost)、查詢文章詳情(queryPostDetail),這些case在service層都對應獨立的業務方法。

思考

  對於較為複雜的case:查詢貼文清單,可能需要根據不同的tag過濾文章,或者查詢不同類型的文章,或者查詢熱門文章,這個時候應當用一個service方法實現呢?還是多個呢?

  考慮這個問題,主要從這兩方面入手:domain的一致性,資料存放區的一致性;如果兩個一致性都滿足,那麼我們可以在一個業務方法中完成,否則就要在獨立的業務方法中完成。

  例如:根據文章運營標籤查詢文章 和 查詢全部貼文清單 這兩個case我們可以放到一個service方法中實現,因為前一個case只是在後一個case的基礎上加了一個過濾條件,這個過濾條件完全可以交給dao層的sql where條件處理掉,除此之外,domain和repository都完全一樣;

  而“查詢熱門文章” 這個case就不能和上面的兩個case共用一個service方法了,因為熱門貼文清單的資料來源並不在資料庫中,而是存在於緩衝中,因此repository的取數邏輯存在很大差異,如果共用一個service方法,勢必要在service層出現if/else判定,這是不友好的。

類圖

 

程式碼範例 
 1 @Service 2 public class PostServiceImpl implements PostService { 3      4     @Autowired 5     private IPostRepository postRepository; 6      7     @Autowired 8     private PostAssembler postAssembler; 9     10 11 12     public PostingRespBody posting(RequestDto<PostingReqBody> requestDto) throws BusinessException {13         PostingReqBody postingReqBody = requestDto.getBody();14         /**15          *NOTE: 請求參數校正交給了validation,這裡無需校正userId和postId是否為空白16          */17         String userId = postingReqBody.getUserId();18         String title = postingReqBody.getTitle();19         String sourceContent = postingReqBody.getSourceContent();20         21         long userIdInLong = Long.valueOf(userId);22         23         /**24          * 組裝domain model entity25          * NOTE:這裡的PostAuthor不需要從repository重載,原因在於:deletePost情境需要使用者登入後才能操作,26          *         在進入service之前,已經在controller層完成了使用者身份鑒權,故到達這裡的userId肯定是合法的使用者27          */28         PostAuthor postAuthor = new PostAuthor(userIdInLong);29         Post post = postAuthor.posting(title, sourceContent);30         31         /**32          * NOTE:使用repository將model entity 寫入儲存33          */34         postRepository.save(post);35         36         /**37          * NOTE:使用postAssembler將Post model組裝成dto返回。38          */39         return postAssembler.assemblePostingRespBody(post);40     }41     42 43     public DeletePostRespBody delete(RequestDto<DeletePostReqBody> requestDto) throws BusinessException {44         DeletePostReqBody deletePostReqBody = requestDto.getBody();45         46         /**47          *NOTE: 請求參數校正交給了validation,這裡無需校正userId和postId是否為空白48          */49         String userId = deletePostReqBody.getUserId();50         String postId = deletePostReqBody.getPostId();51         52         long userIdInLong = Long.valueOf(userId);53         long postIdInLong = Long.valueOf(postId);54         55         /**56          * 組裝domain model entity57          * NOTE:這裡的PostAuthor不需要從repository重載,原因在於:deletePost情境需要使用者登入後才能操作,58          *         在進入service之前,已經在controller層完成了使用者身份鑒權,故到達這裡的userId肯定是合法的使用者59          */60         PostAuthor postAuthor = new PostAuthor(userIdInLong);61         /**62          * 從repository中重載domain model entity63          * 藉此判斷該postId是否真的存在文章64          */65         Post post = postRepository.query(postIdInLong);66         67         postAuthor.deletePost(post);68         69         postRepository.delete(post);        70         71         return null;72     }73 74 75     @Override76     public QueryPostDetailRespBody queryPostDetail(RequestDto<QueryPostDetailReqBody> requestDto)77             throws BusinessException {78         QueryPostDetailReqBody queryPostDetailReqBody = requestDto.getBody();79         80         String readerId = queryPostDetailReqBody.getReaderId();81         String postId = queryPostDetailReqBody.getPostId();82         83         long readerIdInLong = Long.valueOf(readerId);84         long postIdInLong = Long.valueOf(postId);85         86         //TODO 可能有一些許可權校正,比如:判定該讀者是否有查看作者文章的許可權等。這裡暫且不展開討論。87         PostReader postReader = new PostReader(readerIdInLong);88         89         Post post = postRepository.query(postIdInLong);90         91         /**92          * NOTE: 使用postAssembler將domain層的model組裝成dto,組裝過程:93          *         1、完成類型轉換、資料格式化;94          *         2、將多個model組合成一個dto,一併返回。95          */96         return postAssembler.assembleQueryPostDetailRespBody(post);97     }    98 99 }
AssemblerAssembler是組合器

  Assembler是組合器,負責完成domain model對象到dto的轉換,組裝職責包括:

  1. 完成類型轉換、資料格式化;如日誌格式化,狀態enum裝換為前端認識的string;
  2. 將多個domain領域對象組裝為需要的dto對象,比如查詢貼文清單,需要從Post(文章)領域對象中擷取文章的詳情,還需要從User(使用者)領域對象中擷取使用者的社交資訊(暱稱、簡介、頭像等);
  3. 將domain領域對象屬性裁剪並組裝為dto;某些情境下,可能並不需要所有domain領域對象的屬性,比如User領域對象的password屬性屬於隱私相關屬性,在“查詢使用者資訊”case中不需要返回,需要裁剪掉。
範例程式碼  
 1 /** 2  * Post模組的組合器,完成domain model對象到dto的轉換,組裝職責包括: 3  *         1、完成類型轉換、資料格式化;如日誌格式化,狀態enum裝換為前端認識的string; 4  *         2、將多個model組合成一個dto,一併返回。 5  * TODO: 不太好的地方每個assemble方法都需要先判斷入參對象是否為空白。 6  * @author daoqidelv 7  * @createdate 2017年9月24日 8  */ 9 @Component10 public class PostAssembler {11     12     private final static String POSTING_TIME_STRING_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss";13     14     @Autowired15     private ApplicationUtil applicationUtil;16     17     public PostingRespBody assemblePostingRespBody(Post post) {18         if(post == null) {19             return null;20         }21         PostingRespBody postingRespBody = new PostingRespBody();22         postingRespBody.setPostId(String.valueOf(post.getId()));23         return postingRespBody;24     }25     26     public QueryPostDetailRespBody assembleQueryPostDetailRespBody(Post post) {27         /**28          * NOTE: 判定入參post是否為null29          */30         if(post == null) {31             return null;32         }33         QueryPostDetailRespBody queryPostDetailRespBody = new QueryPostDetailRespBody();34         queryPostDetailRespBody.setAuthorId(String.valueOf(post.getAuthorId())); //完成類型轉換35         queryPostDetailRespBody.setPostId(String.valueOf(post.getId()));//完成類型轉換36         queryPostDetailRespBody.setPostingTime(37                 applicationUtil.convertTimestampToString(post.getPostingTime(), POSTING_TIME_STRING_DATE_FORMAT));//完成日期格式化38         queryPostDetailRespBody.setSourceContent(post.getSourceContent());39         queryPostDetailRespBody.setTitle(post.getTitle());40         return queryPostDetailRespBody;41     }42 43 }

 

思考

  上述代碼實現中,每一個assemble方法都需要校正入參對象是否為空白,實踐中發現,這一個關鍵點很容易遺漏,沒有想到好的辦法解決。

類圖

demo

  此demo的代碼已上傳至github,歡迎下載和討論,但拒絕被用於任何商業用途。

  github地址:https://github.com/daoqidelv/community-ddd-demo/tree/master

  branch:master

 

【DDD】領域驅動設計實踐 —— Application層實現

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.