mirror of
https://github.com/ent/ent.git
synced 2026-04-28 13:40:56 +03:00
140 lines
4.8 KiB
Markdown
140 lines
4.8 KiB
Markdown
---
|
|
id: grpc-generating-proto
|
|
title: Generating Protobufs with entproto
|
|
sidebar_label: Generating Protobufs
|
|
---
|
|
As Ent and Protobuf schemas are not identical, we must supply some annotations on our schema to help `entproto` figure out exactly how to generate Protobuf definitions (called "Messages" in protobuf terminology).
|
|
|
|
The first thing we need to do is to add an `entproto.Message()` annotation. This is our opt-in to Protobuf schema generation, we don't necessarily want to generate proto messages or gRPC service definitions from *all* of our schema entities, and this annotation gives us that control. To add it, append to `ent/schema/user.go`:
|
|
|
|
```go title="ent/schema/user.go"
|
|
func (User) Annotations() []schema.Annotation {
|
|
return []schema.Annotation{
|
|
entproto.Message(),
|
|
}
|
|
}
|
|
```
|
|
|
|
Next, we need to annotate each field and assign it a field number. Recall that when [defining a protobuf message type](https://developers.google.com/protocol-buffers/docs/proto3#simple), each field must be assigned a unique number. To do that, we add an `entproto.Field` annotation on each field. Update the `Fields` in `ent/schema/user.go`:
|
|
|
|
```go title="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),
|
|
),
|
|
}
|
|
}
|
|
```
|
|
|
|
Notice that we did not start our field numbers from 1, this is because `ent` implicitly creates the `ID` field for the entity, and that field is automatically assigned the number 1. We can now generate our protobuf message type definitions. To do that, we will add to `ent/generate.go` a `go:generate` directive that invokes the `entproto` command-line tool. It should now look like this:
|
|
|
|
```go title="ent/generate.go"
|
|
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
|
|
```
|
|
|
|
Let's re-generate our code:
|
|
|
|
```console
|
|
go generate ./...
|
|
```
|
|
|
|
Observe that a new directory was created which will contain all protobuf related generated code: `ent/proto`. It now contains:
|
|
|
|
```console
|
|
ent/proto
|
|
└── entpb
|
|
├── entpb.proto
|
|
└── generate.go
|
|
```
|
|
|
|
Two files were created. Let's look at their contents:
|
|
|
|
```protobuf title="ent/proto/entpb/entpb.proto"
|
|
// 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;
|
|
}
|
|
```
|
|
|
|
Nice! A new `.proto` file containing a message type definition that maps to our `User` schema was created!
|
|
|
|
```go title="ent/proto/entpb/generate.go"
|
|
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
|
|
```
|
|
|
|
A new `generate.go` file was created with an invocation to `protoc`, the protobuf code generator instructing it how to generate Go code from our `.proto` file. For this command to work, we must first install `protoc` as well as 3 protobuf plugins: `protoc-gen-go` (which generates Go Protobuf structs), `protoc-gen-go-grpc` (which generates Go gRPC service interfaces and clients), and `protoc-gen-entgrpc` (which generates an implementation of the service interface). If you do not have these installed, please follow these directions:
|
|
|
|
- [protoc installation](https://grpc.io/docs/protoc-installation/)
|
|
- [protoc-gen-go + protoc-gen-go-grpc installation](https://grpc.io/docs/languages/go/quickstart/)
|
|
- To install `protoc-gen-entgrpc`, run:
|
|
|
|
```
|
|
go install entgo.io/contrib/entproto/cmd/protoc-gen-entgrpc@latest
|
|
```
|
|
|
|
After installing these dependencies, we can re-run code-generation:
|
|
|
|
```console
|
|
go generate ./...
|
|
```
|
|
|
|
Observe that a new file named `ent/proto/entpb/entpb.pb.go` was created which contains the generated Go structs for our entities.
|
|
|
|
Let's write a test that uses it to make sure everything is wired correctly. Create a new file named `pb_test.go` and write:
|
|
|
|
```go
|
|
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")
|
|
}
|
|
}
|
|
```
|
|
|
|
To run it:
|
|
|
|
```console
|
|
go get -u ./... # install deps of the generated package
|
|
go test ./...
|
|
```
|
|
|
|
Hooray! The test passes. We have successfully generated working Go Protobuf structs from our Ent schema. Next, let's see how to automatically generate a working CRUD gRPC *server* from our schema.
|
|
|