Working with External gRPC Services

Oftentimes, you will want to include in your gRPC server, methods that are not automatically generated from your Ent schema. To achieve this result, define the methods in an additional service in an additional .proto file in your entpb directory.

External gRPC Services - 图1info

Find the changes described in this section in this pull request.

For example, suppose you want to add a method named TopUser which will return the user with the highest ID number. To do this, create a new .proto file in your entpb directory, and define a new service:

ent/proto/entpb/ext.proto

  1. syntax = "proto3";
  2. package entpb;
  3. option go_package = "github.com/rotemtam/ent-grpc-example/ent/proto/entpb";
  4. import "entpb/entpb.proto";
  5. import "google/protobuf/empty.proto";
  6. service ExtService {
  7. rpc TopUser ( google.protobuf.Empty ) returns ( User );
  8. }

Next, update entpb/generate.go to include the new file in the protoc command input:

ent/proto/entpb/generate.go

  1. - //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
  2. + //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 entpb/ext.proto

Next, re-run code generation:

  1. go generate ./...

Observe some new files were generated in the ent/proto/entpb directory:

  1. tree
  2. .
  3. |-- entpb.pb.go
  4. |-- entpb.proto
  5. |-- entpb_grpc.pb.go
  6. |-- entpb_user_service.go
  7. |-- ext.pb.go
  8. |-- ext.proto
  9. |-- ext_grpc.pb.go
  10. `-- generate.go
  11. 0 directories, 9 files

Now, you can implement the TopUser method in ent/proto/entpb/ext.go:

ent/proto/entpb/ext.go

  1. package entpb
  2. import (
  3. "context"
  4. "github.com/rotemtam/ent-grpc-example/ent"
  5. "github.com/rotemtam/ent-grpc-example/ent/user"
  6. "google.golang.org/protobuf/types/known/emptypb"
  7. )
  8. // ExtService implements ExtServiceServer.
  9. type ExtService struct {
  10. client *ent.Client
  11. UnimplementedExtServiceServer
  12. }
  13. // TopUser returns the user with the highest ID.
  14. func (s *ExtService) TopUser(ctx context.Context, _ *emptypb.Empty) (*User, error) {
  15. id := s.client.User.Query().Aggregate(ent.Max(user.FieldID)).IntX(ctx)
  16. user := s.client.User.GetX(ctx, id)
  17. return toProtoUser(user)
  18. }
  19. // NewExtService returns a new ExtService.
  20. func NewExtService(client *ent.Client) *ExtService {
  21. return &ExtService{
  22. client: client,
  23. }
  24. }

Adding the New Service to the gRPC Server

Finally, update cmd/server.go to include the new service:

cmd/server.go

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "net"
  6. _ "github.com/mattn/go-sqlite3"
  7. "github.com/rotemtam/ent-grpc-example/ent"
  8. "github.com/rotemtam/ent-grpc-example/ent/proto/entpb"
  9. "google.golang.org/grpc"
  10. )
  11. func main() {
  12. // Initialize an ent client.
  13. client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
  14. if err != nil {
  15. log.Fatalf("failed opening connection to sqlite: %v", err)
  16. }
  17. defer client.Close()
  18. // Run the migration tool (creating tables, etc).
  19. if err := client.Schema.Create(context.Background()); err != nil {
  20. log.Fatalf("failed creating schema resources: %v", err)
  21. }
  22. // Initialize the generated User service.
  23. svc := entpb.NewUserService(client)
  24. // Create a new gRPC server (you can wire multiple services to a single server).
  25. server := grpc.NewServer()
  26. // Register the User service with the server.
  27. entpb.RegisterUserServiceServer(server, svc)
  28. // Register the external ExtService service with the server.
  29. entpb.RegisterExtServiceServer(server, entpb.NewExtService(client))
  30. // Open port 5000 for listening to traffic.
  31. lis, err := net.Listen("tcp", ":5000")
  32. if err != nil {
  33. log.Fatalf("failed listening: %s", err)
  34. }
  35. // Listen for traffic indefinitely.
  36. if err := server.Serve(lis); err != nil {
  37. log.Fatalf("server ended: %s", err)
  38. }
  39. }