Skip to main content

Implementing Hexagonal Architecture

ก่อนอื่นการจะเปลี่ยน Architecture แบบชุ้ย(ที่ทำใน Http handle บทความก่อน) ไปเป็น Hexagonal Architecture เราต้องสร้างหลายๆอย่างทำให้โค้ด base เปลี่ยนค่อนข้างเยอะแต่ก็จะขอให้ค่อยๆทำไปในความเร็วของตัวเองครับ

การจะทำ Hexagonal Architecture อย่างที่บอกไปใน introduction เราจำเป็นต้องมี Port ที่เป็น interface ก่อนซึ่ง interface ในที่นี้คือการกำหนดชื่อของ function รวมถึง parameter และ return statement ด้วย งั้นก่อนอื่นเรามาสร้าง folder เพื่อให้เข้าใจง่ายว่าส่วนไหนคือส่วนไหนของ Hexagonal Architecture กันดีกว่า ซึ่งในที่นี้เราจำเป็นต้องสร้าง 3 folder นั้นคือ repository, service, และ handler

เราจะเริ่มสร้างจากชั้นที่ลึกที่สุดที่เก็บข้อมูลไปถึงชั้นที่เอาข้อมูลที่ประมวณผลแล้วไปแสดงให้ user สามารถเรียกได้ หรือก็คือเราจะสร้าง repository -> service -> handler นั้นเอง

Repository

1. สร้างไฟล์ชื่อว่า user.go ขึ้นมาใน folder repository และโค้ดด้านในเราจะเก็บ struct (model ของข้อมูล) และ interface นั้นเอง 

package repository

type User struct {
	Id       int64  `json:"id"`
	Email    string `json:"email"`
	Password string `json:"password"`
	Secret   string `json:"secret"`
}

type UserRepository interface {
	CreateUser(email string, password string, secret string) (*User, error)
	CheckUser(email string) (*User, error)
	GetUsers() ([]*User, error)
}

ซึ่ง model พวกนี้สามารถเปลี่ยนแปลงได้ตามที่เราออกแบบไว้ได้เลย แต่ส่วนมากจะนำข้อมูลที่มีอยู่ใน database มาเป็น struct นั้นเอง ส่วน interface ก็สามารถเปแลี่ยนไปตามที่เราออกแบบได้เช่นกัน ซึ่ง method พวกนี้จะเป็นการ query, insert, update, หรือ delete ข้อมูลต่างๆที่เป็นขั้นสุดท้ายที่ติดต่อกับ database แต่ใน session นี้ขอให้มีเท่านี้ก่อนครับ

2. สร้างไฟล์ user_db.go ขึ้นมาใน folder เดียวกัน ซึ่งไฟล์นี้จะมาทำหน้าที่เป็น "adapter" ของเรานั้นเอง

package repository

import (
	"database/sql"
)

type userRepositoryDB struct {
	db *sql.DB
}

func NewRepositoryDB(db *sql.DB) userRepositoryDB {
	return userRepositoryDB{db: db}
}

func (u userRepositoryDB) CreateUser(email string, password string, secret string) (*User, error) {
	// implement me
	return nil, nil
}
func (u userRepositoryDB) CheckUser(email string) (*User, error) {
	// implement me
	return nil, nil
}

func (u userRepositoryDB) GetUsers() ([]*User, error) {
	// implement me
	return nil, nil
}

ใน file นี้จะมี struct ที่เก็บ instance ของ database ไว้ด้วยเพื่อที่จะสามารถทำ operation ต่างๆได้ (ในครั้งนี้เราจะใช้ MySQL กัน) ซึ่ง struct นี้จะเป็น private ที่จะไม่สามารถสร้างได้ในไฟล์อื่นแต่ต้องเรียกผ่าน function ที่สร้างไว้เท่านั้น และเนื่องจากเราจะทำให้ instance นี้เป็น adapter เราจึงจำเป็นที่ต้องมี function ต่างๆเหมือนกัน port ของเราทั้งหมดด้วย