golang架构_在Golang上尝试清洁架构

golang架构

独立,可测试且清洁

阅读了叔叔Bob的“干净架构概念”后,我正在尝试在Golang中实现它。 这与我们在Kurio-App Berita Indonesi公司中使用的架构类似但结构略有不同。 没什么不同,相同的概念但文件夹结构不同。

您可以在此处https://github.com/bxcodec/go-clean-arch(CRUD管理示例文章)中查找示例项目。

免责声明:
我不建议在此使用任何库或框架。 您可以用自己的或具有相同功能的第三方替换此处的任何内容。

基本的

我们知道在设计Clean Architecture之前的约束是:

  1. 独立于框架。 该体系结构不依赖于某些功能丰富的软件库的存在。 这使您可以将这些框架用作工具,而不必将系统塞入有限的约束中。
  2. 可测试的。 可以在没有UI,数据库,Web服务器或任何其他外部元素的情况下测试业务规则。
  3. 独立于UI。 UI可以轻松更改,而无需更改系统的其余部分。 例如,可以在不更改业务规则的情况下用控制台UI替换Web UI。
  4. 独立于数据库。 您可以将Oracle或SQL Server换成Mongo,BigTable,CouchDB或其他东西。 您的业​​务规则未绑定到数据库。
  5. 独立于任何外部机构。 其实您的业务规则根本不知道在所有关于外world.More什么https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

因此,基于此约束,每一层都必须独立且可测试。

如果Bob叔叔的体系结构具有4层:

  • 实体
  • 用例
  • 控制者
  • 框架和驱动

在我的项目中,我也使用4:

  • 楷模
  • 资料库
  • 用例
  • 交货

楷模

与实体相同,将在所有图层中使用。 该层将存储任何对象的Struct及其方法。 示例:文章,学生,书籍。
示例结构:

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"`
}

任何实体或模型都将存储在此处。

资料库

存储库将存储任何数据库处理程序。 查询或创建/插入任何数据库将存储在此处。 该层仅适用于CRUD数据库。 这里没有业务流程发生。 仅普通功能对数据库。

该层还负责选择应用程序中将使用的数据库。 可能是Mysql,MongoDB,MariaDB,Postgresql等,将在这里决定。

如果使用ORM,则此层将控制输入,并将其直接提供给ORM服务。

如果调用微服务,将在这里处理。 创建对其他服务的HTTP请求,并清理数据。 该层必须完全充当存储库。 处理所有数据输入-输出没有发生特定的逻辑。

此存储库层将取决于Connected DB或其他微服务(如果存在)。

用例

该层将充当业务流程处理程序。 任何过程都将在这里处理。 该层将决定将使用哪个存储库层。 并有责任提供数据以供交付使用。 处理数据以进行计算,否则将在此处完成任何操作。

用例层将接受来自交付层的任何已输入的已消毒的输入,然后处理该输入可存储到DB中,或从DB中提取等。

该用例层将取决于存储库层

交货

该层将充当演示者。 决定如何呈现数据。 可以采用REST API或HTML File或gRPC的形式(无论采用哪种交付方式)。
该层还将接受用户的输入。 清理输入,并将其发送到用例层。

对于我的示例项目,我使用REST API作为交付方式。
客户端将通过网络调用资源终结点,传递层将获取输入或请求,并将其发送到用例层。

该层将取决于用例层。

层间通讯

除模型外,每一层都将通过接口进行通信。 例如,用例层需要存储库层,那么它们如何通信? 储存库将提供一个界面,使其成为他们的合同和通讯方式。

存储库接口示例

package repositoryimport 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)
}

用例层将使用此合同与存储库通信,并且存储库层必须实现此接口,以便供用例使用

用例接口示例

package usecaseimport (
	"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)
}

与用例相同,交付层将使用此合同接口。 用例层必须实现此接口。

测试每一层

众所周知,清洁意味着独立。 可测试的每一层甚至其他层都不存在。

  • 模型层:仅在Struct的任何函数中声明了任何函数/方法时,此层才进行测试。 并且可以轻松地进行测试并且独立于其他层。
  • 存储库:为了测试这一层,更好的方法是进行集成测试。 但是您也可以为每个测试进行模拟。 我使用github.com/DATA-DOG/go-sqlmock作为我的助手来模拟查询过程msyql。
  • 用例:由于此层取决于存储库层,因此意味着此层需要存储库层进行测试。 因此,我们必须基于之前定义的协定接口,制作一个以嘲笑为原型的存储库模型。
  • 交付:与用例相同,因为该层取决于用例层,这意味着我们需要用例层进行测试。 而且,Usecase层还必须根据之前定义的协定接口以嘲弄来嘲笑

对于嘲讽,我在vektra上对golang使用嘲讽,可以在这里看到https://github.com/vektra/mockery

仓库测试

为了测试这一层,就像我之前说过的那样,我正在使用sql-mock模拟我的查询过程。 您可以像在这里github.com/DATA-DOG/go-sqlmock一样使用,也可以使用其他具有类似功能的

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)
}

用例测试

用例层的样本测试,取决于存储库层。

package usecase_testimport (
	"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将为我生成一个存储库层模型。 因此,我不需要先完成我的存储库层。 我可以先完成用例,甚至还没有实现我的存储库层。

交付测试

传递测试将取决于您如何传递数据。 如果使用http REST API,则可以在golang中为httptest使用内置测试包。

因为这取决于Usecase,所以我们需要模拟Usecase。 与存储库相同,我也使用Mockery模拟用例,以进行交付测试。

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))
}

最终输出与合并

完成所有层并已通过测试之后。 您应该在root项目的main.go中合并为一个系统。
在这里,您将定义并创建环境的每种需求,并将所有层合并为一个层。

以我的main.go为例:

package mainimport (
	"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://github.com/bxcodec/go-clean-arch

用于我的项目的库:

  • Glide:用于包裹管理
  • 来自github.com/DATA-DOG/go-sqlmock的go-sqlmock
  • 证明:用于测试
  • Echo Labstack(Golang Web框架)交付层
  • 毒蛇:用于环境配置

进一步了解Clean Architecture:

  • 本文的第二部分:https://hackernoon.com/trying-clean-architecture-on-golang-2-44d615bf8fdf
  • 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/。 Golang的Clean Architecture的另一个版本

如果您有一个问题,或者需要更多说明,或者其他我无法在此处很好地解释的问题,您可以从我的 Linkedin上 发问 发电子邮件 谢谢

翻译自: https://hackernoon.com/golang-clean-archithecture-efd6d7c43047

golang架构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值