<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                >From : https://studygolang.com/articles/12909 在閱讀了 Bob 叔叔的 Clean Architecture Concept 之后,我嘗試在 Golang 中實現它。我們公司也有使用相似的架構,[Kurio - App Berita Indonesia](https://kurio.co.id/), 但是結構有點不同。并不是太不同, 相同的概念,但是文件目錄結構不同。 你可以在這里找到一個示例項目[https://github.com/bxcodec/go-clean-arch](https://github.com/bxcodec/go-clean-arch),這是一個 CRUD 管理示例文章 ![](https://box.kancloud.cn/1cc104783a70b962ee56013fd09519fd_1440x666.png) * 免責聲明: 我不推薦使用這里的任何庫或框架,你可以使用你自己的或者第三方具有相同功能的任何框架來替換。 ## 基礎 在設計簡潔架構之前我們需要了解如下約束: 1. 獨立于框架。該架構不會依賴于某些功能強大的軟件庫存在。這可以讓你使用這樣的框架作為工具,而不是讓你的系統陷入到框架的限制的約束中。 2. 可測試性。業務規則可以在沒有 UI, 數據庫,Web 服務或其他外部元素的情況下進行測試。 3. 獨立于 UI 。在無需改變系統的其他部分情況下, UI 可以輕松的改變。例如,在沒有改變業務規則的情況下,Web UI 可以替換為控制臺 UI。 4. 獨立于數據庫。你可以用 Mongo, BigTable, CouchDB 或者其他數據庫來替換 Oracle 或 SQL Server,你的業務規則不要綁定到數據庫。 5. 獨立于外部媒介。 實際上,你的業務規則可以簡單到根本不去了解外部世界。 更多詳見: [https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) 所以, 基于這些約束,每一層都必須是獨立的和可測試的。 如 Bob 叔叔的架構有 4 層: * 實體層( Entities ) * 用例層( Usecase ) * 控制層( Controller ) * 框架和驅動層( Framework & Driver ) 在我的項目里,我也使用了 4 層架構: * 模型層( Models ) * 倉庫層( Repository ) * 用例層 ( Usecase ) * 表現層( Delivery ) ## 模型層( Models ) 與實體( Entities )一樣, 模型會在每一層中使用,在這一層中將存儲對象的結構和它的方法。例如: Article, Student, Book。 ~~~go import "time" type Article struct { ID int64 `json:"id"` Title string `json:"title"` Content string `json:"content"` UpdatedAt time.Time `json:"updated_at"` CreatedAt time.Time `json:"created_at"` } ~~~ 所以實體或者模型將會被存放在這一層 ## 倉庫層( Repository ) 倉庫將存放所有的數據庫處理器,查詢,創建或插入數據庫的處理器將存放在這一層,該層僅對數據庫執行 CRUD 操作。 該層沒有業務流程。只有操作數據庫的普通函數。 這層也負責選擇應用中將要使用什么樣的數據庫。 可以是 Mysql, MongoDB, MariaDB,Postgresql,無論使用哪種數據庫,都要在這層決定。 如果使用 ORM, 這層將控制輸入,并與 ORM 服務對接。 如果調用微服務, 也將在這層進行處理。創建 HTTP 請求去請求其他服務并清理數據,這層必須完全充當倉庫。 處理所有的數據輸入,輸出,并且沒有特定的邏輯交互。 該倉庫層( Repository )將依賴于連接數據庫 或其他微服務(如果存在的話) ## 用例層( Usecase ) 這層將會扮演業務流程處理器的角色。任何流程都將在這里處理。該層將決定哪個倉庫層被使用。并且負責提供數據給服務以便交付。處理數據進行計算或者在這里完成任何事。 用例層將接收來自傳遞層的所有經過處理的輸入,然后將處理的輸入存儲到數據庫中, 或者從數據庫中獲取數據等。 用例層將依賴于倉庫層。 ## 表現層( Delivery ) 這一層將作為表現者。決定數據如何呈現。任何傳遞類型都可以作為是 REST API, 或者是 HTML 文件,或者是 gRPC 這一層將接收來自用戶的輸入, 并清理數據然后傳遞給用例層。 對于我的示例項目, 我使用 REST API 作為表現方式。客戶端將通過網絡調用資源節點, 表現層將獲取到輸入或請求,然后將它傳遞給用例層。 該層依賴于用例層。 ## 層與層之間的通信 除了模型層, 每一層都需要通過接口進行通信。例如,用例( Usecase )層需要倉庫( Repository )層,那么它們該如何通信呢?倉庫( Repository )層將提供一個接口作為他們溝通橋梁。 倉庫層( Repository )接口示例: ~~~go package repository import models "github.com/bxcodec/go-clean-arch/article" type ArticleRepository interface { Fetch(cursor string, num int64) ([]*models.Article, error) GetByID(id int64) (*models.Article, error) GetByTitle(title string) (*models.Article, error) Update(article *models.Article) (*models.Article, error) Store(a *models.Article) (int64, error) Delete(id int64) (bool, error) } ~~~ 用例層( Usecase )將通過這個接口與倉庫層進行通信,倉庫層( Repository )必須實現這個接口,以便用例層( Usecase )使用該接口。 用例層接口示例: ~~~go package usecase import ( "github.com/bxcodec/go-clean-arch/article" ) type ArticleUsecase interface { Fetch(cursor string, num int64) ([]*article.Article, string, error) GetByID(id int64) (*article.Article, error) Update(ar *article.Article) (*article.Article, error) GetByTitle(title string) (*article.Article, error) Store(*article.Article) (*article.Article, error) Delete(id int64) (bool, error) } ~~~ 與用例層相同, 表現層將會使用這個約定接口。 并且用例層必須實現該接口。 ## 測試 我們知道, 簡潔就意味著獨立。 甚至在其他層還不存在的情況下,每一層都具有可測試性。 * 模型( Models )層 該層僅測試任意結構聲明的函數或方法。 這可以獨立于其他層,輕松的進行測試。 * 倉庫( Repository )層 為了測試該層,更好的方式是進行集成測試,但你也可以為每一個測試進行模擬測試, 我使用 github.com/DATA-DOG/go-sqlmock 作為我的工具來模擬查詢過程 mysql * 用例( Usecase )層 因為該層依賴于倉庫層, 意味著該層需要倉庫層來支持測試。所以我們根據之前定義的契約接口制作一個模擬的倉庫( Repository )模型。 * 表現( Delivery )層 與用例層相同,因為該層依賴于用例層,意味著該層需要用例層來支持測試。基于之前定義的契約接口, 也需要對用例層進行模擬。 對于模擬,我使用 vektra 的 golang的模擬庫: [https://github.com/vektra/mockery](https://github.com/vektra/mockery) ## 倉庫層(Repository)測試 為了測試這層,就如我之前所說, 我使用 sql-mock 來模擬我的查詢過程。 你可以像我一樣使用 github.com/DATA-DOG/go-sqlmock ,或者使用其他具有相似功能的庫。 ~~~go func TestGetByID(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() rows := sqlmock.NewRows([]string{ "id", "title", "content", "updated_at", "created_at"}). AddRow(1, "title 1", "Content 1", time.Now(), time.Now()) query := "SELECT id,title,content,updated_at, created_at FROM article WHERE ID = ?" mock.ExpectQuery(query).WillReturnRows(rows) a := articleRepo.NewMysqlArticleRepository(db) num := int64(1) anArticle, err := a.GetByID(num) assert.NoError(t, err) assert.NotNil(t, anArticle) } ~~~ ## 用例層(Usecase)測試 用于用例層的示例測試,依賴于倉庫層。 ~~~go package usecase_test import ( "errors" "strconv" "testing" "github.com/bxcodec/faker" models "github.com/bxcodec/go-clean-arch/article" "github.com/bxcodec/go-clean-arch/article/repository/mocks" ucase "github.com/bxcodec/go-clean-arch/article/usecase" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func TestFetch(t *testing.T) { mockArticleRepo := new(mocks.ArticleRepository) var mockArticle models.Article err := faker.FakeData(&mockArticle) assert.NoError(t, err) mockListArtilce := make([]*models.Article, 0) mockListArtilce = append(mockListArtilce, &mockArticle) mockArticleRepo.On("Fetch", mock.AnythingOfType("string"), mock.AnythingOfType("int64")).Return(mockListArtilce, nil) u := ucase.NewArticleUsecase(mockArticleRepo) num := int64(1) cursor := "12" list, nextCursor, err := u.Fetch(cursor, num) cursorExpected := strconv.Itoa(int(mockArticle.ID)) assert.Equal(t, cursorExpected, nextCursor) assert.NotEmpty(t, nextCursor) assert.NoError(t, err) assert.Len(t, list, len(mockListArtilce)) mockArticleRepo.AssertCalled(t, "Fetch", mock.AnythingOfType("string"), mock.AnythingOfType("int64")) } ~~~ Mockery 將會為我生成一個倉庫層模型,我不需要先完成倉庫(Repository)層, 我可以先完成用例(Usecase),即使我的倉庫(Repository)層尚未實現。 ## 表現層( Delivery )測試 表現層測試依賴于你如何傳遞的數據。如果使用 http REST API, 我們可以使用 golang 中的內置包 httptest。 因為該層依賴于用例( Usecase )層, 所以 我們需要模擬 Usecase,與倉庫層相同,我使用 Mockery 模擬我的 Usecase 來進行表現層( Delivery )的測試。 ~~~go func TestGetByID(t *testing.T) { var mockArticle models.Article err := faker.FakeData(&mockArticle) assert.NoError(t, err) mockUCase := new(mocks.ArticleUsecase) num := int(mockArticle.ID) mockUCase.On("GetByID", int64(num)).Return(&mockArticle, nil) e := echo.New() req, err := http.NewRequest(echo.GET, "/article/"+ strconv.Itoa(int(num)), strings.NewReader("")) assert.NoError(t, err) rec := httptest.NewRecorder() c := e.NewContext(req, rec) c.SetPath("article/:id") c.SetParamNames("id") c.SetParamValues(strconv.Itoa(num)) handler := articleHttp.ArticleHandler{ AUsecase: mockUCase, Helper: httpHelper.HttpHelper{}, } handler.GetByID(c) assert.Equal(t, http.StatusOK, rec.Code) mockUCase.AssertCalled(t, "GetByID", int64(num)) } ~~~ ## 最終輸出與合并 完成所有層的編碼并通過測試之后。你應該在的根項目的 main.go 文件中將其合并成一個系統。 在這里你將會定義并創建每一個環境需求, 并將所有層合并在一起。 以我的 main.go 為示例: ~~~go package main import ( "database/sql" "fmt" "net/url" httpDeliver "github.com/bxcodec/go-clean-arch/article/delivery/http" articleRepo "github.com/bxcodec/go-clean-arch/article/repository/mysql" articleUcase "github.com/bxcodec/go-clean-arch/article/usecase" cfg "github.com/bxcodec/go-clean-arch/config/env" "github.com/bxcodec/go-clean-arch/config/middleware" _ "github.com/go-sql-driver/mysql" "github.com/labstack/echo" ) var config cfg.Config func init() { config = cfg.NewViperConfig() if config.GetBool(`debug`) { fmt.Println("Service RUN on DEBUG mode") } } func main() { dbHost := config.GetString(`database.host`) dbPort := config.GetString(`database.port`) dbUser := config.GetString(`database.user`) dbPass := config.GetString(`database.pass`) dbName := config.GetString(`database.name`) connection := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUser, dbPass, dbHost, dbPort, dbName) val := url.Values{} val.Add("parseTime", "1") val.Add("loc", "Asia/Jakarta") dsn := fmt.Sprintf("%s?%s", connection, val.Encode()) dbConn, err := sql.Open(`mysql`, dsn) if err != nil && config.GetBool("debug") { fmt.Println(err) } defer dbConn.Close() e := echo.New() middL := middleware.InitMiddleware() e.Use(middL.CORS) ar := articleRepo.NewMysqlArticleRepository(dbConn) au := articleUcase.NewArticleUsecase(ar) httpDeliver.NewArticleHttpHandler(e, au) e.Start(config.GetString("server.address")) } ~~~ 你可以看見,每一層都與它的依賴關系合并在一起了。 ## 結論 總之,如果畫在一張圖上,就如下圖所示: ![](https://box.kancloud.cn/3a63637139ebdfba8f0ef2aa82cd6546_911x518.png) * 在這里使用的每一個庫都可以由你自己修改。因為簡潔架構的重點在于:你使用的庫不重要, 關鍵是你的架構是簡潔的,可測試的并且是獨立的。 * 我項目就是這樣組織的。通過評論和分享, 你可以討論或者贊成,當然能改善它就更好了。 ## 示例項目 示例項目可以在這里看見: [https://github.com/bxcodec/go-clean-arch](https://github.com/bxcodec/go-clean-arch) 我的項目中使用到的庫: * Glide :包管理工具 * go-sqlmock from github.com/DATA-DOG/go-sqlmock * Testify : 測試庫 * Echo Labstack (Golang Web 框架)用于 表現層 * Viper :環境配置 進一步閱讀簡潔架構 : * [https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) * [http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/](http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/)。 這是Golang種另一個版本的簡潔架構。 如果你任何問題,或者需要更多的解釋,或者我在這里沒有解釋清楚的。你可以通過我的[LinkedIn](https://www.linkedin.com/in/imantumorang/)或者[email](https://studygolang.com/articles/iman.tumorang@gmail.com)聯系我。謝謝。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看