1. พื้นฐานของโมเดลและฟิลด์

1.1. การแนะนำถึงการนิยามโมเดล

ในกรอบ ORM โมเดลถูกใช้เพื่ออธิบายความสัมพันธ์ของประเภทของตัวบ่งชี้ในแอปพลิเคชันและตารางฐานข้อมูล โมเดลนิยายคุณสมบัติและความสัมพันธ์ของตัวบ่งชี้ รวมทั้งการกำหนดค่าที่เฉพาะของฐานข้อมูลที่เกี่ยวข้องกับพวกเขา ในเกราะ ent โมเดลทั่งหมดจะถูกใช้เพื่ออธิบายประเภทตัวบ่งชี้ในกราฟ เช่น User หรือ Group

นิยายโมเดลภายในปกครองรวมถึงคำอธิบายของฟิลด์ของตัวบ่งชี้ (หรือคุณสมบัติ) และเส้น (หรือความสัมพันธ์) รวมทั้งตัวเลือกรองฐานข้อมูลที่เฉพาะของพวกเขา คำอธิบายเหล่านี้สามารถช่วยให้เรากำหนดโครงสร้างทุน หลักทรัพย์ และความสัมพันธ์ของสิ่งที่ใช้สร้างโครงสร้างตารางฐานข้อมูลที่สอดคล้องกันตามโมเดล

1.2. ภาพรวมของฟิลด์

ฟิลด์คือส่วนของโมเดลที่แทนคุณสมบัติของตัวบ่งชี้ พวกเขากำหนดคุณสมบัติของตัวบ่งชี้ เช่นชื่อ อายุ วันที่ เป็นต้น ในเกราะ ent ประเภทของฟิลด์รวมถึงชนิดข้อมูลพื้นฐานต่างๆ เช่น integer, string, boolean, time เป็นต้น รวมทั้งหนึ่งตัวเลือกรองฐานข้อมูลที่เฉพาะของ SQL เช่น UUID, []byte, JSON เป็นต้น

ตารางด้านล่างนี้แสดงประเภทของฟิลด์ที่รองรับโดยกรอก ent

ประเภท คำอธิบาย
int ชนิดจำนวนเต็ม
uint8 ชนิดเต็มบิตรบวนตัวที่ไม่มีเครียว
float64 ชนิดจุดลอย
bool ชนิดตรรกะ
string ชนิดสตริง
time.Time ชนิดเวลา
UUID ชนิด UUID
[]byte ชนิดอาร์เรย์ของไบต์ (เฉพาะ SQL)
JSON ชนิด JSON (เฉพาะ SQL)
Enum ชนิด Enum (เฉพาะ SQL)
อื่นๆ ชนิดอื่นๆ (เช่น, ช่วง Postgres)

2. รายละเอียดฟิลด์ของคุณสมบัติ

2.1. ประเภทข้อมูล

ประเภทข้อมูลของคุณสมบัติหรือฟิลด์ในโมเดลตัวบ่งชี้กำหนดรูปแบบของข้อมูลที่สามารถจัดเก็บได้ นี่เป็นส่วนสำคัญของการกำหนดโมเดลในกรอบ ent ด้านล่างคือบางประเภทข้อมูลที่ใช้มากในกรอบ ent

import (
    "time"

    "entgo.io/ent"
    "entgo.io/ent/schema/field"
)

// โครงการผู้ใช้.
type User struct {
    ent.Schema
}

// ฟิลด์ของผู้ใช้.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.Int("age"),             // ชนิดจำนวนเต็ม
        field.String("name"),         // ชนิดสตริง
        field.Bool("active"),         // ชนิดตรรกะ
        field.Float("score"),         // ชนิดจุดลอย
        field.Time("created_at"),     // ชนิดเวลา
    }
}
  • int: แทนค่าจำนวนเต็ม ที่สามารถเป็นได้เป็น int8, int16, int32, int64, เป็นต้น
  • string: แทนข้อมูลสตริง
  • bool: แทนค่าตรรกะที่ใช้เป็นธง
  • float64: แทนตัวเลขทศนิยม ยังสามารถใช้ float32 ได้อีก
  • time.Time: แทนเวลา ที่ใช้เป็นปัจจุบันหรือข้อมูลวันที่ที่กำหนดไว้

พวกเขาจะถูกแมปไปยังประเภทที่สนับสนุนโดยฐานข้อมูลต้นส่วนบน นอกจากนี้ ent ยังสนับสนุนประเภทที่ซับซ้อนมากขึ้น เช่น UUID, JSON, สมการ (Enum), และสนับสนุนประเภทฐานข้อมูลพิเศษเช่น []byte (เฉพาะ SQL) และ Other (เฉพาะ SQL)

2.2. ค่าเริ่มต้น

ฟิลด์สามารถกำหนดค่าเริ่มต้นได้ หากค่าที่เกี่ยวข้องยังไม่ได้ระบุเมื่อสร้างตัวบ่งชี้ ค่าเริ่มต้นจะถูกใช้ ค่าเริ่มต้นสามารถเป็นค่าคงที่หรือค่าที่สร้างขึ้นได้จากฟังก์ชัน ใช้ .Default เมธอดเพื่อกำหนดค่าเริ่มต้นแบบคงที่ หรือใช้ .DefaultFunc เพื่อกำหนดค่าเริ่มต้นที่สร้างขึ้นได้จากฟังก์ชัน

// โครงการผู้ใช้.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.Time("created_at").
            Default(time.Now),  // ค่าเริ่มต้นแบบคงที่ของ time.Now
        field.String("role").
            Default("user"),   // ค่าสตริงคงที่
        field.Float("score").
            DefaultFunc(func() float64 {
                return 10.0  // ค่าเริ่มต้นที่สร้างขึ้นจากฟังก์ชัน
            }),
    }
}

2.3. ความสามารถในการเลือกใช้และค่าศูนย์

ตามค่าเริ่มต้น ฟิลด์จำเป็นต้องกรอกข้อมูลเสมอ เพื่อกำหนดฟิลด์ที่เป็นตัวเลือก ให้ใช้เมธอด .Optional() ได้ เมธอดที่เลือกได้จะถูกกำหนดเป็นฟิลด์ที่สามารถเป็นค่าว่างในฐานข้อมูล ตัวเลือก Nillable อนุญาตให้ฟิลด์ถูกกำหนดโดยชัดเจนเป็น nil เพื่อแยกแยะระหว่างค่าศูนย์ของฟิลด์และสถานะที่ไม่ได้ตั้งค่า.

// โครงสร้างของผู้ใช้
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("nickname").Optional(), // ฟิลด์ที่เป็นตัวเลือกไม่จำเป็นต้องกรอก
        field.Int("age").Optional().Nillable(), // ฟิลด์ที่เป็น Nillable สามารถถูกกำหนดเป็น nil
    }
}

เมื่อใช้โมเดลที่กำหนดไว้ด้านบน ฟิลด์ age สามารถยอมรับทั้งค่า nil เพื่อแสดงว่าไม่ได้ตั้งค่า และค่าศูนย์ที่ไม่เป็น nil ได้เช่นกัน.

2.4. ความเป็นเอกลักษณ์ของฟิลด์

ฟิลด์ที่เป็นเอกลักษณ์จะตรวจสอบให้แน่ใจว่าไม่มีค่าที่ซ้ำกันในตารางฐานข้อมูล ใช้เมธอด Unique() เพื่อกำหนดฟิลด์ที่เป็นเอกลักษณ์ ขณะกำหนดความสำคัญในด้านความสมบูรณ์ข้อมูล อย่างเช่น อีเมลผู้ใช้หรือชื่อผู้ใช้งาน ควรใช้ฟิลด์ที่เป็นเอกลักษณ์.

// โครงสร้างของผู้ใช้
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("email").Unique(),  // ฟิลด์ที่เป็นเอกลักษณ์เพื่อหลีกเลี่ยงที่อยู่อีเมลที่ซ้ำกัน
    }
}

นี้จะสร้างข้อจำกัดที่เป็นเอกลักษณ์ในฐานข้อมูลข้องอุปกรณ์เพื่อป้องกันการเพิ่มค่าที่ซ้ำกัน.

2.5. การสร้างดัชนีของฟิลด์

การสร้างดัชนีของฟิลด์ใช้เพื่อปรับปรุงประสิทธิภาพของคิวรี่ฐานข้อมูล โดยเฉพาะในฐานข้อมูลที่ใหญ่ ในโครงสร้าง ent สามารถใช้เมธอด .Indexes() เพื่อสร้างดัชนี.

import "entgo.io/ent/schema/index"

// โครงสร้างของผู้ใช้
func (User) Indexes() []ent.Index {
    return []ent.Index{
        index.Fields("email"),  // สร้างดัชนีบนฟิลด์ 'email'
        index.Fields("name", "age").Unique(), // สร้างดัชนีผสมที่เป็นเอกลักษณ์
    }
}

การใช้ดัชนีสามารถใช้เมืองฟิลด์ที่ถูกคิวรี่บ่อย แต่สิ่งสำคัญที่สุดคือมีการใช้ดัชนีมากเกินไปอาจทำให้ประสิทธิภาพของการเขียนลดลง เพราะสถานการณ์จริงจะต้องมีการสมดุลในการตัดสินใจสร้างดัชนี.

2.6. แท็กที่กำหนดเอง

ในโครงสร้าง ent คุณสามารถใช้เมธอด StructTag เพื่อเพิ่มแท็กที่กำหนดเองในฟิลด์ของตัวเองที่ถูกสร้างขึ้น แท็กเหล่านี้เป็นประโยชน์อย่างมากสำหรับการนำไปใช้ในการเข้ารหัสแบบ JSON และ XML ในตัวอย่างด้านล่างนี้ เราจะเพิ่มแท็กแบบ JSON และ XML สำหรับฟิลด์ name.

// ฟิลด์ของผู้ใช้
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").
            // เพิ่มแท็กที่กำหนดเองโดยใช้เมธอด StructTag
            // ที่นี่ กำหนดแท็ก JSON สำหรับฟิลด์ชื่อเป็น 'username' และละทิ้งจากการเขียนเมื่อฟิลด์ว่าง (omitempty)
            // นอกจากนั้น กำหนดแท็ก XML สำหรับเข้ารหัสเป็น 'name'
            StructTag(`json:"username,omitempty" xml:"name"`),
    }
}

ขณะที่เข้ารหัสด้วย JSON หรือ XML ตัวเลือก omitempty บ่งบอกว่าหากฟิลด์ name ห่างเปล่า แล้วฟิลด์นี้จะถูกละทิ้งจากผลลัพธ์การเข้ารหัส สิ่งนี้เป็นประโยชน์อย่างมากในการลดขนาดของตัวรายการตอบกลับเมื่อเขียน API.

นี่ยังแสดงถึงว่าจะกำหนดแท็กหลายรายการสำหรับฟิลด์เดียวกันพร้อมกัน JSON ใช้แท็ก json XML ใช้แท็ก xml และพวกเขาถูกคั่นด้วยช่องว่าง แท็กเหล่านี้จะถูกใช้โดยฟังก์ชันไลบรารี่ เช่น encoding/json และ encoding/xml เมื่อทำการวิเคราะห์ข้อมูลไปใช้ในการเข้ารหัสหรือถอดรหัส.

3.1. ตัวตรวจสอบที่ซ่อนอยู่

Framework มีตัวตรวจสอบที่ซ่อนอยู่สำหรับการตรวจสอบความถูกต้องของข้อมูลที่พบบ่อยบนชนิดของฟิลด์ต่าง ๆ การใช้ตัวตรวจสอบที่ซ่อนอยู่นี้สามารถทำให้กระบวนการการพัฒนาง่ายขึ้นและกำหนดขอบเขตข้อมูลที่ถูกต้องหรือรูปแบบสำหรับฟิลด์ได้อย่างรวดเร็ว

ต่อไปนี้คือตัวอย่างของตัวตรวจสอบฟิลด์ที่ซ่อนอยู่:

  • ตัวตรวจสอบสำหรับชนิดตัวเลข:
    • Positive(): ตรวจสอบว่าค่าของฟิลด์เป็นจำนวนเต็มบวกหรือไม่
    • Negative(): ตรวจสอบว่าค่าของฟิลด์เป็นจำนวนเต็มลบหรือไม่
    • NonNegative(): ตรวจสอบว่าค่าของฟิลด์เป็นจำนวนเต็มบวกหรือศูนย์หรือไม่
    • Min(i): ตรวจสอบว่าค่าของฟิลด์มีค่ามากกว่าค่าขั้นต่ำที่กำหนด i
    • Max(i): ตรวจสอบว่าค่าของฟิลด์มีค่าน้อยกว่าค่าสูงสุดที่กำหนด i
  • ตัวตรวจสอบสำหรับชนิด string:
    • MinLen(i): ตรวจสอบความยาวขั้นต่ำของสตริง
    • MaxLen(i): ตรวจสอบความยาวสูงสุดของสตริง
    • Match(regexp.Regexp): ตรวจสอบว่าสตริงตรงกับ regular expression ที่กำหนด
    • NotEmpty: ตรวจสอบว่าสตริงไม่ว่างเปล่า

มาดูตัวอย่างการเขียนโค้ดที่เข้ากันได้กับการใช้งานกันในทางปฏิบัติ ในตัวอย่างนี้มีการสร้างโมเดล User ซึ่งรวมถึงฟิลด์ชนิดจำนวนเต็มบวก age และฟิลด์ email ที่มีรูปแบบคงที่:

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. ตัวตรวจสอบที่กำหนดเอง

แม้ว่าตัวตรวจสอบที่ซ่อนอยู่สามารถจัดการกับความต้องการการตรวจสอบที่ซับซ้อนได้มากมาย แต่บางครั้งคุณอาจต้องการตรวจสอบโลจิกที่ซับซ้อนมากขึ้น เช่นเดียวกับนี้ คุณสามารถเขียนตัวตรวจสอบที่กำหนดเองเพื่อตอบสนองกับกฎธุรกิจที่เฉพาะเจาะจงได้

ตัวตรวจสอบที่กำหนดเองคือฟังก์ชันที่รับค่าของฟิลด์และส่งคืน error ถ้า error ที่ส่งคืนไม่ว่างเปล่า จะหมายถึงการตวจสอบล้มเหลว รูปแบบทั่วไปของตัวตรวจสอบที่กำหนดเองจะเป็นดังนี้:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("phone").
            Validate(func(s string) error {
                // ตรวจสอบว่าเบอร์โทรตรงกับรูปแบบที่คาดหวังหรือไม่
                matched, _ := regexp.MatchString(`^\+?[1-9]\d{1,14}$`, s)
                if !matched {
                    return errors.New("รูปแบบเบอร์โทรไม่ถูกต้อง")
                }
                return nil
            }),
    }
}

เช่นที่แสดงในตัวอย่างข้างต้น เราได้สร้างตัวตรวจสอบที่กำหนดเองเพื่อตรวจสอบรูปแบบของเบอร์โทร

3.3. ข้อจำกัด

ข้อจำกัดคือกฎที่บังคับกำหนดกฎเฉพาะบนวัตถุในฐานข้อมูล ทำให้เกิดความถูกต้องและสอดคล้องข้อมูล เช่นการป้องกันการป้อนข้อมูลที่ไม่ถูกต้องหรือกำหนดความสัมพันธ์ข้อมูล

ข้อจำกัดที่พบบ่อยในฐานข้อมูลรวมถึง:

  • ข้อจำกัด Primary key: ให้แน่ใจว่าแต่ละบันทึกในตารางเป็นค่าที่ไม่ซ้ำกัน
  • ข้อจำกัด Unique: ให้แน่ใจว่าค่าของคอลัมน์หรือคอลัมน์ร่วมกันเป็นค่าที่ไม่ซ้ำกันในตาราง
  • ข้อจำกัด Foreign key: กำหนดความสัมพันธ์ระหว่างตาราง เพื่อให้มั่นใจถึงความสอดคล้องของการอ้างอิง
  • ข้อจำกัด Check: ให้แน่ใจว่าค่าของฟิลด์ตรงตามเงื่อนไขที่กำหนด

ในโมเดลของ entity คุณสามารถกำหนดข้อจำกัดเพื่อรักษาความถูกต้องของข้อมูลตามนี้:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("username").
            Unique(), // ข้อจำกัด Unique เพื่อรักษาให้ชื่อผู้ใช้เป็นค่าที่ไม่ซ้ำกันในตาราง
        field.String("email").
            Unique(), // ข้อจำกัด Unique เพื่อรักษาให้อีเมลเป็นค่าที่ไม่ซ้ำกันในตาราง
    }
}

func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("friends", User.Type).
            Unique(), // ข้อจำกัด Foreign key, สร้างความสัมพันธ์ด้วยผู้ใช้อื่นๆที่เป็นค่าที่ไม่ซ้ำกัน
    }
}

สรุปแล้ว การตรวจสอบฟิลด์และข้อจำกัดเป็นสิ่งสำคัญสำหรับการรักษาคุณภาพของข้อมูลและป้องกันข้อผิดพลาดของข้อมูลที่ไม่คาดคิด การใช้เครื่องมือที่ framework ent ต้องการสามารถทำให้กระบวนการนี้ง่ายขึ้นและน่าเชื่อถือมากขึ้นได้