After reading Uncle Bob’s neat architectural concept, I tried to implement it with Golang. This is a similar architecture that our company kurio-app Berita Indonesia uses, not too different, same concept but slightly different folder structure.

You can find the sample project here github.com/bxcodec/go-… An article on CRUD management.

  • Disclaimer:

    I don’t recommend using any libraries or frameworks here. You can replace anything here with your own or a third party’s that does the same thing.

basic

As we know, before designing a clean architecture, the constraints are:

  1. Independent of the framework. The architecture does not depend on the existence of a rich software library. This allows you to use these frameworks as tools without having to cram systems into their limited constraints.

  2. Can be tested. Business rules can be tested without a UI, database, Web server, or any other external element.

  3. Independent of the user interface. The user interface can be easily changed without changing the rest of the system. For example, the Web UI can be replaced with a console UI without changing business rules.

  4. Database independent. You can replace Oracle or SQL Server with Mongo, BigTable, CouchDB, or something else. Your business rules are not bound to the database.

  5. Independent of any external agency. In fact, your business rules don’t understand the outside world at all.

More: 8 thlight.com/blog/uncle-…

Therefore, based on this constraint, each layer must be independent and testable.

Uncle Bob’s architecture would have the following four layers:

  • Entities
  • Usecase
  • Controller
  • Framework & Driver

In my project, I also used 4 layers:

  • Models
  • Repository
  • Usecase
  • Delivery

Models

As with entities, will be used in all layers. This layer stores the structure of any object and its methods. Article, Student, Book Example structure:

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"`
}
Copy the code

This is where any entity or model will be stored.

Repository

The Repository layer stores any database handlers. Any queries or create/insert databases will be stored here. This layer will only work on THE CRUD database. There is no business process. Perform normal functions only on the database.

This layer is also responsible for selecting the database to use in the application. It could be Mysql, MongoDB, MariaDB, Postgresql, etc., all determined here. If ORM is used, this layer controls the input and provides it directly to the ORM service.

If a microservice is invoked, it will be handled here. Create HTTP requests to other services and clean up the data. This layer must act entirely as a repository. Handles all data input – output without specific logic occurring.

The Repository layer will depend on connected databases or other microservices (if any).

Usecase

This layer will act as a business process handler. Any process will be handled here. This layer determines which repository layer to use. And responsible for the delivery of data. Work with data, do calculations, or do anything here.

The Usecase layer will accept any input from the delivery layer that has already been processed, and then the processing input can be stored in DB, extracted from DB, and so on.

The Usecase layer depends on the Repository layer.

Delivery

This layer will act as the demonstrator. Decide how to present the data. This can be a REST API, AN HTML file, or a gRPC, regardless of the delivery type. This layer will also accept input from the user. Clean up the input and send it to the Usecase layer.

For my example project, I use the REST API as the delivery method.

The client invokes the resource endpoint over the network, and the Delivery layer takes the input or request and sends it to the Usecase layer.

The Delivery layer relies on the Usecase layer.

Communication between the layers

Each layer, except the Models layer, communicates through interfaces. For example, the Usecase layer requires the Repository layer, so how do they communicate? The Repository layer provides an interface for their contract and communication.

Sample Repository layer interface

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)
}
Copy the code

The Usecase layer will use this contract to communicate with the Repository layer, which must implement this interface before it can be used by use cases.

Example Usecase layer interface

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)
}
Copy the code

As with the Usecase layer, the Delivery layer will use this contract interface. The Usecase layer must implement this interface.

The test on each floor

As we know, simplicity means independence. Each layer is testable, even though the others don’t exist yet.

  • The Models layer

    This layer only tests any function/method declared in any structure.

    And can be tested independently of other layers.

  • The Repository layer

    A better way to test this layer is to do integration testing. But you can also do simulations for each test. I am using github.com/DATA-DOG/go-sqlmock as an assistant to simulate the query process MSYQL.

  • Usecase layer

    Because this layer depends on the Repository layer, it means that the layer needs the Repository layer for testing. Therefore, we must build a Repository of SHANzhai simulation based on the previously defined contract interface and use Shanzhai simulation.

  • Delivery layer

    As with use cases, because this layer relies on the Usecase layer, this means we need the Usecase layer for testing. The use case layer must also be modeled with Mockry based on the previously defined contract interface

For the simulation, I used Shanzhai written by Vektra in Golang, which can be seen here github.com/vektra/mock…

The Repository layer test

As mentioned earlier, to test this layer, I used a SQL-mock to simulate my query process. You can use github.com/DATA-DOG/go-sqlmock like I’m using here, or something similar

func TestGetByID(t *testing.T) {
 db, mock, err := sqlmock.New() 
 iferr ! =nil{t.falf (" 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"The title,1", "the Content1", 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)
}
Copy the code

Usecase layer test

Sample tests for the Usecase layer, depending on the Repository layer.

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"))}Copy the code

Mockry will generate a Repository layer model for me. So I don’t need to complete my Repository layer first. I can complete the use case first, even if my Repository layer is not yet implemented.

Delivery layer test

The Delivery layer tests will depend on how you deliver the data. If using HttprestAPI, we can use the built-in httpTest package in Golang.

Since this depends on the Usecase layer, we need a simulation of the Usecase layer. As with Repository, I also used Mockry to simulate my use cases for delivering tests.

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, nilE := 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))
}
Copy the code

Final output and merge

You have completed all layers and passed the tests. You should merge into a main.go in the root project.

Here, you will define and create each requirement for your environment and merge all layers into one environment.

Find my main.go for example:

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(a) {
	config = cfg.NewViperConfig()

	if config.GetBool(`debug`) {
		fmt.Println("Service RUN on DEBUG mode")}}func main(a) {

	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)
	iferr ! =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"))}Copy the code

As you can see, each layer is merged into a layer with its dependencies.

Conclusion:

In short, if you draw it on a diagram, you can see the following

  • You can make your own changes to each of these libraries. Because the point of an architecture introduction is that no matter what your library is, your architecture is clean and testability is independent.

This is how I organize my project, you can argue, or agree, or maybe improve, just leave a comment and share

The sample project

Sample projects can be seen here github.com/bxcodec/go-…

Libraries used in my project:

Glide: Used for package management

  • Glide: Package management
  • Go – sqlmock from github.com/DATA-DOG/go-sqlmock
  • Testify: testing
  • Echo Labstack (Golang Web Framework) : Delivery layer
  • Viper: Environment configuration

Further reading on concise architecture:

The second part of this paper:

  • Hackernoon.com/trying-clea…

  • 8 thlight.com/blog/uncle-…

  • Another version of the compact architecture:

    Manuel.kiessling.net/2012/09/28/…

If you have questions, or need more explanation, or something I can’t explain very well here, you can ask me on my linkedin or send me an email.

Linkedin: www.linkedin.com/in/imantumo…

Email: [email protected]

Thank you

medium.com/hackernoon/…