1. Cơ Bản về Mô Hình và Trường (Field)
1.1. Giới Thiệu về Định Nghĩa Mô Hình
Trong một framework ORM, một mô hình (model) được sử dụng để mô tả mối quan hệ ánh xạ giữa các loại thực thể trong ứng dụng và các bảng cơ sở dữ liệu. Mô hình xác định các thuộc tính và mối quan hệ của thực thể, cũng như cấu hình cụ thể cho cơ sở dữ liệu liên kết với chúng. Trong framework ent, mô hình thường được sử dụng để mô tả các loại thực thể trong một đồ thị, như User
hoặc Group
.
Định nghĩa mô hình thường bao gồm mô tả về các trường (hoặc thuộc tính) của thực thể và cạnh (hoặc mối quan hệ), cũng như một số tùy chọn cụ thể cho cơ sở dữ liệu. Những mô tả này có thể giúp chúng ta xác định cấu trúc, thuộc tính và mối quan hệ của thực thể, và có thể được sử dụng để tạo cấu trúc bảng cơ sở dữ liệu tương ứng dựa trên mô hình.
1.2. Tổng Quan về Trường
Các trường là phần của mô hình mô tả các thuộc tính của thực thể. Chúng xác định các thuộc tính của thực thể, như tên, tuổi, ngày, v.v. Trong framework ent, các loại trường bao gồm các loại dữ liệu cơ bản khác nhau, như số nguyên, chuỗi, boolean, thời gian, v.v., cũng như một số loại cụ thể của SQL, như UUID, []byte, JSON, v.v.
Bảng dưới đây hiển thị các loại trường được hỗ trợ bởi framework ent:
Loại | Mô tả |
---|---|
int | Kiểu số nguyên |
uint8 | Kiểu số nguyên không dấu 8 bit |
float64 | Kiểu số thực |
bool | Kiểu boolean |
string | Kiểu chuỗi |
time.Time | Kiểu thời gian |
UUID | Kiểu UUID |
[]byte | Kiểu mảng byte (chỉ SQL) |
JSON | Kiểu JSON (chỉ SQL) |
Enum | Kiểu Enum (chỉ SQL) |
Khác | Các loại khác (ví dụ: Phạm vi Postgres) |
2. Chi Tiết Thuộc Tính Trường
2.1. Kiểu Dữ Liệu
Kiểu dữ liệu của một thuộc tính hoặc trường trong mô hình thực thể xác định hình thức dữ liệu có thể được lưu trữ. Điều này là một phần quan trọng của định nghĩa mô hình trong framework ent. Dưới đây là một số kiểu dữ liệu thông dụng trong framework ent
.
import (
"time"
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
// Mô hình người dùng.
type User struct {
ent.Schema
}
// Các trường của Người dùng.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age"), // Kiểu số nguyên
field.String("name"), // Kiểu chuỗi
field.Bool("active"), // Kiểu boolean
field.Float("score"), // Kiểu số thực
field.Time("created_at"), // Kiểu thời gian
}
}
-
int
: Đại diện cho giá trị số nguyên, có thể làint8
,int16
,int32
,int64
, v.v. -
string
: Đại diện cho dữ liệu chuỗi. -
bool
: Đại diện cho giá trị boolean, thường được sử dụng như các cờ. -
float64
: Đại diện cho số thực, cũng có thể sử dụngfloat32
. -
time.Time
: Đại diện cho thời gian, thường được sử dụng cho dấu thời gian hoặc dữ liệu ngày.
Các loại trường này sẽ được ánh xạ thành các loại tương ứng được hỗ trợ bởi cơ sở dữ liệu cơ bản. Ngoài ra, ent
hỗ trợ các loại phức tạp hơn như UUID
, JSON
, enum (Enum
), và hỗ trợ cho các loại cơ sở dữ liệu đặc biệt như []byte
(chỉ SQL) và Other
(chỉ SQL).
2.2. Giá Trị Mặc Định
Các trường có thể được cấu hình với các giá trị mặc định. Nếu giá trị tương ứng không được chỉ định khi tạo một thực thể, giá trị mặc định sẽ được sử dụng. Giá trị mặc định có thể là một giá trị cố định hoặc một giá trị được tạo động từ một hàm. Sử dụng phương thức .Default
để đặt một giá trị mặc định cố định, hoặc sử dụng .DefaultFunc
để đặt một giá trị mặc định được tạo động.
// Mô hình người dùng.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Default(time.Now), // Một giá trị mặc định cố định của time.Now
field.String("role").
Default("user"), // Một giá trị chuỗi không đổi
field.Float("score").
DefaultFunc(func() float64 {
return 10.0 // Một giá trị mặc định được tạo bởi một hàm
}),
}
}
2.3. Tùy chọn về Trường và Giá trị Số 0
Mặc định, trường là bắt buộc. Để khai báo một trường tùy chọn, sử dụng phương thức .Optional()
. Các trường tùy chọn sẽ được khai báo là trường có thể null trong cơ sở dữ liệu. Tùy chọn Nillable
cho phép các trường được thiết lập một cách rõ ràng thành nil
, phân biệt giữa giá trị số 0 của một trường và trạng thái không được thiết lập.
// Mô hình Người dùng.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("nickname").Optional(), // Trường tùy chọn không bắt buộc
field.Int("age").Optional().Nillable(), // Trường có thể null có thể được thiết lập thành nil
}
}
Khi sử dụng mô hình được xác định ở trên, trường age
có thể chấp nhận cả giá trị nil
để chỉ trạng thái không được thiết lập, cũng như giá trị số 0 khác nil
.
2.4. Tính Duy nhất của Trường
Các trường duy nhất đảm bảo không có giá trị trùng lặp trong bảng cơ sở dữ liệu. Sử dụng phương thức Unique()
để xác định một trường duy nhất. Khi thiết lập tính toàn vẹn dữ liệu là yêu cầu quan trọng, như trong trường hợp của địa chỉ email hoặc tên người dùng, các trường duy nhất nên được sử dụng.
// Mô hình Người dùng.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("email").Unique(), // Trường duy nhất để tránh các địa chỉ email trùng lặp
}
}
Điều này sẽ tạo ra một ràng buộc duy nhất trong cơ sở dữ liệu cơ bản để ngăn chặn việc chèn các giá trị trùng lặp.
2.5. Trường Chỉ số
Chỉ mục trường được sử dụng để cải thiện hiệu suất của các truy vấn cơ sở dữ liệu, đặc biệt là trong các cơ sở dữ liệu lớn. Trong khung việc ent
, phương thức .Indexes()
có thể được sử dụng để tạo các chỉ mục.
import "entgo.io/ent/schema/index"
// Mô hình Người dùng.
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("email"), // Tạo một chỉ mục trên trường 'email'
index.Fields("name", "age").Unique(), // Tạo một chỉ mục kết hợp duy nhất
}
}
Các chỉ mục có thể được sử dụng cho các trường được truy vấn thường xuyên, nhưng quan trọng nhấ là lưu ý rằng quá nhiều chỉ mục có thể dẫn đến giảm hiệu suất của các hoạt động ghi. Do đó, quyết định tạo chỉ mục nên được cân nhắc dựa trên hoàn cảnh thực tế.
2.6. Nhãn Tùy chỉnh
Trong khung việc ent
, bạn có thể sử dụng phương thức StructTag
để thêm các nhãn tùy chỉnh cho các trường cấu trúc thực thể được tạo ra. Các nhãn này rất hữu ích để thực hiện các hoạt động như mã hóa JSON và mã hóa XML. Trong ví dụ dưới đây, chúng ta sẽ thêm các nhãn JSON và XML tùy chỉnh cho trường name
.
// Các trường của Người dùng.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
// Thêm nhãn tùy chỉnh bằng cách sử dụng phương thức StructTag
// Ở đây, đặt nhãn JSON cho trường tên thành 'username' và bỏ qua nó khi trường trống (omitempty)
// Ngoài ra, đặt nhãn XML cho mã hóa thành 'name'
StructTag(`json:"username,omitempty" xml:"name"`),
}
}
Khi mã hóa với JSON hoặc XML, tùy chọn omitempty
chỉ ra rằng nếu trường name
trống, thì trường này sẽ được loại bỏ khỏi kết quả mã hóa. Điều này rất hữu ích để giảm kích thước của cơ thể phản hồi khi viết APIs.
Điều này cũng chỉ ra cách thiết lập nhiều nhãn cho cùng một trường cùng một lúc. Nhãn JSON sử dụng khóa json
, nhãn XML sử dụng khóa xml
, và chúng được phân tách bằng dấu cách. Những nhãn này sẽ được sử dụng bởi các chức năng thư viện như encoding/json
và encoding/xml
khi phân tích cú pháp cấu trúc để mã hóa hoặc giải mã.
3. Xác nhận và Quy định Trường
Xác nhận trường là một phần quan trọng của thiết kế cơ sở dữ liệu để đảm bảo tính nhất quán và tính hợp lệ của dữ liệu. Trong phần này, chúng ta sẽ nghiên cứu việc sử dụng các trình xác nhận tích hợp sẵn, các trình xác nhận tùy chỉnh và các hạn chế khác nhau để cải thiện tính toàn vẹn và chất lượng của dữ liệu trong mô hình thực thể.
3.1. Các Validator tích hợp sẵn
Khung công việc cung cấp một loạt các validator tích hợp sẵn để thực hiện các kiểm tra tính hợp lệ dữ liệu thông thường trên các loại trường khác nhau. Sử dụng các validator tích hợp sẵn này có thể đơn giản hóa quá trình phát triển và nhanh chóng xác định các phạm vi dữ liệu hợp lệ hoặc định dạng cho các trường.
Dưới đây là một số ví dụ về các validator trường tích hợp sẵn:
- Validator cho các loại số:
-
Positive()
: Xác thực nếu giá trị trường là số dương. -
Negative()
: Xác thực nếu giá trị trường là số âm. -
NonNegative()
: Xác thực nếu giá trị trường là số không âm. -
Min(i)
: Xác thực nếu giá trị trường lớn hơn một giá trị tối thiểu được quy địnhi
. -
Max(i)
: Xác thực nếu giá trị trường nhỏ hơn một giá trị tối đa được quy địnhi
.
-
- Validator cho loại
string
:-
MinLen(i)
: Xác thực độ dài tối thiểu của một chuỗi. -
MaxLen(i)
: Xác thực độ dài tối đa của một chuỗi. -
Match(regexp.Regexp)
: Xác thực nếu chuỗi khớp với biểu thức chính quy đã cho. -
NotEmpty
: Xác thực nếu chuỗi không rỗng.
-
Hãy xem một ví dụ thực tế về mã code. Trong ví dụ này, một model User
được tạo, bao gồm một trường loại số không âm age
và một trường email
có định dạng cố định:
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age").
Positive(),
field.String("email").
Match(regexp.MustCompile(`^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$`)),
}
}
3.2. Validator Tùy chỉnh
Mặc dù validator tích hợp sẵn có thể xử lý nhiều yêu cầu xác thực thông thường, đôi khi bạn có thể cần logic xác thực phức tạp hơn. Trong những trường hợp như vậy, bạn có thể viết các validator tùy chỉnh để đáp ứng các quy tắc kinh doanh cụ thể.
Một validator tùy chỉnh là một hàm nhận giá trị trường và trả về một error
. Nếu error
trả về không trống, nó cho biết xác thực thất bại. Định dạng tổng quát của một validator tùy chỉnh như sau:
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("phone").
Validate(func(s string) error {
// Xác minh nếu số điện thoại đáp ứng định dạng mong muốn
matched, _ := regexp.MatchString(`^\+?[1-9]\d{1,14}$`, s)
if !matched {
return errors.New("Định dạng số điện thoại không chính xác")
}
return nil
}),
}
}
Như đã thể hiện ở trên, chúng ta đã tạo một validator tùy chỉnh để xác thực định dạng số điện thoại.
3.3. Ràng buộc
Ràng buộc là các quy tắc áp đặt các quy tắc cụ thể trên một đối tượng cơ sở dữ liệu. Chúng có thể được sử dụng để đảm bảo tính chính xác và nhất quán của dữ liệu, như ngăn chặn việc nhập dữ liệu không hợp lệ hoặc xác định các mối quan hệ của dữ liệu.
Các ràng buộc cơ sở dữ liệu thông thường bao gồm:
- Ràng buộc khóa chính: Đảm bảo mỗi bản ghi trong bảng là duy nhất.
- Ràng buộc duy nhất: Đảm bảo giá trị của một cột hoặc sự kết hợp của các cột là duy nhất trong bảng.
- Ràng buộc khóa ngoại: Xác định mối quan hệ giữa các bảng, đảm bảo tính nguyên vẹn tham chiếu.
- Ràng buộc kiểm tra: Đảm bảo giá trị trường đáp ứng một điều kiện cụ thể.
Trong mô hình thực thể, bạn có thể xác định ràng buộc để duy trì tính nguyên vẹn dữ liệu như sau:
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("username").
Unique(), // Ràng buộc duy nhất để đảm bảo tên người dùng là duy nhất trong bảng.
field.String("email").
Unique(), // Ràng buộc duy nhất để đảm bảo email là duy nhất trong bảng.
}
}
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", User.Type).
Unique(), // Ràng buộc khóa ngoại, tạo mối quan hệ cạnh duy nhất với người dùng khác.
}
}
Tóm lại, việc xác thực trường và ràng buộc rất quan trọng để đảm bảo chất lượng dữ liệu tốt và tránh lỗi dữ liệu không mong đợi. Sử dụng các công cụ được cung cấp bởi khung công việc ent
có thể làm cho quá trình này đơn giản hơn và đáng tin cậy hơn.