天天看點

Repository 倉儲,你的歸宿究竟在哪?(二)-這樣的應用層代碼,你能接受嗎?

寫在前面

關于“Repository 倉儲,你的歸宿究竟在哪?”這個系列,本來是想寫個上下篇,但是現在覺得,很有多東西需要明确,我也不知道接下來會寫多少篇,是以上一篇的标題就改成了《Repository 倉儲,你的歸宿究竟在哪?(一)-倉儲的概念》,在這篇博文中,主要講了倉儲的概念,并沒有探讨有關倉儲歸宿的任何東西,但你發現,後面評論中的探讨會比博文内容更有價值,這也是我所堅持寫博文的目的之一,也就是分享的價值。

上一篇博文評論中,大部分内容是我和 czcz1024 探讨“Specification-規約”,我也不知道怎麼會扯到這個話題上了,就像 netfocus 兄最後所說:有點偏離主題了。确實如此,本來我覺得這一篇博文也就這樣了,但是最後劉标才給我回複:

我想問下,領域模型裡面到底要不要去引用倉儲接口呢,好像ddd那個關系圖裡面是可以調用的,如果實體不引用的話,一些複雜的邏輯在實體裡面根本無法實作,或者隻能被分割成2部分,一部分在實體,一部分在appcation中,appcation中的一般是查詢方法;比如有一個登記的業務方法(其實就是insert了),在登記前需要判斷目前年份的金額是否大于登記實體的金額,大于那麼可以登記,否則異常,類似這樣的業務,判斷查詢那個到底是在實體裡面判斷還是在appcation裡面判斷呢,如果是實體的話,那麼倉儲接口怎麼注入到實體,實體是new出來的????

如果不嫌多,我再貼出一段:

這個問題我已經糾結很久了,還沒有看到具體是怎麼樣實作的,stackoverflow裡面也有很多老外在讨論,但也沒有最終的答案,大部分人認為沒有必要在實體或者聚合中使用倉儲(或者注入倉儲);我個人認為是一定要的,先不說這樣做對不對,我們可以根據項目的實踐去看問題,很多業務都需要判斷一下是否唯一,面積或者金額之類的是否足夠,這些都是需要查詢才可以得到值的,如果是放在application去實作,就等于把這些業務轉移到了上層,而且随着業務的複雜度增加,這樣的實作就會很多,如果放在領域層實作,看起來是很不錯的選擇,但是實體或者聚合裡面怎麼去注入倉儲接口呢,實體和聚合都是被new出來的,看起來是沒有辦法注入了,隻能在實體裡面調用XXX注入類.GetInstance<倉儲接口>();這樣的話領域層就要依賴某個注入架構了,是以這個倉儲問題,不管放哪都不那麼完美。

我當時看到這段回複的時候,我覺得我找到知音了,為什麼?因為隻有實踐過,你才會感同身受,大道理說一大堆,不去實踐應用,這種問題你不會發現的,更不會去思考怎麼解決?

我為什麼認死理,非要探讨倉儲的歸宿?

其實關于這個問題,netfocus 兄看到,應該會非常無語(哈哈),因為早在《設計窘境:來自 Repository 的一絲線索,Domain Model 再重新設計》這篇博文中,我和他就曾探讨過,當然還有之後其他的一些交流,他的意思是:為什麼非要糾結在倉儲這一塊?如果職責劃分的比較明确及正确,那該怎麼使用就怎麼使用,不管是應用層或是領域中,隻要符合,那它就是正确的。探讨的問題是“領域服務中能不能使用倉儲?”,這個也是我之前一直糾結的地方,其實 netfocus 兄的意思我都懂得,這也是倉儲設計的大前提之一,那就是職責或邊界劃厘清楚。

後來 Luminji 兄發表了一篇博文《面向對象架構模式之:領域模型(Domain Model)》,看完博文内容,再看評論,你會覺得這完全沒有相幹性(還是有一點的),評論中主要探讨的還是倉儲的歸宿(調用問題),但是到評論結束,還是沒有一個準确的結論,為什麼?因為大家都沒有去實踐應用,也就是沒有針對一個具體的業務場景進行探讨,比如針對某一個業務用例,把倉儲的歸宿放在領域服務中,那這個倉儲具體該怎麼設計實作?怎麼調用?怎麼配合領域服務完成一個具體的業務用例?應用層的代碼又該是怎樣的?IOC 容器怎麼去注入?。。。雖然是一個“很小”的問題,實踐應用過後,你會發現,其實這是一個很大的問題,當然前提是,你要去實踐,去應用。

最近,Jesse Liu 兄在小組中釋出了一個話題《讨論一下領域驅動設計》,我覺得這種探讨非常棒,因為大家都是針對同一個具體的業務用例,而不是各個不同的業務用例,而且這種探讨會讓你學到,别人在這種業務用例下是怎麼進行領域驅動設計的?不自覺會糾正你的一些錯誤觀點,當然前提是,你不是偏執的人。

以上我所叙述的一些東西,我個人覺得都是停留在理論階段,就像 Jesse 兄的那個話題,如果針對購物車這個業務用例,接下來的設計會是怎樣?因為之前的探讨内容都是職責和邊界,其實并沒有去實踐與應用,如果實踐了,你會發現這其中的一些其他問題,“倉儲的歸宿”,隻不過是這些問題的其中之一。

這樣的應用層代碼,你能接受嗎?

言歸正題,關于“倉儲,你的歸宿究竟在哪?”這個問題,這篇博文我想曬一下,我現在應用層的代碼,業務場景還是短消息系統,業務用例是發送短消息,代碼如下:

public OperationResponse SendMessage(string title, string content, string senderLoginName, string receiverDisplayName)
        {
            using (IRepositoryContext repositoryContext = new EntityFrameworkRepositoryContext())
            {
                IContactRepository contactRepository = new ContactRepository();
                IMessageRepository messageRepository = new MessageRepository(repositoryContext);
                ISendMessageService sendSiteMessageService = new SendSiteMessageService();

                Contact sender = contactRepository.GetContactByLoginName(senderLoginName);
                if (sender == null)
                {
                    return OperationResponse.Error("抱歉!發送失敗!錯誤:發件人不存在");
                }
                Contact receiver = contactRepository.GetContactByDisplayName(receiverDisplayName);
                if (receiver == null)
                {
                    return OperationResponse.Error("抱歉!發送失敗!錯誤:收件人不存在");
                }
                try
                {
                    Message message = new Message(title, content, sender, receiver);
                    if (messageRepository.GetMessageCountByIP(System.Web.HttpContext.Current.Request.UserHostAddress) > 100)
                    {
                        return OperationResponse.Error("一天内隻能發送100條短消息");
                    }
                    if (messageRepository.GetOutboxCountBySender(sender) > 20)
                    {
                        return OperationResponse.Error("1小時内隻能向20個不同的使用者發送短消息");
                    }
                    if (sendSiteMessageService.SendMessage(message))
                    {
                        messageRepository.Add(message);
                        return OperationResponse.Success("發送成功");
                    }
                    else
                    {
                        return OperationResponse.Error("發送失敗");
                    }
                }
                catch (Exception ex)
                {
                    if (ex.GetType().Equals(typeof(ArgumentException)))
                    {
                        return OperationResponse.Error(ex.Message);
                    }
                    CNBlogs.Infrastructure.Logging.Logger.Default.Error("Application_Error: SendMessage", ex);
                    throw ex;
                }
            }
        }
           

Are you kidding me?沒錯,你沒看錯,這就是現在短消息項目中應用層中的一段代碼,對于 DDD 的狂熱愛好者來說,我覺得他們看到這段代碼,肯定會抓狂的。。。

雖然短短幾行的代碼,但這其中所暴露出來的問題,實在太多了(比如倉儲上下文設計、自定義異常處理、倉儲的定義等等),其實我覺得你最不能接受的應該是,中間那兩個發送消息之前的業務驗證:

  1. 一天内隻能發送100條短消息。
  2. 1小時内隻能向20個不同的使用者發送短消息。

這個是屬于業務規則,怎麼會放在應用層?難道我腦袋鏽掉了?當然沒有,這個我原來是想放在 SendSiteMessageService 領域服務中的,但是我原來的設計是領域服務中不進行倉儲的調用(為了保持領域的純潔),包含業務用例描述,是以,針對這兩個業務驗證,是沒辦法放在領域服務中的,因為這種涉及到到領域對象的讀取,而所有的領域對象讀取接口都設計在倉儲中,領域服務想進行業務驗證,又不想進行領域對象讀取,你覺得可能嗎?

其實這種問題,有兩種解決方案:

  1. SendSiteMessageService 領域服務中實作倉儲的調用。
  2. 領域對象的讀取放在應用層中,擷取之後交由領域服務進行驗證。

我個人覺得,第二種實作方式隻能針對一定的業務場景下,如果在業務驗證過程中,又涉及到領域對象的讀取,這個實作方式就有點不合理了,而且擷取領域對象的操作,其實也是業務的一種展現。

代碼設計是一方面,代碼重構又是另一方面,後一個過程要比前一個過程困難百倍。

寫在最後

這篇博文,我不希望寫的太長,核心内容就是那段應用層中的代碼,我知道兄台你已經發現問題了,那就請兄台大聲的說出來吧。

領域驅動設計中,我再列一下有關倉儲的一些探讨博文:

  • 設計窘境:來自 Repository 的一絲線索,Domain Model 再重新設計
  • Repository 倉儲,你的歸宿究竟在哪?(一)-倉儲的概念
  • 面向對象架構模式之:領域模型(Domain Model)
  • 如何開始DDD(完)
  • 等待發現...

作者:田園裡的蟋蟀

微信公衆号:你好架構

出處:http://www.cnblogs.com/xishuai/

公衆号會不定時的分享有關架構的方方面面,包含并不局限于:Microservices(微服務)、Service Mesh(服務網格)、DDD/TDD、Spring Cloud、Dubbo、Service Fabric、Linkerd、Envoy、Istio、Conduit、Kubernetes、Docker、MacOS/Linux、Java、.NET Core/ASP.NET Core、Redis、RabbitMQ、MongoDB、GitLab、CI/CD(持續內建/持續部署)、DevOps等等。

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接。

分享到:

QQ空間

新浪微網誌

騰訊微網誌

微信

更多

繼續閱讀