使用Golang的簡潔架構(gòu)

原文:https://medium.com/@eminetto/clean-architecture-using-golang-b63587aa5e3f

什么是簡潔架構(gòu)?

著名作者Robert “Uncle Bob” Martin 在他的著作“簡潔架構(gòu):軟件結(jié)構(gòu)和設(shè)計的工匠指南*”中提出了一個架構(gòu)吊趾,其中包含可測試性和框架獨立性蹦浦,數(shù)據(jù)庫和接口等重要方面行施。

Clean Architecture中的約束條件是:

  • 獨立于框架详幽。該體系結(jié)構(gòu)不依賴于某些功能強(qiáng)大的軟件庫的存在露懒。這使您可以使用這樣的框架作為工具寡具,而不必將系統(tǒng)塞進(jìn)有限的約束中牍汹。
  • 可測試铐维。業(yè)務(wù)規(guī)則可以在沒有UI,數(shù)據(jù)庫慎菲,Web服務(wù)器或任何其他外部元素的情況下進(jìn)行測試嫁蛇。
  • 獨立于用戶界面。用戶界面可以輕松更改钧嘶,而無需更改系統(tǒng)的其余部分棠众。例如,Web UI可以替換為控制臺UI有决,而無需更改業(yè)務(wù)規(guī)則闸拿。
  • 獨立于數(shù)據(jù)庫。您可以替換Oracle或SQL Server书幕,而使用Mongo新荤,BigTable,CouchDB或其他台汇。您的業(yè)??務(wù)規(guī)則不綁定到數(shù)據(jù)庫苛骨。
  • 獨立于任何外部機(jī)構(gòu)篱瞎。事實上,你的業(yè)??務(wù)規(guī)則根本就不了解外面的世界痒芝。

更多https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

所以俐筋,基于這個約束,每個層必須是獨立的和可測試的严衬。

根據(jù)Uncle Bob的體系結(jié)構(gòu)設(shè)計澄者,我們可以將代碼分為四層:

  • Entities實體:封裝企業(yè)范圍的業(yè)務(wù)規(guī)則。Go中的實體是一組數(shù)據(jù)結(jié)構(gòu)和功能请琳。
  • Use Cases用例:該層中的軟件包含特定于應(yīng)用程序的業(yè)務(wù)規(guī)則粱挡。它封裝并實現(xiàn)了系統(tǒng)的所有用例。
  • Controller控制器:該層中的軟件是一組適配器俄精,可將數(shù)據(jù)從用例和實體最方便的格式轉(zhuǎn)換為最適合某些外部機(jī)構(gòu)(如數(shù)據(jù)庫或Web)的格式
  • Framework & Driver 框架和驅(qū)動程序:該層通常由框架和工具組成询筏,如數(shù)據(jù)庫,Web框架等竖慧。

Golang的簡潔架構(gòu)

讓我們以包用戶為例:

ls -ln pkg/user
-rw-r?—?r?—?1 501 20 5078 Feb 16 09:58 entity.go
-rw-r?—?r?—?1 501 20 3747 Feb 16 10:03 mongodb.go
-rw-r?—?r?—?1 501 20 509 Feb 16 09:59 repository.go
-rw-r?—?r?—?1 501 20 2403 Feb 16 10:30 service.go

在文件entity.go中嫌套,我們有我們的實體:

//User data
type User struct {
    ID                 entity.ID    `json:"id" bson:"_id,omitempty"`
    Picture            string       `json:"picture" bson:"picture,omitempty"`
    Email              string       `json:"email" bson:"email"`
    Password           string       `json:"password" bson:"password,omitempty"`
    Type               Type         `json:"type" bson:"type"`
    Company            []*Company   `json:"company" bson:"company,omitempty"`
    CreatedAt          time.Time    `json:"created_at" bson:"created_at"`
    ValidatedAt        time.Time    `json:"validated_at" bson:"validated_at,omitempty"`
}

在文件repository.go中,我們有定義存儲庫的接口测蘑,其中實體將被存儲灌危。在這種情況下,存儲庫意味著Bob的Uncle Bob體系結(jié)構(gòu)中的Framework&Driver層碳胳。他的內(nèi)容是:

package user

import "github.com/thecodenation/stamp/pkg/entity"

//Repository repository interface
type Repository interface {
    Find(id entity.ID) (*User, error)
    FindByEmail(email string) (*User, error)
    FindByChangePasswordHash(hash string) (*User, error)
    FindByValidationHash(hash string) (*User, error)
    FindAll() ([]*User, error)
    Update(user *User) error
    Store(user *User) (entity.ID, error)
    AddCompany(id entity.ID, company *Company) error
    AddInvite(userID entity.ID, companyID entity.ID) error
}

這個接口可以在任何類型的存儲層中實現(xiàn),比如MongoDB沫勿,MySQL等等挨约。在我們的例子中,我們使用MongoDB來實現(xiàn)产雹,如mongodb.go中所示:

package user

import (
    "errors"
    "os"
    "github.com/juju/mgosession"
    "github.com/thecodenation/stamp/pkg/entity"
    mgo "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
)

type repo struct {
    pool *mgosession.Pool
}

//NewMongoRepository create new repository
func NewMongoRepository(p *mgosession.Pool) Repository {
    return &repo{
        pool: p,
    }
}

func (r *repo) Find(id entity.ID) (*User, error) {
    result := User{}
    session := r.pool.Session(nil)
    coll := session.DB(os.Getenv("MONGODB_DATABASE")).C("user")
    err := coll.Find(bson.M{"_id": id}).One(&result)
    if err != nil {
        return nil, err
    }
    return &result, nil
}

func (r *repo) FindByEmail(email string) (*User, error) {
}

func (r *repo) FindByChangePasswordHash(hash string) (*User, error) {
}

func (r *repo) FindAll() ([]*User, error) {
}

func (r *repo) Update(user *User) error {
}

func (r *repo) Store(user *User) (entity.ID, error) {
}

func (r *repo) AddCompany(id entity.ID, company *Company) error {
}

func (r *repo) AddInvite(userID entity.ID, companyID entity.ID) error {
}

func (r *repo) FindByValidationHash(hash string) (*User, error) {
}

文件service.go代表由Uncle Bob定義的用例層诫惭。在文件中我們有接口Service和他的實現(xiàn)。該service接口是:

//Service service interface
type Service interface {
    Register(user *User) (entity.ID, error)
    ForgotPassword(user *User) error
    ChangePassword(user *User, password string) error
    Validate(user *User) error
    Auth(user *User, password string) error
    IsValid(user *User) bool
    GetRepo() Repository
}

最后一層蔓挖,我們架構(gòu)中的Controller實現(xiàn)在api的內(nèi)容中:

cd api ; tree
.
|____handler
| |____company.go
| |____user.go
| |____address.go
| |____skill.go
| |____invite.go
| |____position.go
|____rice-box.go
|____main.go

在下面的代碼中夕土,從api / main.go中,我們可以看到如何使用這些服務(wù):

session, err := mgo.Dial(os.Getenv("MONGODB_HOST"))
if err != nil {
    elog.Error(err)
}
mPool := mgosession.NewPool(nil, session, 1)
queueService, err := queue.NewAWSService()
if err != nil {
        elog.Error(err)
}
userRepo := user.NewMongoRepository(mPool)
userService := user.NewService(userRepo, queueService)

現(xiàn)在我們可以輕松的為我們的軟件包創(chuàng)建測試瘟判,例如:

package user

import (
    "testing"
    "time"

    "github.com/thecodenation/stamp/pkg/entity"
    "github.com/thecodenation/stamp/pkg/queue"
)

func TestIsValidUser(t *testing.T) {
    u := User{
        ID:        entity.NewID(),
        FirstName: "Bill",
        LastName:  "Gates",
    }
    userRepo := NewInmemRepository()
    queueService, _ := queue.NewInmemService()
    userService := NewService(userRepo, queueService)

    if userService.IsValid(&u) == true {
        t.Errorf("got %v want %v",
            true, false)
    }

    u.ValidatedAt = time.Now()
    if userService.IsValid(&u) == false {
        t.Errorf("got %v want %v",
            false, true)
    }
}

使用Clean Architecture怨绣,我們可以將數(shù)據(jù)庫從MongoDB更改為Neo4j,而不用改寫其他應(yīng)用層拷获。我們可以在不損失質(zhì)量和開發(fā)效率的情況下進(jìn)行迭代篮撑。

References

https://hackernoon.com/golang-clean-archithecture-efd6d7c43047

https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市匆瓜,隨后出現(xiàn)的幾起案子赢笨,更是在濱河造成了極大的恐慌未蝌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茧妒,死亡現(xiàn)場離奇詭異萧吠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)桐筏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門怎憋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人九昧,你說我怎么就攤上這事绊袋。” “怎么了铸鹰?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵癌别,是天一觀的道長。 經(jīng)常有香客問我蹋笼,道長展姐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任剖毯,我火速辦了婚禮圾笨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逊谋。我一直安慰自己擂达,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布胶滋。 她就那樣靜靜地躺著板鬓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪究恤。 梳的紋絲不亂的頭發(fā)上俭令,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音部宿,去河邊找鬼抄腔。 笑死,一個胖子當(dāng)著我的面吹牛理张,可吹牛的內(nèi)容都是我干的赫蛇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼涯穷,長吁一口氣:“原來是場噩夢啊……” “哼棍掐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拷况,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤作煌,失蹤者是張志新(化名)和其女友劉穎掘殴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粟誓,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡奏寨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鹰服。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片病瞳。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖悲酷,靈堂內(nèi)的尸體忽然破棺而出套菜,到底是詐尸還是另有隱情,我是刑警寧澤设易,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布逗柴,位于F島的核電站,受9級特大地震影響顿肺,放射性物質(zhì)發(fā)生泄漏戏溺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一屠尊、第九天 我趴在偏房一處隱蔽的房頂上張望旷祸。 院中可真熱鬧,春花似錦讼昆、人聲如沸托享。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫌吠。三九已至,卻和暖如春掺炭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凭戴。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工涧狮, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人么夫。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓者冤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親档痪。 傳聞我的和親對象是個殘疾皇子涉枫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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