contrib icon indicating copy to clipboard operation
contrib copied to clipboard

entproto: make it possible to configure generated output so that it can work with Buf/Connect

Open ivanvanderbyl opened this issue 2 years ago • 3 comments

Hi Team,

This PR adds some extra configuration to the generate Hook so that it can output generated code that works with BufBuild's Connect.

Specific changes:

  • Make all method names unique so that specific keys don't collide or generate the method delete() in JS, and follow the naming conventions specified by the linter.
  • Don't create the generate.go file since we're using buf generate instead of protoc.
  • Allow configuring the generation target so we can collocate generated .proto with other hand written files outside of ./ent

ivanvanderbyl avatar Jan 27 '23 05:01 ivanvanderbyl

Hey @ivanvanderbyl

Thanks for working on this. I prefer to review / discuss each option in its own PR. This allows for more granular discussion on each change as it doesn't bundle them together.

In addition, please add tests 🙏

rotemtam avatar Jan 27 '23 07:01 rotemtam

Hi @rotemtam — okay, I will find some time this week to break it apart.

ivanvanderbyl avatar Jan 27 '23 07:01 ivanvanderbyl

I love to see this. I'm working with Connect as well. It requires a bit of boilerplate, but I'm just wrapping ent proto's service implementation inside of Connect methods. For example, my handlers look like this:

package handlers

import (
	"context"
	"github.com/bufbuild/connect-go"
	"github.com/pgdevelopers/xxxx/ent/generated"
	entpbv1 "github.com/pgdevelopers/xxxx/ent/proto/entpb"
	xxxxv1connect "github.com/xxxx/ent/proto/entpb/entpbconnect"
	"google.golang.org/protobuf/types/known/emptypb"
)

type DeviceStateServer struct{}

var deviceStateSvc *entpbv1.DeviceStateService

// NewDeviceStateServer returns a new DeviceStateServer.
func NewDeviceStateServer(client *generated.Client) xxxxv1connect.DeviceStateServiceHandler {
	deviceStateSvc = entpbv1.NewDeviceStateService(client)
	return &DeviceStateServer{}
}

func (s *DeviceStateServer) Get(ctx context.Context, c *connect.Request[entpbv1.GetDeviceStateRequest]) (*connect.Response[entpbv1.DeviceState], error) {
	resp, err := deviceStateSvc.Get(ctx, c.Msg)
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(resp), nil
}

And I register them like this:

...
func setupHandlers(client *generated.Client) *http.ServeMux {
	mux := http.NewServeMux()

	mux.Handle(xxxxv1connect.NewDeviceStateServiceHandler(
		handlers.NewDeviceStateServer(client),
		connect.WithInterceptors(
			otelconnect.NewInterceptor(),
			interceptors.NewAuthInterceptor(tokenHeader, authToken),
		),
	))

	return mux
}

func setupServer(mux *http.ServeMux) *http.Server {
	srv := &http.Server{
		Addr: ":" + port,
		Handler: h2c.NewHandler(
			mux,
			&http2.Server{},
		),
		ReadHeaderTimeout: time.Second,
		ReadTimeout:       5 * time.Minute,
		WriteTimeout:      5 * time.Minute,
		MaxHeaderBytes:    8 * 1024, // 8KiB
	}
	return srv
}

func main() {
        srv := setupServer(setupHandlers(devicesClient))

	if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
		log.Fatalf("HTTP listen and serve: %v", err)
	}
}

I'd love to talk more about this at some point!

hartmamt avatar Feb 23 '23 14:02 hartmamt