metaverse/truss

how to use truss creating a micro-service with service discovery function?

always-waiting opened this issue · 7 comments

Due to svc/server/run.go is generated by truss, i have to modify the file to make service has SD.

func Run(cfg handlers.Config) {
	service := handlers.NewService()
	endpoints := NewEndpoints(service)

	// Mechanical domain.
	errc := make(chan error)

	// register SD
	registar, err := DefaultConfig.GetSDRegister()
	if err != nil {
		panic(err)
	}
	// save config to consul
	go func() {
		if err := DefaultConfig.SaveBasicCfg(); err != nil {
			errc <- err
		}
	}()
	// Interrupt handler.
	go handlers.InterruptHandler(errc)

	// Debug listener.
	go func() {
		log.Println("transport", "debug", "addr", cfg.DebugAddr)

		m := http.NewServeMux()
		m.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
		m.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
		m.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
		m.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
		m.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))

		errc <- http.ListenAndServe(cfg.DebugAddr, m)
	}()

	// HTTP transport.
	go func() {
		log.Println("transport", "HTTP", "addr", cfg.HTTPAddr)
		h := svc.MakeHTTPHandler(endpoints)
		registar.Register()
		errc <- http.ListenAndServe(cfg.HTTPAddr, h)
	}()

	// gRPC transport.
	go func() {
		log.Println("transport", "gRPC", "addr", cfg.GRPCAddr)
		ln, err := net.Listen("tcp", cfg.GRPCAddr)
		if err != nil {
			errc <- err
			return
		}

		srv := svc.MakeGRPCServer(endpoints)
		s := grpc.NewServer()
		pb.RegisterAdminServer(s, srv)

		errc <- s.Serve(ln)
	}()

	// Run!
	log.Println("exit", <-errc)
	registar.Deregister()
}

is there any other methods to make it?

@zaquestion
Could you help me please?

@always-waiting I wrote the below, but then noticed you're calling functions on DefaultConfig that don't exist, I might need a bit more context on how you modified those? Also if those changes could be done elsewhere?

I might be overlooking something in your code sample, but it looks like you just need a bit of logic in your startup and shutdown paths? Sorry I usually do SD at an infra layer. I don't think you need a custom "main.go" for this (if you end up needing one see: https://github.com/metaverse/truss/wiki/Using-a-custom-main.go-with-generated-truss-services).

For startup and shutdown logic truss uses NewService in handlers/handlers.go and InterruptHandler in handlers/hooks.go respectively

// NewService returns a naïve, stateless implementation of Service.
func NewService() pb.EchoServer {
	// register SD
	registar, err := DefaultConfig.GetSDRegister()
	if err != nil {
		panic(err)
	}
	// save config to consul
	go func() {
		if err := DefaultConfig.SaveBasicCfg(); err != nil {
			errc <- err
		}
	}()
	return echoService{}
}
func InterruptHandler(errc chan<- error) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	terminateError := fmt.Errorf("%s", <-c)

	// Place whatever shutdown handling you want here
	registar.Deregister()

	errc <- terminateError
}

of course theres's always the matter of scoping the registar variable. You can either make it a package level variable or modifiy hooks.go:InterruptHandler to be a function variable and use a closure to scope registar. Personally I find the former to be simpler and without consequence, but it can be considered a smell...

@always-waiting did that help clear anything up?

@zaquestion
Thank you for your attention!

I prefer to the method which modifies NewService in handlers/handlers.go and InterruptHandler in handlers/hooks.go respectively. But, I have a worry about abnormal exit. For example, if a error occur at

	go func() {
		log.Println("transport", "HTTP", "addr", cfg.HTTPAddr)
		h := svc.MakeHTTPHandler(endpoints)
		registar.Register()
                // A error occur
		errc <- http.ListenAndServe(cfg.HTTPAddr, h)
	}()

or

go func() {
		log.Println("transport", "gRPC", "addr", cfg.GRPCAddr)
		ln, err := net.Listen("tcp", cfg.GRPCAddr)
		if err != nil {
			errc <- err
			return
		}

		srv := svc.MakeGRPCServer(endpoints)
		s := grpc.NewServer()
		pb.RegisterAdminServer(s, srv)
                // A error occur
		errc <- s.Serve(ln)
}()

in svc/server/run.go , registar.Deregister() have no chance to execute.

@always-waiting Sorry for the long delay -- life be cray rn. I think you've exposed an edgecase with our shutdown hook. Without having thought to critically about this, it does seem like we need an additional hook to guarantee certain graceful shutdown in cases like these.

fwiw, you can definitely work around this with a custom main.go, but truss is lacking a "native" mechanism. Surprisingly this hasn't come up until this point.

@zaquestion
thanks a lot, very helpful!