Clean Architecture實(shí)踐:如何設(shè)計(jì)業(yè)務(wù)層(Golang實(shí)現(xiàn))

The Clean Architecture

Clean Architecture背后的核心想法其實(shí)很簡(jiǎn)單:“非核心”應(yīng)該依賴(lài)于“核心”撒汉。

怎么算“核心”己单?對(duì)于一個(gè)應(yīng)用來(lái)說(shuō)塞淹,最最核心的當(dāng)然就是業(yè)務(wù)數(shù)據(jù)(及其結(jié)構(gòu))和業(yè)務(wù)邏輯。這些信息應(yīng)該屬于一個(gè)模塊(也就是常說(shuō)的Service層)栖茉,在實(shí)現(xiàn)上篮绿,應(yīng)該自成一體,對(duì)數(shù)據(jù)持有化(用什么數(shù)據(jù)庫(kù)和什么driver)吕漂、用戶(hù)交互(是通過(guò)RESTful接口還是說(shuō)本身是某個(gè)桌面應(yīng)用的一部分)亲配、或者如何與其它服務(wù)模塊如何交互(消息系統(tǒng)?HTTP請(qǐng)求?)等技術(shù)有任何假定和依賴(lài)吼虎。

舉個(gè)例子犬钢,在設(shè)計(jì)業(yè)務(wù)數(shù)據(jù)結(jié)構(gòu)的時(shí)候,比如創(chuàng)建一個(gè)用戶(hù)的結(jié)構(gòu)思灰,常常需要有個(gè)Id字段玷犹。這時(shí)候,就算我們知道數(shù)據(jù)庫(kù)會(huì)選用MongoDB洒疚,而驅(qū)動(dòng)技術(shù)用的是Mongo Go Driver歹颓,也不能簡(jiǎn)單地把Id的類(lèi)型做如下定義:

import "go.mongodb.org/mongo-driver/bson/primitive"

type User struct {
    Id primitive.ObjectID
    ...
}

因?yàn)槿绱艘粊?lái),“核心”的業(yè)務(wù)數(shù)據(jù)就對(duì)“非核心”的技術(shù)選擇產(chǎn)生了依賴(lài)油湖。萬(wàn)一技術(shù)選擇發(fā)生變化巍扛,如從MongoDB改為MySQL,或者棄Mongo Go Driver而改選mgo乏德,業(yè)務(wù)層就要做相應(yīng)改動(dòng)撤奸。

對(duì)上面這個(gè)問(wèn)題,可以這么解決:

type UserId string

type User struct {
    Id UserId
    ...
}

相應(yīng)地鹅经,在Controller層應(yīng)該將用戶(hù)傳入的查詢(xún)鍵轉(zhuǎn)換為UserId類(lèi)型寂呛,在持久層也應(yīng)該將具體技術(shù)實(shí)現(xiàn)的類(lèi)型轉(zhuǎn)換為UserId類(lèi)型怎诫。且Controller層和持久層之間也不能做任何數(shù)據(jù)類(lèi)型上的假設(shè)瘾晃。

另外一個(gè)類(lèi)似的問(wèn)題就是如何處理異常。不好的做法是不區(qū)分業(yè)務(wù)異常和技術(shù)異常幻妓。所謂業(yè)務(wù)異常蹦误,舉個(gè)例子,當(dāng)通過(guò)Id來(lái)查詢(xún)用戶(hù)的時(shí)候肉津,也許與之對(duì)應(yīng)的用戶(hù)信息并不存在强胰,這個(gè)可能性就是一個(gè)業(yè)務(wù)異常。而由于和數(shù)據(jù)庫(kù)的網(wǎng)絡(luò)連接中斷妹沙,而發(fā)生的異常偶洋,就屬于技術(shù)異常。業(yè)務(wù)異常通常是要反饋給終端用戶(hù)的距糖,而技術(shù)異常通常是內(nèi)部異常玄窝,其具體信息一般不需要暴露給用戶(hù)。如果是開(kāi)發(fā)RESTful API悍引,那么對(duì)于技術(shù)異常恩脂,簡(jiǎn)單返回500內(nèi)部錯(cuò)誤就行,具體的錯(cuò)誤信息和原因趣斤,不要暴露給客戶(hù)端(要假定我們的API是給不知名的客戶(hù)端使用的)俩块,而是在日志中寫(xiě)明。

在開(kāi)發(fā)業(yè)務(wù)層時(shí),一般來(lái)說(shuō)應(yīng)該考慮服務(wù)中所提供的每個(gè)操作會(huì)產(chǎn)生哪些業(yè)務(wù)異常玉凯,并且為其定義專(zhuān)門(mén)的類(lèi)型:

type NoUserForSuchIdError struct {
    Id UserId
}

func (e *NoUserForSuchIdError) Error() string {
    return fmt.Sprintf("no matching user found for id %v", e.Id)
}

在業(yè)務(wù)層里势腮,應(yīng)該使用如上的業(yè)務(wù)錯(cuò)誤類(lèi)型,而不是使用其他層(如持久層)的技術(shù)帶來(lái)的錯(cuò)誤類(lèi)型:

func (s *UsersService) FindUserById(id UserId) (User, *NoUserForSuchIdError, error) {
    return s.Repo.FindUserById(id)
}

type UsersRepo interface {
    FindUserById(id UserId) (User, *NoUserForSuchIdError, error)
}

而在實(shí)現(xiàn)持續(xù)層時(shí)漫仆,我們要把具體技術(shù)里的相對(duì)應(yīng)錯(cuò)誤類(lèi)型轉(zhuǎn)換成我們自己定義的業(yè)務(wù)錯(cuò)誤:

func (r *MongoGoDriverUsersRepo) FindUserById(id UserId) (User, *NoUserForSuchIdError, error) {
    u := User{}
    e := r.usersCollection.FindOne(context.TODO(), bson.M{"id", id}).Decode(&u)
    if e == mongo.ErrNoDocuments {
        return User{}, &domain.NoUserForSuchIdError{Id: id}, nil
    }
    return u, nil, nil
}

而在Controller層嫉鲸,也應(yīng)該辨認(rèn)這種業(yè)務(wù)錯(cuò)誤,相應(yīng)地設(shè)置HTTP status及報(bào)文Body歹啼。

    ...
    u, noUser, e := c.service.FindUserById(id)
    if noUser != nil {
        w.WriteHeader(http.StatusNoeFound)
        _ = json.NewEncoder(w).Encode(newErrorResponse(noUser.Error()))
        return
    }
    if e != nil {
        w.WriteHeader(http.StatusInternalServerError)
        _ = json.NewEncoder(w).Encode(new500Response())
        return
    }
    _ = json.NewEncoder(w).Encode(newNormalResponse(u))

值得一提的是玄渗,從開(kāi)頭的圖可以看出,Controller層與持久層都是依賴(lài)于業(yè)務(wù)層的狸眼,所以上面的代碼里藤树,它們可以看到并使用定義在業(yè)務(wù)層的數(shù)據(jù)結(jié)構(gòu)和錯(cuò)誤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拓萌,一起剝皮案震驚了整個(gè)濱河市岁钓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌微王,老刑警劉巖屡限,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異炕倘,居然都是意外死亡钧大,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)罩旋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)啊央,“玉大人,你說(shuō)我怎么就攤上這事涨醋」霞ⅲ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵浴骂,是天一觀的道長(zhǎng)乓土。 經(jīng)常有香客問(wèn)我,道長(zhǎng)溯警,這世上最難降的妖魔是什么趣苏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮愧膀,結(jié)果婚禮上拦键,老公的妹妹穿的比我還像新娘。我一直安慰自己檩淋,他們只是感情好芬为,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布萄金。 她就那樣靜靜地躺著,像睡著了一般媚朦。 火紅的嫁衣襯著肌膚如雪氧敢。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天询张,我揣著相機(jī)與錄音孙乖,去河邊找鬼。 笑死份氧,一個(gè)胖子當(dāng)著我的面吹牛唯袄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蜗帜,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼恋拷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了厅缺?” 一聲冷哼從身側(cè)響起蔬顾,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎湘捎,沒(méi)想到半個(gè)月后诀豁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窥妇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年舷胜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秩伞。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逞带,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纱新,到底是詐尸還是另有隱情,我是刑警寧澤穆趴,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布脸爱,位于F島的核電站,受9級(jí)特大地震影響未妹,放射性物質(zhì)發(fā)生泄漏簿废。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一络它、第九天 我趴在偏房一處隱蔽的房頂上張望族檬。 院中可真熱鬧,春花似錦化戳、人聲如沸单料。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扫尖。三九已至白对,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間换怖,已是汗流浹背甩恼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沉颂,地道東北人条摸。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像铸屉,于是被迫代替她去往敵國(guó)和親屈溉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 原文: iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案 iOS應(yīng)用架構(gòu)談 開(kāi)篇 iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計(jì)方案 i...
    難卻卻閱讀 1,255評(píng)論 0 7
  • 轉(zhuǎn)自http://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de...
    嚴(yán)木木閱讀 1,520評(píng)論 1 8
  • 每天早上叫醒我的不是夢(mèng)想不是鬧鐘抬探,而且一只討厭蚊子飛過(guò)的嗡嗡聲子巾,不知道晚上它是躲在了哪里,還是說(shuō)每天早上不到6點(diǎn)是...
    cc1111閱讀 180評(píng)論 0 0
  • 走在繁華大道上 雨輕輕的撫摸著我的臉 我揚(yáng)起頭讓雨沖刷著我的眼淚 我想笑小压,可是眼淚不聽(tīng)話(huà)的往下掉 也許我活在世界上...
    江瀟然閱讀 349評(píng)論 3 1
  • 我心中裝著一所學(xué)校如圣地线梗,甚至甘愿忽略廣受詬病的起居條件,甘愿放下親戚友人心中知名度極高的學(xué)校怠益,轉(zhuǎn)身投入無(wú)名的深海...
    秦望舒閱讀 393評(píng)論 0 1