Golang — Clean Architecture with Demo

Prach CM
4 min readDec 12, 2020

--

ก็จากหลายๆบทความก่อนหน้า จะเห็นว่า จริงๆแล้วผมมาจากสาย Java การวางโครงหลักๆ ก็จะเป็น MVC แต่ว่าใน Golang นั้นอีกคอนเซปนึงที่นิยมก็คือ นิยามของลุงบ๊อบนั้นเอง ( Uncle Bob’s Clean Architecture Concept ) ซึ่งมีหัวใจหลัก คือ เทสได้ code แต่ละส่วนกระทบกันน้อย และมีการทำงานจาก layer นอกไปใน !แล้วมันเป็นยังไงไปดูกัน เดี๋ยวมี demo ตอนท้ายให้เห็นภาพด้วย!

source : Clean Coder Blog

The Dependency Rule. This rule says that source code dependencies can only point inwards.

source code ต้องไล่ลำดับจากบน layer นอกสู่ใน เท่านั้น และส่วน layer ชั้นใน ต้องไม่รับรู้ความเป็นไปของ layer ชั้นนอก

มาเรามาเข้าใจคอนเซปลุงบ๊อบกันแบบภาษา programmer ง่ายๆก็คือ สมมติมี controller แล้วเรียก services(usecase) ในส่วนของ service นั้นจะต้องไม่มีการเรียก code ในส่วนของ controller มาใช้ เช่นกันกับ repository ก็ไม่กลับมาเรียก services เพื่อไม่ให้ code ในแต่ละ layer รบกวนกัน

We don’t want anything in an outer circle to impact the inner circles. by ลุงบ๊อบ

โดยลุงบ๊อบแกมีหลักการสำคัญ 5 ข้อให้ตามรอยก็คือ

  1. Independent of Frameworks การเขียน ต้องเป็นอิสระจาก framework หรือก็คือ ถ้ามี layer ไหนที่แตะต้องกับ framework แล้ว ก็แตะต้องไป แต่ส่วนอื่นไม่ควร เช่นหากใครเขียน gin framework ก็ไม่ควรโยน gin.context ไปสู่ repository โดยเราอาจให้ชั้น usecase กับ delivery จัดการกับ gin.context ก่อนแล้วค่อยคุยกับ repository ด้วย object อีกที ดังนั้น การนำชุด code ชุดนี้ไปใช้กับ framework อื่น หรือนำไปใส่ใน project อื่นก็จะทำได้ง่ายขึ้น
  2. Testable เมื่อ code แต่ละส่วนแยกกันแล้ว แต่ละส่วนก็ต้อง testing ได้ด้วยโดยที่ไม่ต้องเกี่ยวข้องกับ UI, Database, Web Server, หรือ external element อื่นๆ.
  3. Independent of UI ต้องแยกส่วน UI และ logic ออกจากกัน เพื่อให้สามารถเปลี่ยน UI ได้ เช่นใครที่เขียน web server โดยใช้ text template หรือ html template ก็ต้องแยกส่วนนี้ออกเพื่อในอนาคต เราจะเปลี่ยนไปใช้ frontend framework เช่น Vue หรือ Angular ก็ได้
  4. Independent of Database เราต้องสามารถเปลี่ยน database ได้ตลอด ไม่ว่าจะเป็น Oracle or SQL Server, for Mongo, BigTable, CouchDB, หรืออะไรก็แล้วแต่ ดังนั้น ตัว business logic (usecase) ต้องไม่เกี่ยวข้องกับ database หรือก็คือแยก repository ไว้สำหรับจัดการ database
  5. Independent of any external agency ตัว Business logic ต้องไม่รู้อะไรเลยในโลกใบนี้ นอกจากงานของมันเท่านั้น (555 ลุงงงงใจเย็นนน)

ซึ่งจากหลักการนี้เอง ก็ทำให้ code ที่เขียนแบบ clean นั้นมีความง่ายในการ หยิบ layer อื่นมาแทนที่ layer เดิมในลำดับเดียวกันได้ เพราะ แต่ละส่วนของ code สามารถตัดขาดจากกันได้ เท่านั้นยังไม่พอ ยังทำให้ ทุก layer สามารถ testing โดยไม่ข้องเกี่ยวกันอีกต่างหาก omg ตรงนี้นี่เองที่ทำให้ชีวิต TDD ของคุณง่ายขึ้นเป็นกอง ขอบคุณครับลุงบ๊อบ ผมจะพยายามเขียนให้ได้ และทำให้ลุงภูมิใจ 555555

ต่อมาครับ เพื่อให้การเขียน code เกิดขึ้นจริงตามหลักการ clean architecture และวาง layer ต่างๆเป็นไปตามอุดมการณ์ลุงแก ลุงแกเลยเขียนแบ่ง layer คร่าวๆไว้ให้เราดังนี้ (จากนอกเข้าใน)

  1. Framework and Drivers จัดการกับ framework และสิ่งที่เข้ามาสู่ application ของเรา เช่นแปลงค่า gin.context มาเป็น object และส่งต่อ และจัดการกับการส่ง ข้อมูลกลับคืนให้ UI และ จัดการ driver ของการเชื่อมต่อกับ datasource เช่นสร้าง db injection
  2. Interface Adapter (Controller & Presenter) ทำหน้าที่เพียงแค่ รับและจัดการข้อมูล เพื่อรอส่งต่อ และเรียกใช้ Usecase (specific business rules) ต่างๆ
  3. Usecase (Services) ใช้เก็บ code ส่วนที่เป็น specific business หรือคืองานเฉพาะหนึ่งงาน และอย่าลืมว่าเวลาเราเขียนส่วนนี้ ต้องไม่ dependency กับ entities database UI และ frameworks หรือคือ เมื่อมีการเปลี่ยนแปลง code ส่วนอื่น ตัว usecase ก็ไม่เปลี่ยนแปลง และเช่นกัน เมื่อ code ส่วนนี้เปลี่ยนแปลง ก็ต้องไม่กระทบ code ส่วนอื่นให้เปลี่ยนแปลงตามไปด้วย
  4. Entities (Models) เป็นส่วนที่อยู่ลึกที่สุด เปลี่ยนแปลงน้อยที่สุด ใช้เป็น policy ในการส่งต่อกับ application อื่นๆ และใช้เพื่อเก็บ data structure ที่ใช้ใน project เท่านั้น

ป่ะว่าแล้วก็เอามาเขียน ใน code กัน โดยใน project ที่ผมวาง จะแบ่งออกเป็น

  1. Database — จะทำหน้าที่เป็น ส่วนจัดการ DB connection และการ injection db to repositories
  2. Deliveries — จะทำหน้าที่เป็น ส่วนจัดการ Framework และ Controller และ Presenters
  3. Use Cases — จัดการเรื่อง business logic
  4. Repositories — จัดการเรื่องการ query
  5. Models — จัดการเรื่อง models
  6. Domains — ในส่วนนี้จะไม่เกี่ยวข้องกับ clean ซักทีเดียว แต่ผมแยกออกมาเพื่อเก็บ interface เพื่อให้มองเห็นภาพโปรเจคได้ง่าย ในครั้งเดียว อาจไม่เหมาะกับการใช้งานใน project ขนาดใหญ่ ที่มีหลาย module ใน project เดียว

มาเขียน code กันดีกว่า โดยผมจะเริ่มจากส่วนที่กระทบน้อยสุดไปสู่มากสุด ดังนั้นเราจะไล่จาก Models ออกไปสู่ Deliveries โดยเราจะใช้ go module project, gin framework และ connect db ด้วย gorm กันใครยังไม่เคยลองก็ลองอ่านที่นี่ดู

ส่วนแรก models โดยมี func TableName() เพื่อใช้บอก gorm ว่าให้ใช้ table Todo กับ model นี้

เพื่อให้เห็นภาพ project คร่าวๆ เราสร้าง domain.go เพื่อเก็บ interface ของ project

เมื่อได้โครงของ Repository และ Usecase แล้ว เราก็มา implement กัน

ในส่วนของ repository นั้นแต่ละ func จะทำหน้าที่เดียว คือ query เท่านั้นและไม่เกี่ยวข้องกับ business logic เช่นเดียวกันในส่วนของ usecase เราจะไม่ให้ usecase handle ตัว response data เองแต่จะส่งออกค่าไปให้ คนที่มาเรียกใช้งาน

และเพื่อส่งออกให้ UI ใช้งาน เราก็สร้าง route ต่อเลย

ที่ขาดไม่ได้ก็คือ DB connection ซึ่งในอนาคตเราอาจเปลี่ยน config ออกไปเก็บแบบ viper

เมื่อครบทุกอย่างก็ทำ main.go กัน

จะเห็นว่าใน main จะทำหน้าที่หลักคือการ connecting to database โดยนำ connection ที่ได้มาเก็บใน database.DB ซึ่งเป็นตัวแปรประเภท *gorm.DB หลังจากนั้นก็ทำการ inject dependency ต่างๆสู่ route หนึ่งในนั้นคือการนำ ตัวแปร DB จาก pakage database มา inject สู่ reposirtory เมื่อเสร็จทุกขั้นตอน ก็เปิดการ connect service ผ่าน http

โอเคครับก่อนที่จะ run service ก็คือ dump DB และ import postman collection ซึ่งก็สามารถไปเอาจาก krittawatcode/go-todo-clean-arch (github.com) ได้เลย หรือจะ pull โปรเจคไปรันเลยก็ได้ แล้วก็อย่าลืมเซท DB config ใหม่ตาม DB ของคุณด้วยหละ

เมื่อได้ทุกอย่างครบก็ลองรัน DB และ Service และยิงเล่นกันเถอะ

[POST] http://localhost:8080/v1/todo
[GET] http://localhost:8080/v1/todo

โอเคครับ น่าจะเท่านี้แหละครับกับ concept clean architecture และ demo todo API

คิดเห็นยังไง วางโครงกันยังไง มาแลกเปลี่ยนกันได้นะครับ แล้วเจอกันใหม่ในเรื่อง testing :D

--

--

Prach CM
Prach CM

Written by Prach CM

Gopher <3 and Blockchain Dev

Responses (2)