1. D trong SOLID
Nếu là 1 developer đã từng tiếp nhận nhiều source code khác nhau, thì việc đọc những code cũ là 1 công việc vô cùng đau đầu bởi rõ ràng không phải developer nào cũng "siêng" ghi lại document cũng như viết mã code thật sự "clean". Chưa kể việc viết thêm những mã code phục vụ tính năng mới trong những source code lâu đời và dirty cũng gây ra không ít khó khăn. Ví dụ như là code thêm tính năng nhưng lại ảnh hưởng tính năng cũ, hay đụng phải những technial dept mà những developer "hào phóng" trước đó để lại. SOLID là nguyên tắc là đa phần developer hướng tới và áp dụng, những nguyên tắc trong SOLID giúp cho việc mở rộng, phát triển code dễ dàng hơn. Bảo trì code cũng như giúp những người mới có thể đọc code dễ dàng hơn. D là Dependecies Injection và nội dung trong đó chủ yếu:
- Các module cấp cao không nên phụ thuộc vào các modules cấp thấp.
- Cả 2 nên phụ thuộc vào abstraction.
- Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại ( Các class giao tiếp với nhau thông qua interface, không phải thông qua implementation.)
2. Áp dụng Dependecies Injection với Wire trong Golang
Trong golang, có các thư viện hỗ trợ cho dependencies injection khá tốt và trong số đó, thư viện mình dùng khá quen thuộc là google-wire. Google-wire là cơ chế tự động generate các mã code nhúng các interface đã khai báo mà chúng ta không cần chủ động, việc ta quan tâm là đưa các interface vào, và wire sẽ làm phần việc còn lại.
Link google-wire: https://github.com/google/wire
Sau đây mình sẽ code ví dụ, ở các ví dụ sau mình sẽ setup 1 server api , 1 server consumer có dùng wire
Đầu tiên ở terminal ứng dụng golang , chạy lệnh
go install github.com/google/wire/cmd/wire@latest
để cài đặt thư viện wire vô máy
Gõ wire
để kiểm tra cài đặt thành công hay chưa ở terminal
Ở ứng dụng api này, mình sẽ dùng labstack-echo để làm server http api
Đầu tiên mình sẽ khởi tạo HealthService. Ở health.go, mình khai báo interface cho HealthService
type IHealthService interface { GetHealth() string
}
Tạo HealthService và implement IHealthService
type healthService struct {
} func (h healthService) GetHealth() string { return "Health"
}
Viết hàm khởi tạo HealthService
func NewHealthService() IHealthService { return &healthService{}
}
Như vậy mình đã xong việc khai báo cũng như Interface của HealthService Vậy file code đầy đủ của HealthService sẽ là
type IHealthService interface { GetHealth() string
} type healthService struct {
} func (h healthService) GetHealth() string { return "Health"
} func NewHealthService() IHealthService { return &healthService{}
}
Ở file main.go, mình sẽ thực hiện việc khai báo ứng dụng app api và thực hiện Inject cho HealthService
type ApiApplication struct { IHealthService service.IHealthService
} func NewApiApplication(IHealthService service.IHealthService) ApiApplication { return ApiApplication{IHealthService: IHealthService}
} func (api ApiApplication) RunApp() { e := echo.New() e.GET("/", func(c echo.Context) error { response := api.IHealthService.GetHealth() return c.String(http.StatusOK, response) }) e.Logger.Fatal(e.Start(":1323"))
}
Ở ví dụ trên, mình đã thực hiện khởi tạo ứng dụng API cũng như inject HealthService và phục vụ cho 1 api, ta có thể thấy, ở đây ApiApplication chỉ đang Inject đúng interface của HealthService. Vậy hàm NewApiApplication sẽ dùng ở đâu? Tiếp theo mình sẽ tạo 1 file wire.go và thực hiện Inject các interface vào
func InitApplication() (ApiApplication, func()) { wire.Build( NewHealthService, NewApiApplication, ) return ApiApplication{}, func() { }
}
Nói rõ hơn thì mọi interface mà mình muốn Inject vào đều sẽ khai báo khởi tạo new ở đây. Sau đó mình chạy lệnh wire ./... Khi chạy lệnh này, thư việc wire sẽ rà soát toàn bộ khai báo interface ở thư mục wire.go, tạo ra 1 file mới là wire_gen.go, trong thư mục wire_gen.go sẽ chưa toàn bộ code inject vào. wire_gen.go
func InitApplication() (ApiApplication, func()) { iHealthService := service.NewHealthService(iCassandraProvider) apiApplication := NewApiApplication(iHealthService) return apiApplication, func() { }
}
tiếp theo để start app thì ở func main() sẽ code
app,cleanup := InitApplication()
defer cleanUp()
app.RunApp()
Giải thích về function cleanUp khi trả về sẽ chứa các func mà ta muốn thực hiện sau khi đóng app, ví dụ các hàm close database, warning. Sau này khi thực hiện 1 inject khác, chúng ta chỉ cần chạy lệnh wire./... để thực hiện generate ra 1 file wire_gen.go mới là xong, ta không cần chủ động thực inject gì khác. Nếu có 1 interface mới cần inject thì vô file wire.go để khai báo và chạy wire ./...