Saltar al contenido principal

· 3 min de lectura
[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Migrar a un nuevo ORM no es un proceso sencillo, y el coste de transición puede ser prohibitivo para muchas organizaciones. Por mucho que a los desarrolladores nos cautiven las "novedades relucientes", la realidad es que rara vez tenemos la oportunidad de trabajar en un proyecto realmente "desde cero". Durante la mayor parte de nuestras carreras, operamos en contextos donde múltiples limitaciones técnicas y de negocio (también conocidas como sistemas heredados) dictan y restringen nuestras opciones para avanzar. Las tecnologías emergentes que quieran triunfar deben ofrecer capacidades de interoperabilidad y vías de integración que ayuden a las organizaciones a transitar sin problemas hacia nuevas formas de resolver problemas existentes.

Para ayudar a reducir el coste de migrar a Ent (o simplemente experimentar con él), hemos lanzado la "Schema Import Initiative" para respaldar numerosos casos de uso de generación de esquemas de Ent desde recursos externos. La pieza central de este esfuerzo es el paquete schemast (código fuente, documentación), que permite a los desarrolladores crear fácilmente programas que generen y manipulen esquemas de Ent. Con este paquete, los desarrolladores pueden programar usando una API de alto nivel, liberándoles de preocuparse por el análisis de código y las manipulaciones de AST.

Compatibilidad con importación de Protobuf

El primer proyecto en usar esta nueva API es protoc-gen-ent, un plugin de protoc para generar esquemas de Ent desde archivos .proto (documentación). Las organizaciones que ya tienen esquemas definidos en Protobuf pueden usar esta herramienta para generar automáticamente código de Ent. Por ejemplo, tomando una definición de mensaje sencilla:

syntax = "proto3";

package entpb;

option go_package = "github.com/yourorg/project/ent/proto/entpb";

message User {
string name = 1;
string email_address = 2;
}

Y estableciendo la opción ent.schema.gen a true:

syntax = "proto3";

package entpb;

+import "options/opts.proto";

option go_package = "github.com/yourorg/project/ent/proto/entpb";

message User {
+ option (ent.schema).gen = true; // <-- tell protoc-gen-ent you want to generate a schema from this message
string name = 1;
string email_address = 2;
}

Los desarrolladores pueden invocar el comando estándar protoc (compilador de protobuf) para usar este plugin:

protoc -I=proto/ --ent_out=. --ent_opt=schemadir=./schema proto/entpb/user.proto

Para generar esquemas de Ent a partir de estas definiciones:

package schema

import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)

type User struct {
ent.Schema
}

func (User) Fields() []ent.Field {
return []ent.Field{field.String("name"), field.String("email_address")}
}
func (User) Edges() []ent.Edge {
return nil
}

¡Para empezar a usar protoc-gen-ent hoy mismo y conocer todas las opciones de configuración disponibles, visita la documentación!

Únete a la Schema Import Initiative

¿Tienes esquemas definidos en otros lugares que te gustaría importar automáticamente a Ent? Con el paquete schemast, es más fácil que nunca crear la herramienta que necesitas para hacerlo. ¿No sabes por dónde empezar? ¿Quieres colaborar con la comunidad para planificar y desarrollar tu idea? ¡Contáctanos a través de nuestro servidor de Discord, canal de Slack o inicia un debate en GitHub!

[Para más noticias y actualizaciones de Ent:]

· 12 min de lectura
[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

ent + gRPC

Introducción

Definir esquemas de entidades en un formato centralizado y neutral al lenguaje ofrece muchas ventajas a medida que crece la escala de las organizaciones de desarrollo de software. Para lograrlo, muchas organizaciones utilizan Protocol Buffers como su lenguaje de definición de interfaces (IDL). Además, gRPC, un framework RPC basado en Protobuf inspirado en el sistema interno Stubby de Google, está ganando popularidad debido a su eficiencia y capacidades de generación de código.

Al ser un IDL, gRPC no establece pautas específicas para implementar la capa de acceso a datos, por lo que las implementaciones varían enormemente. Ent es un candidato natural para construir la capa de acceso a datos en cualquier aplicación Go, por lo que existe un gran potencial al integrar ambas tecnologías.

Hoy anunciamos una versión experimental de entproto, un paquete Go y una herramienta de línea de comandos para añadir soporte de Protobuf y gRPC a usuarios de Ent. Con entproto, los desarrolladores pueden configurar un servidor gRPC CRUD completamente funcional en pocos minutos. En este artículo, mostraremos exactamente cómo hacerlo.

Configuración inicial

La versión final de este tutorial está disponible en GitHub. Puedes clonarlo si prefieres seguirla de esa manera.

Empecemos inicializando un nuevo módulo Go para nuestro proyecto:

mkdir ent-grpc-example
cd ent-grpc-example
go mod init ent-grpc-example

A continuación usamos go run para invocar el generador de código de Ent e inicializar un esquema:

go run -mod=mod entgo.io/ent/cmd/ent new User

Nuestro directorio debería verse ahora así:

.
├── ent
│   ├── generate.go
│   └── schema
│   └── user.go
├── go.mod
└── go.sum

Ahora, añadamos el paquete entproto a nuestro proyecto:

go get -u entgo.io/contrib/entproto

A continuación, definiremos el esquema para la entidad User. Abre ent/schema/user.go y edita:

package schema

import (
"entgo.io/ent"
"entgo.io/ent/schema"
)

// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Unique(),
field.String("email_address").
Unique(),
}
}

En este paso, añadimos dos campos únicos a nuestra entidad User: name y email_address. El ent.Schema es solo la definición del esquema; para generar código utilizable a partir de él necesitamos ejecutar la herramienta de generación de código de Ent. Ejecuta:

go generate ./...

Observa que ahora se han creado varios archivos nuevos a partir de nuestra definición de esquema:

├── ent
│   ├── client.go
│   ├── config.go
// .... many more
│   ├── user
│   ├── user.go
│   ├── user_create.go
│   ├── user_delete.go
│   ├── user_query.go
│   └── user_update.go
├── go.mod
└── go.sum

En este punto, podemos abrir una conexión a una base de datos, ejecutar una migración para crear la tabla users, y comenzar a leer y escribir datos en ella. Esto se cubre en el Tutorial de Configuración, así que vayamos al grano y aprendamos sobre la generación de definiciones Protobuf y servidores gRPC a partir de nuestro esquema.

Generando Protobufs en Go con entproto

Dado que los esquemas de Ent y Protobuf no son idénticos, debemos añadir algunas anotaciones en nuestro esquema para ayudar a entproto a determinar exactamente cómo generar las definiciones Protobuf (llamadas "Mensajes" en la jerga de protobuf).

Lo primero que necesitamos hacer es añadir una anotación entproto.Message(). Este es nuestro opt-in para la generación del esquema Protobuf; no necesariamente queremos generar mensajes proto o definiciones de servicios gRPC para todas nuestras entidades de esquema, y esta anotación nos da ese control. Para añadirla, agrega en ent/schema/user.go:

func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entproto.Message(),
}
}

A continuación, debemos anotar cada campo y asignarle un número de campo. Recuerda que al definir un tipo de mensaje protobuf, cada campo debe tener un número único. Para ello, añadimos una anotación entproto.Field en cada campo. Actualiza los Fields en ent/schema/user.go:

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Unique().
Annotations(
entproto.Field(2),
),
field.String("email_address").
Unique().
Annotations(
entproto.Field(3),
),
}
}

Observa que no comenzamos nuestros números de campo desde 1, esto se debe a que ent crea implícitamente el campo ID para la entidad, y a ese campo se le asigna automáticamente el número 1. Ahora podemos generar nuestras definiciones de tipos de mensajes protobuf. Para hacerlo, añadiremos en ent/generate.go una directiva go:generate que invoque la herramienta de línea de comandos entproto. Ahora debería verse así:

package ent

//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema
//go:generate go run -mod=mod entgo.io/contrib/entproto/cmd/entproto -path ./schema

Vamos a regenerar nuestro código:

go generate ./...

Observa que se ha creado un nuevo directorio que contendrá todo el código generado relacionado con protobuf: ent/proto. Ahora contiene:

ent/proto
└── entpb
├── entpb.proto
└── generate.go

Se crearon dos archivos. Veamos su contenido:

// Code generated by entproto. DO NOT EDIT.
syntax = "proto3";

package entpb;

option go_package = "ent-grpc-example/ent/proto/entpb";

message User {
int32 id = 1;

string user_name = 2;

string email_address = 3;
}

¡Genial! Se ha creado un nuevo archivo .proto que contiene una definición de tipo de mensaje que se corresponde con nuestro esquema User.

package entpb
//go:generate protoc -I=.. --go_out=.. --go-grpc_out=.. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative --entgrpc_out=.. --entgrpc_opt=paths=source_relative,schema_path=../../schema entpb/entpb.proto

Se ha creado un nuevo archivo generate.go con una invocación a protoc, el generador de código protobuf, que le indica cómo generar código Go desde nuestro archivo .proto. Para que este comando funcione, primero debemos instalar protoc y 3 plugins de protobuf: protoc-gen-go (que genera estructuras Protobuf de Go), protoc-gen-go-grpc (que genera interfaces de servicio gRPC de Go y clientes), y protoc-gen-entgrpc (que genera una implementación de la interfaz de servicio). Si no los tienes instalados, sigue estas instrucciones:

Tras instalar estas dependencias, podemos volver a ejecutar la generación de código:

go generate ./...

Observa que se ha creado un nuevo archivo llamado ent/proto/entpb/entpb.pb.go que contiene las estructuras Go generadas para nuestras entidades.

Observa que se ha creado un nuevo archivo llamado pb_test.go que contiene las estructuras Go generadas para nuestras entidades.

package main

import (
"testing"

"ent-grpc-example/ent/proto/entpb"
)

func TestUserProto(t *testing.T) {
user := entpb.User{
Name: "rotemtam",
EmailAddress: "rotemtam@example.com",
}
if user.GetName() != "rotemtam" {
t.Fatal("expected user name to be rotemtam")
}
if user.GetEmailAddress() != "rotemtam@example.com" {
t.Fatal("expected email address to be rotemtam@example.com")
}
}

Para ejecutarlo:

go get -u./... # install deps of the generated package
go test ./...

¡Bravo! La prueba ha pasado. Hemos generado con éxito estructuras Protobuf de Go funcionales a partir de nuestro esquema Ent. A continuación, veamos cómo generar automáticamente un servidor gRPC CRUD funcional a partir de nuestro esquema.

¡Genial! La prueba pasa. Hemos generado con éxito estructuras Go Protobuf funcionales a partir de nuestro esquema Ent. A continuación, veamos cómo generar automáticamente un servidor gRPC CRUD funcional a partir de nuestro esquema.

Tener estructuras Protobuf generadas desde nuestro ent.Schema puede ser útil, pero lo que realmente nos interesa es obtener un servidor real que pueda crear, leer, actualizar y eliminar entidades de una base de datos real. ¡Para hacerlo, solo necesitamos actualizar una línea de código! Cuando anotamos un esquema con entproto.Service, le indicamos al code-gen de entproto que queremos generar una definición de servicio gRPC, que protoc-gen-entgrpc leerá para generar una implementación del servicio. Edita ent/schema/user.go y modifica las Annotations del esquema:

func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entproto.Message(),
+ entproto.Service(), // <-- add this
}
}

Ahora vuelve a ejecutar la generación de código:

go generate ./...

Observa algunos cambios interesantes en ent/proto/entpb:

ent/proto/entpb
├── entpb.pb.go
├── entpb.proto
├── entpb_grpc.pb.go
├── entpb_user_service.go
└── generate.go

Primero, entproto añadió una definición de servicio a entpb.proto:

service UserService {
rpc Create ( CreateUserRequest ) returns ( User );

rpc Get ( GetUserRequest ) returns ( User );

rpc Update ( UpdateUserRequest ) returns ( User );

rpc Delete ( DeleteUserRequest ) returns ( google.protobuf.Empty );
}

Además, se crearon dos archivos nuevos. El primero, ent_grpc.pb.go, contiene el stub del cliente gRPC y la definición de la interfaz. Si abres el archivo, encontrarás en él (entre muchas otras cosas):

// UserServiceClient is the client API for UserService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type UserServiceClient interface {
Create(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*User, error)
Get(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
Update(ctx context.Context, in *UpdateUserRequest, opts ...grpc.CallOption) (*User, error)
Delete(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
}

El segundo archivo, entpub_user_service.go, contiene una implementación generada para esta interfaz. Por ejemplo, una implementación para el método Get:

// Get implements UserServiceServer.Get
func (svc *UserService) Get(ctx context.Context, req *GetUserRequest) (*User, error) {
get, err := svc.client.User.Get(ctx, int(req.GetId()))
switch {
case err == nil:
return toProtoUser(get), nil
case ent.IsNotFound(err):
return nil, status.Errorf(codes.NotFound, "not found: %s", err)
default:
return nil, status.Errorf(codes.Internal, "internal error: %s", err)
}
}

¡Nada mal! A continuación, creemos un servidor gRPC que pueda atender las solicitudes de nuestro servicio.

Creación del servidor

Crea un nuevo archivo cmd/server/main.go y escribe:

package main

import (
"context"
"log"
"net"

_ "github.com/mattn/go-sqlite3"
"ent-grpc-example/ent"
"ent-grpc-example/ent/proto/entpb"
"google.golang.org/grpc"
)

func main() {
// Initialize an ent client.
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()

// Run the migration tool (creating tables, etc).
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}

// Initialize the generated User service.
svc := entpb.NewUserService(client)

// Create a new gRPC server (you can wire multiple services to a single server).
server := grpc.NewServer()

// Register the User service with the server.
entpb.RegisterUserServiceServer(server, svc)

// Open port 5000 for listening to traffic.
lis, err := net.Listen("tcp", ":5000")
if err != nil {
log.Fatalf("failed listening: %s", err)
}

// Listen for traffic indefinitely.
if err := server.Serve(lis); err != nil {
log.Fatalf("server ended: %s", err)
}
}

Fíjate que hemos añadido una importación de github.com/mattn/go-sqlite3, por lo que debemos añadirla a nuestro módulo:

go get -u github.com/mattn/go-sqlite3

A continuación, vamos a ejecutar el servidor mientras escribimos un cliente que se comunicará con él:

go run -mod=mod ./cmd/server

Creación del cliente

Vamos a crear un cliente simple que realizará algunas llamadas a nuestro servidor. Crea un nuevo archivo llamado cmd/client/main.go y escribe:

package main

import (
"context"
"fmt"
"log"
"math/rand"
"time"

"ent-grpc-example/ent/proto/entpb"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)

func main() {
rand.Seed(time.Now().UnixNano())

// Open a connection to the server.
conn, err := grpc.Dial(":5000", grpc.WithInsecure())
if err != nil {
log.Fatalf("failed connecting to server: %s", err)
}
defer conn.Close()

// Create a User service Client on the connection.
client := entpb.NewUserServiceClient(conn)

// Ask the server to create a random User.
ctx := context.Background()
user := randomUser()
created, err := client.Create(ctx, &entpb.CreateUserRequest{
User: user,
})
if err != nil {
se, _ := status.FromError(err)
log.Fatalf("failed creating user: status=%s message=%s", se.Code(), se.Message())
}
log.Printf("user created with id: %d", created.Id)

// On a separate RPC invocation, retrieve the user we saved previously.
get, err := client.Get(ctx, &entpb.GetUserRequest{
Id: created.Id,
})
if err != nil {
se, _ := status.FromError(err)
log.Fatalf("failed retrieving user: status=%s message=%s", se.Code(), se.Message())
}
log.Printf("retrieved user with id=%d: %v", get.Id, get)
}

func randomUser() *entpb.User {
return &entpb.User{
Name: fmt.Sprintf("user_%d", rand.Int()),
EmailAddress: fmt.Sprintf("user_%d@example.com", rand.Int()),
}
}

Nuestro cliente crea una conexión al puerto 5000, donde nuestro servidor está escuchando, luego emite una solicitud Create para crear un nuevo usuario, y luego emite una segunda solicitud Get para recuperarlo de la base de datos. Ejecutemos nuestro código de cliente:

go run ./cmd/client

Ejecutemos nuestro código de cliente:

2021/03/18 10:42:58 user created with id: 1
2021/03/18 10:42:58 retrieved user with id=1: id:1 name:"user_730811260095307266" email_address:"user_7338662242574055998@example.com"

¡Increíble! Con unas pocas anotaciones en nuestro esquema, usamos los superpoderes de la generación de código para crear un servidor gRPC funcional en muy poco tiempo.

¡Increíble! Con solo unas anotaciones en nuestro esquema, hemos aprovechado los superpoderes de la generación de código para crear un servidor gRPC funcional en un instante.

entproto aún se encuentra en etapa experimental y carece de cierta funcionalidad básica. Por ejemplo, muchas aplicaciones probablemente querrán un método List o Find en su servicio, pero estos aún no están soportados. Además, hay otros problemas que planeamos abordar en un futuro próximo:

  • Actualmente solo se admiten aristas "únicas" (O2O, O2M).

  • Actualmente solo se admiten relaciones "únicas" (O2O, O2M).

  • Los métodos de "mutación" generados (Create/Update) actualmente establecen todos los campos, sin considerar valores cero/nulos ni la nulabilidad de los campos.

Próximos pasos

Creemos que ent + gRPC puede ser una excelente manera de construir aplicaciones de servidor en Go. Por ejemplo, para establecer controles de acceso granulares a las entidades gestionadas por nuestra aplicación, los desarrolladores ya pueden usar Políticas de Privacidad que funcionan sin configuración adicional con la integración gRPC. Para ejecutar código Go arbitrario en los diferentes eventos del ciclo de vida de las entidades, los desarrolladores pueden utilizar Hooks personalizados.

¿Quieres construir servidores gRPC con ent? Si necesitas ayuda para configurarlo o quieres que la integración admita tu caso de uso, contáctanos a través de nuestra Página de Discusiones en GitHub, en el canal #ent de Gophers Slack o en nuestro servidor de Discord.

[Para más noticias y actualizaciones de Ent:]

· 5 min de lectura
[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Durante los últimos meses, ha habido mucha discusión en los issues del proyecto Ent sobre añadir soporte para recuperar el campo de clave externa al obtener entidades con relaciones Uno-a-Uno o Uno-a-Muchos. Nos complace anunciar que a partir de v0.7.0 Ent soporta esta funcionalidad.

Antes del soporte para campos de borde

Antes de fusionar esta rama, un usuario que quería recuperar el campo de clave externa para una entidad necesitaba usar eager-loading. Supongamos que nuestro esquema era así:

// ent/schema/user.go:

// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Unique().
NotEmpty(),
}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.From("pets", Pet.Type).
Ref("owner"),
}
}

// ent/schema/pet.go

// Pet holds the schema definition for the Pet entity.
type Pet struct {
ent.Schema
}

// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.String("name").
NotEmpty(),
}
}

// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.To("owner", User.Type).
Unique().
Required(),
}
}

El esquema describe dos entidades relacionadas: User y Pet, con una relación Uno-a-Muchos entre ellas: un usuario puede tener varias mascotas y una mascota puede tener un único dueño.

Al recuperar mascotas del almacenamiento de datos, es común que los desarrolladores quieran acceder al campo de clave externa en la mascota. Sin embargo, dado que este campo se creaba implícitamente desde el borde owner, no era accesible automáticamente al recuperar una entidad. Para obtenerlo, un desarrollador necesitaba hacer algo como:

func Test(t *testing.T) {
ctx := context.Background()
c := enttest.Open(t, dialect.SQLite, "file:ent?mode=memory&cache=shared&_fk=1")
defer c.Close()

// Create the User
u := c.User.Create().
SetUserName("rotem").
SaveX(ctx)

// Create the Pet
p := c.Pet.
Create().
SetOwner(u). // Associate with the user
SetName("donut").
SaveX(ctx)

petWithOwnerId := c.Pet.Query().
Where(pet.ID(p.ID)).
WithOwner(func(query *ent.UserQuery) {
query.Select(user.FieldID)
}).
OnlyX(ctx)
fmt.Println(petWithOwnerId.Edges.Owner.ID)
// Output: 1
}

Además de ser muy verboso, recuperar la mascota con el dueño de esta manera era ineficiente en términos de consultas a la base de datos. Si ejecutamos la consulta con .Debug() podemos ver las consultas SQL que genera Ent para satisfacer esta llamada:

SELECT DISTINCT `pets`.`id`, `pets`.`name`, `pets`.`pet_owner` FROM `pets` WHERE `pets`.`id` = ? LIMIT 2 
SELECT DISTINCT `users`.`id` FROM `users` WHERE `users`.`id` IN (?)

En este ejemplo, Ent primero recupera la mascota con ID 1, luego vuelve a obtener redundante el campo id de la tabla users para usuarios con ID 1.

Con soporte para campos de borde

El soporte para campos de borde simplifica enormemente y mejora la eficiencia de este flujo. Con esta funcionalidad, los desarrolladores pueden definir el campo de clave externa como parte de Fields() del esquema, y usando el modificador .Field(..) en la definición del borde, indicar a Ent que exponga y mapee la columna externa a este campo. Así que en nuestro esquema de ejemplo, lo modificaríamos así:

// user.go stays the same

// pet.go
// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.String("name").
NotEmpty(),
field.Int("owner_id"), // <-- explicitly add the field we want to contain the FK
}
}

// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.To("owner", User.Type).
Field("owner_id"). // <-- tell ent which field holds the reference to the owner
Unique().
Required(),
}
}

Para actualizar nuestro código cliente necesitamos volver a ejecutar la generación de código:

go generate ./...

Ahora podemos modificar nuestra consulta para que sea mucho más simple:

func Test(t *testing.T) {
ctx := context.Background()
c := enttest.Open(t, dialect.SQLite, "file:ent?mode=memory&cache=shared&_fk=1")
defer c.Close()

u := c.User.Create().
SetUserName("rotem").
SaveX(ctx)

p := c.Pet.Create().
SetOwner(u).
SetName("donut").
SaveX(ctx)

petWithOwnerId := c.Pet.GetX(ctx, p.ID) // <-- Simply retrieve the Pet

fmt.Println(petWithOwnerId.OwnerID)
// Output: 1
}

Al ejecutar con el modificador .Debug() podemos ver que las consultas SQL tienen más sentido ahora:

SELECT DISTINCT `pets`.`id`, `pets`.`name`, `pets`.`owner_id` FROM `pets` WHERE `pets`.`id` = ? LIMIT 2

¡Hurra 🎉!

Migrando esquemas existentes a campos de borde

Si ya usas Ent con un esquema existente, probablemente ya tengas relaciones O2M cuyas columnas de clave externa ya existen en tu base de datos. Dependiendo de cómo configuraste tu esquema, es posible que estén almacenadas en una columna con nombre diferente al campo que ahora añades. Por ejemplo, quieres crear un campo owner_id, pero Ent creó automáticamente la columna de clave externa como pet_owner.

Para verificar qué nombre de columna está usando Ent para este campo, puedes mirar en el archivo ./ent/migrate/schema.go:

PetsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
{Name: "name", Type: field.TypeString},
{Name: "pet_owner", Type: field.TypeInt, Nullable: true}, // <-- this is our FK
}

Para permitir una migración fluida, debes indicar explícitamente a Ent que siga usando el nombre de columna existente. Puedes hacer esto usando el modificador StorageKey (ya sea en el campo o en el borde). Por ejemplo:

// In schema/pet.go:

// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.String("name").
NotEmpty(),
field.Int("owner_id").
StorageKey("pet_owner"), // <-- explicitly set the column name
}
}

En un futuro próximo planeamos implementar Schema Versioning, que almacenará el historial de cambios de esquema junto al código. Tener esta información permitirá a Ent soportar estas migraciones de forma automática y predecible.

Conclusión

El soporte para campos de borde está disponible inmediatamente y puede instalarse con go get -u entgo.io/ent@v0.7.0.

Muchas gracias 🙏 a todas las personas que se tomaron el tiempo de dar su opinión y ayudaron a diseñar adecuadamente esta característica: Alex Snast, Ruben de Vries, Marwan Sulaiman, Andy Day, Sebastian Fekete y Joe Harvey.

Para más noticias y actualizaciones de Ent:

· 3 min de lectura
[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

El estado de Go en Facebook Connectivity Tel Aviv

Hace 20 meses, me uní al equipo de Facebook Connectivity (FBC) en Tel Aviv tras aproximadamente 5 años programando en Go e implementándolo en varias empresas.
Me incorporé a un equipo que trabajaba en un nuevo proyecto y necesitábamos elegir un lenguaje para esta misión. Comparamos varias opciones y decidimos usar Go.

Desde entonces, Go se ha expandido a otros proyectos de FBC con gran éxito, contando ya con unos 15 ingenieros especializados en Go solo en Tel Aviv. Los nuevos servicios ahora se desarrollan en Go.

La motivación para crear un nuevo ORM en Go

Durante mis 5 años anteriores a Facebook, la mayor parte de mi trabajo fue en herramientas de infraestructura y microservicios sin mucho modelado de datos. Los servicios que requerían poca interacción con bases de datos SQL usaban soluciones open-source existentes, pero para modelos de datos complejos se recurría a otros lenguajes con ORMs robustos, como Python con SQLAlchemy.

En Facebook nos gusta conceptualizar nuestros modelos de datos como grafos. Hemos tenido buenas experiencias con este enfoque internamente.
La falta de un ORM basado en grafos adecuado para Go nos llevó a desarrollar uno con estos principios:

  • Esquema como código: definir tipos, relaciones y restricciones debe hacerse en código Go (no mediante struct tags) y validarse con una herramienta CLI. Contamos con experiencia previa en herramientas similares internamente.

  • API estáticamente tipada y explícita mediante generación de código: las APIs repletas de interface{} afectan la productividad de los desarrolladores, especialmente a los nuevos en el proyecto.

  • Consultas, agregaciones y recorridos de grafos deben ser simples: los desarrolladores no quieren lidiar con consultas SQL crudas ni terminología SQL.

  • Los predicados deben estar estáticamente tipados. Evitar cadenas de texto por todas partes.

  • Soporte completo para context.Context: esto proporciona visibilidad en nuestros sistemas de trazas y logs, y es crucial para características como la cancelación.

  • Independiente del almacenamiento: mantuvimos la capa de almacenamiento dinámica usando plantillas de generación de código, ya que el desarrollo comenzó con Gremlin (AWS Neptune) y luego migró a MySQL.

Lanzamiento open-source de ent

ent es un framework de entidades (ORM) para Go construido con los principios anteriores. ent permite definir fácilmente cualquier modelo de datos o estructura de grafos en código Go; la configuración del esquema es verificada por entc (el generador de código de ent) que produce una API idiomática y estáticamente tipada que mantiene productivos y satisfechos a los desarrolladores Go. Es compatible con MySQL, MariaDB, PostgreSQL, SQLite y bases de datos de grafos basadas en Gremlin.

Hoy lanzamos ent como open-source y te invitamos a empezar → entgo.io/docs/getting-started.