Mencoba Clean Architecture pada Golang

Independent , Testable, and Clean

Iman Tumorang
GolangID
6 min readOct 20, 2017

--

Halo.. Mungkin postingan saya kali ini sedikit berbeda dari yang sebelumnya. Kali ini saya akan menulis ulang artikel saya sebelumnya tetapi bukan menerjemahkan artikel tersebut secara langsung. Artikel yang asli dapat anda lihat disini.

UPDATE!!!: Tulisan terupdate dan lebih lengkap dapat dilihat di versi Englishnya di: https://hackernoon.com/golang-clean-archithecture-efd6d7c43047. Serta di repo projectnya: https://github.com/bxcodec/go-clean-arch

Saya tidak akan membahas panjang lebar seperti yang ada pada artikel original saya, namun saya hanya akan menjelaskan beberapa hal yang penting pada artikel tersebut.

Untuk project yang dipakai pada artikel ini bisa langsung dilihat di sini :

Oke, langsung saja.

Dalam konsep clean architecture, setiap komponen yang ada bersifat independen dan tidak bergantung pada library external yang spesifik. Seperti tidak tergantung pada spesifik framework atau tidak bergantung pada spesifik database yang dipakai.

Jika teman-teman sudah familiar dengan Uncle Bob dan konsep clean achitecture yang di jelaskan oleh beliau, yakni tentang arsitektur kue lapis 😄
No offense, saya menyebutnya kue lapis, karena struktur arsitekturnya yang berlapis lapis untuk setiap module/domain hehe 😄

Oleh Uncle Bob beliau menyebutkan 4 layer pada arsitekturnya:
1. Entities
2. Usecase
3. Controller
4. Framework dan Driver

Nah, pada project saya, saya juga memiliki 4 layer:
1. Models
2. Repository
3. Usecase
4. Delivery

Models

Layer ini merupakan layer yang menyimpan model yang dipakai pada domain lainnya. Layer ini dapat diakses oleh semua layer dan oleh semua domain.

Contoh struct :

Repository

Layer ini merupakan layer yang menyimpan database handler. Querying, Inserting, Deleting akan dilakukan pada layer ini. Tidak ada business logic disini. Yang ada hanya fungsi standard untuk input-output dari datastore.

Layer ini memiliki tugas utama yakni menentukan datastore apa yang di gunakan. Teman-teman boleh memilih sesuai kepada kebutuhan, mungkin RDBMS (Mysql,PostgreSql, dsb) atau NoSql (Mongodb,CouchDB dsb).

Jika menggunakan arsitektur microservice, maka layer ini akan bertugas sebagai penghubung kepada service lain. Layer ini akan terikat dan bergantung pada datastore yang digunakan.

Usecase

Layer ini merupakan layer yang akan bertugas sebagai pengontrol, yakni menangangi business logic pada setiap domain. Layer ini juga bertugas memilih repository apa yang akan digunakan, dan domain ini bisa memiliki lebih dari satu repository layer.

Tugas utama terbesar dari layer ini, yaitu menjadi penghubung antara datastore (repository layer) dengan delivery layer. Sehingga, layer ini juga bertanggung jawab atas kevalidan data, jika sesuatu terjadi data yang tidak valid pada repository atau delivery, maka layer ini yang pertama kali disalahkan.
Layer ini benar benar harus berisi business logic, contohnya: penjumlahan, total masukan, atau membentuk response yang merupakan gabungan dari beberapa repository/model. Layer ini bergantung pada repository layer. Jadi jika terjadi perubahan di repository secara besar-besaran tentu saja mempengaruhi layer ini.

Delivery

Layer ini merupakan layer yang akan bertugas sebagai presenter atau menjadi output dari aplikasi. Layer ini bertugas menentukan metode penyampaian yang dipakai, bisa dengan Rest API, HTML, gRPC, File dsb.

Tugas lain dari layer ini, menjadi dinding penghubung antara user dan sistem. Menerima segala input dan validasi input sesuai standar yang digunakan.

Untuk contoh project yang saya gunakan, saya memilih Rest API sebagai delivery layernya. Sehingga, komunikasi antara client/user terhadap sistem dilakukan melalui REST API

Komunikasi Antar Layer

Baiklah, kita sudah tahu ke-4 layer, lalu bagaimana cara memakainya? Bagaimana komunikasi antar layer dilakukan ?

Untuk setiap layer, kecuali Model, akan berkomunikasi menggunakan interface. Interface ini bertindak seperti kontrak/perjanjian antara 2 layer.

Contoh Repository Interface

Interface ini akan menjadi atribut utama, dan di-inject pada usecase layer. Di harapkan, ketika membuat interface ini untuk lebih berhati-hati dan bertanggung jawab. Pembuatan interface ini harus selesai diawal dan tidak berubah, karena akan dipakai oleh 2 layer yang berbeda bersamaan dan tentu saja jika terjadi perubahan akan menyulitkan nantinya. Usecase hanya mengetahui fungsi di repository melalui interface ini, sehingga repository harus implement interface ini.

Contoh Usecase Interface

Cara mengimplementasikan interface pada golang dapat dilihat di sini :

Testing Setiap Layer

Pada hakikatnya, clean artinya adalah independen.Setiap layer dapat di-test secara independen, meski layer lain masih belum selesai.

  • Model Layer
    Layer ini dapat di-test secara independen karena tidak berketergantungan kepada layer apapun, layer ini dapat di-test, jika dan hanya jika layer ini memiliki fungsi tersendiri
  • Repository
    Untuk membuat test pada layer ini, idealnya adalah dengan melakukan integration testing. Tetapi kita juga dapat menggunakan unit testing. Terimakasih kepada library github.com/DATA-DOG/go-sqlmock yang sudah memberi kemudahan untuk melakukan unit testing mysql pada project saya. Jika anda menggunakan datastore yang berbeda, pastikan anda memiliki unit testing helper-nya atau dengan melakukan integration testing.
  • Usecase
    Karena untuk melakukan test pada layer ini, dibutuhkan sebuah injection dari repository layer, maka kita dapat melakukan mocking pada repository, dan inject mockingnya pada unit test. Untuk mocking berdasarkan interface, saya menggunakan mockery.
  • Delivery
    Seperti usecase, karena layer ini bergantung pada usecase layer, maka dibutuhkan proses inject dari usecase untuk melakukan testing. Karena test bersifat independen, maka kita harus melakukan mocking pada usecase.

Untuk keperluan mocking, library yang saya pakai adalah https://github.com/vektra/mockery.

Repository Test

Seperti yang saya tulis sebelumnya di atas, saya menggunakan go-sqlmock untuk melakukan mocking connection ke database, karena kebetulan untuk project yang saya buat menggunakan mysql.

Contoh test dengan 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)
}

Usecase Test

Contoh usecase test

Delivery Test

Untuk melakukan test pada delivery layer, bergantung pada bagaimana data anda akan di representasikan. Untuk contoh yang saya buat, saya menggunakan HTTP Rest API sebagai delivery methodnya.

Untuk melakukan test pada HTTP, kita dapat melakukan package built-in dari golang: httptest untuk membantu dalam melakukan testing. Selain itu, karena bergantung pada usecase, maka saya akan menggunakan usecase yang sudah di-mocking pada delivery layer.

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

Final Output and The Merging

Tahap terakhir yaitu menggabungkan semua layer, agar aplikasi dapat berjalan. Penggabungan di lakukan di root folder, di package main.

Kesimpulan:

  • Jika digambarkan dalam diagram, keseluruhan proses akan tampak seperti berikut :
  • Setiap library yang saya pakai, tidak saya rekomendasikan untuk dipakai, anda dapat menggukan library lain yang sejenis. Karena clean-architecture adalah independen dan tidak terikat pada spesifik library.
  • Saya disini hanya belajar, dan berusaha berkembang hehe 😃. Saya tidak menggurui, jika teman sekalian merasa ada yang kurang dan salah, teman-teman dapat memberi komentar di bawah untuk memperbaiki ke yang lebih bagus

Contoh Project

Untuk contoh project yang saya bangun dapat di lihat di:

Library yang saya gunakan:
1. Glide : untuk package management
2. go-sqlmock from github.com/DATA-DOG/go-sqlmock
3. Testify : untuk testing
4. Echo Labstack (Golang Web Framework) untuk Delivery layer
5. Viper : untuk environment configurations

Bacaan lainnya :

If you have a question , or need more explanation, or something, that I can not explain well here, you can ask me from my linkedin or email me. Thank you

--

--

Iman Tumorang
GolangID

Software Architect @ Xendit | Independent Software Architect @ SoftwareArchitect.ID | Reach me at https://imantumorang.com for fast response :)