// New returns a service that's load-balanced over instances of restsvc found // in the provided Consul server. The mechanism of looking up restsvc // instances in Consul is hard-coded into the client. func New(consulAddr string, logger log.Logger) (restsvc.Service, error) { apiclient, err := consulapi.NewClient(&consulapi.Config{ Address: consulAddr, }) if err != nil { return nil, err } // As the implementer of restsvc, we declare and enforce these // parameters for all of the restsvc consumers. var ( consulService = "restsvc" consulTags = []string{"prod"} passingOnly = true retryMax = 3 retryTimeout = 500 * time.Millisecond ) var ( sdclient = consul.NewClient(apiclient) endpoints restsvc.Endpoints ) { factory := factoryFor(restsvc.MakePostConfigEndpoint) subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(retryMax, retryTimeout, balancer) endpoints.PostConfigEndpoint = retry } { factory := factoryFor(restsvc.MakeGetConfigEndpoint) subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(retryMax, retryTimeout, balancer) endpoints.GetConfigEndpoint = retry } { factory := factoryFor(restsvc.MakePutConfigEndpoint) subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(retryMax, retryTimeout, balancer) endpoints.PutConfigEndpoint = retry } { factory := factoryFor(restsvc.MakePatchConfigEndpoint) subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(retryMax, retryTimeout, balancer) endpoints.PatchConfigEndpoint = retry } { factory := factoryFor(restsvc.MakeDeleteConfigEndpoint) subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(retryMax, retryTimeout, balancer) endpoints.DeleteConfigEndpoint = retry } { factory := factoryFor(restsvc.MakeGetAddressesEndpoint) subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(retryMax, retryTimeout, balancer) endpoints.GetAddressesEndpoint = retry } { factory := factoryFor(restsvc.MakeGetAddressEndpoint) subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(retryMax, retryTimeout, balancer) endpoints.GetAddressEndpoint = retry } { factory := factoryFor(restsvc.MakePostAddressEndpoint) subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(retryMax, retryTimeout, balancer) endpoints.PostAddressEndpoint = retry } { factory := factoryFor(restsvc.MakeDeleteAddressEndpoint) subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(retryMax, retryTimeout, balancer) endpoints.DeleteAddressEndpoint = retry } return endpoints, nil }
func main() { var ( httpAddr = flag.String("http.addr", ":8000", "Address for HTTP (JSON) server") consulAddr = flag.String("consul.addr", "", "Consul agent address") retryMax = flag.Int("retry.max", 3, "per-request retries to different instances") retryTimeout = flag.Duration("retry.timeout", 500*time.Millisecond, "per-request timeout, including retries") ) flag.Parse() // Logging domain. var logger log.Logger { logger = log.NewLogfmtLogger(os.Stderr) logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC) logger = log.NewContext(logger).With("caller", log.DefaultCaller) } // Service discovery domain. In this example we use Consul. var client consulsd.Client { consulConfig := api.DefaultConfig() if len(*consulAddr) > 0 { consulConfig.Address = *consulAddr } consulClient, err := api.NewClient(consulConfig) if err != nil { logger.Log("err", err) os.Exit(1) } client = consulsd.NewClient(consulClient) } // Transport domain. tracer := stdopentracing.GlobalTracer() // no-op ctx := context.Background() r := mux.NewRouter() // Now we begin installing the routes. Each route corresponds to a single // method: sum, concat, uppercase, and count. // addsvc routes. { // Each method gets constructed with a factory. Factories take an // instance string, and return a specific endpoint. In the factory we // dial the instance string we get from Consul, and then leverage an // addsvc client package to construct a complete service. We can then // leverage the addsvc.Make{Sum,Concat}Endpoint constructors to convert // the complete service to specific endpoint. var ( tags = []string{} passingOnly = true endpoints = addsvc.Endpoints{} ) { factory := addsvcFactory(addsvc.MakeSumEndpoint, tracer, logger) subscriber := consulsd.NewSubscriber(client, factory, logger, "addsvc", tags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(*retryMax, *retryTimeout, balancer) endpoints.SumEndpoint = retry } { factory := addsvcFactory(addsvc.MakeConcatEndpoint, tracer, logger) subscriber := consulsd.NewSubscriber(client, factory, logger, "addsvc", tags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(*retryMax, *retryTimeout, balancer) endpoints.ConcatEndpoint = retry } // Here we leverage the fact that addsvc comes with a constructor for an // HTTP handler, and just install it under a particular path prefix in // our router. r.PathPrefix("addsvc/").Handler(addsvc.MakeHTTPHandler(ctx, endpoints, tracer, logger)) } // stringsvc routes. { // addsvc had lots of nice importable Go packages we could leverage. // With stringsvc we are not so fortunate, it just has some endpoints // that we assume will exist. So we have to write that logic here. This // is by design, so you can see two totally different methods of // proxying to a remote service. var ( tags = []string{} passingOnly = true uppercase endpoint.Endpoint count endpoint.Endpoint ) { factory := stringsvcFactory(ctx, "GET", "/uppercase") subscriber := consulsd.NewSubscriber(client, factory, logger, "stringsvc", tags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(*retryMax, *retryTimeout, balancer) uppercase = retry } { factory := stringsvcFactory(ctx, "GET", "/count") subscriber := consulsd.NewSubscriber(client, factory, logger, "stringsvc", tags, passingOnly) balancer := lb.NewRoundRobin(subscriber) retry := lb.Retry(*retryMax, *retryTimeout, balancer) count = retry } // We can use the transport/http.Server to act as our handler, all we // have to do provide it with the encode and decode functions for our // stringsvc methods. r.Handle("/stringsvc/uppercase", httptransport.NewServer(ctx, uppercase, decodeUppercaseRequest, encodeJSONResponse)) r.Handle("/stringsvc/count", httptransport.NewServer(ctx, count, decodeCountRequest, encodeJSONResponse)) } // Interrupt handler. errc := make(chan error) go func() { c := make(chan os.Signal) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) errc <- fmt.Errorf("%s", <-c) }() // HTTP transport. go func() { logger.Log("transport", "HTTP", "addr", *httpAddr) errc <- http.ListenAndServe(*httpAddr, r) }() // Run! logger.Log("exit", <-errc) }