1. การแนะนำ ent

Ent เป็นกรอบการทำงานของ entity ที่ถูกพัฒนาโดย Facebook โดยเฉพาะสำหรับภาษา Go เพื่อความง่ายในการสร้างและบำรุงรักษาแอปพลิเคชันแบบโมเดลข้อมูลมาตรฐานในขนาดใหญ่ เฟรมเวิร์ก ent ที่ใช้หลักการหลักการดังต่อไปนี้:

  • สามารถสร้างโครงสร้างฐานข้อมูลในรูปแบบกราฟได้อย่างง่าย
  • กำหนดโครงสร้างในรูปแบบของโค้ด Go
  • ดำเนินการประเภททางสถิติขึ้นอยู่กับการสร้างโค้ด
  • การเขียนคิวรีฐานข้อมูลและการเดินทางกราฟเป็นเรื่องง่าย
  • สามารถขยายออกและปรับแต่งได้อย่างง่ายโดยใช้ Go templates

2. การติดตั้งสภาพแวดล้อม

ก่อนที่จะเริ่มใช้เฟรมเวิร์ก ent ตรวจสอบให้แน่ใจว่าภาษา Go ได้ถูกติดตั้งในสภาพแวดล้อมการพัฒนาของคุณ

หากไดเรกทอรีโปรเจคของคุณอยู่นอกจาก GOPATH หรือหากคุณไม่คุ้นเคยกับ GOPATH คุณสามารถใช้คำสั่งต่อไปนี้เพื่อสร้างโปรเจคโมดูล Go ใหม่:

go mod init entdemo

การดำเนินการนี้จะเริ่มต้นโมดูล Go ใหม่และสร้างไฟล์ go.mod ใหม่สำหรับโปรเจค entdemo ของคุณ

3. กำหนดการเขียนชุดตัวอย่างแรก

3.1. การสร้าง Schema ใช้ ent CLI

ต้องการที่จะเรียกใช้คำสั่งต่อไปนี้ในไดเรกทอรีหลักของโปรเจคของคุณเพื่อสร้าง schema ที่ชื่อ User ด้วยเครื่องมือ ent CLI:

go run -mod=mod entgo.io/ent/cmd/ent new User

คำสั่งข้างต้นจะสร้าง schema User ในไดเรกทอรี entdemo/ent/schema/ ด้วย:

ไฟล์ entdemo/ent/schema/user.go:

package schema

import "entgo.io/ent"

// User holds the schema definition for the User entity.
type User struct {
    ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
    return nil
}

// Edges of the User.
func (User) Edges() []ent.Edge {
    return nil
}

3.2. เพิ่มฟิลด์

ต่อไปเราจำเป็นต้องเพิ่มนิยามการเขียนฟิลด์ไปยัง Schema ของ User ด้านล่างเป็นตัวอย่างการเพิ่มฟิลด์สองตัวให้กับ Entity ของ User

ไฟล์ที่แก้ไข entdemo/ent/schema/user.go:

package schema

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

// Fields of the User.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.Int("age").
            Positive(),
        field.String("name").
            Default("unknown"),
    }
}

ชิ้นโค้ดนี้กำหนดฟิลด์สองตัวสำหรับโมเดล User: age และ name, โดยที่ age เป็นจำนวนเต็มบวกและ name คือสตริงที่มีค่าเริ่มต้นเป็น "unknown"

3.3. การสร้าง Entity ในฐานข้อมูล

หลังจากกำหนด schema เสร็จ, คุณจำเป็นต้องรันคำสั่ง go generate เพื่อสร้างเลขว่าในการเข้าถึงฐานข้อมูล

รันคำสั่งต่อไปนี้ในไดเรกทอรีหลักของโปรเจคของคุณ:

go generate ./ent

คำสั่งนี้จะสร้างโค้ด Go ที่สอดคล้องกับ schema ที่กำหนดไว้ก่อนหน้านี้ รวมถึงการสร้างโครงสร้างไฟล์ต่อไปนี้:

ent
├── client.go
├── config.go
├── context.go
├── ent.go
├── generate.go
├── mutation.go
... (ข้ามไฟล์หลายไฟล์เพื่อความกระจ่าง)
├── schema
│   └── user.go
├── tx.go
├── user
│   ├── user.go
│   └── where.go
├── user.go
├── user_create.go
├── user_delete.go
├── user_query.go
└── user_update.go

4.1. เริ่มต้นเชื่อมต่อฐานข้อมูล

เพื่อเชื่อมต่อกับฐานข้อมูล MySQL เราสามารถใช้ฟังก์ชั่น Open ที่ ent framework จัดเตรียมไว้ ก่อนอื่นให้นำเข้าไดรเวอร์ MySQL แล้วจึงระบุสตริงการเชื่อมต่อที่ถูกต้องเพื่อเริ่มต้นเชื่อมต่อกับฐานข้อมูล

package main

import (
    "context"
    "log"

    "entdemo/ent"
    
    _ "github.com/go-sql-driver/mysql" // นำเข้าไดรเวอร์ MySQL
)

func main() {
    // ใช้ ent.Open เพื่อเชื่อมต่อกับฐานข้อมูล MySQL
    // อย่าลืมแทนที่สตริงตำแหน่งว่า "your_username", "your_password", และ "your_database" ด้านล่างด้วย
    client, err := ent.Open("mysql", "your_username:your_password@tcp(localhost:3306)/your_database?parseTime=True")
    if err != nil {
        log.Fatalf("เปิดการเชื่อมต่อไปยัง mysql ล้มเหลว: %v", err)
    }
    defer client.Close()

    // ทำการรันเครื่องมือกำหนดรูปแบบโดยอัตโนมัติ
    ctx := context.Background()
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("การสร้างโครงสร้างทรัพยากรล้มเหลว: %v", err)
    }
    
    // ทำกิริยาธุรกิจเพิ่มเติมได้เขียนได้ที่นี่
}

4.2. สร้าง Entity

การสร้าง Entity ผู้ใช้ เริ่มจากการสร้างอ็อบเจ็ค Entity ใหม่และทำการบันทึกลงในฐานข้อมูลโดยใช้เมธอด Save หรือ SaveX ต่อไปนี้คือรหัสที่สาธิตการสร้าง Entity ผู้ใช้ใหม่ และเริ่มต้นสองฟิลด์ age และ name

// ฟังก์ชัน CreateUser ใช้สร้าง Entity ผู้ใช้ใหม่
func CreateUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
    // ใช้ client.User.Create() เพื่อสร้างคำขอสำหรับการสร้าง User,
    // จากนั้นเชือการใช้ SetAge และ SetName เพื่อตั้งค่าค่าฟิลด์ของ Entity
    u, err := client.User.
        Create().
        SetAge(30).    // ตั้งค่าอายุผู้ใช้
        SetName("a8m"). // ตั้งค่าชื่อผู้ใช้
        Save(ctx)     // เรียกใช้ Save เพื่อบันทึก Entity ลงในฐานข้อมูล
    if err != nil {
        return nil, fmt.Errorf("การสร้างผู้ใช้ล้มเหลว: %w", err)
    }
    log.Println("สร้างผู้ใช้แล้ว: ", u)
    return u, nil
}

ในฟังก์ชัน main คุณสามารถเรียกใช้ฟังก์ชัน CreateUser เพื่อสร้าง Entity ผู้ใช้ใหม่

func main() {
    // ...ส่วนของการเชื่อมต่อกับฐานข้อมูลถูกข้าม

    // สร้าง Entity ผู้ใช้
    u, err := CreateUser(ctx, client)
    if err != nil {
        log.Fatalf("การสร้างผู้ใช้ล้มเหลว: %v", err)
    }
    log.Printf("สร้างผู้ใช้: %#v\n", u)
}

4.3. การค้นหา Entity

ในการค้นหา Entity เราสามารถใช้ตัวสร้างคำขอโดย ent ที่สร้างขึ้น โดยระบุเงื่อนไขสอบถาม ต่อไปนี้คือรหัสที่สาธิตการค้นหาผู้ใช้ที่ชื่อ "a8m":

// ฟังก์ชัน QueryUser ใช้สอบถาม Entity ผู้ใช้ที่มีชื่อที่กำหนด
func QueryUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
    // ใช้ client.User.Query() เพื่อสร้างคำขอสำหรับ User,
    // จากนั้นเชือง  Where method เพื่อเพิ่มเงื่อนไขสอบถาม เช่น การสอบถามตามชื่อผู้ใช้
    u, err := client.User.
        Query().
        Where(user.NameEQ("a8m")).      // เพิ่มเงื่อนไขสอบถาม ในกรณีนี้ชื่อคือ "a8m"
        Only(ctx)                      // การใช้ Only บอกว่ามีคาดหวังแค่หนึ่งผลลัพธ์
    if err != nil {
        return nil, fmt.Errorf("การสอบถามผู้ใช้ล้มเหลว: %w", err)
    }
    log.Println("ผู้ใช้ที่คืน: ", u)
    return u, nil
}

ในฟังก์ชัน main คุณสามารถเรียกใช้ฟังก์ชัน QueryUser เพื่อสอบถาม Entity ผู้ใช้

func main() {
    // ...ส่วนการเชื่อมต่อกับฐานข้อมูลและการสร้างผู้ใช้ถูกข้าม

    // สอบถาม Entity ผู้ใช้
    u, err := QueryUser(ctx, client)
    if err != nil {
        log.Fatalf("การสอบถามผู้ใช้ล้มเหลว: %v", err)
    }
    log.Printf("ผู้ใช้ที่ถูกสอบถาม: %#v\n", u)
}

5.1. เข้าใจเส้นขอบและเส้นขอบการกลับ

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

เส้นขอบการกลับคือการอ้างอิงย้อนกลับไปยังเส้นขอบ โดยตรรกะแทนความสัมพันธ์ย้อนกลับระหว่างเอนทิตี้ แต่ไม่สร้างความสัมพันธ์ใหม่ในฐานข้อมูล ตัวอย่างเช่น ผ่านเส้นขอบการกลับของ Car เราสามารถหา User ที่เป็นเจ้าของรถคันนี้ได้

ความสำคัญสำคัญของเส้นขอบและเส้นขอบการกลับ อยู่ในความสามารถที่ทำให้การนำทางระหว่างเอนทิตี้ที่เกี่ยวข้องกันเป็นไปอย่างเข้าใจและตรงไปตรงมา

เคล็ดลับ: ใน ent เส้นขอบสอดคล้องกับคีย์ต่างประเภทในฐานข้อมูลแบบด้านและถูกใช้เพื่อกำหนดความสัมพันธ์ระหว่างตาราง

5.2. กำหนดเส้นขอบในสกีม่า

ก่อนอื่น เราจะใช้ ent CLI เพื่อสร้างสกีม่าเริ่มต้นสำหรับ Car และ Group:

go run -mod=mod entgo.io/ent/cmd/ent new Car Group

ต่อมา ในสกีม่าของ User เรากำหนดเส้นขอบด้วย Car เพื่อแทนความสัมพันธ์ระหว่างผู้ใช้และรถ เราสามารถเพิ่มเส้นขอบ cars ที่ชี้ไปยังประเภท Car ในเอนทิตี้ของผู้ใช้ ทำให้เข้าใจว่าผู้ใช้สามารถเป็นเจ้าของรถได้หลายคัน:

// entdemo/ent/schema/user.go

// เส้นขอบของผู้ใช้
func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("cars", Car.Type),
    }
}

หลังจากกำหนดเส้นขอบแล้ว เราจำเป็นต้องเรียกใช้ go generate ./ent อีกครั้งเพื่อสร้างรหัสที่สอดคล้องกัน

5.3. ดำเนินการกับข้อมูลของเส้นขอบ

การสร้างรถที่เกี่ยวข้องกับผู้ใช้เป็นกระบวนการที่ง่ายดาย โดยให้เอนทิตี้ผู้ใช้ เราสามารถสร้างเอนทิตี้รถใหม่และเชื่อมโยงกับผู้ใช้ได้:

import (
    "context"
    "log"
    "entdemo/ent"
    // ตรวจสอบการนำเข้าของการกำหนดสกีม่าของรถ
    _ "entdemo/ent/schema"
)

func CreateCarsForUser(ctx context.Context, client *ent.Client, userID int) error {
    user, err := client.User.Get(ctx, userID)
    if err != nil {
        log.Fatalf("การเรียกข้อมูลผู้ใช้ล้มเหลว: %v", err)
        return err
    }

    // สร้างรถใหม่และเชื่อมโยงกับผู้ใช้
    _, err = client.Car.
        Create().
        SetModel("Tesla").
        SetRegisteredAt(time.Now()).
        SetOwner(user).
        Save(ctx)
    if err != nil {
        log.Fatalf("การสร้างรถสำหรับผู้ใช้ล้มเหลว: %v", err)
        return err
    }

    log.Println("รถถูกสร้างและเชื่อมโยงกับผู้ใช้แล้ว")
    return nil
}

หากเราต้องการค้นหารถที่เป็นเจ้าของโดยผู้ใช้ ทำได้โดยง่ายดาย หากเราต้องการดึงรายการของรถที่เป็นเจ้าของโดยผู้ใช้ทั้งหมด เราสามารถทำดังนี้:

func QueryUserCars(ctx context.Context, client *ent.Client, userID int) error {
    user, err := client.User.Get(ctx, userID)
    if err != nil {
        log.Fatalf("การเรียกข้อมูลผู้ใช้ล้มเหลว: %v", err)
        return err
    }

    // ค้นหารถทั้งหมดที่เป็นเจ้าของโดยผู้ใช้
    cars, err := user.QueryCars().All(ctx)
    if err != nil {
        log.Fatalf("การค้นหารถล้มเหลว: %v", err)
        return err
    }

    for _, car := range cars {
        log.Printf("รถ: %v, รุ่น: %v", car.ID, car.Model)
    }
    return nil
}

ผ่านขั้นตอนที่กล่าวมาข้างต้น เราไม่เพียงเรียนรู้วิธีกำหนดเส้นขอบในสกีม่า แต่ยังแสดงวิธีการสร้างและค้นหาข้อมูลที่เกี่ยวข้องกับเส้นขอบ

6. การค้นหาและการสืบค้นกราฟ

6.1. เข้าใจโครงสร้างกราฟ

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

6.2. การท่องบทของโครงสร้างกราฟ

การเขียนโค้ดการท่องบทกราฟเกิดจากการค้นหาและเชื่อมโยงข้อมูลผ่านเส้นเชื่อมระหว่างตัวแทน ตัวอย่างง่ายด้านล่างนี้แสดงถึงวิธีการท่องโครงสร้างกราฟใน ent:

import (
    "context"
    "log"

    "entdemo/ent"
)

// GraphTraversal คือตัวอย่างการท่องโครงสร้างกราฟ
func GraphTraversal(ctx context.Context, client *ent.Client) error {
    // ค้นหาผู้ใช้ชื่อ "Ariel"
    a8m, err := client.User.Query().Where(user.NameEQ("Ariel")).Only(ctx)
    if err != nil {
        log.Fatalf("การค้นหาผู้ใช้ล้มเหลว: %v", err)
        return err
    }

    // ท่องรถทั้งหมดที่เป็นของ Ariel
    cars, err := a8m.QueryCars().All(ctx)
    if err != nil {
        log.Fatalf("การค้นหารถล้มเหลว: %v", err)
        return err
    }
    for _, car := range cars {
        log.Printf("Ariel มีรถรุ่น: %s", car.Model)
    }

    // ท่องกลุ่มทั้งหมดที่ Ariel เป็นสมาชิก
    groups, err := a8m.QueryGroups().All(ctx)
    if err != nil {
        log.Fatalf("การค้นหากลุ่มล้มเหลว: %v", err)
        return err
    }
    for _, g := range groups {
        log.Printf("Ariel เป็นสมาชิกของกลุ่ม: %s", g.Name)
    }

    return nil
}

โค้ดด้านบนเป็นตัวอย่างขั้นพื้นฐานของการท่องโครงสร้างกราฟ ซึ่งจะค้นหาผู้ใช้ก่อน แล้วจึงท่องหารถและกลุ่มของผู้ใช้นั้น

7. การแสดงแผนฐานฐานข้อมูล

7.1. การติดตั้งเครื่องมือ Atlas

เพื่อแสดงแผนฐานฐานข้อมูลที่ถูกสร้างโดย ent เราสามารถใช้เครื่องมือ Atlas การติดตั้ง Atlas ง่ายมาก เช่น ใน macOS คุณสามารถติดตั้งด้วย brew:

brew install ariga/tap/atlas

หมายเหตุ: Atlas เป็นเครื่องมือย้ายข้อมูลฐานข้อมูลที่สามารถจัดการโครงสร้างตารางสำหรับฐานข้อมูลต่างๆ รายละเอียดเพิ่มเติมเกี่ยวกับ Atlas จะถูกกล่าวถึงในบทต่อไป

7.2. สร้าง ERD และสกีมา SQL

การใช้ Atlas เพื่อดูและส่งออกแผนฐานฐานข้อมูลเป็นเรื่องง่ายมาก หลังจากติดตั้ง Atlas คุณสามารถใช้คำสั่งต่อไปนี้เพื่อดูแผนฐานสมตารฐี-ความสัมพันธ์ (ERD):

atlas schema inspect -d [database_dsn] --format dot

หรือสร้างสกีมา SQL โดยตรง:

atlas schema inspect -d [database_dsn] --format sql

ที่ [database_dsn] ชี้ไปยังชื่อของแหล่งข้อมูล (DSN) ของฐานข้อมูลของคุณ ตัวอย่างเช่นสำหรับฐานข้อมูล SQLite อาจจะเป็น:

atlas schema inspect -d "sqlite://file:ent.db?mode=memory&cache=shared" --format dot

ผลลัพธ์ที่ถูกสร้างโดยคำสั่งเหล่านี้สามารถแปลงให้เป็นมุมมองหรือเอกสารเพิ่มเติมโดยใช้เครื่องมือที่เกี่ยวข้อง

8. การย้าย Schema

8.1. การย้ายแบบอัตโนมัติและการย้ายเวอร์ชัน

ent รองรับกลยุทธ์การย้ายแบบอัตโนมัติและแบบย้ายเวอร์ชัน การย้ายแบบอัตโนมัติคือกระบวนการการตรวจสอบและนำการเปลี่ยนแปลงโครงสร้างในระหว่างการใช้งาน ที่เหมาะสำหรับการพัฒนาและทดสอบ การย้ายเวอร์ชันที่เป็นกระบวนการสร้างสคริปต์การย้ายและต้องมีการทบทวนและทดสอบอย่างรอบคอบก่อนการใช้งานในการดำเนินงาน

เคล็ดลับ: สำหรับการย้ายแบบอัตโนมัติ ให้อ้างถึงเนื้อหาในส่วน 4.1

8.2. การทำการย้ายเวอร์ชัน

กระบวนการการย้ายแบบเวอร์ชันนำมาซึ่งสร้างไฟล์การย้ายผ่าน Atlas ดังต่อไปนี้คือคำสั่งที่เกี่ยวข้อง:

ในการสร้างไฟล์การย้าย:

atlas migrate diff -d ent/schema/path --dir migrations/dir

ภายหลังไฟล์การย้ายเหล่านี้สามารถนำไปใช้กับฐานข้อมูล:

atlas migrate apply -d migrations/dir --url database_dsn

ก่อนและตามกระบวนการนี้ คุณสามารถรักษาประวัติการย้ายฐานข้อมูลไว้ในระบบควบคุมรุ่นและยืนยันการทบทวนก่อนทุกการย้าย

เคล็ดลับ: อ้างอิงโค้ดตัวอย่างได้ที่ https://github.com/ent/ent/tree/master/examples/start