Our (Awesome) Software Architecture — Dependency Injection in Go

Yusuf Sholeh
PPL A-4 YUK RECYCLE
5 min readApr 1, 2019

Hai! kembali lagi bersama saya, ucup. Pada kesempatan kali ini saya akan menjelaskan Software Architecture kelompok kami. Karena saya berperan sebagai back-end developer, saya tidak begitu mengerti mengenai cara pengaturan docker pada portrainer. Untuk itu, saya akan lebih memfokuskan untuk menjelaskan secara detail mengenai architecture dari back-end kami!

Oke, langsung saja.

Our Back-end Clean Architecture (Golang)

Mengapa saya menyebutnya clean? Ternyata, terdapat sebuah konsep clean architecture, lho. Konsep tersebut pertama kali dicetuskan oleh Uncle Bob di dalam sebuah bukunya yang berjudul “Clean Architecture: A Craftsman’s Guide to Software Structure and Design”.

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 (Services)
4. Delivery (Controller)

Agar lebih mudah memahami, pertama-tama saya berikan sebuah ilustrasi apabila sebuah user ingin melihat profilnya:

  • Client (Android):

Client melakukan request, contohnya GET /users/1234 dan sebuah token untuk autentikasi.

  • Middleware

Setiap request yang masuk ke dalam back-end kita harus melewati tahap ini. Tahap ini berguna untuk memvalidasi atau mengecek request yang diberikan. Pengecekan ini terdiri dari 4 step:

  1. Check Request URL
    Perhatikan bahwa terdapat URL yang tidak perlu membutuhkan JWT Token, seperti /login atau /register. Sehingga apabila terdapat pemanggilan URL tersebut, tidak perlu dilakukan autentikasi dan lanjut ke controller (skip step 2, 3, 4).
    Namun untuk URL lainnya, wajib memiliki sebuah JWT untuk autentikasi dan lanjut ke step 2.
  2. Parse Header
    Pengambilan header pada request. Header harus berisi “Authorization: Bearer Token” . Ambil token pada header tersebut dan lanjut ke step 3.
  3. Authentication
    Pada tahap ini, JWT yang diberikan oleh user dicek dan diautentikasi. JWT yang diberikan harus valid dengan secret-key yang kami miliki dan belum expire. JWT juga harus menyimpan informasi mengenai siapa user yang melakukan request.
  4. Context Propagation
    Tahap ini mentransformasikan isi JWT menjadi sebuah context, agar isinya dapat diakses akses pada layer controller maupun services. JWT tersebut lalu di-extract dan dimasukkan ke dalam context untuk menyimpan informasi siapa user yang melakukan request. Pada project kami, isi JWT yaitu UserID, UserType, dan LoginType. Setiap masing-masing nilai UserID, UserType, dan LoginType dimasukkan ke dalam context pada request. Nantinya, apabila ingin melihat siapa user yang melakulan request cukup lihat isi r.Context(). Setelah itu barulah lanjut ke controller!
  • Controller (Delivery)

Controller mem-parse body dan mem-validasi body untuk menghindari adanya ke salahan pada saat request. Controller melakukan return bad request apabila terdapat error pada saat parsing maupun validasi. Disamping itu, controller juga berguna untuk mengatur format response yang dikembalikan pada REST-API.

  • Services (Usecase)

Disinilah letak otak project kami, yaitu berisi kumpulan business logic yang berguna untuk memproses apa yang diinginkan. Pada contoh ini, service melakukan query dengan UserID = 1234 (Perhatikan bahwa angka tersebut didapatkan dengan melakukan ID=r.Context(“UserID”)). Query tersebut lalu memanggil UserRepositories dengan Method GetByID(1234). Setelah mendapatkan user dari repository, service juga memilih data apa saja yang ingin dikembalikan.

  • Repositories (Models + CRUD databases)

Repositories hanya berisi kumpulan CRUD terhadap database. Karena kami menggunakan ORM, setiap tabel yang dibuat di database tentunya dibuat sebuah class pada repositories, dan berisi kumpulan method seperti GetByID(), GetAll(), Update(), Delete(), Save(), dll.

Sudah jelas bukan mengapa clean? Lalu sebenarnya setiap layer cara komunikasinya bagaimana sih? nah! saya akan jelaskan lagi, nih.

Dependency Injection Pattern: Controller -> Services -> Repositories

Coba lihat kembali pada flow diatas, setiap layer controller, services, dan repositories memiliki dependency terhadap layer dibawahnya.

Controller memiliki dependency terhadap services, dan services memiliki dependency terhadap repositories.

Sekarang pasti bertanya, bagaimana sih cara pengimplementasiannya?

Tahap pertama pada pengimplementasian pattern ini yaitu dengan membuat interface pada setiap layer. Setiap method yang diimplementasi pada masing-masing controller, services, dan repositories harus dimasukkan ke dalam interface. Sehingga terdapat 3 interface, yaitu “interface controller”, “interface services’, dan “interface repositories”. Berikut contoh dari project kami:

Services: Class UserProfile implements an interface named IUserProfile
Repositories: Class UserRepository implements an interface named IUserRepository

Tahap kedua yaitu pembuatan “dependency”. Setiap layer sekarang mempunyai sebuah object interface pada layer dibawahnya. Perhatikan contoh berikut:

Services UserProfile has dependency to UserRepository. Notice that UserRepository has a method GetByID(), so we can use it (e.g line 13).

Tahap ketiga yaitu melakukan “injection”. Cukup inisialisasi class, lalu inject class tersebut dan jadilah sebuah kontrak!

Inject userRepository to userProfileService

Why Dependency Injection?

Pertama, seperti yang sudah saya jelaskan pada blog 1 saya, pattern ini membuat kode sangat sangat sangattttttttt mudah untuk di test. Di dalam blog tersebut juga dijelaskan cara dalam membuat unit testnya.

Kedua, low coupling. Kontrak hanya terjadi di awal, dimana service hanya ingin kumpulan method yang diimplementasikan pada repositories. Service tidak perlu tahu bagaimana repositories mengimplementasikan, hanya saja service ingin sebuah method GetByID() dengan parameter userID dan mengembalikan object user. Di sinilah menariknya, apabila terdapat perubahan logic kode pada repositories, maka perubahan tersebut tidak akan berpengaruh terhadap seluruh kode yang ada di services. Inilah mengapa dependency injection bersifat low coupling.

Best Practice Golang

Kode yang saya implementasikan secara implisit sudah mengikuti best practice dalam Go. Contohnya, terdapat error handling. Jika terdapat error, segera di atasi dan di return. Contoh berikutnya adalah kami menghindari dependency dengan mengimplementasikan interface. Untuk formatting dalam Go, kami meng-indentasi dan memberi spasi yang pas, nama variable yang deskriptif, terdapat komentar apabila dibutuhkan. Untuk urutan penulisan, import selalu ditaruh dipaling atas, dilanjut dengan struct lalu interface, baru method yang diimplementasikan. Perhatikan juga bahwa, satu file hanya melakukan “satu” pekerjaan, sehingga setiap file mempunyai pekerjaan masing-masing (keep independent).

Our Higher Level Architecture

Sekarang, coba lihat deh, bagaimana secara general aplikasi kami berjalan. Kelompok kami mendeploynya di portrainer. Seperti biasa, saya akan memberikan diagram, dan apa sih maksud dari tiap-tiap element yang ada disana?

Higher Level Architecture used at Yuk Recycle
  • Flutter App (Customer + Mitra)

Ini adalah aplikasi client yang dapat didapatkan di android. Aplikasi tersebut terhubung dengan sebuah server REST API back-end.

  • Docker Network

Back-end dan database kami ter-deploy di docker di dalam portrainer. Di dalam portrainer, kami membuat sebuah network, dimana di dalamnya terdiri dari 2 container. Container tersebut dapat saling terhubung melalui satu network yang telah kami buat.

  • Dockerized Environment (Container)

Terdapat 2 container, yaitu database (PostgreSQL) dan back-end (golang). Keduanya kami expose dan ditempatkan di port yang berbeda. Port yang kami gunakan yaitu 214xx. Dengan ini, client dapat berinteraksi dengan back-end melalui IP portrainer dengan port 214xx.

  • Third Party Firebase (Push Notification + Google OAuth)

Karena app kami membutuhkan Push Notification dan Google OAuth, kami menggunakan service yang disediakan oleh Firebase.

Nah, begitulah aplikasi kami berjalan. Komen jika ada pertanyaan, ya. Sekian, semoga bermanfaat!

--

--