Integración de Proto como Fuente Única de Verdad en Go con gorm.io/gen y para TypeScript
Resumen
Este documento describe un flujo avanzado para utilizar Proto como la fuente única de verdad en sistemas que requieren consistencia entre los modelos de datos, la base de datos, el backend en Go y el cliente en TypeScript. El enfoque incluye:
Definir modelos, relaciones (PK, FK) y servicios gRPC en un archivo Proto.
Generar automáticamente modelos enriquecidos en Go con
gorm.io/gen
.Generar tipos en TypeScript para el cliente.
Implementar un servicio gRPC que gestione las operaciones CRUD.
Introducción
La centralización de las definiciones de datos en un archivo Proto permite mantener consistencia entre las diferentes capas de una aplicación, desde la base de datos hasta el cliente. Este enfoque minimiza errores, acelera el desarrollo y asegura escalabilidad.
En este paper, implementaremos un flujo funcional y escalable que:
Usa Proto para definir esquemas y servicios.
Genera modelos compatibles con Gorm en Go.
Crea funciones enriquecidas con
gorm.io/gen
para optimizar las operaciones en la base de datos.Genera tipos en TypeScript para mantener consistencia entre backend y cliente.
1. Definición del Esquema Proto
El archivo Proto incluye modelos, relaciones y un servicio gRPC para manejar las operaciones de User
y Order
.
Archivo models.proto
syntax = "proto3";
package example;
import "gorm.proto"; // Requiere el plugin `protoc-gen-gorm`
// Modelo User
message User {
int32 id = 1 [(gorm.field).primary_key = true]; // Clave primaria
string name = 2 [(gorm.field).size = 255]; // Nombre del usuario
repeated Order orders = 3; // Relación uno-a-muchos
}
// Modelo Order
message Order {
int32 id = 1 [(gorm.field).primary_key = true]; // Clave primaria
int32 user_id = 2 [(gorm.field).foreign_key = "UserID"]; // Clave foránea hacia User
string product = 3 [(gorm.field).size = 255]; // Producto asociado
}
// Servicio gRPC para manejar User
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
}
// Mensajes para el servicio
message GetUserRequest {
int32 id = 1;
}
message GetUserResponse {
User user = 1;
}
message CreateUserRequest {
string name = 1;
}
message CreateUserResponse {
User user = 1;
}
2. Generación de Modelos en Go
Comando para Generar Modelos
Usa protoc-gen-gorm
para generar modelos compatibles con Gorm:
protoc --gorm_out=. --go_out=. models.proto
Salida Generada: Modelos en Go
Modelo User
type User struct {
ID int32 `gorm:"primaryKey"`
Name string `gorm:"size:255"`
Orders []Order `gorm:"foreignKey:UserID"` // Relación uno-a-muchos
}
Modelo Order
type Order struct {
ID int32 `gorm:"primaryKey"`
UserID int32 `gorm:"index"` // FK hacia User.ID
Product string `gorm:"size:255"`
}
3. Configurar gorm.io/gen
Instalar y Configurar
Instala gorm.io/gen
y crea un archivo main.go
para configurar el generador.
go get -u gorm.io/gen
Archivo main.go
:
package main
import (
"gorm.io/driver/postgres"
"gorm.io/gen"
"gorm.io/gorm"
)
func main() {
db, err := gorm.Open(postgres.Open("host=localhost user=postgres dbname=mydb password=mypass sslmode=disable"))
if err != nil {
panic("failed to connect database")
}
g := gen.NewGenerator(gen.Config{
OutPath: "./query",
Mode: gen.WithoutContext | gen.WithDefaultQuery,
})
g.UseDB(db)
g.ApplyBasic("User", "Order") // Registra los modelos
g.Execute()
}
Ejecutar Generador
go run main.go
Esto genera funciones enriquecidas en ./query
.
4. Generar Modelos en TypeScript
Comando para Generar TS
Usa ts-proto
para generar tipos TypeScript compatibles con el servicio gRPC:
protoc \
--plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \
--ts_proto_out=. \
models.proto
Salida Generada: Tipos en TypeScript
Modelo User
export interface User {
id: number;
name: string;
orders: Order[];
}
Modelo Order
export interface Order {
id: number;
userId: number;
product: string;
}
Servicio UserService
export interface UserService {
GetUser(request: GetUserRequest): Promise<GetUserResponse>;
CreateUser(request: CreateUserRequest): Promise<CreateUserResponse>;
}
5. Implementar el Servicio gRPC en Go
Archivo service/user_service.go
:
package service
import (
"context"
"query" // Código generado por gorm.io/gen
pb "path/to/models" // Código generado por protoc
)
type UserService struct {
db *query.Query
}
func NewUserService(db *query.Query) *UserService {
return &UserService{db: db}
}
func (s *UserService) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
user, err := s.db.User.Preload(s.db.User.Orders).Where(s.db.User.ID.Eq(req.Id)).First()
if err != nil {
return nil, err
}
return &pb.GetUserResponse{
User: &pb.User{
Id: user.ID,
Name: user.Name,
Orders: mapOrdersToProto(user.Orders),
},
}, nil
}
func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
newUser := &query.User{
Name: req.Name,
}
if err := s.db.User.Create(newUser); err != nil {
return nil, err
}
return &pb.CreateUserResponse{
User: &pb.User{
Id: newUser.ID,
Name: newUser.Name,
},
}, nil
}
func mapOrdersToProto(orders []query.Order) []*pb.Order {
var protoOrders []*pb.Order
for _, o := range orders {
protoOrders = append(protoOrders, &pb.Order{
Id: o.ID,
UserId: o.UserID,
Product: o.Product,
})
}
return protoOrders
}
6. Uso del Servicio en TypeScript
Cliente gRPC en TypeScript:
import { UserServiceClientImpl, GetUserRequest, CreateUserRequest } from './models';
const client = new UserServiceClientImpl(rpc);
async function fetchUser(id: number) {
const req: GetUserRequest = { id };
const res = await client.GetUser(req);
console.log('User:', res.user);
}
async function createUser(name: string) {
const req: CreateUserRequest = { name };
const res = await client.CreateUser(req);
console.log('New User:', res.user);
}
Conclusión
Este flujo:
Centraliza el control en Proto, eliminando duplicaciones y manteniendo consistencia.
Automatiza la generación de modelos, funciones enriquecidas y tipos.
Escala perfectamente: Agregar nuevos modelos o servicios es sencillo y coherente.
Con estas herramientas, puedes desarrollar aplicaciones robustas, escalables y consistentes desde la base de datos hasta el cliente.