code ด้วย Clean Architecture
มาเริ่ม code กัน
เราจะนำ Diagram จากหน้าที่แล้วมา code ไปในแต่ละส่วนกัน โดยจะทำการวาง Structure ประมาณนี้ออกมา
เมื่อลองมาเรียบเรียงเป็น code
.
├── adapters --> ส่วนของ adapter
│ ├── gorm_order_repository.go
│ └── http_order_handler.go
├── entities --> ส่วนของ entities
│ └── order.go
├── main.go --> เป็นตัวหลักที่เชื่อมทั้งหมดเข้าด้วยกัน
└── usecases --> ส่วนของ use case
├── order_repository.go
└── order_use_case.go
Entities
เริ่มจากส่วนแรกคือ Entities คือส่วนของ Order Entity
ที่เป็นการประกาศโครงสร้างของข้อมูล Order เอาไว้ โดยไฟล์ที่เกี่ยวข้องจะมีเพียงไฟล์เดียวคือ order.go
.
├── adapters --> ส่วนของ adapter
│ ├── gorm_order_repository.go
│ └── http_order_handler.go
├── entities --> ส่วนของ entities
│ └── order.go
├── main.go --> เป็นตัวหลักที่เชื่อมทั้งหมดเข้าด้วยกัน
└── usecases --> ส่วนของ use case
├── order_repository.go
└── order_use_case.go
Idea ของ Entities คือการประกาศโครงสร้างของ Data ไว้ ดังนั้น code ของ Entities จึงเป็นเพียงการประกาศ struct ของข้อมูลเพื่อเป็นการบอกว่าข้อมูลมีหน้าตาออกมาประมาณไหน
และนี่คือ code ของ order.go
โดยเป็นการประกาศโครงสร้างของ Order data เอาไว้ว่าจะเก็บ 2 ค่าคือ ID และ Total
package entities
type Order struct {
ID uint
Total float64
}
Usecase
ส่วนต่อมา Use case คือส่วนของการใช้งาน ซึ่งในที่นี่คือ function createOrder
สำหรับการสร้าง Order ซึ่งจะเก็บเอาไว้ใน Order Service
- โดย
Order Service
นั้นก็จะอ้างอิงโครงสร้างของ Order ตามOrder Entity
ที่อยู่ใน layer ด้านในสุดอีกที - เพิ่มเติมอีกตัวคือ ในการจัดการกับ Use case ต้องมีการจัดการผ่าน
Repository
(เป็นตัวแทนของการคุยกับ Database) ด้วย ดังนั้น ในการทำ Use case ต้องมีการเพิ่ม interface ของ Repository ด้วย เพื่อเป็นการบอกไปยังAdapter
ที่จะ implement - ว่าจะต้องส่งคำสั่งไหนมาบ้างเพื่อให้ใช้งานตาม Use case ได้
ดังนั้น file ที่เกี่ยวข้องจะมี 2 files คือ
order_repository.go
เป็นตัวแทนของ interface ของ Repositoryorder_use_case.go
เป็นไฟล์สำหรับการเก็บ Business Logic ของ use case ไว้
.
├── adapters --> ส่วนของ adapter
│ ├── gorm_order_repository.go
│ └── http_order_handler.go
├── entities --> ส่วนของ entities
│ └── order.go
├── main.go --> เป็นตัวหลักที่เชื่อมทั้งหมดเข้าด้วยกัน
└── usecases --> ส่วนของ use case
├── order_repository.go
└── order_use_case.go
และนี่คือส่วน code ของทั้ง 2 files
- order_use_case.go
- order_repository.go
package usecases
import (
"mike/entities"
)
type OrderUseCase interface {
CreateOrder(order entities.Order) error
}
type OrderService struct {
repo OrderRepository
}
func NewOrderService(repo OrderRepository) OrderUseCase {
return &OrderService{repo: repo}
}
func (s *OrderService) CreateOrder(order entities.Order) error {
return s.repo.Save(order)
}
package usecases
import "mike/entities"
type OrderRepository interface {
Save(order entities.Order) error
}
อธิบาย code
order_repository.go
ทำการเก็บ Repository ของ Order ไว้ จึงมี code เพียงแค่ interface ของOrderRepository
เท่านั้นและเป็นการบอกว่า Repository ต้องมี methodSave()
มาด้วย จึงจะตรงตามOrderRepository
order_use_case.go
ทำการเก็บคำสั่งสำหรับจัดการสร้าง order เอาไว้ซึ่งก็คือCreateOrder()
โดยจะทำการ implement ผ่าน method ของ usecase ไว้ในตัวชื่อOrderUseCase
และจะมีOrderService
เป็นตัวแทนของการรับOrderRepository
จากภายนอก (ที่ส่งเข้ามาจาก Adapter) มาอีกที
Adapter
ต่อมา Adapter คือส่วนของการแปลงข้อมูลเพื่อทำการส่งไปยัง Use case เพื่อให้ Use case สามารถจัดการต่อได้ถูกได้จะมีทั้งหมด 2 ส่วน (โดยจะแยกออกเป็น 2 files) คือ
gorm_order_repository.go
(GORM Order Repository) คือส่วนที่จะเก็บ function สำหรับจัดการ database เอาไว้ โดยจะเก็บคำสั่งที่เกี่ยวข้องกับการ query database ไว้ โดยจะต้อง implement ตาม spec ของ interface ที่กำหนดไว้ในOrder Service
http_order_handler.go
(HTTP Order Handler) คือส่วนที่จะเก็บ function สำหรับจัดการ data ที่ผ่านเข้ามาทาง HTTP Request โดยทำการแปลงข้อมูลให้ถูกต้องตาม Pattern ของ Entities เพื่อส่งให้Order Service
สามารถจัดการต่อได้
ไฟล์ที่แตะก็จะมี 2 files นี้
.
├── adapters --> ส่วนของ adapter
│ ├── gorm_order_repository.go
│ └── http_order_handler.go
├── entities --> ส่วนของ entities
│ └── order.go
├── main.go --> เป็นตัวหลักที่เชื่อมทั้งหมดเข้าด้วยกัน
└── usecases --> ส่วนของ use case
├── order_repository.go
└── order_use_case.go
และนี่คือหน้าตา code ของ Adapter ทั้ง 2 ตัว
- gorm_order_repository.go
- http_order_handler.go
package adapters
import (
"gorm.io/gorm"
"mike/usecases"
"mike/entities"
)
type GormOrderRepository struct {
db *gorm.DB
}
func NewGormOrderRepository(db *gorm.DB) usecases.OrderRepository {
return &GormOrderRepository{db: db}
}
func (r *GormOrderRepository) Save(order entities.Order) error {
return r.db.Create(&order).Error
}
package adapters
import (
"github.com/gofiber/fiber/v2"
"mike/entities"
"mike/usecases"
)
type HttpOrderHandler struct {
orderUseCase usecases.OrderUseCase
}
func NewHttpOrderHandler(useCase usecases.OrderUseCase) *HttpOrderHandler {
return &HttpOrderHandler{orderUseCase: useCase}
}
func (h *HttpOrderHandler) CreateOrder(c *fiber.Ctx) error {
var order entities.Order
if err := c.BodyParser(&order); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request"})
}
if err := h.orderUseCase.CreateOrder(order); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.Status(fiber.StatusCreated).JSON(order)
}
อธิบาย code เพิ่มเติม
gorm_order_repository.go
ทำการ implementSave()
โดยทำการใส่ query ที่เกี่ยวข้องกับการจัดการ Order เข้าไป (ซึ่งก็คือคำสั่ง Gorm)http_order_handler.go
ทำการ implementHttpOrderHandler
โดยเป็นการรับ ตัวแทนของ Use case มาเพื่อใช้คำสั่งภายในOrder Service
(คำสั่งสำหรับการสร้าง Order) และทำการสร้าง methodCreateOrder()
เพื่อใช้สำหรับการเรียกใช้งานจากฝั่งของ HTTP Request ออกมา โดยจะเรียกไปยังOrder Service
และทำการแปลงข้อมูลจาก HTTP Request มาเป็นข้อมูลของOrder Entity
เพื่อให้สามารถใช้งานที่Order Service
ได้