摘要:而只需要調(diào)用對(duì)象完成業(yè)務(wù)邏輯即可。領(lǐng)域建模的好處面向?qū)ο蠓庋b的相關(guān)操作都封裝在上,提高了內(nèi)聚性和可重用性。對(duì)于這樣簡(jiǎn)單的場(chǎng)景,這個(gè)建模就差不多了。這就是模型的統(tǒng)一。其功能性及說明性急速增強(qiáng),而復(fù)雜性卻隨之消失。
你還在用面向?qū)ο蟮恼Z言寫面向過程的代碼嗎?你是否正在被復(fù)雜的業(yè)務(wù)邏輯折磨?是否有時(shí)覺得應(yīng)用開發(fā)沒意思、沒挑戰(zhàn)、技術(shù)含量低?其實(shí),應(yīng)用開發(fā)一點(diǎn)都不簡(jiǎn)單,也不無聊,業(yè)務(wù)的變化比底層基礎(chǔ)實(shí)施的變化要多得多,封裝這些變化需要很好的業(yè)務(wù)理解力,抽象能力和建模能力。
今天我們邀請(qǐng)阿里高級(jí)技術(shù)專家張建飛,一起來聊聊為什么需要領(lǐng)域建模,什么是好的模型,又該如何搭建。
為什么要領(lǐng)域建模?軟件的世界里沒有銀彈,是用事務(wù)腳本還是領(lǐng)域模型沒有對(duì)錯(cuò)之分,關(guān)鍵看是否合適。實(shí)際上,CQRS就是對(duì)事務(wù)腳本和領(lǐng)域模型兩種模式的綜合,因?yàn)閷?duì)于Query和報(bào)表的場(chǎng)景,使用領(lǐng)域模型往往會(huì)把簡(jiǎn)單的事情弄復(fù)雜,此時(shí)完全可以用奧卡姆剃刀把領(lǐng)域?qū)犹甑?,直接訪問Infrastructure。我個(gè)人也是堅(jiān)決反對(duì)過度設(shè)計(jì)的,因此對(duì)于簡(jiǎn)單業(yè)務(wù)場(chǎng)景,我強(qiáng)力建議還是使用事務(wù)腳本,其優(yōu)點(diǎn)是簡(jiǎn)單、直觀、易上手。但對(duì)于復(fù)雜的業(yè)務(wù)場(chǎng)景,你再這么玩就不行了,因?yàn)橐坏I(yè)務(wù)變得復(fù)雜,事務(wù)腳本就很難應(yīng)對(duì),容易造成代碼的“一鍋粥”,系統(tǒng)的腐化速度和復(fù)雜性呈指數(shù)級(jí)上升。
目前比較有效的治理辦法就是領(lǐng)域建模,因?yàn)轭I(lǐng)域模型是面向?qū)ο蟮模诜庋b業(yè)務(wù)邏輯的同時(shí),提升了對(duì)象的內(nèi)聚性和重用性,因?yàn)槭褂昧送ㄓ谜Z言(Ubiquitous Language),使得隱藏的業(yè)務(wù)邏輯得到顯性化表達(dá),使得復(fù)雜性治理成為可能。talk is cheap,直接上一個(gè)銀行轉(zhuǎn)賬的例子,對(duì)事務(wù)腳本和領(lǐng)域模型進(jìn)行比較,孰優(yōu)孰劣一目了然。
銀行轉(zhuǎn)賬事務(wù)腳本實(shí)現(xiàn)在事務(wù)腳本的實(shí)現(xiàn)中,關(guān)于在兩個(gè)賬號(hào)之間轉(zhuǎn)賬的領(lǐng)域業(yè)務(wù)邏輯都被寫在了MoneyTransferService的實(shí)現(xiàn)里面了,而Account僅僅是getters和setters的數(shù)據(jù)結(jié)構(gòu),也就是我們說的貧血模式。
上面的代碼大家看起來應(yīng)該比較眼熟,因?yàn)槟壳按蟛糠窒到y(tǒng)都是這么寫的。需求評(píng)審?fù)?,工程師畫幾張UML圖完成設(shè)計(jì),就開始向上面這樣懟業(yè)務(wù)代碼了,這樣寫基本不用太費(fèi)腦,完全是面向過程的代碼風(fēng)格。有些同學(xué)可能會(huì)說,我這樣寫也可以實(shí)現(xiàn)系統(tǒng)功能啊,還是那句話“just because you can, doesn"t mean you should”。說句不好聽的,正是有這么多“沒有追求”、“不求上進(jìn)”的碼農(nóng)才造成了應(yīng)用系統(tǒng)的混亂、敗壞了應(yīng)用開發(fā)的名聲。這也是為什么很多應(yīng)用開發(fā)工程師覺得工作沒意思,技術(shù)含量低,覺得整天就是寫if-else的業(yè)務(wù)邏輯代碼,系統(tǒng)又爛,工作繁瑣、無聊、沒有成長(zhǎng)、沒有成就感,所以轉(zhuǎn)向去做中間件啊,去寫JDK啊,覺得那個(gè)NB。
實(shí)際上,應(yīng)用開發(fā)一點(diǎn)都不簡(jiǎn)單也不無聊,業(yè)務(wù)的變化比底層Infrastructure的變化要多得多,解決的難度也絲毫不比寫底層代碼容易,只是很多人選擇了用無聊的方式去做。其實(shí)我們是有辦法做的更優(yōu)雅的,這種優(yōu)雅的方式就是領(lǐng)域建模,唯有掌握了這種優(yōu)雅你才能實(shí)現(xiàn)從工程師向應(yīng)用架構(gòu)的轉(zhuǎn)型。同樣的業(yè)務(wù)邏輯,接下來就讓我們看一下用DDD是怎么做的。
銀行轉(zhuǎn)賬領(lǐng)域模型實(shí)現(xiàn)如果用DDD的方式實(shí)現(xiàn),Account實(shí)體除了賬號(hào)屬性之外,還包含了行為和業(yè)務(wù)邏輯,比如debit( )和credit( )方法。
而且透支策略O(shè)verdraftPolicy也不僅僅是一個(gè)Enum了,而是被抽象成包含了業(yè)務(wù)規(guī)則并采用了策略模式的對(duì)象。
而Domain Service只需要調(diào)用Domain Entity對(duì)象完成業(yè)務(wù)邏輯即可。
通過上面的DDD重構(gòu)后,原來在事務(wù)腳本中的邏輯,被分散到Domain Service,Domain Entity和OverdraftPolicy三個(gè)滿足SOLID的對(duì)象中,在繼續(xù)閱讀之前,我建議可以自己先體會(huì)一下DDD的好處。
領(lǐng)域建模的好處面向?qū)ο?/strong>
封裝:Account的相關(guān)操作都封裝在Account Entity上,提高了內(nèi)聚性和可重用性。
多態(tài):采用策略模式的OverdraftPolicy(多態(tài)的典型應(yīng)用)提高了代碼的可擴(kuò)展性。
業(yè)務(wù)語義顯性化
通用語言:“一個(gè)團(tuán)隊(duì),一種語言”,將模型作為語言的支柱。確保團(tuán)隊(duì)在內(nèi)部的所有交流中,代碼中,畫圖,寫東西,特別是講話的時(shí)候都要使用這種語言。例如賬號(hào),轉(zhuǎn)賬,透支策略,這些都是非常重要的領(lǐng)域概念,如果這些命名都和我們?nèi)粘S懻撘约癙RD中的描述保持一致,將會(huì)極大提升代碼的可讀性,減少認(rèn)知成本。
顯性化:就是將隱式的業(yè)務(wù)邏輯從一推if-else里面抽取出來,用通用語言去命名、去寫代碼、去擴(kuò)展,讓其變成顯示概念,比如“透支策略”這個(gè)重要的業(yè)務(wù)概念,按照事務(wù)腳本的寫法,其含義完全淹沒在代碼邏輯中沒有突顯出來,看代碼的人自然也是一臉懵逼,而領(lǐng)域模型里面將其用策略模式抽象出來,不僅提高了代碼的可讀性,可擴(kuò)展性也好了很多。
如何進(jìn)行領(lǐng)域建模?建模方法
領(lǐng)域建模這個(gè)話題太大,關(guān)于此的長(zhǎng)篇大論和書籍也很多,比如什么通過語法和句法深入分析法,在我看來這些方法論有些繁瑣了。好的模型應(yīng)該是建立在對(duì)業(yè)務(wù)深入理解的基礎(chǔ)上,如果業(yè)務(wù)理解不到位,你再怎么分析句子也不可能產(chǎn)出好的模型。就我自己的經(jīng)驗(yàn)而言,建模也是一個(gè)不斷迭代的過程,所以一開始可以簡(jiǎn)單點(diǎn)來,就采用兩步建模法抓住一些核心概念,然后寫一些代碼驗(yàn)證一下run一下,看看順不順,如果很順滑,說明沒毛病,否則就要看看是不是需要調(diào)整一下模型,隨著項(xiàng)目的進(jìn)行和對(duì)業(yè)務(wù)理解的不斷深入,這種迭代將持續(xù)進(jìn)行。
那什么是兩步建模法呢?也就是只需要兩個(gè)步驟就能建模了,首先從User Story找名詞和動(dòng)詞,然后用UML類圖畫出領(lǐng)域模型。是不是很簡(jiǎn)約?簡(jiǎn)約并不意味著簡(jiǎn)單,對(duì)于業(yè)務(wù)架構(gòu)師和系統(tǒng)分析師來說,見功力的地方往往就在于此。
舉個(gè)栗子,比如讓你設(shè)計(jì)一個(gè)中介系統(tǒng),一個(gè)典型的User Story可能是“小明去找工作,中介說你留個(gè)電話,有工作機(jī)會(huì)我會(huì)通知你”,這里面的關(guān)鍵名詞很可能就是我們需要的領(lǐng)域?qū)ο?,小明是求職者,電話是求職者的屬性,中介包含了中介公司,中介員工兩個(gè)關(guān)鍵對(duì)象;工作機(jī)會(huì)肯定也是關(guān)鍵領(lǐng)域?qū)ο?;通知這個(gè)動(dòng)詞暗示我們這里用觀察者模式會(huì)比較合適。然后再梳理一下領(lǐng)域?qū)ο笾g的關(guān)系,一個(gè)求職者可以應(yīng)聘多個(gè)工作機(jī)會(huì),一個(gè)工作機(jī)會(huì)也可以被多個(gè)求職者應(yīng)聘,M2M的關(guān)系,中介公司可以包含多個(gè)員工,O2M的關(guān)系。對(duì)于這樣簡(jiǎn)單的場(chǎng)景,這個(gè)建模就差不多了。
當(dāng)然我們的業(yè)務(wù)場(chǎng)景往往比這個(gè)要復(fù)雜,而且不是所有的名詞都是領(lǐng)域?qū)ο笠部赡苁菍傩?,也不是所有的?dòng)詞都是方法也可能是領(lǐng)域?qū)ο?,所以要具體問題具體對(duì)待,這個(gè)對(duì)待的過程需要我們有很好的業(yè)務(wù)理解力,抽象能力以及建模的經(jīng)驗(yàn)(知道為什么公司的job model里那么強(qiáng)調(diào)技術(shù)人員的業(yè)務(wù)理解力和抽象能力了吧),比如通常情況下,價(jià)格和庫存只是訂單和商品的一個(gè)屬性,但是在阿里系電商業(yè)務(wù)場(chǎng)景下,價(jià)格計(jì)算和庫存扣減的復(fù)雜程度可以讓你懷疑人生,因此作為電商中臺(tái),把價(jià)格和庫存多帶帶當(dāng)成一個(gè)域(Domain)去對(duì)待是很必要的。
另外,建模不是一個(gè)一次性的工作,往往隨著業(yè)務(wù)的變化以及我們對(duì)業(yè)務(wù)的理解越來越深入才能看清系統(tǒng)的全貌,所以迭代重構(gòu)是免不了的,也就是要Agile Modelling。
模型統(tǒng)一和模型演化建模的過程很像盲人摸象,不同背景人用不同的視角看同一個(gè)東西,其理解也是不一樣的。比如兩個(gè)盲人都摸到大象鼻子,一個(gè)人認(rèn)為是像蛇(活的能動(dòng)),而另一個(gè)人認(rèn)為像消防水管(可以噴水),那么他們將很難集成。雙方都無法接受對(duì)方的模型,因?yàn)槟遣环献约旱捏w驗(yàn)。事實(shí)上,他們需要一個(gè)新的抽象,這個(gè)抽象需要把蛇的“活著的特性”與消防水管的“噴水功能”合并到一起,而這個(gè)抽象還應(yīng)該排除先前兩個(gè)模型中一些不確切的含義和屬性,比如毒牙,或者卷起來放到消防車上去的行為。這就是模型的統(tǒng)一。
世界上唯一不變的就是變化,模型和代碼一樣也需要不斷的重構(gòu)和精化,每一次的精化之后,開發(fā)人員應(yīng)該對(duì)領(lǐng)域知識(shí)有了更加清晰的認(rèn)識(shí)。這使得理解上的突破成為可能,之后,一系列快速的改變得到了更符合用戶需要并更加切合實(shí)際的模型。其功能性及說明性急速增強(qiáng),而復(fù)雜性卻隨之消失。
這種突破需要我們對(duì)業(yè)務(wù)有更加深刻的領(lǐng)悟和思考,然后再加上重構(gòu)的勇氣和能力,勇氣是項(xiàng)目工期很緊你敢不敢重構(gòu),能力是你有沒有完備的CI保證你的重構(gòu)不破壞現(xiàn)有的業(yè)務(wù)邏輯。還是以開篇的轉(zhuǎn)賬來舉個(gè)例子,假如轉(zhuǎn)賬業(yè)務(wù)開始變的復(fù)雜,要支持現(xiàn)金,信用卡,支付寶,比特幣等多種通道,且沒種通道的約束不一樣,還要支持一對(duì)多的轉(zhuǎn)賬。那么你還是用一個(gè)transfer(fromAccount, toAccount)就不合適了,可能需要抽象出一個(gè)專門的領(lǐng)域?qū)ο骉ransaction,這樣才能更好的表達(dá)業(yè)務(wù),其演化過程如下:
什么是領(lǐng)域服務(wù)?有些領(lǐng)域中的動(dòng)作,它們是一些動(dòng)詞,看上去卻不屬于任何對(duì)象。它們代表了領(lǐng)域中的一個(gè)重要的行為,所以不能忽略它們或者簡(jiǎn)單地把它們合并到某個(gè)實(shí)體或者值對(duì)象中。當(dāng)這樣的行為從領(lǐng)域中被識(shí)別出來時(shí),最佳實(shí)踐是將它聲明成一個(gè)服務(wù)。這樣的對(duì)象不再擁有內(nèi)置的狀態(tài)。它的作用僅僅是為領(lǐng)域提供相應(yīng)的功能。Service往往是以一個(gè)活動(dòng)來命名,而不是Entity來命名。例如開篇轉(zhuǎn)賬的例子,轉(zhuǎn)賬(transfer)這個(gè)行為是一個(gè)非常重要的領(lǐng)域概念,但是它是發(fā)生在兩個(gè)賬號(hào)之間的,歸屬于賬號(hào)Entity并不合適,因?yàn)橐粋€(gè)賬號(hào)Entity沒有必要去關(guān)聯(lián)他需要轉(zhuǎn)賬的賬號(hào)Entity,這種情況下,使用MoneyTransferDomainService就比較合適了。
識(shí)別領(lǐng)域服務(wù),主要看它是否滿足以下三個(gè)特征:
服務(wù)執(zhí)行的操作代表了一個(gè)領(lǐng)域概念,這個(gè)領(lǐng)域概念無法自然地隸屬于一個(gè)實(shí)體或者值對(duì)象。
被執(zhí)行的操作涉及到領(lǐng)域中的其他的對(duì)象。
操作是無狀態(tài)的。
應(yīng)用服務(wù)和領(lǐng)域服務(wù)如何劃分?在領(lǐng)域建模中,我們一般將系統(tǒng)劃分三個(gè)大的層次,即應(yīng)用層(Application Layer),領(lǐng)域?qū)樱―omain Layer)和基礎(chǔ)實(shí)施層(Infrastructure Layer),關(guān)于這三個(gè)層次的詳細(xì)內(nèi)容可以參考我的另一篇SOFA框架的分層設(shè)計(jì)。可以看到在App層和Domain層都有服務(wù)(Service),這兩個(gè)Service如何劃分呢,什么樣的功能應(yīng)該放在應(yīng)用層,什么樣的功能應(yīng)該放在領(lǐng)域?qū)幽兀?/p>
決定一個(gè)服務(wù)(Service)應(yīng)該歸屬于哪一層是很困難的。如果所執(zhí)行的操作概念上屬于應(yīng)用層,那么服務(wù)就應(yīng)該放到這個(gè)層。如果操作是關(guān)于領(lǐng)域?qū)ο蟮?,而且確實(shí)是與領(lǐng)域有關(guān)的、為領(lǐng)域的需要服務(wù),那么它就應(yīng)該屬于領(lǐng)域?qū)印?偟膩碚f,涉及到重要領(lǐng)域概念的行為應(yīng)該放在Domain層,而其它非領(lǐng)域邏輯的技術(shù)代碼放在App層,例如參數(shù)的解析,上下文的組裝,調(diào)用領(lǐng)域服務(wù),消息發(fā)送等。還是銀行轉(zhuǎn)賬的case為例,下圖給出了劃分的建議:
業(yè)務(wù)可視化和可配置化好的領(lǐng)域建??梢越档蛻?yīng)用的復(fù)雜性,而可視化和可配置化主要是幫助大家(主要是非技術(shù)人員,比如產(chǎn)品,業(yè)務(wù)和客戶)直觀地了解系統(tǒng)和配置系統(tǒng),提供了一種“code free”的解決方案,也是SaaS軟件的主要賣點(diǎn)。要注意的是可視化和可配置化難免會(huì)給系統(tǒng)增加額外的復(fù)雜度,必須慎之又慎,最好是能使可視化和配置化的邏輯與業(yè)務(wù)邏輯盡量少的耦合,否則破壞了原有的架構(gòu),把事情搞的更復(fù)雜就得不償失了。
在可擴(kuò)展設(shè)計(jì)中,我已經(jīng)介紹了我們SOFA架構(gòu)是如何通過擴(kuò)展點(diǎn)的設(shè)計(jì)來支撐不同業(yè)務(wù)差異化的需求的,那么可否更進(jìn)一步,我們將領(lǐng)域的行為(也叫能力)和擴(kuò)展點(diǎn)用可視化的方式呈現(xiàn)出來,并對(duì)于一些不需要編碼實(shí)現(xiàn)的擴(kuò)展點(diǎn)用配置的方式去完成呢。當(dāng)然是可以的,比如還是開篇轉(zhuǎn)賬的例子,對(duì)于透支策略O(shè)verdraftPolicy這個(gè)業(yè)務(wù)擴(kuò)展點(diǎn),新來一個(gè)業(yè)務(wù)說透支額度不能超過1000,我們可以完全結(jié)合規(guī)則引擎進(jìn)行配置化完成,而不需要編碼。
所以我能想到的一種還比較優(yōu)雅的方式,是通過Annotation注解的方式對(duì)領(lǐng)域能力和擴(kuò)展點(diǎn)進(jìn)行標(biāo)注,然后在系統(tǒng)bootstrap階段,通過代碼掃描的方式,將這些能力點(diǎn)和擴(kuò)展點(diǎn)收集起來上傳到中心服務(wù)器,然后再通過GUI的方式呈現(xiàn)出來,從而做到業(yè)務(wù)的可視化和可配置化。大概的示意圖如下:
有同學(xué)可能會(huì)問流程要不要可視化,這里要分清楚兩個(gè)概念,業(yè)務(wù)邏輯流和工作流,很多同學(xué)混淆了這兩個(gè)概念。業(yè)務(wù)邏輯流是響應(yīng)一次用戶請(qǐng)求的業(yè)務(wù)處理過程,其本身就是業(yè)務(wù)邏輯,對(duì)其編排和可視化的意義并不是很大,無外乎只是把代碼邏輯可視化了,在我們的SOFA框架中,是通過擴(kuò)展點(diǎn)和策略模式來處理業(yè)務(wù)的分支情況,而我看到我們阿里很多的內(nèi)部系統(tǒng)將這種響應(yīng)一次用戶請(qǐng)求的業(yè)務(wù)邏輯用很重的工作流引擎來做,美其名曰流程可編排,實(shí)質(zhì)上往往是把簡(jiǎn)單的事情復(fù)雜化了。而工作流是指完成一項(xiàng)任務(wù)所需要不同節(jié)點(diǎn)的連接,節(jié)點(diǎn)主要分為自動(dòng)節(jié)點(diǎn)和人工節(jié)點(diǎn),其中每個(gè)人工節(jié)點(diǎn)都需要用戶的參與,也就是響應(yīng)一次用戶的請(qǐng)求,比如審批流程中的經(jīng)理審批節(jié)點(diǎn),CRM銷售過程的業(yè)務(wù)員的處理節(jié)點(diǎn)等等。
此時(shí)可以考慮使用工作流引擎,特別是當(dāng)你的系統(tǒng)需要讓用戶自定義流程的時(shí)候,那就不得不使用可視化和可配置的工作流引擎了,除此之外,最好不要自找麻煩。當(dāng)然也不排除有用的特別合適的案例,只是我還沒看見,如果有看見的同學(xué)也請(qǐng)告訴我一聲,一起交流學(xué)習(xí)。
本文作者:張建飛
詳情請(qǐng)閱讀原文
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/11842.html
摘要:而只需要調(diào)用對(duì)象完成業(yè)務(wù)邏輯即可。領(lǐng)域建模的好處面向?qū)ο蠓庋b的相關(guān)操作都封裝在上,提高了內(nèi)聚性和可重用性。對(duì)于這樣簡(jiǎn)單的場(chǎng)景,這個(gè)建模就差不多了。這就是模型的統(tǒng)一。其功能性及說明性急速增強(qiáng),而復(fù)雜性卻隨之消失。 showImg(https://segmentfault.com/img/bV62vN?w=677&h=393); 云棲君導(dǎo)讀:你還在用面向?qū)ο蟮恼Z言寫面向過程的代碼嗎?你是否...
摘要:以下將分別從五大技術(shù)專場(chǎng)維度介紹本屆峰會(huì)的部分聯(lián)席主席與精選案例。天時(shí)間集中分享年最值得學(xué)習(xí)的個(gè)研發(fā)案例實(shí)踐。 從萬維網(wǎng)到物聯(lián)網(wǎng),從信息傳播到人工智能,20年間軟件研發(fā)行業(yè)趨勢(shì)發(fā)生了翻天覆地的變化。大數(shù)據(jù)、云計(jì)算、AI等新興領(lǐng)域逐漸改變我們的生活方式,Devops、容器、深度學(xué)習(xí)、敏捷等技術(shù)方式和工作理念對(duì)軟件研發(fā)從業(yè)者提出更高要求。 由麥思博(msup)有限公司主辦的第六屆全球軟件案...
摘要:在系統(tǒng)正常運(yùn)行時(shí),可以變更爬蟲的配置,一旦實(shí)時(shí)監(jiān)控爬蟲出現(xiàn)異常,可實(shí)時(shí)修正配置進(jìn)行干預(yù)。從數(shù)據(jù)庫中實(shí)時(shí)讀取配置信息,響應(yīng)業(yè)務(wù)層的配置請(qǐng)求。處理系統(tǒng)通過服務(wù)層,每次去取配置信息可能維護(hù)人員在實(shí)時(shí)修正及待抓取的列表進(jìn)行處理。 showImg(https://segmentfault.com/img/bVLa4V?w=960&h=540); 一 ?緣起 在我工作的多家公司,有眾多的領(lǐng)域,如房...
摘要:由于文章內(nèi)容較長(zhǎng),所以我把它分成兩篇小文章,在第一篇優(yōu)秀架構(gòu)師必須掌握的架構(gòu)思維中,我會(huì)先介紹抽象分層分治和演化這四種應(yīng)對(duì)復(fù)雜性的基本思維。另外,上面的算法是兩路歸并,也可以采用多路歸并,甚至是采用堆排序進(jìn)行優(yōu)化,但是總體分治思路沒有變化。 showImg(https://segmentfault.com/img/bVbeYpP?w=642&h=400); 介紹 架構(gòu)的本質(zhì)是管理復(fù)雜性...
閱讀 5774·2021-11-24 10:25
閱讀 2708·2021-11-16 11:44
閱讀 3861·2021-10-11 11:09
閱讀 3180·2021-09-02 15:41
閱讀 3262·2019-08-30 14:14
閱讀 2292·2019-08-29 14:10
閱讀 2357·2019-08-29 11:03
閱讀 1134·2019-08-26 13:47