metaverse / truss

Truss helps you build go-kit microservices without having to worry about writing or maintaining boilerplate code.
Other
734 stars 143 forks source link

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

Closed always-waiting closed 3 years ago

always-waiting commented 3 years ago

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?

always-waiting commented 3 years ago

@zaquestion Could you help me please?

zaquestion commented 3 years ago

@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...

zaquestion commented 3 years ago

@always-waiting did that help clear anything up?

always-waiting commented 3 years ago

@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.

zaquestion commented 3 years ago

@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.

zaquestion commented 3 years ago

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.

always-waiting commented 3 years ago

@zaquestion thanks a lot, very helpful!