今天我想與你聊一聊,DDD概念復(fù)雜、難懂,實際落地該怎么設(shè)計代碼實現(xiàn)模型。關(guān)于這個話題,先說說整體框架、思路,我打算結(jié)合兩部分分享給你,每一部分,相信仔細看完,都會或多或少有所收獲。以下內(nèi)容,預(yù)計1分鐘左右可快速看完:
前一部分,方法篇,旨在詳細介紹DDD所包含的幾個核心概念,以及圍繞這些概念所構(gòu)建的DDD代碼實現(xiàn)模型的組成結(jié)構(gòu)。
后半部分,實踐篇,進一步思考。我繼續(xù)接著說,承接前面的內(nèi)容,要想讓這些代碼實現(xiàn)模型真正落地,我們需要把它們與具體的應(yīng)用場景結(jié)合起來。我將側(cè)重詳細闡述DDD代碼實現(xiàn)模型的設(shè)計方法,并給出一個具體的案例分析。
伴隨著業(yè)務(wù)系統(tǒng)復(fù)雜度的不斷提升,以及微服務(wù)架構(gòu)等分布式技術(shù)體系的大行其道,領(lǐng)域驅(qū)動設(shè)計(Domain Driven Design,DDD),日漸成為系統(tǒng)建模領(lǐng)域的主流設(shè)計思想和模式。在DDD中,引入了限界上下文、聚合、實體、值對象、領(lǐng)域事件、資源庫、應(yīng)用服務(wù)等一系列核心概念。
通過這些概念,開發(fā)人員可以開展系統(tǒng)設(shè)計和實現(xiàn)工作。但是,DDD中的這些概念相對都比較抽象,甚至有些晦澀難懂。再往相通或類似問題點上靠,我認為實質(zhì)上對于復(fù)雜難懂的概念的理解和把握,我們一開始不必過于糾結(jié)這些概念本身,而是可以把它們與現(xiàn)實中的具體實現(xiàn)模型對應(yīng)起來。
通過兩者之間的合理映射,來促進對概念本身的理解,如下圖所示。這里多說一句,即便你是其他技術(shù)領(lǐng)域的朋友,或許也曾遇到過類似問題,并有著共通性。希望看完今天的分享,可以或多或少幫助到你,并有所啟發(fā)、思考。
在上圖中,我們一方面嘗試把復(fù)雜概念映射到實現(xiàn)模型。另一方面,基于對實現(xiàn)模型的把握,可以反推對復(fù)雜概念的理解程度,從而更好地掌握這些概念。這也更足以見得實踐才能出真知,也只有設(shè)計過實現(xiàn)模型,才能真正掌握這些概念,從而把它們應(yīng)用到各種具體的場景中。
這是一種行之有效的辦法。
那么問題就來了,在日常開發(fā)過程中,如何確保DDD真正落地,把這些抽象概念轉(zhuǎn)為具體代碼模式,是我們今天要討論的內(nèi)容。
01
想設(shè)計代碼實現(xiàn)模型,咱非得了解DDD中這幾個核心概念?
總體來說,DDD提供的是一種開展業(yè)務(wù)建模和軟件設(shè)計的方法論。DDD認為良好的系統(tǒng)架構(gòu),應(yīng)該是技術(shù)架構(gòu)和業(yè)務(wù)架構(gòu)相互融合的結(jié)果,開發(fā)人員不能脫離業(yè)務(wù)領(lǐng)域來設(shè)計技術(shù)架構(gòu)。為了實現(xiàn)這一目標,DDD提出了一組核心概念,如圖1所示。
我們先來看第一個核心概念,就比較難于理解,即限界上下文(Boundary Context)。在DDD中,當我們把業(yè)務(wù)領(lǐng)域拆分成多個子域之后,限界上下文明確了子域的業(yè)務(wù)界限,并實現(xiàn)子域與子域之間的隔離,如圖2所示。
有了限界上下文,我們就需要圍繞業(yè)務(wù)場景設(shè)計領(lǐng)域模型對象(Domain Model Object)。領(lǐng)域模型對象,包含了豐富的業(yè)務(wù)邏輯和操作行為,這點和只包含數(shù)據(jù)屬性的傳統(tǒng)數(shù)據(jù)對象,有本質(zhì)區(qū)別。
因此,領(lǐng)域模型對象是我們在應(yīng)用DDD時,最應(yīng)該關(guān)注的一組對象,也是最難把握的一組對象。
在DDD中,領(lǐng)域模型對象包括三大類,即聚合(Aggregate)、實體(Entity)和值對象(Value Object),這三類對象各有特點。
相較領(lǐng)域模型對象,領(lǐng)域事件誕生較晚,但也是領(lǐng)域模型的一個重要組成部分,因為現(xiàn)實中很多場景,都可以抽象成事件(Event),如圖4。
在DDD中,通過領(lǐng)域事件可以實現(xiàn)業(yè)務(wù)狀態(tài)變化的有效傳播,并在單個限界上下文內(nèi)部或在多個上下文之間,對這些狀態(tài)變化做出響應(yīng)。
業(yè)務(wù)領(lǐng)域中的各種狀態(tài)變化最終都需要進行存儲。為此,DDD提供了一個針對業(yè)務(wù)數(shù)據(jù)的統(tǒng)一訪問入口,這就是資源庫(Repository)。通過資源庫,我們可以實現(xiàn)對各種領(lǐng)域?qū)ο蟮某志没僮?,如圖5所示。
最后,我們來引入應(yīng)用服務(wù)的概念。應(yīng)用服務(wù)包括命令(Command)服務(wù)和查詢(Query)服務(wù)兩大類,本質(zhì)上起到的是一種解耦和協(xié)調(diào)作用,確保各種領(lǐng)域模型對象之間的交互和協(xié)作。因此,在涉及到多個限界上下文之間的交互時,我們需要重點關(guān)注應(yīng)用服務(wù)。如圖6所示。
02
概念復(fù)雜又難懂,想實際業(yè)務(wù)場景下真正落地,需引入DDD代碼實現(xiàn)模型
關(guān)于DDD中的核心概念,我就簡單介紹到這里,下一步就是要討論一個所有開發(fā)人員都必須面對的話題,即如何將這些復(fù)雜難懂的概念,在現(xiàn)實的開發(fā)過程中能夠真正落地?這就需要引入DDD代碼實現(xiàn)模型。
要想設(shè)計代碼實現(xiàn)模型,先得搞清楚它有哪幾部分組成?
無論設(shè)計方法有多好,能夠轉(zhuǎn)換為可運行的代碼才是王道,這點對于DDD而言尤為如此。
可惜的是,目前業(yè)界關(guān)于如何實施這些概念,并沒有一套統(tǒng)一的標準和規(guī)范,這就導(dǎo)致我們在具體的開發(fā)過程中,常常感到無從下手。為此,本文專門提煉了一整套DDD代碼實現(xiàn)模型。接下來,讓我們從DDD代碼實現(xiàn)模型的基本概念和組織結(jié)構(gòu)展開討論。
在講代碼實現(xiàn)模型之前,先弄清楚什么是實現(xiàn)模型
說起模型(Model),業(yè)界主流的方法論認為存在三種不同的類型,即領(lǐng)域模型、設(shè)計模型和代碼模型,如圖7所示。
關(guān)于領(lǐng)域模型,我們在前面的內(nèi)容中已經(jīng)做了介紹。在DDD中,聚合、實體、值對象、領(lǐng)域事件等,都可以歸屬到這一模型的范疇。
而設(shè)計模型(Design Model),可以分成邊界模型和內(nèi)部模型兩個組成部分。邊界模型明確系統(tǒng)邊界,抽象系統(tǒng)集成和交互方案。而內(nèi)部模型細化邊界模型,在明確系統(tǒng)邊界的前提下,實現(xiàn)系統(tǒng)內(nèi)部模塊和組件的抽象和構(gòu)建。因此,在DDD中,我們往往從限界上下文的角度出發(fā),來開展設(shè)計模型的建設(shè),如下圖所示。
最后,代碼模型為現(xiàn)實世界的解決方案,提供可執(zhí)行的系統(tǒng)環(huán)境。我們可以通過在領(lǐng)域模型和設(shè)計模型中嵌入代碼的方式來構(gòu)建代碼模型,該模型是將DDD各個復(fù)雜概念轉(zhuǎn)換為可執(zhí)行代碼的關(guān)鍵所在,也是我們今天要討論的主要內(nèi)容。
顯然,領(lǐng)域模型、設(shè)計模型和代碼模型之間,存在一種層次依賴關(guān)系,如圖9所示。
首先,領(lǐng)域模型代表領(lǐng)域的固有業(yè)務(wù);
設(shè)計模型指向領(lǐng)域模型,關(guān)注對外部接口的承諾以及交互關(guān)系;
代碼模型提供了完整實現(xiàn)過程,是對設(shè)計模型的細化。
正是通過這三種模型的整合,完成了從現(xiàn)實問題到最終能夠落地的實現(xiàn)方案的演進。
DDD代碼實現(xiàn)模型,應(yīng)包含哪些部分?
針對DDD代碼實現(xiàn)模型的討論,我們也將遵循上述三種模型的整合過程。結(jié)合DDD中的各種核心概念,我們梳理DDD代碼實現(xiàn)模型組成結(jié)構(gòu),如圖10所示。
在上圖中,我們可以清晰看到DDD代碼實現(xiàn)模型的四個組成部分,分別面向領(lǐng)域?qū)ο?、?yīng)用服務(wù)、基礎(chǔ)設(shè)施以及上下文集成。講到這里,你可能會問,為什么我們要這樣設(shè)計DDD的代碼實現(xiàn)模型呢?
我們知道一個完整的DDD應(yīng)用程序,通常由多個限界上下文構(gòu)成。因此,對于代碼實現(xiàn)模型而言,我們需要重點考慮兩個維度,即:
- 單個限界上下文實現(xiàn)過程中的代碼模型
- 多個限界上下文之間集成過程中的代碼模型
在上圖中,關(guān)于領(lǐng)域?qū)ο蟆?yīng)用服務(wù)、基礎(chǔ)設(shè)施代碼實現(xiàn)模型的討論,屬于單個限界上下文的范疇,而上下文實現(xiàn)代碼集成模型,顯然面向多個限界上下文,如圖11所示。
通過前面內(nèi)容的學(xué)習(xí),相信你對DDD代碼實現(xiàn)模型的組成結(jié)構(gòu),已了然在胸。
那么,在日常開發(fā)過程中,我們應(yīng)該如何設(shè)計這些代碼實現(xiàn)模型呢?有沒有具體的案例可以參考呢?這幾個問題點,你可以先停下來琢磨下。
03 如何設(shè)計DDD代碼實現(xiàn)模型?
在分析DDD代碼實現(xiàn)模型時,對于上一篇提到的四個組成部分,我們需要梳理它們的代碼結(jié)構(gòu)和依賴關(guān)系。針對代碼結(jié)構(gòu),我們需要明確代碼包的組成,以及內(nèi)部所包含的技術(shù)組件。
在明確了包結(jié)構(gòu)之后,依賴關(guān)系指的是我們需要進一步明確這些代碼包和技術(shù)組件之間的交互關(guān)系?;谶@兩點,讓我們先來討論領(lǐng)域?qū)ο蟮拇a實現(xiàn)模型。
領(lǐng)域?qū)ο蟠a實現(xiàn)模型
針對領(lǐng)域?qū)ο螅覀兺ǔS谩癲omain”這個單詞,對代碼包結(jié)構(gòu)的頂層包進行命名,在該包結(jié)構(gòu)下的所有技術(shù)組件,都屬于領(lǐng)域?qū)ο蟮姆懂牎?/p>
具體而言,在DDD中,領(lǐng)域?qū)ο蟀I(lǐng)域模型對象、領(lǐng)域事件、資源庫以及應(yīng)用服務(wù)所涉及到的命令和查詢對象,其中領(lǐng)域模型對象可以分為聚合、實體和值對象這三大類。
因此,在DDD所有的代碼實現(xiàn)模型中,領(lǐng)域?qū)ο笊婕暗拇a結(jié)構(gòu)最為復(fù)雜,可以分成兩個層次,如圖1所示。
圖1
可以看到,這里的“domain”代表整個領(lǐng)域?qū)ο螅癿odel”則代表領(lǐng)域模型對象,請注意這兩者在命名上的區(qū)別,以及它們之間的從屬關(guān)系。領(lǐng)域?qū)ο笫荄DD代碼實現(xiàn)模型的基礎(chǔ),包含核心業(yè)務(wù)邏輯的實現(xiàn)。
應(yīng)用服務(wù)代碼實現(xiàn)模型
類似地,針對應(yīng)用服務(wù),我們通常使用“application”來命名頂層包結(jié)構(gòu)。應(yīng)用服務(wù)包含查詢服務(wù)和命令服務(wù)這兩大類,所以在子包的命名上,也會用“commandservice”和“queryservice”加以區(qū)分,如圖2所示。
圖2
顯然,命令服務(wù)和查詢服務(wù),分別依賴于領(lǐng)域?qū)ο蟠a實現(xiàn)模型中的命令對象和查詢對象,我們用虛線表示這層依賴關(guān)系。在DDD的代碼實現(xiàn)模型中,應(yīng)用服務(wù)可以說是交互關(guān)系最為復(fù)雜的一個代碼模型。
一方面,它需要將命令和查詢操作,分派給聚合對象等領(lǐng)域模型對象。
另一方面,它也需要分別和基礎(chǔ)設(shè)施,以及其他限界上下文進行交互。
關(guān)于后者,我們在討論到案例分析時,還會做進一步展開。
基礎(chǔ)設(shè)施代碼實現(xiàn)模型
其實,所謂的基礎(chǔ)設(shè)施,指的是DDD應(yīng)用程序中所使用到的各種具體技術(shù)、工具和框架。常見的基礎(chǔ)設(shè)施類組件主要包括這幾個方面:
- 數(shù)據(jù)持久化(Persistence)
- 消息通信(Messaging)
- 系統(tǒng)配置(Config)
- 安全控制(Security)
因此,基礎(chǔ)設(shè)施的包結(jié)構(gòu)并不是固定的,而是根據(jù)具體的技術(shù)開發(fā)要求進行靈活的組織,這里給出一個常見的包結(jié)構(gòu),如圖3所示。針對基礎(chǔ)設(shè)施,我們使用了“infrastructure”,對這一包結(jié)構(gòu)進行命名。
圖3上圖中有一點需要注意,代表數(shù)據(jù)持久化的“persistence”包,和代表消息通信的“messaging”包,在基礎(chǔ)設(shè)施代碼實現(xiàn)模型中是最常見的,因為它們分別對應(yīng)著領(lǐng)域?qū)ο笾械馁Y源庫和領(lǐng)域事件。
在DDD中,資源庫和領(lǐng)域事件的定義位于領(lǐng)域?qū)ο蟠a實現(xiàn)模型中,它們與具體的實現(xiàn)技術(shù)無關(guān)。而與具體實現(xiàn)技術(shù)相關(guān)的持久化和消息通信,則位于基礎(chǔ)設(shè)施代碼實現(xiàn)模型中。這里體現(xiàn)了領(lǐng)域?qū)ο笈c實現(xiàn)技術(shù)相互分離的設(shè)計原則。
上下文集成代碼實現(xiàn)模型
最后,我們來討論上下文集成代碼實現(xiàn)模型。需要注意的是,這個模型實現(xiàn)起來難度最大,因為涉及到多種系統(tǒng)集成技術(shù)體系。
針對這一代碼實現(xiàn)模型,我們首先需要明確它是面向多個限界上下文的,所以我們需要考慮數(shù)據(jù)的流向,也就是所謂的內(nèi)向(Inbound)數(shù)據(jù)和外向(Outbound)數(shù)據(jù)。
一方面,限界上下文,需要暴露訪問入口供其他上下文進行使用。站在當前上下文角度看,這是一個Inbound操作。而當某一個上下文向外部上下文發(fā)起請求時,這就是一個Outbound操作,如圖4所示。
圖4
在代碼實現(xiàn)模型的設(shè)計上,我們也將采用“inbound”和“outbound”來命名包結(jié)構(gòu)。那么這兩個包結(jié)構(gòu)下,應(yīng)該包含哪些技術(shù)組件呢?
我們先來討論“outbound”包結(jié)構(gòu),如圖5所示。 圖中,“rest”包中的REST API將外部請求,轉(zhuǎn)化為內(nèi)部的Command和Query對象,并交由應(yīng)用服務(wù)進行處理。在這個轉(zhuǎn)化過程中,通常需要引入專門的DTO(Data Transfer Object,數(shù)據(jù)傳輸對象)對象,和組裝器(Assembler)對象。
圖5
同時,“eventpublisher”包中的事件發(fā)布器(Event Publisher),則用來面向外部限界上下文發(fā)布領(lǐng)域事件。
接著,我們討論“inbound”包結(jié)構(gòu)。在一個限界上下文中,數(shù)據(jù)的Inbound操作主要有兩類,一類是防腐層(Anti-Corruption Layer,ACL),用來向遠程REST API發(fā)起請求并獲取結(jié)果。另一類是用來完成對領(lǐng)域事件進行響應(yīng)的事件處理器(Event Handler),如圖6所示。
圖6
基于上下文集成過程,兩個上下文中的“inbound”和“outbound”包結(jié)構(gòu)中所包含的技術(shù)組件,實際上是一一對應(yīng)的,如圖7所示。
可以看到,一個限界上下文“inbound”中的“acl”和“eventhandler”,分別對應(yīng)著另一個限界上下文“outbound”中的“rest”和“eventpublisher”。
圖7
至此,關(guān)于DDD中四大類代碼實現(xiàn)模型,已介紹完。在接下來的內(nèi)容中,我們將基于一個具體的應(yīng)用場景,通過案例分析,將這些代碼實現(xiàn)模型付諸于實踐?;谶@個案例,你可以將本文前面介紹的所有內(nèi)容,和日常開發(fā)過程聯(lián)系起來,進一步掌握將模型轉(zhuǎn)化為具體代碼的實現(xiàn)方法和技巧。
04 DDD代碼實現(xiàn)模型案例分析
在現(xiàn)實世界中,工單處理是一個非常常見的業(yè)務(wù)需求。而工單的發(fā)起,通常都是因為用戶需要對訂單進行咨詢或投訴。
在這個場景中,基于DDD的設(shè)計方法,我們可以分別拆分出工單(Ticket)、客服(Staff),以及訂單(Order)這三個限界上下文。在這三個上下文中,Ticket上下文,會分別與Staff和Order這兩個上下文進行集成,從而創(chuàng)建工單申請,如圖8所示。
請注意,圖中展示了Ticket上下文,所具備的兩種不同的上下文集成方式。
針對Staff上下文,Ticket上下文將使用REST API,完成對工單中客服數(shù)據(jù)的獲取。
而針對Order上下文,則使用了領(lǐng)域事件,即一旦Order的狀態(tài)發(fā)生變化,Order上下文會發(fā)送對應(yīng)的領(lǐng)域事件到Ticket上下文中。
圖8
Ticket上下文代碼實現(xiàn)模型示例
顯然,針對這一場景,Ticket上下文同時具備了Inbound和Outbound操作。因此,它的代碼實現(xiàn)模型是最完整的,如圖9所示。
圖9
上圖中,我們使用IDEA這款開發(fā)工具和Spring Boot這一特定的開發(fā)框架,構(gòu)建了Ticket限界上下文的代碼實現(xiàn)模型。我們可以很清晰地看到,DDD四種代碼實現(xiàn)模型的表現(xiàn)形式,就是五個頂層的代碼包結(jié)構(gòu)。其中,上下文集成代碼實現(xiàn)模型同時包含了“inbound”和“outbound”這兩個代碼包。
我們再對這些頂層代碼包結(jié)構(gòu)做展開,可以得到如圖10所示的子代碼包結(jié)構(gòu)。
圖10(上下滑動查看)
上圖所示的所有子代碼包結(jié)構(gòu),在前面的內(nèi)容中也都已經(jīng)給出了相應(yīng)的描述,這里便不再贅述。
Ticket上下文中,命令服務(wù)TicketCommandService完成了對Staff服務(wù)的上下文集成,這時候采用的是防腐層ACL組件,示例代碼如下所示。
可以看到,這里使用AclStaffService這個ACL組件,對Staff服務(wù)發(fā)起了遠程調(diào)用,然后把返回結(jié)果填充到命令對象,并創(chuàng)建Ticket聚合。最終,我們通過TicketRepository完成了對聚合對象的持久化操作。
圖11
上述AclStaffService,就完成了對Staff上下文所提供的REST API的調(diào)用,示例代碼如下所示。這里用到了Spring自帶的RestTemplate模板工具類,完成對遠程HTTP端點的訪問操作。
圖12
Staff上下文代碼實現(xiàn)模型示例
在Staff上下文,我們需要完成對上述REST API的構(gòu)建,它的代碼工程結(jié)構(gòu)如下圖所示。
可以看到,相較Ticket上下文,Staff上下文的代碼結(jié)構(gòu)比較簡單,因為該上下文只需要提供對外的“outbound”包,而基礎(chǔ)設(shè)施部分也只需要完成對領(lǐng)域?qū)ο蟮某志没僮骷纯伞?/p>
圖13
Order上下文代碼實現(xiàn)模型示例
最后,我們來到Order限界上下文,它的代碼實現(xiàn)模型是這樣的,可以一同看下。
圖14
我們知道Order上下文,提供了針對Order數(shù)據(jù)的領(lǐng)域事件發(fā)布機制,所以它的“outbound”包中包含了用于發(fā)布領(lǐng)域事件的“eventpublisher”子包,并提供了一個OrderEventPublisherService,如下所示。
圖15
這里通過Spring Cloud Stream,實現(xiàn)了領(lǐng)域事件的發(fā)布。而在Ticket上下文中,我們同樣可以基于Spring Cloud Stream,實現(xiàn)對該領(lǐng)域事件的監(jiān)聽和消費,示例代碼如下所示。
圖16
請注意,上述OrderUpdatedEventHandler,位于Ticket上下文“inbound”包的”eventhandler”子包中。
關(guān)于這些具體實現(xiàn)代碼的講解不是本文的重點,你可以參考筆者在Github上的案例代碼進行系統(tǒng)學(xué)習(xí):https://github.com/tianminzheng/customer-service。
05 總結(jié)和延伸思考
今天的分享到這里就結(jié)束了。本文內(nèi)容詳細回答了開發(fā)人員,在實現(xiàn)DDD應(yīng)用程序中所碰到的一個核心問題,即如何構(gòu)建DDD的代碼實現(xiàn)模型。之所以要討論這個話題,原因在于DDD中的很多概念都比較晦澀難懂,而業(yè)界也沒有為如何實現(xiàn)這些概念,提供統(tǒng)一的開發(fā)規(guī)范和標準。
而通過將DDD中的各種復(fù)雜概念與具體代碼實現(xiàn)模型進行映射,在幫我們更好地理解這些概念的同時,也能夠?qū)⑺鼈冎苯討?yīng)用到日常開發(fā)過程中。
通過本文內(nèi)容的介紹,開發(fā)人員可以結(jié)合自身的業(yè)務(wù)開發(fā)需求,設(shè)計一套完整的DDD代碼實現(xiàn)模型。這里也附上全文思維導(dǎo)圖,助你回顧、梳理思路等。
圖17 全文思維框架導(dǎo)圖-幫助你快速回顧、梳理、總結(jié)
最后,我覺得還是有必要強調(diào)一點
本文中給出的DDD代碼實現(xiàn)模型,也只是一個參考模型。而代碼實現(xiàn)模型的設(shè)計,也與具體所采用的技術(shù)體系有一定關(guān)聯(lián)。在本文所展示的案例中,我們使用了Spring Boot、Spring Cloud Stream等Spring家族中的開發(fā)框架,來開發(fā)DDD應(yīng)用程序。
而如果你使用Axon這種基于事件溯源模式的DDD開發(fā)框架,那么在代碼實現(xiàn)模型中,就需要引入用于事件分發(fā)和存儲的Gateway、EventStore等組件,而位于基礎(chǔ)設(shè)施中的傳統(tǒng)數(shù)據(jù)持久化組件,可能就不一定會被使用到。
當然,基于我們今天介紹的內(nèi)容,相信你并不難對這套DDD代碼實現(xiàn)模型進行擴展。DDD作為一種系統(tǒng)建模方法論,也存在一些諸如分層架構(gòu)、整潔架構(gòu)、六邊形架構(gòu)等多種架構(gòu)風格。
針對每種架構(gòu)風格,我們都需要設(shè)計對應(yīng)的代碼實現(xiàn)模型。
而基于本文中介紹的內(nèi)容,通過對DDD中各個核心概念與實現(xiàn)模型之間進行合理的映射,我在文中提供了一套設(shè)計代碼實現(xiàn)模型的系統(tǒng)方法,從而幫助你可以應(yīng)對不同架構(gòu)風格的實現(xiàn)要求。
這也是本文的核心價值所在。