0

Go Zero to Hero - Bài 34: Struct Tags và ứng dụng thực tế

Trong Golang, Struct Tags là một trong những tính năng mạnh mẽ nhất giúp cấu trúc dữ liệu của bạn kết nối linh hoạt với thế giới bên ngoài (API, Database, Validator) mà không cần viết quá nhiều mã nguồn lặp đi lặp lại.


1. Struct Tags là gì?

Struct Tag đơn giản là một đoạn chuỗi (string) được bọc trong cặp dấu backtick ` và đặt ở ngay sau kiểu dữ liệu của các Field bên trong Struct.

Nó đóng vai trò như một "siêu dữ liệu" (metadata). Bản thân trình biên dịch Go (Compiler) khi chạy nội bộ sẽ hoàn toàn ngó lơ các Tag này. Nhưng các thư viện chuẩn (như encoding/json) hoặc các thư viện bên thứ 3 (như GORM, Validator) sẽ sử dụng kỹ thuật Reflection (Phản chiếu) để đọc các Tag này lúc runtime, từ đó biết cách xử lý dữ liệu sao cho đúng.

Cú pháp chuẩn:

type TênStruct struct {
    TênField KiểuDữLiệu `key1:"value1" key2:"value2"`
}

2. Ứng dụng 1: Giao tiếp RESTful API với JSON

Đây là ứng dụng bạn sẽ gõ mòn bàn phím hàng ngày. Thư viện encoding/json cung cấp các tag để kiểm soát việc chuyển đổi từ Struct sang JSON (Marshal) và ngược lại (Unmarshal).

Hãy xem ví dụ về một Giao dịch nạp tiền:

package main

import (
    "encoding/json"
    "fmt"
)

type TopUpRequest struct {
    // Ép key JSON thành "transaction_id"
    TxnID     string  `json:"transaction_id"`
    
    // Nếu Amount = 0 (Zero Value), trường này sẽ BỊ XÓA khỏi chuỗi JSON (omitempty)
    Amount    float64 `json:"amount,omitempty"`
    
    // Dấu - nghĩa là TUYỆT ĐỐI BỎ QUA field này khi parse JSON
    // Cực kỳ hữu dụng để giấu mật khẩu, token nội bộ khỏi API Response
    SecretPin string  `json:"-"`
}

func main() {
    req := TopUpRequest{
        TxnID:     "AFC-2026-999",
        Amount:    0, // Cố tình để 0
        SecretPin: "123456",
    }

    // Convert Struct thành JSON byte array
    jsonData, _ := json.Marshal(req)
    
    // Kết quả: {"transaction_id":"AFC-2026-999"}
    // Amount biến mất vì omitempty, SecretPin biến mất vì dấu -
    fmt.Println(string(jsonData))
}

3. Ứng dụng 2: Tương tác Database (PostgreSQL/MySQL) với GORM

Khi viết Backend, hiếm ai dùng thư viện database/sql chay để viết từng câu query thô. Các hệ thống hiện đại thường sử dụng ORM (Object-Relational Mapping), và ở thế giới Go, cái tên số 1 chính là GORM.

GORM dùng Struct Tags để map chính xác một Field trong Go với một Cột (Column) trong bảng Database.

type TicketRecord struct {
    // Xác định đây là Khóa chính (Primary Key)
    ID          uint      `gorm:"primaryKey"`
    
    // Map với cột 'ticket_code', kiểu varchar(50), không được phép NULL
    TicketCode  string    `gorm:"column:ticket_code;type:varchar(50);not null"`
    
    // Cột lưu giá vé, mặc định là 0 nếu không insert
    Price       float64   `gorm:"column:price;default:0"`
    
    // Đánh index để tăng tốc độ truy vấn (tìm kiếm lịch sử quét thẻ)
    CardNumber  string    `gorm:"index"`
}

Nhờ những tag này, hàm db.AutoMigrate(&TicketRecord{}) của GORM có thể tự động sinh ra câu lệnh CREATE TABLE trong PostgreSQL cực kỳ chuẩn xác mà bạn không cần phải đụng tay vào SQL.


4. Ứng dụng 3: Ràng buộc dữ liệu (Validation)

Đừng bao giờ tin tưởng dữ liệu từ Client gửi lên! Nếu Frontend gửi một request trừ tiền vé, bạn phải chắc chắn ID không được rỗng và số tiền phải hợp lệ trước khi đẩy xuống tầng xử lý logic.

Thay vì viết hàng tá lệnh if req.Amount < 0, cộng đồng Go có một thư viện huyền thoại là go-playground/validator. Nó cũng xài Struct Tags!

type PaymentPayload struct {
    // Bắt buộc phải có (required), độ dài chính xác 16 ký tự
    CardNo string  `validate:"required,len=16"`
    
    // Bắt buộc phải có, giá trị từ 5,000 đến 500,000
    Amount float64 `validate:"required,min=5000,max=500000"`
    
    // Bắt buộc là định dạng email hợp lệ
    Email  string  `validate:"required,email"`
}

Chỉ cần gọi hàm validate.Struct(payload), nó sẽ quét qua tất cả các tag và trả về danh sách lỗi cực kỳ chi tiết nếu dữ liệu không thỏa mãn điều kiện. Các Web Framework như Gin hay Echo đều tích hợp sẵn bộ Validator này thông qua tag binding.


5. Bí mật hậu trường: Package reflect

Làm sao các thư viện kia đọc được những cái tag nằm chết cứng trong code?

Đó là nhờ gói reflect (Reflection) của Go. Reflection cho phép chương trình Go tự "nhìn" vào cấu trúc bộ nhớ của chính nó khi đang chạy.

Mặc dù bạn hiếm khi phải tự viết code Reflection (vì nó chậm và phức tạp), nhưng đây là cách nó hoạt động ở tầng lõi:

package main

import (
    "fmt"
    "reflect"
)

type Config struct {
    Host string `env:"DB_HOST" default:"localhost"`
}

func main() {
    c := Config{}
    
    // Lấy thông tin type của Struct
    t := reflect.TypeOf(c)
    
    // Lấy field đầu tiên (Host)
    field, _ := t.FieldByName("Host")
    
    // Trích xuất giá trị của Tag
    fmt.Println("Biến môi trường:", field.Tag.Get("env"))     // In ra: DB_HOST
    fmt.Println("Giá trị mặc định:", field.Tag.Get("default")) // In ra: localhost
}

Tổng kết

Struct Tags là vũ khí bí mật giúp Struct trong Go biến hóa khôn lường khi giao tiếp với thế giới bên ngoài:

  1. JSON (json): Kiểm soát input/output của API (đổi tên key, bỏ qua biến rỗng với omitempty, ẩn dữ liệu mật với -).
  2. Database (gorm): Ánh xạ cấu trúc bảng, khóa chính, khóa ngoại, index xuống PostgreSQL/MySQL.
  3. Validation (validate/binding): Kiểm tra tính hợp lệ của dữ liệu đầu vào mà không cần viết quá nhiều logic if-else.

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí