Laravel’de UUID kullanımı

Güncelleme: Laravel v9.30 sürümü ile birlikte modellerdeki primary keylerde UUID kullanımı için dahili bir trait geliyor. Detaylar: https://twitter.com/enunomaduro/status/1589598736715370496


Buraya kadar geldiğinize göre muhtemelen ne olduğunu biliyorsunuzdur ama yine de Laravel’de UUID’nin nasıl kullanıldığını anlatmaya başlamadan önce kısaca bir UUID tanımını yapalım ve ne olduğunu anlatmaya çalışalım.

UUID, "Universally unique identifier" nin kısaltılmışıdır. Kabaca çevirirsek, evrensel(?) benzersiz tanımlayıcı diyebiliriz. Normalde veritabanında satırları tanımlayıcı olarak integer kullanırız. UUID ise, 36 karakterlik, sayılar ve harflerden oluşan, bir settir. Ve en önemli özelliği, yüksek derecede benzersiz olmasıdır. (Bilgisayarda rastgele üretilen hiçbir şey aslında tam olarak rastgele değildir. Rastgeleliği arttırabiliriz. Değişik bi konu.)

Örnek olarak şöyle bir kaç tane gösterebilirim:

UUID genellikle büyük projelerde işe yarayabiliyor. Biliyorsunuz, veritabanında integer alanının maximum değeri var. Mysql’deki değeleri şuradan görebilirsiniz: https://dev.mysql.com/doc/refman/5.7/en/integer-types.html

Burada minimum-maximum değerlere bir paragraf açıyorum. Veritabanında id alanı, Primary Key olarak tanımlanır. PrimaryKey alanı doğası gereği unsigned olarak tanımlanır. Bu yüzden ilgilendiğiniz alan unsigned olmalı. Peki unsigned değer ne demek? Negatif olmayan sayılar demek. Linkteki tabloya baktığımızda, TinyInt türü için signed [-127, 127] aralığındayken, unsigned [0, 255] aralığında değer alabiliyor.

Primary key, varsayılan olarak INT, unsigned tanımlanır. Bu da demektir ki, o tabloya maksimum 4294967295 kayıt eklenebilir. Sanırsam, 4 milyar 294 milyon 967 bin 295 kayıt. Her ne kadar çok büyük bir sayı olsa da, sonsuz değil. Eğer uuid olarak tanımlarsanız, sonsuz kayıt ekleyebilirsiniz. (Acaba öyle mi? Keşke birisi bunun doğrulamasını yapsa) Maksimum kaç kayıt eklenebileceği ile ilgili yanıt: https://twitter.com/hkanaktas/status/906891710155849730

Bunun haricinde, çok olası değil ama, bir tablonuz dağıtık biçimde çalışıyorsa, primary key alanınızın çakışmasını önler. Farazi bir örnek verelim. Diyelim ki kullanıcının gezdiği sayfaları kayıt altına alıyorsunuz. Öyle ki, çok yoğun bir sistem ve kayıtları %50 A sunucusundaki logs tablosuna, %50 B sunucusundaki logs tablosuna kaydediyorsunuz. Günün sonunda verileri C sunucusunda birleştirmek istediğinizde, id alanı çakışacaktır. Bunun için idleri yeniden vermeniz gerekmektedir. Fakat uuid kullanırsanız, herhangi bir yenileme yapmadan doğrudan verileri birleştirebilirsiniz.

Son olarak şunu söyleyeyim, tahmin edilmesi oldukça zordur. Örneğin kullanıcılar tablomuzda int kullansaydık, üye kayıtları 1,2,3,4,5 diye düzenli olarak gidecekti. Ve emin olun saldırganlar ilk olarak url’deki idleri değiştirerek erişim sağlamaya çalışır. Gerekli kontrolleri aldıysanız sıkıntı olmaz. Fakat uuid ile bu sıkıntı daha da az yaşanır. Çünkü bir sonraki kullanıcının id’si tahmin edilemez hale gelir. Siz yine de arka tarafta gerekli kontrolleri yapmayı unutmayın.

Gelelim Laravel Kulanımına

UUID’leri üretmek için ek paket kullanmamız gerekiyor. Ben ramsey/uuid paketini kullanıyorum.

$ composer require ramsey/uuid

Bu paket sayesinde artık UUID üretebiliriz. README dosyasında kullanımı var, ama kısaca ben de buradan tekrar edeyim:

<?php
 // Generate a version 1 (time-based) UUID object
    $uuid1 = Uuid::uuid1();
    echo $uuid1->toString() . "n"; // i.e. e4eaaaf2-d142-11e1-b3e4-080027620cdd

    // Generate a version 3 (name-based and hashed with MD5) UUID object
    $uuid3 = Uuid::uuid3(Uuid::NAMESPACE_DNS, 'php.net');
    echo $uuid3->toString() . "n"; // i.e. 11a38b9a-b3da-360f-9353-a5a725514269

    // Generate a version 4 (random) UUID object
    $uuid4 = Uuid::uuid4();
    echo $uuid4->toString() . "n"; // i.e. 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a

    // Generate a version 5 (name-based and hashed with SHA1) UUID object
    $uuid5 = Uuid::uuid5(Uuid::NAMESPACE_DNS, 'php.net');
    echo $uuid5->toString() . "n"; // i.e. c4a760a8-dbcf-5254-a0d9-6a4474bd1b62

Bu pakette toplam 4 çeşit üretme yöntemi var. Hangisini kullanacağınız konusunda tercih size kalmış. Ben farklarını bilmiyorum, araştırmadım.

Göç yolları, göründü bize

Paketi yükledikten sonra, ilk değişikliğimiz migration yazımında karşımıza çıkıyor.

Normalde bir migration oluşturduğunuzda şu şekilde oluyor:

$table->increments('id');

Bu fonksiyonun detayına baktığımızda, unsignedInteger tipinde oluşturduğunu göreceksiniz. Bunun yerine şunu yazmamız daha manidar:

$table->uuid('id')->primary();

ID alanımızın uuid türünde olduğunu, ve bu alanın tablomuzun birincil alanı(reyizi) olduğunu söyleyerek tablomuu oluşturuyoruz. Bu da aslında özel bir kolon türü değil, 36 karakterlik char alanı oluşturuyor tablomuzda.

Model dosyalarında değişiklik

İlk önce modelin primary key’in otomatik artma özelliğini kapatalım. Eloquent’te primary key, varsayılan olarak int, auto increment olarak kabul edilir. Bunu değiştirelim.

<?php

class Bilmemne extends \Illuminate\Database\Eloquent\Model {

    public $incrementing = false;

}

Otomatik arttırmayı kapattık ama, şimdi de bir modeli kaydetmek istediğimizde, otomatik olarak sayı yazmaya çalışacak eloquent. Bunun için Eloquent event’lerinden yararlanıyoruz:

<?php

class Bilmemne extends \Illuminate\Database\Eloquent\Model {

    public $incrementing = false;

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            $model->{$model->getKeyName()} = \Ramsey\Uuid\Uuid::uuid1()->toString();
        });
    }

}

Peki bu kod ne yapıyor? $model->save() komutunu çalıştırdığınızda, model verileri, veritabanına kaydedilmek üzere hazırlanıyor. Bu kod sayesinde, modelimiz veritabanına kaydedilmeden önce ‘id’ alanının değerini uuid ile değiştiriyoruz.

Hepsi bu! Artık id alanımız integer yerine uuid olarak kaydediliyor. Fakat burada göze güzel görünmeyen birşey var. Bu kodu tüm modellere tek tek kopyalayıp yapıştıracağız. Bunun önüne geçmek için yapabileceğimiz 2 şey var. Birincisi, extend edilen sınıfı değiştirmek. İkincisi ise Trait kullanmak.

1. Yöntem: Extend edilen sınıfı değiştirmek

Öncelikle yeni bir model sınıfı üretelim.

<?php

class MyModel extends \Illuminate\Database\Eloquent\Model
{
    public $incrementing = false;

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            $model->{$model->getKeyName()} = \Ramsey\Uuid\Uuid::uuid1()->toString();
        });
    }
}

Daha sonra kendi sınıfımızın ayarlarıyla oynayalım:

<?php 

class Bilmemne extends MyModel { 

}

Bilmemne modelimiz artık integer yerine uuid üretiyor!

2. Yöntem: Trait kullanmak

Bildiğiniz gibi bir sınıf birden fazla sınıftan türetilemiyor(extend edilemiyor). Siz birden fazla sınıfın özelliğini kullanmak isterseniz ya hepsini birbirinden extend edeceksiniz -ki etmeyin- ya da trait kullanarak istediğiniz methodları kullanacaksanız. Laravel’e baktığımızda trait kullanımının yoğun olduğunu görürüz. Traitler bize kodların yeniden kullanımı konusunda yardımcı olur. Tekrar yazımların önüne geçer. Kodumuzu parçalara bölebilmemizi sağlar ve kodun kolay takibini sağlar. Örnek olarak Laravel’in model sınıfının neler kullandığına bakalım:

<?php

abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
    use ConcernsHasAttributes,
        ConcernsHasEvents,
        ConcernsHasGlobalScopes,
        ConcernsHasRelationships,
        ConcernsHasTimestamps,
        ConcernsHidesAttributes,
        ConcernsGuardsAttributes;
}

Model sınıfı yorumlar da dahil 1460 satır. Traitlerin içindeki methodları da bu sınıfa dahil etseydik kim bilir kaç satır olacaktı. Kodun birbiriyle ilgili bölümlerini alıp trait içerisine koyup, traiti de model içerisinde sadece use ile çağırmak oldukça güzel bir fikir. Daha övülecek çok yanı vardır da, hem şimdi aklıma gelmiyor(hepsini bilmiyor da olabilirim) hem de artık kendi trait kodumuza geçelim.

<?php

trait UuidsTrait
{
    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            $model->{$model->getKeyName()} = \Ramsey\Uuid\Uuid::uuid1()->toString();
        });
    }
}

Tek yapmamız gereken, bunu modelimiz içerisinde kullanmak:

<?php

class Bilmemne extends \Illuminate\Eloquent\Database\Model { 

    use UuidsTrait;
}

Bu da tam olarak istediğimiz gibi sonuç verecek ve integer yerine uuid kullanmamızı sağlayacak.

Trait olarak kullanmak ve extend etmek arasında belli farklar var fakat onları başka bir yazı konusu yapabiliriz. Şimdilik ben size bu iş için trait kullanmanızı öneririm.

Esenlikle kalın.

 

2 Comments

  1. Güzel yazı olmuş ancak, en sonda `UuidsTrait` traid’ini use etmişiz ama hala `Bilmemne` class’ını `MyModel` den extend etmişsin.

Yıldıray Ünlü için bir yanıt yazınCancel Reply

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir