7

領域驅動設計學習筆記(6):Aggregate (中)

 2 years ago
source link: http://teddy-chen-tw.blogspot.com/2019/07/6aggregate.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

領域驅動設計學習筆記(6):Aggregate (中)

July 02 21:45~22:48

wi9Juw8otEp_pTEYqp4ibbPsaxX9240JTl9intOUVgBLdpNNwWkzh1MCmvfz6TQPF-CHvsSTbDYqzjARAGc2VnIGtbMuuFtUp2ux10TbF3nx4dSaieI7XoV3E5Ngp20rjWPnE5HmlFwstzFHVuyH-FqBMbddduWUdn52IV-p5pJi8TuIs-yKXKSD1gnVjhFF4iATDXtCqHm1jh4E02UrsAu2RTPmQeVEVhU0gCur4OtcBuFeacfmgNJS55BUhgUgrRthY2OuJAy5dxArdW4GH-qbVprnfH1qlHOsQ89tqArCGvaeICArOGYiB4QrNRuAGlyAyCoAE88hCvdMDgufSzVbypqlm7YRQX_rwjPE3ZY7RSyYTJGOe3KlLN7ixQNIFm8i9DJh4PAybJPKIHwTsvHUVajB69nZsjc73ijhZgySSssP8vEkiwxPDm456f9_MtQYUSrUqVIMCltSWEh7vwDhpTwxPTMqh_OG5XBtIIDMdky0TvZzjZjKRjQa38nRkr_8hFGNOdfTzjLrU_fTyMfVCeClaBaw2Fs7CNJPFE5OwOR6hyNJ51i7oHFuEuesd5iSbutbfsGjya925XDjV47AY1Nj6sEAiZTqDqPj52oh_ejLzvZx8Jf97CGeQYSgQbumJKt1qDycJFNSbZHktA8wXsVhzmaxmKwcAyc9hk2Rrc49hSmoZAJf4weAgI0E_MQ5gfwIjzh-Zu1tSSZsVjv4ih8fM96-wTZPMCfb38fgLgYudw=w1368-h1038-no

在上一集〈領域驅動設計學習筆記(5):Aggregate (上)〉提到為了避免破壞aggregate invariant(聚合不變量或聚合規則),aggregate root在回傳資料的時候,需要考慮:

  1. 完全禁止回傳Aggregate內部的Entity。
  2. 可以回傳Entity,但只回傳immutable Entity或是Entity的deep copy。
  3. 設計immutable interface,讓Entity實作immutable interface並透過immutable interface回傳給客戶端。

今天討論這幾種做法的優缺點,並展示一個透過自動產生immutable proxy的工具來解決這問題的範例。

禁止回傳Aggregate內部的Entity

  • 優點:這個方法因為禁止aggregate root回傳內部entity,所以客戶端不可能透過aggregate內部entity來破壞aggregate invariant。
  • 缺點:為了提供資料給客戶端,aggregate root可能需要將內部entity轉成DTO(data transfer object)再往外傳。這種做法有點類似clean architecture中,use case層定義雙向介面將entity往外傳遞。從架構的角度來看可以切得很乾淨,但是需要花費額外的功夫包一層DTO。

回傳immutable entity或是entity的deep copy

  • 優點:用相同的domain model(直接回傳aggregate內部的entity)來處理程式邏輯,而且因為回傳的entity是不可修改的物件,或是原本物件的複製版本,所以也不可能透過這個entity破壞aggregate invariant。
  • 缺點:無論是實作immutable entity或是deep copy(GoF的Prototype設計模式),都需要花費額外的設計、實作與測試的功夫。特別是deep copy,不小心沒有實作好變成shallow copy就GG了。

設計immutable interface

Immutable interface是一種設計模式,請參考c2。簡單講,immutable interface是一個只有getter沒有setter的介面。設計好immutable interface之後,讓你的entity實作此介面。如果aggregate root要回傳entity,只能傳回entity所實作的immutable interface,如此一來客戶端就不可能修改到aggregate的資料。

Immutable interface的缺點和回傳DTO類似,domain model裡面除了原本的entity,又多了另一種代表「不可修改」的immutable interface。

折衷作法

如果程式語言可以支援自動回傳immutable object,這個問題就不是問題。Teddy不知道有沒有哪個程式語言有這種功能,目前只能從上述三種做法中挑一種來實做。

Teddy想用方法二。方法二有兩種實作,考慮到回傳entity的deep copy比較不好實作,所以決定優先選擇回傳immutable entity的方式。

要實作immutable entity,可以套用Proxy設計模式,概念如下:

wE9MpRz9A8AuEKWEqNqXuhqkUtkI5KdtmUYRCYPKQecDxDA6dsM1qQ6WvjXgspANc-HZKTf80ZjQ_wuqjxEfwWudHv3_iwg7SOQFaxK-1T9_ECpEwgDCKALta150VWyjBypXT45Qm68kTKNKFalqhLjf18p1uM3v6wU_iWe_vIpUqqkjrFzqRjOGlFpKKqbkoxinRun8rMkhwbous4qhD78KoNrYFNf9WuFs_F0GrZMKLi2lCZ7lExv3979CrLF6--rOryi6uSANySq7tpGqqTlAyAXQtDEuQRp_J-8DDMfPeyYZHTYUyJmQhAPGY4wmn1X896pqQ4LfqNghGd4VcV03T5WoRTmaR-NF8hJ0ebwPClPqySo3beI5fUH0_RXa_1ikZ2FF5Zkxpw4ndSrO_EYm8-taYMw_mLKhB-YjkGpQnD2UIRwzuFlDmSAQ72MNuUOaP_CAtS006G3fouP8EuhrG2o8MTWxoa12gjMsxGHeeSkRPATqvSqWXE1waAZbG3HsOpuxOEIiRWExBfvU_3EMER3lZ49oRxzX3uxlZLhvtCuQXFevAYmjE6Jdxa3Ea0MLn_t3JVUI_bNlL8tBoGUMycHb5t7SVY1ufKoFqfvYc3Y5jmF_PShtMk06z9c_66GlmdbC0AU5WuhUxgTpYgJoN6f_Pe8iTSMm-zjA9wbHiIFmuFHpXjatENLlF9PoVSivfz3Xohfy0EEryhKqP5ADmQbp_51IsSl6ZOrVFeUqUGbQGQ=w852-h386-no

Ministage是一個mutable介面,MinistageImpl是這個介面的實作,而ImmutableMinistage則是將MinistageImpl包裝一層,只要客戶端呼叫到setter的函數,就丟出例外。

這種方法的優點是,從客戶端的角度,他看到的就是Ministage這個domain model的概念。至於它是mutable還是immutable,則只是實作細節。

這種做法當然也有缺點,首先設計上當然比沒套用Proxy設計模式要來的複雜。其次,因為在編譯期間(compile time)客戶端不知道拿到的Ministage是mutable還是immutable,如果不小心拿到immutable但卻呼叫到它的setter函數,則會出現runtime exception。

程式設計師都有偷懶的天性,Teddy想回傳immutable entity但又懶得手動實作proxy,於是google了一下,找到一個Java語言的reflection-util工具,可以自動產生immutable proxy。

▼使用方法很簡單,以maven建構工具為例,首先加入reflection-util的參考。

IM1lYuATYljgYLIN84nq8EVHIoYDdqP7L574VZ6VgvO3cTeHoX4ETYSnmaq7oRlneACRRb7eAO955ndZqoGUPfA3YpJtvZqVHjttCOMd51L--QH5YJe9UJDOXXrhAo0pGT4Cm4b7IEhLx1UzRHOp5LmNrgiUz0qP4TedO6-dJH7smphbNe93TJYIQt86fCZ5kE946yYzB2qOG2j0vxdCj5RgpSTdyjwcoxymzH5fHg_-xuw5FZoiGPW0HCJZtotqmyp_-XBjdZZwRY2SyhbPTViiFPIFfY9JjSzwanfW50USlxN_WLFB-o4FOW8XbcTjXza-2zNG0FrAg_SS2JK_Z5Bc3rDjtuTV33z-M7ISdibErztLyKWwPx5iaD5NKiy6XTQwvsGqHERRk0O0Wdb6BdykB658gToV1MY8gnuRpVAnzZngishJkSoUwCf56sm8hwnk4FwAB7DnkZV8MZeADJ49UaqD_NhfY7aGj5Kxj6jTJ5fD6ak2Ilz8kHOb8vyN3HrcHLhrcZgo8qwN2wQSFDdKYsBErb7bsS9eiRjmYjGgOWif5BN81mBCsM3N_bBj85VkNjfCOLMJK87jo5uy5sJWNALWHNisBnrRRhS6NgFRcctI3MwsJ2zLPrgvfapf0lhvToF0GZ6fqxLkZpnHBojxJeLERw1vTxB6LDeU31lJyuZLF6EG4bjUbJ5akByZfGdkVXyns-5m7Y6Tjr0N5MnaQLprwB3HQ-a7yNeQvMB961O2hQ=w1324-h332-no

▼接著在程式中透過ImmutableProxy.create()方法,可以直接產生immutable entity,非常容易使用。

eam3vIlIvr075dvF96VQORoJgyVpAQm9S36lmGwyaWW1kjaykd6jvLjW429Gy8i25CACXFpkjfRF1GcFvgSDXH0qKPptV5cEhCYDKnNX8vxK_af5bfAFHeXzOJIeLm1M8jRCTikqW_QMp2ts9kGuPbHS4wbxVLavaIuGaX5jHA0xgebTVcxbxZSjR4ka0sPT8pz-H1_r0M589zj-T6bSIjPeJYBZO5_eL8N7cicOjKwnSKcs3m2Cujw7Hnq2Hia3unmCfDRPxLnrVwMPVjhRHM7_mh9NFOPGXc2sxEVvZUD3Si6Y4gPO4k4WhNY4Ar2B9KxdIQ9Ff9swbXpUY_VCqWdq54OvxghTGhqzKdXBZtcTIv64C0HnqE3MjkBg2BIO5XSWmsPzG-itkPOnPCbYNxu4QPih5OdfFWeb4mydk_jpPsrf5uwZXMd-wVuHGvfKj1aihOA3ZLVTM7botMx4QRXk_YFxHlOLYlgGo3M6Ah6qiQTcrKVG5BskBX6C5EXkLid-9x5pQPjmuRXGI5BXG8vSSZg4OkkYkaPMyQV81iCeHnF9fFPuoRAhk3HafOTJmX-MZxltpebU7Q6G2XUXnmZmQPIPVicAs5RcqE7CpPP_gEFu09q-jlOMvti_vI0WeIEHWyMZYX1DsU7AyqJlmfgf0faoPjPlKtmCTmGO5QHwUgjgkVIa8Kt0jauCu_7Zbj15-wQguuh5H2iPcrDu4RLM4M4rQ_w9Rjs-PijHYRAyrkYKug=w1400-h978-no

從Clean Architecture的角度來看…

使用reflection-util這種工具,可以幾乎無痛產生immutable entity。但是從clean architecture的角度來看,在最核心的entity層引用外部工具,違反了相依性原則,你的架構就變得不再那麼乾淨了。

Teddy覺得這個「小違規」目前是可以接受的折衷方案。因為你可以透過將reflection-util再包一層,讓aggregate root透過間接的方式呼叫reflection-util來獲得immutable entity。有朝一日如果真的不想使用reflection-util,也可以在影響最小的情況下,以其他實作方式將它取而代之。

所以,雖不完美,但還可接受。

友藏內心獨白:設計就是取捨後的結果。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK