Si estás utilizando Git dentro del curso, la forma para cambiar de nombre el directorio es:
git mv proto/ studentpb/
# git mv <nombre actual> <nombre nuevo>
Con esto, a Git no le genera conflicto el rastreo de esos archivos.
¿Cómo crear un servidor gRPC en Go que implemente métodos reales de lectura y escritura? En esta guía aprenderás a construir tu primer servidor gRPC conectado a un repositorio de base de datos, implementar los métodos GetStudent y SetStudent, y registrarlo para que escuche conexiones TCP. Es ideal para quienes ya definieron su capa de datos y quieren dar el salto a la comunicación entre servicios.
Antes de tocar gRPC, ya habíamos definido a nivel de base de datos y de repositorio cómo insertar y leer estudiantes. Ahora toca lo interesante: darle vida al servidor.
Cómo defino la struct del servidor y su constructor
Todo empieza creando una carpeta nueva llamada server y dentro un archivo server.go con el paquete server. Ahí defines una struct que recibe un tipo repositorio como valor interno. Ese repositorio es la puerta de entrada a la base de datos.
Luego creas el constructor NewStudentServer, que recibe un repo de tipo repository y devuelve un servidor con ese repo adentro [00:38]. Con eso tienes la base lista para implementar las funciones.
¿Para qué sirve el constructor en un servidor gRPC en Go? Sirve para inyectar dependencias, como el repositorio de base de datos, dentro de la struct del servidor. Así el servidor puede leer y escribir datos sin crear esas conexiones por su cuenta.
Un detalle práctico: antes de importar el código generado por proto buffer, conviene renombrar esa carpeta con el mismo nombre del paquete, StudentPB. Así Go no tiene problemas identificándolo al importarlo [01:25].
Cómo implemento GetStudent y SetStudent en gRPC
Aquí es donde conectas el servidor con el archivo proto que generaste previamente. Cada método recibe un contexto y un request cuyo tipo viene directamente del proto.
Qué recibe y qué devuelve GetStudent
El método GetStudent recibe un contexto y un request de tipo StudentPB.GetStudentRequest, y devuelve un StudentPB.Student junto con un error [02:00]. Dentro, pides al repositorio el estudiante usando el contexto y el ID que viene en el request.
Tienes dos formas de leer ese ID: acceder directo a la propiedad id o usar el método GetId. Ambas hacen prácticamente lo mismo, así que elige la que te resulte más cómoda [02:38].
Si hay error, retornas un valor nulo y el error. Si todo sale bien, construyes un StudentPB.Student con tres propiedades:
Y aquí viene un punto clave: el Student que devuelves viene del paquete StudentPB, mientras que el que lees viene del modelo de datos que definiste. No son la misma estructura de datos, son diferentes [03:30]. Confundirlas es un error común al empezar.
Cómo funciona SetStudent al insertar datos
El SetStudent cambia respecto al anterior. Su request no es un GetStudentRequest, sino un StudentPB.Student, y en vez de devolver un estudiante devuelve un StudentPB.SetStudentResponse [04:10].
Primero construyes el estudiante que mandarás al repositorio, usando el modelo models.Student con id, name y age tomados del request mediante GetId, Name y GetAge. Luego llamas a SetStudent del repositorio pasando el contexto y el estudiante.
Si el error es distinto de nulo, lo devuelves. Si la inserción fue correcta, devuelves un SetStudentResponse con el id del estudiante y un valor nulo [05:30].
Cómo levanto el servidor gRPC con un listener TCP
Con los métodos listos, creas una carpeta server/student y dentro un main.go con package main y la función main. El primer paso real es abrir un listener de conexión TCP.
Para eso usas net.Listen, especificas que es tipo TCP y defines el puerto. En este ejemplo se usa el puerto 5060, aunque es totalmente configurable [06:30]. Si algo falla, un log.Fatal con el error finaliza el programa automáticamente.
Después traes el repositorio con NewPostgresRepository usando la misma URL de cursos previos, y creas el servidor con server.NewStudentServer pasando ese repositorio [07:10].
¿Qué hace grpc.NewServer en Go? Crea una nueva instancia de servidor gRPC capaz de recibir y procesar llamadas remotas. Sobre esa instancia registras tus servicios para exponer los métodos definidos en el proto.
Luego llamas a StudentPB.RegisterStudentServiceServer, pasando la conexión gRPC y el servidor que implementa los métodos definidos [08:00].
Por qué aparece el error de interfaz y cómo lo corrijo
Al registrar el servicio es probable que aparezca un error diciendo que fallas al implementar algo. El registro espera una interfaz StudentServiceServer que tu servidor todavía no cumple [08:20].
La solución está en la composición. Vas al archivo server.go y agregas dentro de la struct del servidor el campo StudentPB.UnimplementedServiceServer.
¿Por qué Go usa composición en vez de herencia? Porque Go no tiene herencia tradicional. Al embeber una struct dentro de otra, la externa adquiere todas las propiedades y métodos de la interna. Así tu servidor hereda la implementación base requerida por la interfaz.
Con ese embebido, tu servidor pasa a poseer todas esas propiedades y cumple la definición de StudentServiceServer. Al volver al main.go, el error desaparece [09:00].
Qué es Reflection y por qué activarlo antes de iniciar
Antes de levantar el servidor conviene agregar algo llamado Reflection. Su función es proveer metadata a los clientes sobre los servicios disponibles.
Esa metadata es justo lo que necesita una herramienta como Postman para testear tu API gRPC. Por eso es importante activarlo antes de inicializar el servidor [09:40].
Finalmente levantas todo con s.Serve, pasando el listener creado al inicio. Si el error es distinto de nulo, la aplicación termina [10:10]. Con esto ya tienes tu primer servidor de estudiantes implementando SetStudent y GetStudent.
Aún falta probarlo con Postman, igual que en cursos anteriores. ¿Te animas a dejar en los comentarios en qué puerto configuraste tu servidor y por qué?
¿Cómo crear un servidor gRPC en Go que implemente métodos reales de lectura y escritura? En esta guía aprenderás a construir tu primer servidor gRPC conectado a un repositorio de base de datos, implementar los métodos GetStudent y SetStudent, y registrarlo para que escuche conexiones TCP. Es ideal para quienes ya definieron su capa de datos y quieren dar el salto a la comunicación entre servicios.
Antes de tocar gRPC, ya habíamos definido a nivel de base de datos y de repositorio cómo insertar y leer estudiantes. Ahora toca lo interesante: darle vida al servidor.
Todo empieza creando una carpeta nueva llamada server y dentro un archivo server.go con el paquete server. Ahí defines una struct que recibe un tipo repositorio como valor interno. Ese repositorio es la puerta de entrada a la base de datos.
Luego creas el constructor NewStudentServer, que recibe un repo de tipo repository y devuelve un servidor con ese repo adentro [00:38]. Con eso tienes la base lista para implementar las funciones.
¿Para qué sirve el constructor en un servidor gRPC en Go? Sirve para inyectar dependencias, como el repositorio de base de datos, dentro de la struct del servidor. Así el servidor puede leer y escribir datos sin crear esas conexiones por su cuenta.
Un detalle práctico: antes de importar el código generado por proto buffer, conviene renombrar esa carpeta con el mismo nombre del paquete, StudentPB. Así Go no tiene problemas identificándolo al importarlo [01:25].
Aquí es donde conectas el servidor con el archivo proto que generaste previamente. Cada método recibe un contexto y un request cuyo tipo viene directamente del proto.
El método GetStudent recibe un contexto y un request de tipo StudentPB.GetStudentRequest, y devuelve un StudentPB.Student junto con un error [02:00]. Dentro, pides al repositorio el estudiante usando el contexto y el ID que viene en el request.
Tienes dos formas de leer ese ID: acceder directo a la propiedad id o usar el método GetId. Ambas hacen prácticamente lo mismo, así que elige la que te resulte más cómoda [02:38].
Si hay error, retornas un valor nulo y el error. Si todo sale bien, construyes un StudentPB.Student con tres propiedades:
Y aquí viene un punto clave: el Student que devuelves viene del paquete StudentPB, mientras que el que lees viene del modelo de datos que definiste. No son la misma estructura de datos, son diferentes [03:30]. Confundirlas es un error común al empezar.
El SetStudent cambia respecto al anterior. Su request no es un GetStudentRequest, sino un StudentPB.Student, y en vez de devolver un estudiante devuelve un StudentPB.SetStudentResponse [04:10].
Primero construyes el estudiante que mandarás al repositorio, usando el modelo models.Student con id, name y age tomados del request mediante GetId, Name y GetAge. Luego llamas a SetStudent del repositorio pasando el contexto y el estudiante.
Si el error es distinto de nulo, lo devuelves. Si la inserción fue correcta, devuelves un SetStudentResponse con el id del estudiante y un valor nulo [05:30].
Con los métodos listos, creas una carpeta server/student y dentro un main.go con package main y la función main. El primer paso real es abrir un listener de conexión TCP.
Para eso usas net.Listen, especificas que es tipo TCP y defines el puerto. En este ejemplo se usa el puerto 5060, aunque es totalmente configurable [06:30]. Si algo falla, un log.Fatal con el error finaliza el programa automáticamente.
Después traes el repositorio con NewPostgresRepository usando la misma URL de cursos previos, y creas el servidor con server.NewStudentServer pasando ese repositorio [07:10].
¿Qué hace grpc.NewServer en Go? Crea una nueva instancia de servidor gRPC capaz de recibir y procesar llamadas remotas. Sobre esa instancia registras tus servicios para exponer los métodos definidos en el proto.
Luego llamas a StudentPB.RegisterStudentServiceServer, pasando la conexión gRPC y el servidor que implementa los métodos definidos [08:00].
Al registrar el servicio es probable que aparezca un error diciendo que fallas al implementar algo. El registro espera una interfaz StudentServiceServer que tu servidor todavía no cumple [08:20].
La solución está en la composición. Vas al archivo server.go y agregas dentro de la struct del servidor el campo StudentPB.UnimplementedServiceServer.
¿Por qué Go usa composición en vez de herencia? Porque Go no tiene herencia tradicional. Al embeber una struct dentro de otra, la externa adquiere todas las propiedades y métodos de la interna. Así tu servidor hereda la implementación base requerida por la interfaz.
Con ese embebido, tu servidor pasa a poseer todas esas propiedades y cumple la definición de StudentServiceServer. Al volver al main.go, el error desaparece [09:00].
Antes de levantar el servidor conviene agregar algo llamado Reflection. Su función es proveer metadata a los clientes sobre los servicios disponibles.
Esa metadata es justo lo que necesita una herramienta como Postman para testear tu API gRPC. Por eso es importante activarlo antes de inicializar el servidor [09:40].
Finalmente levantas todo con s.Serve, pasando el listener creado al inicio. Si el error es distinto de nulo, la aplicación termina [10:10]. Con esto ya tienes tu primer servidor de estudiantes implementando SetStudent y GetStudent.
Aún falta probarlo con Postman, igual que en cursos anteriores. ¿Te animas a dejar en los comentarios en qué puerto configuraste tu servidor y por qué?
Si estás utilizando Git dentro del curso, la forma para cambiar de nombre el directorio es:
git mv proto/ studentpb/
# git mv <nombre actual> <nombre nuevo>
Con esto, a Git no le genera conflicto el rastreo de esos archivos.
Objetivo de la clase: crear servidor gRPC.
El nombre del directorio que contiene el protobuffer, tiene que ser el mismo nombre de paquete que declaramos en el archivo .proto:
option go_package = "github.com/neecosanudo/platzi-grpc;studentpb";
Renombramos el directorio proto a studentpb con Git:
git mv proto/ studentpb/
++Nota++: escribí comentarios sobre los archivos de los servidores para tener como notas. Dejo el contenido de ambos archivos en los comentarios de este comentario.
go get google.golang.org/grpc/reflection
Una persona que tenga poco experiencia con este maestro se lo llevo candonga...
muy poca explicación del código
Nicolás Sañudo
Si estás utilizando Git dentro del curso, la forma para cambiar de nombre el directorio es:
git mv proto/ studentpb/ # git mv <nombre actual> <nombre nuevo>
Con esto, a Git no le genera conflicto el rastreo de esos archivos.
Nicolás Sañudo
Estos cambios deben ser incluidos en el nuevo commit:
git status # On branch main # Your branch is up to date with 'origin/main'. # Changes to be committed: # (use "git restore --staged <file>..." to unstage) # renamed: proto/student.pb.go -> studentpb/student.pb.go # renamed: proto/student.proto -> studentpb/student.proto # renamed: proto/student_grpc.pb.go -> studentpb/student_grpc.pb.go
Pero si cambian el nombre sin decirle a Git que lo hicieron, Git va a pensar que eliminaron el directorio y su contenido, y crearon un nuevo directorio con nuevos archivos.
Nicolás Sañudo
Objetivo de la clase: crear servidor gRPC.
El nombre del directorio que contiene el protobuffer, tiene que ser el mismo nombre de paquete que declaramos en el archivo .proto:
option go_package = "github.com/neecosanudo/platzi-grpc;studentpb";
Renombramos el directorio proto a studentpb con Git:
git mv proto/ studentpb/
++Nota++: escribí comentarios sobre los archivos de los servidores para tener como notas. Dejo el contenido de ambos archivos en los comentarios de este comentario.
Nicolás Sañudo
Nicolás Sañudo
Cristóbal Contreras
go get google.golang.org/grpc/reflection
Gregory Taveras Ramirez
Una persona que tenga poco experiencia con este maestro se lo llevo candonga...
muy poca explicación del código
Dentro de server-student/:
/** filename: server.go */ package main import ( "log" "net" "github.com/neecosanudo/platzi-grpc/database" "github.com/neecosanudo/platzi-grpc/server" "github.com/neecosanudo/platzi-grpc/studentpb" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ) func main() { list, err := net.Listen("tcp", ":5060") if err != nil { log.Fatal(err) } repo, err := database.NewPostgresRepository("postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable") server := server.NewStudentServer(repo) if err != nil { log.Fatal(err) } // Utilizamos gRPC por primera vez s := grpc.NewServer() studentpb.RegisterStudentServiceServer(s, server) // reflection va a servir para poder proveer cierta meta-data a los clientes reflection.Register(s) // Levantamos el servidor err = s.Serve(list) if err != nil { log.Fatal(err) } }
Dentro de server/:
/** filename: server.go */ package server import ( "context" "github.com/neecosanudo/platzi-grpc/models" "github.com/neecosanudo/platzi-grpc/repository" "github.com/neecosanudo/platzi-grpc/studentpb" ) type Server struct { repo repository.Repository // Composition Over Inheritance: importante para que el servidor grpc admita este server studentpb.UnimplementedStudentServiceServer } // Constructor del servidor func NewStudentServer(repo repository.Repository) *Server { return &Server{repo: repo} } // Implementamos las funciones GetStudent y SetStudent func (s *Server) GetStudent(ctx context.Context, req *studentpb.GetStudentRequest) (*studentpb.Student, error) { // Traemos el estudiante que se encontró a través del repository student, err := s.repo.GetStudent(ctx, req.GetId()) /** req.GetId() puede ser pasado como req.Id Ambos refieren al ID que declaramos en el archivo .proto: message GetStudentRequest { string id = 1; } */ if err != nil { return nil, err } // &studentpb.Student viene del protobuffer que definimos. return &studentpb.Student{ // student.Id, student.Name y student.Age, vienen del struct en models/ Id: student.Id, Name: student.Name, Age: student.Age, }, nil } func (s *Server) SetStudent(ctx context.Context, req *studentpb.Student) (*studentpb.SetStudentResponse, error) { // Creamos el estudiante que vamos a enviar por el servidor student := &models.Student{ // struct que definimos en models/ // req.GetId(), req.GetName() y req.GetAge(), vienen del protobuffer Id: req.GetId(), Name: req.GetName(), Age: req.GetAge(), } err := s.repo.SetStudent(ctx, student) if err != nil { return nil, err } return &studentpb.SetStudentResponse{ Id: student.Id, }, nil }