1. Model ve Field Temelleri

1.1. Model Tanımına Giriş

Bir ORM çerçevesinde, bir model uygulama içindeki varlık türleri ile veritabanı tabloları arasındaki eşleşme ilişkisini açıklamak için kullanılır. Model, varlığın özelliklerini ve ilişkilerini, bunlarla ilişkili veritabanı özgü yapılandırmaları tanımlar. Ent çerçevesinde, modeller genellikle Kullanıcı veya Grup gibi grafikte varlık türlerini tanımlamak için kullanılır.

Model tanımları genellikle varlığın alanlarının (veya özelliklerinin) ve kenarlarının (veya ilişkilerinin) yanı sıra bunlarla ilişkili veritabanı özgü seçeneklerin açıklamalarını içerir. Bu açıklamalar, bize varlığın yapısını, özelliklerini ve ilişkilerini tanımlamamıza yardımcı olur ve modele dayalı olarak karşılık gelen veritabanı tablo yapısını oluşturmak için kullanılabilir.

1.2. Alan Genel Bakışı

Alanlar, varlık özelliklerini temsil eden modelin bir parçasıdır. Varlığın özelliklerini (örneğin: isim, yaş, tarih vb.) tanımlar. Ent çerçevesinde, alan tipleri, tamsayı, dize, mantıksal, zaman vb. gibi çeşitli temel veri tiplerini içerir; ayrıca UUID, []byte, JSON gibi bazı SQL özel tiplerini de içerir.

Aşağıdaki tablo, ent çerçevesi tarafından desteklenen alan tiplerini göstermektedir:

Tip Açıklama
int Tamsayı tipi
uint8 8-bit işaretsiz tamsayı tipi
float64 Ondalık sayı tipi
bool Mantıksal tip
string Dize tipi
time.Time Zaman tipi
UUID UUID tipi
[]byte Bayt dizisi tipi (Sadece SQL)
JSON JSON tipi (Sadece SQL)
Enum Enum tipi (Sadece SQL)
Diğer Diğer tipler (ör. PostgreSQL Aralığı)

2. Alan Özelliği Detayları

2.1. Veri Tipleri

Bir varlık modelindeki özniteliğin veya alanın veri tipi depolanabilecek veri biçimini belirler. Bu, ent çerçevesinde model tanımının kritik bir parçasıdır. Aşağıda, ent çerçevesinde sıkça kullanılan bazı veri tipleri bulunmaktadır.

import (
    "time"

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

// Kullanıcı şeması.
type User struct {
    ent.Schema
}

// Kullanıcının alanları.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.Int("age"),             // Tamsayı tipi
        field.String("name"),         // Dize tipi
        field.Bool("active"),         // Mantıksal tip
        field.Float("score"),         // Ondalık sayı tipi
        field.Time("created_at"),     // Zaman damgası tipi
    }
}
  • int: Tamsayı değerleri temsil eder, int8, int16, int32, int64 vb. olabilir.
  • string: Dize verisini temsil eder.
  • bool: Mantıksal değerleri temsil eder, genellikle bayrak olarak kullanılır.
  • float64: Ondalık sayıları temsil eder, aynı zamanda float32 kullanılabilir.
  • time.Time: Zamanı temsil eder, genellikle zaman damgaları veya tarih verileri için kullanılır.

Bu alan tipleri, altta yatan veritabanı tarafından desteklenen karşılık gelen tiplere eşlenecektir. Ek olarak, ent, UUID, JSON, enum'lar (Enum) gibi daha karmaşık tipleri ve []byte (Sadece SQL) gibi özel veritabanı tiplerini ve Diğer (Sadece SQL) desteğini destekler.

2.2. Varsayılan Değerler

Alanlar, varsayılan değerlerle yapılandırılabilir. Bir varlık oluşturulurken karşılık gelen değer belirtilmezse, varsayılan değer kullanılacaktır. Varsayılan değer, sabit bir değer veya bir işlevden dinamik olarak oluşturulan bir değer olabilir. Statik bir varsayılan değer belirlemek için .Default yöntemini kullanın veya dinamik olarak oluşturulan bir varsayılan değer belirlemek için .DefaultFunc'ı kullanın.

// Kullanıcı şeması.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.Time("created_at").
            Default(time.Now),  // time.Now'un sabit varsayılan değeri
        field.String("role").
            Default("user"),   // Sabit bir dize değeri
        field.Float("score").
            DefaultFunc(func() float64 {
                return 10.0  // Bir işlev tarafından oluşturulan varsayılan değer
            }),
    }
}

2.3. Alan Opsiyonellik ve Sıfır Değerler

Varsayılan olarak, alanlar zorunludur. Bir isteğe bağlı alanı tanımlamak için .Optional() yöntemini kullanın. İsteğe bağlı alanlar veritabanında null olarak bildirilecektir. Nillable seçeneği, alanların açıkça nil olarak ayarlanmasına izin verir, bir alanın sıfır değeri ve ayarlanmamış bir durum arasındaki farkı belirtir.

// Kullanıcı şeması.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("takmaAd").Optional(), // Opsiyonel alan gerekli değil
        field.Int("yaş").Optional().Nillable(), // Null olabilen alan `nil` olarak ayarlanabilir
    }
}

Yukarıdaki modeli kullanırken, yaş alanı hem ayarlanmamış durumu gösteren nil değerlerini kabul edebilir hem de non-nil sıfır değerlerini kabul edebilir.

2.4. Alanın Benzersizliği

Benzersiz alanlar, veritabanı tablosunda yinelenen değerler olmamasını sağlar. Bir benzersiz alanı tanımlamak için Unique() yöntemini kullanın. Özellikle kullanıcı e-postaları veya kullanıcı adları gibi veri bütünlüğünü sağlamak için kritik bir gereksinim olduğunda benzersiz alanlar kullanılmalıdır.

// Kullanıcı şeması.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("email").Unique(),  // Tekrarlanan e-posta adreslerini önlemek için benzersiz alan
    }
}

Bu, altta yatan veritabanında yinelenen değerlerin eklenmesini engellemek için benzersiz bir kısıtlama oluşturacaktır.

2.5. Alan Endeksleme

Alan endeksleme, özellikle büyük veritabanlarında veritabanı sorgularının performansını artırmak için kullanılır. ent çerçevesinde, .Indexes() yöntemi endeks oluşturmak için kullanılabilir.

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

// Kullanıcı şeması.
func (User) Indexes() []ent.Index {
    return []ent.Index{
        index.Fields("email"),  // 'email' alanı üzerinde bir endeks oluştur
        index.Fields("ad", "yaş").Unique(), // Benzersiz bileşik bir endeks oluştur
    }
}

Endeksler sıkça sorgulanan alanlar için kullanılabilir, ancak çok fazla endeks yazma işlemi performansını azaltabilir. Bu nedenle, endeks oluşturma kararı gerçek durumlara göre dengelenmelidir.

2.6. Özel Etiketler

ent çerçevesinde, oluşturulan varlık yapısı alanlarına özel etiketler eklemek için StructTag yöntemini kullanabilirsiniz. Bu etiketler, JSON kodlaması ve XML kodlaması gibi işlemleri gerçekleştirmek için çok kullanışlıdır. Aşağıdaki örnekte, ad alanı için özel JSON ve XML etiketleri ekleyeceğiz.

// Kullanıcı alanları.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("ad").
            // `StructTag` yöntemini kullanarak özel etiketler ekleyin
            // Burada, ad alanı için JSON etiketini 'kullanıcıadı' olarak ayarlayın ve alan boşken (omitempty) görmezden gelin
            // Ayrıca kodlamada 'ad' için XML etiketini ayarlayın
            StructTag(`json:"kullanıcıadı,omitempty" xml:"ad"`),
    }
}

JSON veya XML ile kodlarken, omitempty seçeneği, ad alanı boşsa, bu alanın kodlama sonucundan çıkarılacağını belirtir. Bu, API'ler yazarken yanıt gövdesinin boyutunu azaltmak için çok kullanışlıdır.

Bu ayrıca aynı alana birden fazla etiket eklemeyi aynı anda nasıl ayarlayacağımızı göstermektedir. JSON etiketleri json anahtarını kullanır, XML etiketleri ise xml anahtarını kullanır ve boşluklarla ayrılır. Bu etiketler, encoding/json ve encoding/xml gibi kütüphane işlevleri tarafından, yapıyı kodlamak veya kodlamak için kullanılırken kullanılacaktır.

3. Alan Doğrulama ve Kısıtları

Alan doğrulama, varlık modelindeki veri tutarlılığını ve geçerliliğini sağlamak için önemli bir yönüdür. Bu bölümde, yerleşik doğrulayıcıları, özel doğrulayıcıları ve çeşitli kısıtları kullanarak veri bütünlüğünü ve kalitesini artırmak için inceleyeceğiz.

3.1. Yerleşik Doğrulayıcılar

Çerçeve, farklı türlerdeki alanlarda yaygın veri geçerlilik kontrollerini gerçekleştirmek için bir dizi yerleşik doğrulayıcı sağlar. Bu yerleşik doğrulayıcıların kullanılması geliştirme sürecini basitleştirebilir ve alanlar için geçerli veri aralıklarını veya formatlarını hızlı bir şekilde tanımlamanıza yardımcı olabilir.

İşte bazı yerleşik alan doğrulayıcılarının örnekleri:

  • Sayısal türler için doğrulayıcılar:
    • Positive(): Alanın değerinin pozitif bir sayı olup olmadığını doğrular.
    • Negative(): Alanın değerinin negatif bir sayı olup olmadığını doğrular.
    • NonNegative(): Alanın değerinin non-negatif bir sayı olup olmadığını doğrular.
    • Min(i): Alanın değerinin verilen minimum değer i'den büyük olup olmadığını doğrular.
    • Max(i): Alanın değerinin verilen maksimum değer i'den küçük olup olmadığını doğrular.
  • string türü için doğrulayıcılar:
    • MinLen(i): Bir dizenin minimum uzunluğunu doğrular.
    • MaxLen(i): Bir dizenin maksimum uzunluğunu doğrular.
    • Match(regexp.Regexp): Dizenin belirtilen düzenli ifadeyle eşleşip eşleşmediğini doğrular.
    • NotEmpty: Dizenin boş olup olmadığını doğrular.

Pratik bir kod örneği için aşağıdaki örneğe göz atalım. Bu örnekte, bir User modeli oluşturulur; bu modelde non-negatif bir tamsayı türünde age alanı ve belirli bir formata sahip email alanı bulunmaktadır:

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. Özel Doğrulayıcılar

Yerleşik doğrulayıcılar çoğu yaygın doğrulama gereksinimini karşılayabilirken, bazen daha karmaşık doğrulama mantığına ihtiyaç duyabilirsiniz. Bu tür durumlarda, belirli iş kurallarını karşılamak için özel doğrulayıcılar yazabilirsiniz.

Bir özel doğrulayıcı, bir alan değeri alır ve bir error döndürür. Döndürülen error boş değilse, doğrulama hatasını gösterir. Bir özel doğrulayıcının genel formatı aşağıdaki gibidir:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("phone").
            Validate(func(s string) error {
                // Telefon numarasının beklenen formata uygun olup olmadığını doğrulayın
                eşleşmiş, _ := regexp.MatchString(`^\+?[1-9]\d{1,14}$`, s)
                if !eşleşmiş {
                    return errors.New("Telefon numarası formatı yanlış")
                }
                return nil
            }),
    }
}

Yukarıdaki gibi, bir telefon numarasının formatını doğrulamak için özel bir doğrulayıcı oluşturduk.

3.3. Kısıtlamalar

Kısıtlamalar, belirli kuralları bir veritabanı nesnesine zorlayan kurallardır. Bunlar, geçersiz veri girişini önlemek veya verilerin ilişkilerini tanımlamak gibi verilerin doğruluğunu ve tutarlılığını sağlamak için kullanılabilir.

Yaygın veritabanı kısıtlamaları şunları içerir:

  • Birincil anahtar kısıtlaması: Tablodaki her kaydın benzersiz olmasını sağlar.
  • Benzersiz kısıtlama: Bir sütunun veya sütun kombinasyonunun tabloda benzersiz olmasını sağlar.
  • Yabancı anahtar kısıtlaması: Tablolar arasındaki ilişkileri tanımlayarak referans bütünlüğünü sağlar.
  • Kontrol kısıtlaması: Bir alan değerinin belirli bir koşulu karşılamasını sağlar.

Varlık modelinde, veri bütünlüğünü korumak için kısıtlamaları aşağıdaki gibi tanımlayabilirsiniz:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("username").
            Unique(), // Kullanıcı adının tabloda benzersiz olmasını sağlamak için benzersiz kısıtlama.
        field.String("email").
            Unique(), // E-postanın tabloda benzersiz olmasını sağlamak için benzersiz kısıtlama.
    }
}

func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("friends", User.Type).
            Unique(), // Yabancı anahtar kısıtlaması, başka bir kullanıcıyla benzersiz bir kenar ilişkisi oluşturur.
    }
}

Özetle, alan doğrulama ve kısıtlamalar, iyi veri kalitesini sağlamak ve beklenmeyen veri hatalarını önlemek için hayati öneme sahiptir. ent çerçevesinin sağladığı araçları kullanmak bu süreci daha basit ve güvenilir hale getirebilir.