Trong bài viết này chúng ta sẽ cùng tìm hiểu khái niệm Middleware và cách chúng được sử dụng trong backend REST API.
Middleware là gì
Middleware là phần mềm trung gian để kết nối các thành phần trong hệ thống, phầm mềm hay service.
Trong lập trình backend, middleware nằm giữa Request và Response, có nhiệm vụ tiếp nhận và xử lý thông tin hỗ trợ cho các middleware tiếp theo hoặc cho chính Request/Response đó.
Trong các hệ thống phân tán, Middleware sẽ là các service trung gian để kết nối các thành phần quan trọng với nhau.
Phân loại Middleware
Dựa theo Wikipedia, ta có 4 loại middleware:
- Transactional: Xử lý những transaction đồng bộ và bất đồng bộ như một nhóm các yêu cầu liên kết từ các hệ thống phân tán, chẳng hạn như giao dịch ngân hàng hoặc thanh toán thẻ tín dụng.
- Message: Xử dụng trong các message queue và các kiến trúc gởi message, hỗ trợ giao tiếp đồng bộ và bất đồng bộ.
- Procedural: Các kiến trúc từ xa (remote) và cục bộ (local) để kết nối, chuyển đỏi và truy xuất các phản hồi của các giao tiếp hệ thống không đồng bộ.
- Object-oriented: Tương tự như phần mềm trung gian hướng thủ tục (Procedural), tuy nhiên, loại phần mềm trung gian này kết hợp các nguyên tắc thiết kế lập trình hướng đối tượng (OOP). Về mặt phân tích, thành phần của nó bao gồm các tham chiếu đối tượng, exceptions và kế thừa các thuộc tính thông qua các yêu cầu đối tượng phân tán.
Vì sao phải sử dụng Middleware
Mục đích sử dụng Middleware nhằm chi nhỏ các thành phần trong hệ thống và phần mềm, giúp dễ dàng tái sử dụng và lắp ghép cho nhiều trường hợp khác nhau.
Trong bài viết này mình sẽ tập trung vào middleware trong lập trình backend, cụ thể là REST API với Golang.
Xử lý một Request trong REST API sẽ bao gồm rất nhiều các logic nghiệp vụ. Trong đó sẽ có rất nhiều logic lặp đi lặp lại ở nhiều Request khác như: Logging, Authen, Parse body data,... Để có thể dễ dàng tái sử dụng cũng như phối hợp các logic trên, việc sử dụng Middleware là rất cần thiết.
Lấy ví dụ với trường hợp check Authorization (quyền truy cập) cho các Requests yêu cầu client gởi kèm access token. Nếu access token không hợp lệ hoặc hết hạn, Middlware có thể giúp phản hồi lỗi trước đi chạm đến các hàm xử lý tiếp theo.
Chính vì điều này, hầu hết các framework xây dựng website, REST API hỗ trợ Middleware như: Express, Gin, Echo, Lavarel,...
Sử dụng Middleware trong GIN Golang
Về bản chất các Middleware trong GIN được định nghĩa là các hàm HandlerFunc, tương đương một hàm xử lý Request bình thường:
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
Định nghĩa một Middleware đơn giản trong GIN
Để minh hoạ đơn giản, mình sẽ khai báo một Middleware in log mỗi khi có Request vào như sau:
func logger() gin.HandlerFunc {
return func(c *gin.Context) {
log.Printf("Request from %s: %s", c.ClientIP(), c.Request.URL.Path)
c.Next()
}
}
Hàm Next()
được sử dụng khi các bạn muốn chuyển tiếp Request tới các Handler/Middleware phía sau. Ngược lại hàm Abort()
sẽ dừng Request lại và Response về client ngay lập tức.
Sử dụng Middleware trong GIN
Có vài cách để dùng được Middleware trong GIN hay đúng hơn là giới hạn tầm hoạt động của Middleware:
Dùng cho tất cả request hay toàn bộ application
//...
func main() {
router := gin.New()
router.Use(logger())
router.Run()
}
//...
Với cách này tất cả Request đều sẽ chạy qua Middleware logger.
Dùng cho một group
//...
func main() {
router := gin.New()
v1 := router.Group("/v1", logger())
router.Run()
}
//...
Tất cả request /v1/*
đều sẽ chạy qua Middleware logger.
Dùng cho từng URL cụ thể
//...
func main() {
router := gin.New()
router.GET("/ping", logger(), func(c *gin.Context) {
c.JSON(200, gin.H{"data": "pong"})
})
router.Run()
}
//...
Chỉ áp dụng cho API GET /ping
.
Phối hợp liên tiếp các Middleware với nhau
//...
func main() {
router := gin.New()
router.GET("/ping", logger(), logger(), logger(), func(c *gin.Context) {
c.JSON(200, gin.H{"data": "pong"})
})
router.Run()
}
//...
Trên thực tế chúng ta có thể phối hợp không giới hạn số lượng Middleware để thực hiện các logic phức tạp hơn.
Case study phối hợp nhiều Middleware
- Khai báo Middleware thứ nhất chịu trách nhiệm kiểm tra Access Token và lấy ra thông tin User dựa trên Token. Nếu OK thì được phép Next(), ngược lại thì Abort() - gọi là
(1)
. - Khai báo Middleware thứ hai kiểm tra nếu role User là Admin/Mod thì được phép Next() ngược lại thì Arbort() - gọi là
(2)
.
Giả sử với API GET /v1/users/1
thì bạn chỉ cần dùng Middleware thứ nhất là đủ. Nhưng với các API trong group /admin
thì sẽ cần 2 Middlware theo đúng thứ tự (1)
, (2)
.
Đương nhiên bạn không cần phải khai báo thêm 1 Middleware để làm lại 2 công việc này. Đây là lợi thế của việc tái sử dụng và kết nối các Middleware với nhau để thực hiện các logic phức tạp hơn.
Mặc dù bài viết sử dụng Golang làm ví dụ, các bạn vẫn có thể sử dụng tư duy trên để sử dụng cho các framework tương đương. Chúc các bạn nắm vững được Middleware và vận dụng linh hoạt và hiệu quả nhé.
Việt Trần
Yêu thích tìm hiểu các công nghệ cốt lõi, kỹ thuật lập trình và thích chia sẻ chúng tới cộng đồng
follow me :
Bài viết liên quan
Giới thiệu Kiến trúc Backend for Frontend (BFF)
Nov 16, 2024 • 10 min read
Flask là gì? Hướng dẫn tạo Ứng dụng Web với Flask
Nov 15, 2024 • 7 min read
Webhook là gì? So sánh Webhook và API
Nov 15, 2024 • 8 min read
Spring Boot là gì? Hướng dẫn Khởi tạo Project Spring Boot với Docker
Nov 14, 2024 • 6 min read
Two-Factor Authentication (2FA) là gì? Vì sao chỉ Mật khẩu thôi là chưa đủ?
Nov 13, 2024 • 7 min read
Test-Driven Development (TDD) là gì? Hướng dẫn thực hành TDD
Nov 13, 2024 • 6 min read