1. Dasar Model dan Field

1.1. Pengenalan Definisi Model

Pada kerangka ORM, model digunakan untuk menggambarkan hubungan pemetaan antara jenis entitas dalam aplikasi dan tabel database. Model ini mendefinisikan properti dan hubungan dari entitas, serta konfigurasi khusus database yang terkait dengan mereka. Dalam kerangka ent, model biasanya digunakan untuk menggambarkan jenis entitas dalam graf, seperti Pengguna atau Grup.

Definisi model biasanya mencakup deskripsi bidang entitas (atau properti) dan sisi (atau hubungan), serta beberapa opsi khusus database. Deskripsi ini dapat membantu kita mendefinisikan struktur, properti, dan hubungan entitas, dan dapat digunakan untuk menghasilkan struktur tabel database yang sesuai berdasarkan model.

1.2. Tinjauan Field

Field adalah bagian dari model yang mewakili properti entitas. Mereka mendefinisikan properti dari entitas, seperti nama, usia, tanggal, dll. Dalam kerangka ent, tipe field mencakup berbagai tipe data dasar, seperti integer, string, boolean, time, dll, serta beberapa tipe khusus SQL, seperti UUID, []byte, JSON, dll.

Tabel di bawah ini menunjukkan tipe-tipe field yang didukung oleh kerangka ent:

Tipe Deskripsi
int Tipe integer
uint8 Tipe integer 8-bit tanpa tanda
float64 Tipe bilangan desimal
bool Tipe boolean
string Tipe string
time.Time Tipe waktu
UUID Tipe UUID
[]byte Tipe array byte (hanya SQL)
JSON Tipe JSON (hanya SQL)
Enum Tipe Enum (hanya SQL)
Lainnya Tipe lainnya (mis., Rentang Postgres)

2. Rincian Properti Field

2.1. Tipe Data

Tipe data dari atribut atau field dalam model entitas menentukan bentuk data yang dapat disimpan. Ini adalah bagian penting dari definisi model dalam kerangka ent. Berikut adalah beberapa tipe data yang umum digunakan dalam kerangka ent.

import (
    "time"

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

// Skema Pengguna.
type Pengguna struct {
    ent.Schema
}

// Bidang-bidang Pengguna.
func (Pengguna) Fields() []ent.Field {
    return []ent.Field{
        field.Int("usia"),             // Tipe integer
        field.String("nama"),         // Tipe string
        field.Bool("aktif"),         // Tipe boolean
        field.Float("skor"),         // Tipe bilangan desimal
        field.Time("dibuat_pada"),     // Tipe timestamp
    }
}
  • int: Mewakili nilai integer, yang bisa berupa int8, int16, int32, int64, dll.
  • string: Mewakili data string.
  • bool: Mewakili nilai boolean, umumnya digunakan sebagai tanda.
  • float64: Mewakili bilangan desimal, juga dapat menggunakan float32.
  • time.Time: Mewakili waktu, umumnya digunakan untuk timestamp atau data tanggal.

Tipe field ini akan dipetakan ke tipe-tipe yang sesuai didukung oleh database yang mendasarinya. Selain itu, ent mendukung tipe-tipe yang lebih kompleks seperti UUID, JSON, enum (Enum), dan dukungan untuk tipe-tipe database khusus seperti []byte (hanya SQL) dan Lainnya (hanya SQL).

2.2. Nilai Default

Field dapat dikonfigurasi dengan nilai default. Jika nilai yang sesuai tidak ditentukan saat membuat entitas, nilai default akan digunakan. Nilai default bisa berupa nilai tetap atau nilai yang dihasilkan secara dinamis dari sebuah fungsi. Gunakan metode .Default untuk mengatur nilai default statis, atau gunakan .DefaultFunc untuk mengatur nilai default yang dihasilkan secara dinamis.

// Skema Pengguna.
func (Pengguna) Fields() []ent.Field {
    return []ent.Field{
        field.Time("dibuat_pada").
            Default(time.Now),  // Nilai default tetap dari time.Now
        field.String("peran").
            Default("pengguna"),   // Nilai string konstan
        field.Float("skor").
            DefaultFunc(func() float64 {
                return 10.0  // Nilai default yang dihasilkan oleh sebuah fungsi
            }),
    }
}

2.3. Opsi Keterwajiban dan Nilai Nol

Secara default, bidang-bidang diwajibkan. Untuk mendeklarasikan suatu bidang opsional, gunakan metode .Optional(). Bidang opsional akan dinyatakan sebagai bidang-bidang yang dapat bernilai null di dalam basis data. Opsional Nillable memungkinkan bidang-bidang untuk secara eksplisit diatur sebagai nil, membedakan antara nilai nol dari suatu bidang dan suatu keadaan yang belum diatur.

// Skema pengguna.
func (Pengguna) Fields() []ent.Field {
    return []ent.Field{
        field.String("namaPanggilan").Optional(), // Bidang opsional tidak diwajibkan
        field.Int("umur").Optional().Nillable(), // Bidang yang dapat diatur sebagai nil
    }
}

Dengan menggunakan model yang telah didefinisikan di atas, bidang umur dapat menerima baik nilai nil untuk menunjukkan belum diatur, maupun nilai nol non-nil.

2.4. Unik Bidang

Bidang unik memastikan bahwa tidak ada nilai duplikat di dalam tabel basis data. Gunakan metode Unique() untuk mendefinisikan suatu bidang unik. Ketika menjaga integritas data sebagai suatu persyaratan yang kritis, seperti untuk alamat email atau nama pengguna pengguna, bidang unik harus digunakan.

// Skema pengguna.
func (Pengguna) Fields() []ent.Field {
    return []ent.Field{
        field.String("email").Unique(),  // Bidang unik untuk menghindari alamat email ganda
    }
}

Ini akan membuat suatu kendala unik di dalam basis data yang mendasarinya untuk mencegah penyisipan nilai-nilai duplikat.

2.5. Pemasangan Indeks Bidang

Pemasangan indeks bidang digunakan untuk meningkatkan kinerja kueri basis data, terutama dalam basis data yang besar. Di dalam kerangka kerja ent, metode .Indexes() dapat digunakan untuk membuat indeks.

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

// Skema pengguna.
func (Pengguna) Indexes() []ent.Index {
    return []ent.Index{
        index.Fields("email"),  // Buat indeks pada bidang 'email'
        index.Fields("nama", "umur").Unique(), // Buat indeks komposit unik
    }
}

Indeks dapat dimanfaatkan untuk bidang-bidang yang sering ditanya, namun penting untuk dicatat bahwa terlalu banyak indeks dapat mengakibatkan penurunan kinerja operasi tulis. Oleh karena itu, keputusan untuk membuat indeks harus seimbang berdasarkan keadaan yang sebenarnya.

2.6. Tag Kustom

Di dalam kerangka kerja ent, Anda dapat menggunakan metode StructTag untuk menambahkan tag-tag kustom ke bidang-bidang struktur entitas yang dihasilkan. Tag-tag ini sangat berguna untuk menerapkan operasi seperti penyandian JSON dan penyandian XML. Pada contoh di bawah, kita akan menambahkan tag-tag JSON dan XML kustom untuk bidang nama.

// Bidang-bidang Pengguna.
func (Pengguna) Fields() []ent.Field {
    return []ent.Field{
        field.String("nama").
            // Tambahkan tag-tag kustom menggunakan metode StructTag
            // Di sini, aturlah tag JSON untuk bidang nama menjadi 'username' dan abaikan jika bidang tersebut kosong (omitempty)
            // Juga, aturlah tag XML untuk penyandian menjadi 'nama'
            StructTag(`json:"username,omitempty" xml:"name"`),
    }
}

Saat menyandikan dengan JSON atau XML, opsi omitempty menunjukkan bahwa jika bidang nama kosong, maka bidang ini akan dihilangkan dari hasil penyandian. Ini sangat berguna untuk mengurangi ukuran tubuh respons saat menulis API.

Ini juga menunjukkan bagaimana mengatur tag-tag ganda untuk bidang yang sama secara bersamaan. Tag-tag JSON menggunakan kunci json, tag-tag XML menggunakan kunci xml, dan mereka dipisahkan oleh spasi. Tag-tag ini akan digunakan oleh fungsi-fungsi perpustakaan seperti encoding/json dan encoding/xml saat menguraikan struktur untuk penyandian atau penguraian.

3. Validasi Bidang dan Kendala

Validasi bidang adalah aspek penting dari desain basis data untuk memastikan konsistensi dan validitas data. Di bagian ini, kita akan membahas penggunaan validator bawaan, validator kustom, dan berbagai kendala untuk meningkatkan integritas dan kualitas data dalam model entitas.

3.1. Validator Bawaan

Framework menyediakan serangkaian validator bawaan untuk melakukan pemeriksaan validitas data umum pada berbagai jenis bidang. Dengan menggunakan validator bawaan ini, proses pengembangan dapat disederhanakan dan rentang data yang valid atau format bidang dapat dengan cepat ditentukan.

Berikut beberapa contoh validator bawaan bidang:

  • Validator untuk tipe numerik:
    • Positive(): Memvalidasi apakah nilai bidang merupakan angka positif.
    • Negative(): Memvalidasi apakah nilai bidang merupakan angka negatif.
    • NonNegative(): Memvalidasi apakah nilai bidang merupakan angka non-negatif.
    • Min(i): Memvalidasi apakah nilai bidang lebih besar dari nilai minimum i yang diberikan.
    • Max(i): Memvalidasi apakah nilai bidang lebih kecil dari nilai maksimum i yang diberikan.
  • Validator untuk tipe string:
    • MinLen(i): Memvalidasi panjang minimum dari sebuah string.
    • MaxLen(i): Memvalidasi panjang maksimum dari sebuah string.
    • Match(regexp.Regexp): Memvalidasi apakah string cocok dengan ekspresi reguler yang diberikan.
    • NotEmpty: Memvalidasi apakah string tidak kosong.

Mari kita lihat contoh kode praktis. Pada contoh ini, dibuat model User, yang mencakup bidang bertipe bilangan bulat non-negatif age dan bidang email dengan format tetap:

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 Kustom

Meskipun validator bawaan dapat menangani banyak kebutuhan validasi umum, terkadang Anda mungkin memerlukan logika validasi yang lebih kompleks. Dalam kasus seperti itu, Anda dapat menulis validator kustom untuk memenuhi aturan bisnis tertentu.

Validator kustom merupakan fungsi yang menerima nilai bidang dan mengembalikan error. Jika error yang dikembalikan tidak kosong, itu menunjukkan kegagalan validasi. Format umum dari validator kustom adalah sebagai berikut:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("phone").
            Validate(func(s string) error {
                // Verifikasi apakah nomor telepon memenuhi format yang diharapkan
                cocok, _ := regexp.MatchString(`^\+?[1-9]\d{1,14}$`, s)
                if !cocok {
                    return errors.New("Format nomor telepon tidak benar")
                }
                return nil
            }),
    }
}

Seperti yang ditunjukkan di atas, kita telah membuat validator kustom untuk memvalidasi format nomor telepon.

3.3. Kendala

Kendala adalah aturan yang menegakkan aturan tertentu pada objek basis data. Mereka dapat digunakan untuk menjamin kebenaran dan konsistensi data, seperti mencegah input data yang tidak valid atau menentukan hubungan data.

Kendala basis data umum meliputi:

  • Kendala kunci primer: Memastikan bahwa setiap catatan dalam tabel bersifat unik.
  • Kendala unik: Memastikan bahwa nilai kolom atau kombinasi kolom bersifat unik di tabel.
  • Kendala kunci asing: Mendefinisikan hubungan antara tabel, memastikan integritas referensial.
  • Kendala periksa: Memastikan bahwa nilai bidang memenuhi kondisi spesifik.

Dalam model entitas, Anda dapat mendefinisikan kendala untuk menjaga integritas data sebagai berikut:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("username").
            Unique(), // Kendala unik untuk memastikan username unik dalam tabel.
        field.String("email").
            Unique(), // Kendala unik untuk memastikan email unik dalam tabel.
    }
}

func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("friends", User.Type).
            Unique(), // Kendala kunci asing, menciptakan hubungan tepi unik dengan pengguna lain.
    }
}

Secara keseluruhan, validasi bidang dan kendala sangat penting untuk memastikan kualitas data yang baik dan menghindari kesalahan data yang tidak terduga. Memanfaatkan alat yang disediakan oleh kerangka kerja ent dapat membuat proses ini lebih sederhana dan dapat diandalkan.