Trabalhando com UUID no Laravel
guias ecossistema
UUID ou Universally Unique Identifier, também conhecido como GUID (globally unique identifier) é uma especificação de identificador capaz de garantir singularidade universal. No post de hoje vamos ver como podemos trabalhar com estes identificadores no Laravel e quais suas vantagens e desvantagens.
O que é UUID
A especificação do UUID foi apresentada em julho de 2005 e pode ser verificada no anexo da RFC 4122 . Um UUID possui tamanho de 128 bits e não requer uma unidade central de registro, isso porque cada versão utiliza de um ou mais identificadores de espaço e tempo para geração da combinação de caracteres baseado no local de onde são gerados.
Os 128 bits de um UUID são representados por 32 caracteres hexadecimais divididos em 5 blocos e cada bloco é separado por hífens, ocupando um total de 36 caracteres. A implementação de UUID era usada originalmente na Apollo Network Computing System e posteriormente em organizações como a OSF e Microsoft.
O principal objetivo do UUID é garantir uma identificação única e universal entre sistemas distribuídos (ou não) sem a necessidade de uma entidade centralizadora (como o auto increment de BDs por exemplo). Existem algumas versões do UUID e cada versão utiliza itens diferentes para a geração da combinação dos caracteres como mac address, id/grupo do usuário técnicas de hashing e até aleatoriedade.
Abaixo segue o exemplo de um UUID válido:
dafe4fcb-5bc4-4568-bb35-10813dec9715
Devo utilizar UUID?
Esta é mais uma daquelas perguntas que infelizmente precisamos responder com depende!
Em alguns casos e alguns tipos de entidades como de usuários por exemplo, acho interessante utilizar UUID independente de outras questões… Há quem diga que utilizar IDs sequências seja uma brecha de segurança, há quem defenda que se não formos expor APIs não faça diferença usar IDs sequenciais ou não. A ideia aqui é apresentar a especificação, mostrar como podemos utilizá-la com Laravel e dar ferramentas para que cada um decida o que é melhor para sua aplicação.
Vantagens da utilização de UUID
- Não criar registros sequências
Pode parecer um motivo pequeno mas registros sequenciais estimulam tentativas de encontrar brechas nos nossos sistemas, normalmente passamos IDs para a URL a fim de identificar um recurso e ao seguirmos REST esta é uma prática comum. Qualquer usuário pode ter a curiosidade de saber o que acontece se trocar aquele número na url, não é mesmo? Se o seu sistema não estiver preparado para mitigar este comportamento isto pode ser um problemão!
- Criação descentralizada
UUID é uma especificação como vimos acima, por conta disso não precisamos depender de geradores externos (como auto increment dos BDs ou similar) e nossa própria aplicação pode se encarregar da geração de identificadores.
- Distribuição e sincronização
Utilizar UUID facilita sistemas distribuídos e/ou que precisam funcionar offline, podemos gerar registros completos e independentes e posteriormente apenas sincronizá-los com uma base ou estrutura similar.
Desvantagens na utilização de UUID
- Debug mais complexo
Ao desenvolver uma aplicação é natural precisarmos fazer o debug de algum trecho, quando se trata de recursos REST ou baseados no ID de um recurso a leitura pode ficar um pouco prejudicada e atrasar um pouco esse lado quando utilizamos UUID.
Desempenho e armazenamento
O tamanho de um UUID é muito maior que o que geralmente utilizamos para IDs convencionais e isso pode trazer pequenas perdas de desempenho e requer maior armazenamento.
Utilizando UUID no Laravel
Agora que já falamos bastante sobre o que é e as vantagens e desvantagens do UUID vamos logo falar sobre como utilizá-los com o Laravel!
Por padrão o Laravel possui alguns helpers para trabalharmos com UUID, eles podem ser encontrados na documentação oficial na seção de strings. O método Str::uuid()
encontrado na documentação oficial pode ser uma excelente opção caso a necessidade seja trabalhar com UUID na versão 4, mas se por alguma razão seja necessário utilizar outras versões este helper padrão do Laravel não servirá.
Para gerar e trabalhar com UUID no Laravel é utilizado o pacote Ramsey\UUID e este pacote já vem instalado na instalação padrão do framework. Com ele será possível trabalhar com praticamente todas as versões da especificação de UUID, sejam elas oficiais ou não oficiais.
Abaixo segue alguns exemplos utilizando tanto o helper padrão quanto o pacote Ramsey\UUID
:
use Illuminate\Support\Str;
use Ramsey\Uuid\Uuid;
Str::uuid(); // gera um UUID versão 4, ex: 85c44b2b-c65a-43b9-8619-6f6d3e45008b
Uuid::uuid1(); // gera um UUID versão 1, ex: aa3ccbb2-4c43-11eb-a8a1-0242ac1b0002
Uuid::uuid4(); // gera um UUID versão 4, ex: d066faaf-c49c-44e2-a32c-c138dee7b5e2
Todos os métodos acima retornam a instância de uma classe que implementa a interface UuidInterface , esta interface garante que tenhamos os métodos toStrig()
e __toString()
o que nos permite fazer algo como:
(string) Uuid::uuid1(); // d066faaf-c49c-44e2-a32c-c138dee7b5e2
UUID e Laravel Models
Para criar registros utilizando UUID precisamos informar as models do Laravel que não iremos utilizar IDs da forma padrão. No final das contas um UUID é uma string e para utilizarmos strings como IDs nas models precisamos adicionar a propriedade $keyType
na model em questão, vamos utilizar a model User
como exemplo:
// app/Models/User.php
class User extends Authenticatable
{
...
protected $keyType = 'string';
…
}
Vale lembrar que também será necessário alterar o tipo da coluna “id” no banco de dados pois por padrão a model User
é criada como bigint(20). Vamos criar uma migration para isso:
php artisan make:migration alter_id_to_uuid_users_table
No método up()
da migration criada vamos alterar a coluna ‘id’ para receber UUID:
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->uuid('id')
->change();
});
}
As migrations no Laravel já possui um método específico para trabalhar com UUID que gera uma coluna do tipo char(36)
e não possui auto increment. Por fim basta rodar a migration para que a tabela seja alterada:
php artisan migrate
Agora já somos capazes de criar registros na tabela de usuários utilizado UUID, vamos ver um exemplo utilizando a UserFactory
que já existe por padrão nas instalações do Laravel:
// database/factories/UserFactory.php
...
public function definition()
{
return [
'id' => (string) Uuid::uuid4(),
'name' => $this->faker->name,
'email' => $this->faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
Note que no primeiro índice do array foi adicionada a geração do UUID utilizando a versão 4 e foi feito o casting para string. Vamos utilizar o Tinker para ver como ficaria um usuário criado através desta factory:
php artisan tinker
Psy Shell v0.10.5 (PHP 8.0.0 — cli) by Justin Hileman
>>> User::factory()->make()
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
=> App\Models\User {#3388
id: "33e84958-0e6a-4596-9686-3ce29c7d803b",
name: "Casimir Wehner",
email: "[email protected]",
email_verified_at: "2021-01-01 15:51:54",
}
>>>
Ótimo, agora temos um usuário criado utilizando UUID!
Criando UUID automaticamente
Um ponto importante que devemos levar em consideração é que agora os IDs não são mais gerados automaticamente e todos os lugares onde for necessário utilizar a criação de novos usuários será necessário gerar o UUID “na mão” e passar para a model. Podemos utilizar os eventos das models (Model Events) para automatizar esta tarefa para nós, vamos ver como funciona.
Dentre outras maneiras é possível utilizar closures no método booted()
das models para executarmos alguma ação quando algum evento for disparado. No exemplo a seguir vamos criar o método booted()
na model User
e adicionar a geração de um UUID no momento de criação de um usuário:
// app/Models/User.php
protected static function booted()
{
static::creating(fn(User $user) => $user->id = (string) Uuid::uuid4());
}
Desta forma toda vez que um novo registro for criado utilizando a model User
um UUID será gerado e salvo automaticamente para nós. Um ponto interessante desta abordagem é que não se torna necessário adicionar a propriedade id
ao array protected $fillable
da model, o que evita uma brecha de segurança.
Agora qualquer código de criação de usuários poderá utilizar a model normalmente sem precisar passar um UUID criado previamente:
User::create([
'name' => 'Jose',
'email' => '[email protected]',
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
]
Neste exemplo mesmo sem ter passado um UUID (que está a cargo da model gerar automaticamente) foi criado o registro com o UUID “29af5fad-22d4-4dcf-b7e6-3682b402c055”.
Este UUID criado é apenas um exemplo, ao executar o código em seu ambiente o UUID não será o mesmo
Trabalhando com Observers
Em algumas ocasiões podemos querer ter mais controle sobre nossas entidades (models) e pode não ser o ideal “poluir” a model com a regra de geração de UUID. Nestes casos podemos utilizar Observers para extrair esta regra das classes do tipo model e deixá-las mais isoladas.
Para gerar um observer podemos utilizar o comando artisan make:observer
, este comando pode receber a opção da model a qual se refere (no nosso caso a model User
):
php artisan make:observer UserObserver --model=User
Se tudo deu certo um novo observer foi criado na pasta app/Observers
, se este diretório não existia o comando se encarregará de criá-lo.
Os observers como o nome já sugere funcionam como uma espécie de observadores em ações específicas na model que registrarmos junto ao EventProvider
. Com isso também podemos utilizar os mesmos hooks que utilizamos na model User
. Agora podemos adicionar a regra para gerar e gravar UUIDs fora da model no método creating()
do observer recém criado, este método recebe uma instância da model que será registrada e a partir disso podemos realizar quaisquer ações referentes a instância:
// app/Observers/UserObserver.php
use App\Models\User;
use Ramsey\Uuid\Uuid;
class UserObserver
{
public function creating(User $user)
{
$user->id = Uuid::uuid4();
}
}
A última ação necessária será “registrar” este observer e isso é feito junto ao EventServiceProvider
no método boot()
:
// app/Providers/EventServiceProvider.php
public function boot()
{
User::observe(UserObserver::class);
}
Com isso podemos remover o método booted()
da model User
que mesmo assim um UUID será gerado automaticamente a cada novo registro.
Conclusão
É importante avaliar os prós e contras para decidir sobre a utilização de UUIDs em aplicações e espero que este texto possa ajudar nesta decisão.
Vale ressaltar que podemos utilizar UUID apenas nas tabelas do nosso interesse, não vejo problema algum em utilizar UUID na tabela de usuário mas utilizar IDs incrementais na tabela de categorias por exemplo. Use esta flexibilidade a seu favor!