Los Identificadores Únicos Universales (UUIDs) son esenciales en aplicaciones Go para crear identificadores únicos en sistemas distribuidos sin coordinación. Go, con su enfoque en simplicidad y rendimiento, proporciona varias excelentes bibliotecas para la generación de UUID, cada una ofreciendo diferentes características y rendimiento adecuados para varios casos de uso.
Esta guía completa explora múltiples enfoques para generar UUIDs en aplicaciones Go, desde el uso de bibliotecas populares hasta la implementación de soluciones personalizadas. Ya sea que estés construyendo microservicios, APIs o sistemas distribuidos, comprender cómo implementar y aprovechar correctamente los UUIDs en Go es crucial para el desarrollo de aplicaciones modernas.
El paquete google/uuid es una de las bibliotecas UUID más populares y bien mantenidas para Go. Proporciona una API simple y soporta todas las versiones de UUID definidas en RFC 4122.
package main
import (
"fmt"
"log"
"github.com/google/uuid"
)
func main() {
// Generar un nuevo UUID aleatorio (Versión 4)
id := uuid.New()
fmt.Printf("UUID generado: %s\n", id)
// Generar UUID como cadena directamente
idString := uuid.NewString()
fmt.Printf("UUID como cadena: %s\n", idString)
// Generar UUID nulo
nilUUID := uuid.Nil
fmt.Printf("UUID nulo: %s\n", nilUUID)
// Parsear un UUID desde cadena
parsed, err := uuid.Parse("550e8400-e29b-41d4-a716-446655440000")
if err != nil {
log.Fatal(err)
}
fmt.Printf("UUID parseado: %s\n", parsed)
}
El paquete google/uuid proporciona una API limpia y directa. La función uuid.New() genera un UUID Versión 4 (aleatorio), mientras que uuid.NewString() lo devuelve directamente como cadena. El paquete también incluye capacidades de análisis y constantes predefinidas como uuid.Nil para el UUID nulo. Esta biblioteca es recomendada para la mayoría de las aplicaciones Go debido a su simplicidad y el respaldo de Google.
El paquete satori/go.uuid ofrece más flexibilidad con soporte para diferentes versiones de UUID y características adicionales como ordenamiento de UUID y generadores personalizados.
package main
import (
"fmt"
"log"
"github.com/satori/go.uuid"
)
func main() {
// Generar UUID Versión 1 (timestamp + dirección MAC)
v1, err := uuid.NewV1()
if err != nil {
log.Fatal(err)
}
fmt.Printf("UUID Versión 1: %s\n", v1)
// Generar UUID Versión 4 (aleatorio)
v4, err := uuid.NewV4()
if err != nil {
log.Fatal(err)
}
fmt.Printf("UUID Versión 4: %s\n", v4)
// Generar UUID Versión 5 (namespace + nombre)
v5 := uuid.NewV5(uuid.NamespaceURL, "https://example.com")
fmt.Printf("UUID Versión 5: %s\n", v5)
// Convertir a diferentes formatos
fmt.Printf("Bytes: %x\n", v4.Bytes())
fmt.Printf("Cadena: %s\n", v4.String())
}
El paquete satori/go.uuid proporciona funciones explícitas para generar diferentes versiones de UUID. Los UUIDs Versión 1 incluyen información de timestamp y dirección MAC, los UUIDs Versión 4 son aleatorios, y los UUIDs Versión 5 son deterministas basados en namespace y nombre. Este paquete es ideal cuando necesitas versiones específicas de UUID o más control sobre el proceso de generación.
Diferentes versiones de UUID sirven para diferentes propósitos. Aquí te mostramos cómo generar varias versiones de UUID usando el paquete google/uuid, que soporta Versión 1, 3, 4, 5 y 7.
package main
import (
"fmt"
"time"
"github.com/google/uuid"
)
func main() {
// Versión 1: UUID basado en tiempo
// Nota: Requiere acceso a dirección MAC
uuid.SetNodeID([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab})
v1, _ := uuid.NewUUID()
fmt.Printf("Versión 1 (Basado en tiempo): %s\n", v1)
// Versión 3: Hash MD5 de namespace y nombre
v3 := uuid.NewMD5(uuid.NamespaceDNS, []byte("example.com"))
fmt.Printf("Versión 3 (MD5): %s\n", v3)
// Versión 4: UUID aleatorio (más común)
v4 := uuid.New()
fmt.Printf("Versión 4 (Aleatorio): %s\n", v4)
// Versión 5: Hash SHA-1 de namespace y nombre
v5 := uuid.NewSHA1(uuid.NamespaceURL, []byte("https://example.com"))
fmt.Printf("Versión 5 (SHA-1): %s\n", v5)
// Versión 7: Ordenado por tiempo con timestamp Unix (si está soportado)
// Nota: La Versión 7 es una adición más reciente y puede requerir la versión más reciente del paquete
v7, _ := uuid.NewV7()
fmt.Printf("Versión 7 (Ordenado por tiempo): %s\n", v7)
// Extraer timestamp del UUID Versión 1
timestamp, _ := uuid.GetTime()
fmt.Printf("Timestamp de V1: %s\n", time.Unix(timestamp.UnixTime()))
}
Cada versión de UUID tiene casos de uso específicos: Versión 1 incluye timestamp y dirección MAC (bueno para depuración pero puede filtrar información), Versión 3 y 5 crean UUIDs deterministas desde namespaces (útil para identificación consistente), Versión 4 es puramente aleatoria (más común para uso general), y Versión 7 proporciona ordenamiento por tiempo con mejor rendimiento de índice de base de datos. Elige la versión basándote en tus requisitos específicos.
Para propósitos de aprendizaje o requisitos específicos, puedes implementar un generador básico de UUID usando el paquete crypto/rand de Go para generación segura de números aleatorios.
package main
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io"
)
// UUID representa un UUID
type UUID [16]byte
// NewUUID genera un nuevo UUID Versión 4
func NewUUID() (UUID, error) {
var uuid UUID
// Leer 16 bytes aleatorios
_, err := io.ReadFull(rand.Reader, uuid[:])
if err != nil {
return uuid, err
}
// Establecer bits de versión (4) y variante según RFC 4122
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Versión 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variante 10
return uuid, nil
}
// String devuelve el UUID en formato estándar
func (u UUID) String() string {
var buf [36]byte
hex.Encode(buf[0:8], u[0:4])
buf[8] = '-'
hex.Encode(buf[9:13], u[4:6])
buf[13] = '-'
hex.Encode(buf[14:18], u[6:8])
buf[18] = '-'
hex.Encode(buf[19:23], u[8:10])
buf[23] = '-'
hex.Encode(buf[24:], u[10:16])
return string(buf[:])
}
func main() {
uuid, err := NewUUID()
if err != nil {
panic(err)
}
fmt.Printf("UUID personalizado: %s\n", uuid)
}
Esta implementación personalizada demuestra la estructura básica de un generador de UUID Versión 4. Utiliza crypto/rand para bytes aleatorios criptográficamente seguros, establece los bits apropiados de versión y variante según RFC 4122, y formatea la salida en el formato UUID estándar. Aunque es educativo, se recomienda usar bibliotecas bien probadas para aplicaciones en producción.
Para aplicaciones que usan frecuentemente UUIDs, es beneficioso crear un servicio que centralice la lógica de generación de UUID y proporcione utilidades adicionales:
package uuidservice
import (
"database/sql/driver"
"fmt"
"sync"
"github.com/google/uuid"
)
// Service proporciona funciones de generación y utilidad de UUID
type Service struct {
mu sync.Mutex
generated int64
namespace uuid.UUID
}
// NewService crea un nuevo servicio UUID
func NewService(namespace string) (*Service, error) {
ns, err := uuid.Parse(namespace)
if err != nil {
// Usar un namespace por defecto si el parseo falla
ns = uuid.NamespaceDNS
}
return &Service{
namespace: ns,
}, nil
}
// GenerateV4 genera un UUID v4 aleatorio
func (s *Service) GenerateV4() uuid.UUID {
s.mu.Lock()
defer s.mu.Unlock()
s.generated++
return uuid.New()
}
// GenerateV5 genera un UUID v5 determinista desde un nombre
func (s *Service) GenerateV5(name string) uuid.UUID {
return uuid.NewSHA1(s.namespace, []byte(name))
}
// GenerateBatch genera múltiples UUIDs eficientemente
func (s *Service) GenerateBatch(count int) []uuid.UUID {
uuids := make([]uuid.UUID, count)
s.mu.Lock()
defer s.mu.Unlock()
for i := 0; i < count; i++ {
uuids[i] = uuid.New()
s.generated++
}
return uuids
}
// GetStats devuelve estadísticas de generación
func (s *Service) GetStats() int64 {
s.mu.Lock()
defer s.mu.Unlock()
return s.generated
}
// NullableUUID maneja UUIDs nulables para operaciones de base de datos
type NullableUUID struct {
UUID uuid.UUID
Valid bool
}
// Scan implementa la interfaz sql.Scanner
func (n *NullableUUID) Scan(value interface{}) error {
if value == nil {
n.UUID, n.Valid = uuid.Nil, false
return nil
}
n.Valid = true
return n.UUID.Scan(value)
}
// Value implementa la interfaz driver.Valuer
func (n NullableUUID) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.UUID.Value()
}
// Ejemplo de uso
func Example() {
service, _ := NewService("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
// Generar UUID único
id := service.GenerateV4()
fmt.Printf("Generado: %s\n", id)
// Generar UUID determinista
userId := service.GenerateV5("user@example.com")
fmt.Printf("ID de usuario: %s\n", userId)
// Generar lote
batch := service.GenerateBatch(10)
fmt.Printf("Generados %d UUIDs\n", len(batch))
// Verificar estadísticas
fmt.Printf("Total generado: %d\n", service.GetStats())
}
Este servicio UUID proporciona una forma centralizada de gestionar la generación de UUID en tu aplicación. Incluye generación segura para hilos, seguimiento de estadísticas, generación por lotes para rendimiento, y soporte de UUID nulable compatible con bases de datos. El patrón de servicio facilita la simulación de generación de UUID en pruebas y mantiene un manejo consistente de UUID en toda tu aplicación.
Al aceptar UUIDs de fuentes externas, la validación adecuada es crucial. Aquí te mostramos cómo parsear y validar UUIDs efectivamente en Go:
package main
import (
"fmt"
"regexp"
"strings"
"github.com/google/uuid"
)
// Validación personalizada de UUID usando regex
var uuidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$`)
// ValidateUUID valida una cadena UUID
func ValidateUUID(input string) (uuid.UUID, error) {
// Intentar parsear con el paquete uuid
parsed, err := uuid.Parse(input)
if err != nil {
return uuid.Nil, fmt.Errorf("formato UUID inválido: %w", err)
}
// Verificar si es el UUID nulo
if parsed == uuid.Nil {
return uuid.Nil, fmt.Errorf("UUID nulo no permitido")
}
return parsed, nil
}
// ValidateUUIDVersion verifica si un UUID es de una versión específica
func ValidateUUIDVersion(id uuid.UUID, expectedVersion uuid.Version) error {
if id.Version() != expectedVersion {
return fmt.Errorf("se esperaba UUID versión %d, se obtuvo %d", expectedVersion, id.Version())
}
return nil
}
// NormalizeUUID maneja varios formatos de entrada de UUID
func NormalizeUUID(input string) (uuid.UUID, error) {
// Eliminar caracteres envolventes comunes
cleaned := strings.Trim(input, "{}()")
// Eliminar espacios y convertir a minúsculas
cleaned = strings.ToLower(strings.ReplaceAll(cleaned, " ", ""))
// Manejar UUID sin guiones
if len(cleaned) == 32 && !strings.Contains(cleaned, "-") {
// Insertar guiones en las posiciones correctas
cleaned = fmt.Sprintf("%s-%s-%s-%s-%s",
cleaned[0:8],
cleaned[8:12],
cleaned[12:16],
cleaned[16:20],
cleaned[20:32],
)
}
return uuid.Parse(cleaned)
}
func main() {
testCases := []string{
"550e8400-e29b-41d4-a716-446655440000", // Válido
"550e8400e29b41d4a716446655440000", // Válido sin guiones
"{550e8400-e29b-41d4-a716-446655440000}", // Válido con llaves
"not-a-uuid", // Inválido
"00000000-0000-0000-0000-000000000000", // UUID nulo
}
for _, test := range testCases {
fmt.Printf("Probando: %s\n", test)
// Validación básica
if id, err := ValidateUUID(test); err != nil {
fmt.Printf(" Validación falló: %v\n", err)
} else {
fmt.Printf(" UUID válido: %s (Versión %d)\n", id, id.Version())
}
// Intentar normalización
if normalized, err := NormalizeUUID(test); err == nil {
fmt.Printf(" Normalizado: %s\n", normalized)
}
fmt.Println()
}
}
Este ejemplo de validación muestra múltiples enfoques para la validación de UUID en Go. La función ValidateUUID usa el análisis incorporado del paquete uuid, mientras que NormalizeUUID maneja varios formatos de entrada que los usuarios podrían proporcionar. El código también demuestra la verificación de versión y la detección de UUID nulo. Para uso en producción, considera qué reglas de validación son apropiadas para tu caso de uso específico.
Al generar UUIDs a escala, entender las características de rendimiento se vuelve importante. Aquí te mostramos cómo hacer benchmark y optimizar la generación de UUID en Go:
package main
import (
"fmt"
"runtime"
"sync"
"testing"
"time"
"github.com/google/uuid"
)
// BenchmarkUUIDGeneration hace benchmark de diferentes métodos de generación de UUID
func BenchmarkUUIDGeneration(b *testing.B) {
b.Run("Secuencial", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = uuid.New()
}
})
b.Run("Paralelo", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = uuid.New()
}
})
})
b.Run("Cadena", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = uuid.NewString()
}
})
}
// ConcurrentUUIDGenerator genera UUIDs concurrentemente
type ConcurrentUUIDGenerator struct {
workers int
buffer chan uuid.UUID
wg sync.WaitGroup
stop chan struct{}
}
// NewConcurrentGenerator crea un nuevo generador UUID concurrente
func NewConcurrentGenerator(workers, bufferSize int) *ConcurrentUUIDGenerator {
g := &ConcurrentUUIDGenerator{
workers: workers,
buffer: make(chan uuid.UUID, bufferSize),
stop: make(chan struct{}),
}
g.start()
return g
}
// start inicia las goroutines trabajadoras
func (g *ConcurrentUUIDGenerator) start() {
g.wg.Add(g.workers)
for i := 0; i < g.workers; i++ {
go func() {
defer g.wg.Done()
for {
select {
case <-g.stop:
return
case g.buffer <- uuid.New():
// UUID generado y enviado al buffer
}
}
}()
}
}
// Get recupera un UUID del generador
func (g *ConcurrentUUIDGenerator) Get() uuid.UUID {
return <-g.buffer
}
// Stop detiene el generador graciosamente
func (g *ConcurrentUUIDGenerator) Stop() {
close(g.stop)
g.wg.Wait()
close(g.buffer)
}
func main() {
// Comparación de rendimiento
fmt.Println("Prueba de Rendimiento de Generación UUID")
fmt.Printf("Núcleos CPU: %d\n\n", runtime.NumCPU())
count := 1_000_000
// Generación secuencial
start := time.Now()
for i := 0; i < count; i++ {
_ = uuid.New()
}
sequential := time.Since(start)
fmt.Printf("Secuencial: %v para %d UUIDs\n", sequential, count)
fmt.Printf("Tasa: %.0f UUIDs/segundo\n\n", float64(count)/sequential.Seconds())
// Generación concurrente
generator := NewConcurrentGenerator(runtime.NumCPU(), 1000)
start = time.Now()
for i := 0; i < count; i++ {
_ = generator.Get()
}
concurrent := time.Since(start)
generator.Stop()
fmt.Printf("Concurrente: %v para %d UUIDs\n", concurrent, count)
fmt.Printf("Tasa: %.0f UUIDs/segundo\n", float64(count)/concurrent.Seconds())
fmt.Printf("Aceleración: %.2fx\n", sequential.Seconds()/concurrent.Seconds())
// Estimación de uso de memoria
fmt.Printf("\nMemoria por UUID: %d bytes\n", len(uuid.New()))
fmt.Printf("Memoria para %d UUIDs: %.2f MB\n", count, float64(count*16)/(1024*1024))
}
Este ejemplo de rendimiento demuestra tanto técnicas de benchmarking como un generador UUID concurrente para escenarios de alto rendimiento. El generador concurrente usa goroutines trabajadoras para generar UUIDs en paralelo, lo que puede mejorar significativamente el rendimiento en sistemas multi-núcleo. Los benchmarks te ayudan a entender las características de rendimiento de diferentes métodos de generación. Para la mayoría de las aplicaciones, el uuid.New() estándar es suficiente, pero para escenarios de alto volumen, la generación concurrente puede proporcionar mejor rendimiento.
Al trabajar con UUIDs en aplicaciones Go, elige la biblioteca y versión de UUID apropiadas basándote en tus requisitos específicos. El paquete google/uuid es recomendado para la mayoría de los casos de uso debido a su simplicidad, rendimiento y mantenimiento por Google. Para aplicaciones que requieren versiones específicas de UUID o características adicionales, satori/go.uuid proporciona más flexibilidad. Siempre valida los UUIDs de fuentes externas y considera las implicaciones de rendimiento al generar grandes cantidades de UUIDs.
Recuerda que aunque los UUIDs proporcionan excelentes garantías de unicidad, vienen con compensaciones como mayores requisitos de almacenamiento (16 bytes vs 4-8 bytes para enteros) y posibles impactos de rendimiento en índices de base de datos. Para datos de series temporales o cuando el ordenamiento es importante, considera usar UUIDs Versión 1 o Versión 7. Para identificadores únicos de propósito general, los UUIDs Versión 4 aleatorios son la opción más común. Siempre maneja los errores apropiadamente y considera implementar lógica de reintento para la generación de UUID en rutas críticas.
Para generación rápida de UUID sin escribir código, siempre puedes usar nuestra herramienta generadora de UUID en línea, que proporciona varias versiones y formatos de UUID con un simple clic.