ULID em coluna UUID: a interop que evitou uma migração inteira

Como armazenar ULIDs em colunas UUID sem perder funcionalidade nem performance.

Camverly usa ULID como identificador em todas as entidades. Mas Postgres não tem tipo nativo ULID, e usar TEXT pra ID destrói indexação. A solução que adotamos: armazenar ULID em coluna UUID. Os dois tipos têm 16 bytes; a interop é gratuita se você faz a conversão certa. Levou dois bugs em produção pra descobrir os pontos onde isso vaza, mas hoje é tema fechado.

ULID é especificado como 16 bytes: 6 bytes de timestamp seguidos de 10 bytes randômicos. UUID v4 é também 16 bytes mas com layout diferente (variant + version bits embutidos). Como representação string, ULID usa Crockford Base32 (26 chars), UUID usa hex com hífens (36 chars). O truque é: como nunca decodificamos o UUID pra extrair version/variant, o layout interno não importa. Os 16 bytes podem ser interpretados como ULID ou UUID indiferentemente.

O código que faz a ponte é pequeno e mora num arquivo uuid_helper.go em cada package de persistência: uuidStr(u ulid.ULID) string converte os bytes do ULID pra formato UUID hex com hífens. parseAnyULID(s string) aceita tanto 26-char Crockford quanto 36-char UUID hex, retornando ulid.ULID. Esses dois helpers estão em cada repository, e toda interação com o banco passa por eles.

Onde dói: APIs externas e contratos REST. Se você expõe IDs como JSON, qual formato escolhe? Nós optamos por aceitar os dois na entrada (parseID() aceita ambos), mas serializar saída como UUID hex 36-char. Razão: clientes que consomem da gateway provavelmente vão escrever direto em banco ou comparar com colunas UUID, então UUID hex é o formato amigável. ULID Crockford fica como representação interna do Go.

O bug que apareceu em produção: o outbox publisher publicava `aggregate_id` como Crockford 26-char num campo UUID. Postgres aceita string '01HXX...' como UUID? Não. Erro 'invalid input syntax for type uuid'. A correção foi sed-style — substituir todas chamadas `.String()` por `uuidStr()` nos repositórios. Treze repos atingidos. Hoje a regra de ouro é: nunca chame .String() de ULID em contexto de banco. Existe gosec lint custom rodando no CI que detecta isso e marca como erro.

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly