relocate examples to a separate repository
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
Mark Sagi-Kazar
2 years ago
60 | 60 | EUREKA_ADDR: http://localhost:${{ job.services.eureka.ports[8761] }}/eureka |
61 | 61 | run: go test -v -race -coverprofile=coverage.coverprofile -covermode=atomic -tags integration ./... |
62 | 62 | |
63 | - name: Run example tests | |
64 | run: | | |
65 | cd examples | |
66 | go test -v -race ./... | |
67 | ||
68 | 63 | - name: Upload coverage |
69 | 64 | uses: codecov/codecov-action@v1 |
70 | 65 | with: |
0 | 0 | # Examples |
1 | 1 | |
2 | For more information about these examples, | |
3 | including a walkthrough of the stringsvc example, | |
4 | see [gokit.io/examples](https://gokit.io/examples). | |
2 | Examples have been relocated to a separate repository: https://github.com/go-kit/examples |
0 | # addsvc | |
1 | ||
2 | addsvc is an example microservice which takes full advantage of most of Go | |
3 | kit's features, including both service- and transport-level middlewares, | |
4 | speaking multiple transports simultaneously, distributed tracing, and rich | |
5 | error definitions. The server binary is available in cmd/addsvc. The client | |
6 | binary is available in cmd/addcli. | |
7 | ||
8 | Finally, the addtransport package provides both server and clients for each | |
9 | supported transport. The client structs bake-in certain middlewares, in order to | |
10 | demonstrate the _client library pattern_. But beware: client libraries are | |
11 | generally a bad idea, because they easily lead to the | |
12 | [distributed monolith antipattern](https://www.microservices.com/talks/dont-build-a-distributed-monolith/). | |
13 | If you don't _know_ you need to use one in your organization, it's probably best | |
14 | avoided: prefer moving that logic to consumers, and relying on | |
15 | [contract testing](https://docs.pact.io/best_practices/contract_tests_not_functional_tests.html) | |
16 | to detect incompatibilities. |
0 | package main | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "flag" | |
5 | "fmt" | |
6 | "os" | |
7 | "strconv" | |
8 | "text/tabwriter" | |
9 | "time" | |
10 | ||
11 | "google.golang.org/grpc" | |
12 | ||
13 | "github.com/apache/thrift/lib/go/thrift" | |
14 | lightstep "github.com/lightstep/lightstep-tracer-go" | |
15 | stdopentracing "github.com/opentracing/opentracing-go" | |
16 | zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing" | |
17 | zipkin "github.com/openzipkin/zipkin-go" | |
18 | zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http" | |
19 | "sourcegraph.com/sourcegraph/appdash" | |
20 | appdashot "sourcegraph.com/sourcegraph/appdash/opentracing" | |
21 | ||
22 | "github.com/go-kit/kit/log" | |
23 | ||
24 | "github.com/go-kit/kit/examples/addsvc/pkg/addservice" | |
25 | "github.com/go-kit/kit/examples/addsvc/pkg/addtransport" | |
26 | addthrift "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc" | |
27 | ) | |
28 | ||
29 | func main() { | |
30 | // The addcli presumes no service discovery system, and expects users to | |
31 | // provide the direct address of an addsvc. This presumption is reflected in | |
32 | // the addcli binary and the client packages: the -transport.addr flags | |
33 | // and various client constructors both expect host:port strings. For an | |
34 | // example service with a client built on top of a service discovery system, | |
35 | // see profilesvc. | |
36 | fs := flag.NewFlagSet("addcli", flag.ExitOnError) | |
37 | var ( | |
38 | httpAddr = fs.String("http-addr", "", "HTTP address of addsvc") | |
39 | grpcAddr = fs.String("grpc-addr", "", "gRPC address of addsvc") | |
40 | thriftAddr = fs.String("thrift-addr", "", "Thrift address of addsvc") | |
41 | jsonRPCAddr = fs.String("jsonrpc-addr", "", "JSON RPC address of addsvc") | |
42 | thriftProtocol = fs.String("thrift-protocol", "binary", "binary, compact, json, simplejson") | |
43 | thriftBuffer = fs.Int("thrift-buffer", 0, "0 for unbuffered") | |
44 | thriftFramed = fs.Bool("thrift-framed", false, "true to enable framing") | |
45 | zipkinURL = fs.String("zipkin-url", "", "Enable Zipkin tracing via HTTP reporter URL e.g. http://localhost:9411/api/v2/spans") | |
46 | zipkinBridge = fs.Bool("zipkin-ot-bridge", false, "Use Zipkin OpenTracing bridge instead of native implementation") | |
47 | lightstepToken = fs.String("lightstep-token", "", "Enable LightStep tracing via a LightStep access token") | |
48 | appdashAddr = fs.String("appdash-addr", "", "Enable Appdash tracing via an Appdash server host:port") | |
49 | method = fs.String("method", "sum", "sum, concat") | |
50 | ) | |
51 | fs.Usage = usageFor(fs, os.Args[0]+" [flags] <a> <b>") | |
52 | fs.Parse(os.Args[1:]) | |
53 | if len(fs.Args()) != 2 { | |
54 | fs.Usage() | |
55 | os.Exit(1) | |
56 | } | |
57 | ||
58 | // This is a demonstration of the native Zipkin tracing client. If using | |
59 | // Zipkin this is the more idiomatic client over OpenTracing. | |
60 | var zipkinTracer *zipkin.Tracer | |
61 | { | |
62 | if *zipkinURL != "" { | |
63 | var ( | |
64 | err error | |
65 | hostPort = "" // if host:port is unknown we can keep this empty | |
66 | serviceName = "addsvc-cli" | |
67 | reporter = zipkinhttp.NewReporter(*zipkinURL) | |
68 | ) | |
69 | defer reporter.Close() | |
70 | zEP, _ := zipkin.NewEndpoint(serviceName, hostPort) | |
71 | zipkinTracer, err = zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(zEP)) | |
72 | if err != nil { | |
73 | fmt.Fprintf(os.Stderr, "unable to create zipkin tracer: %s\n", err.Error()) | |
74 | os.Exit(1) | |
75 | } | |
76 | } | |
77 | } | |
78 | ||
79 | // This is a demonstration client, which supports multiple tracers. | |
80 | // Your clients will probably just use one tracer. | |
81 | var otTracer stdopentracing.Tracer | |
82 | { | |
83 | if *zipkinBridge && zipkinTracer != nil { | |
84 | otTracer = zipkinot.Wrap(zipkinTracer) | |
85 | zipkinTracer = nil // do not instrument with both native and ot bridge | |
86 | } else if *lightstepToken != "" { | |
87 | otTracer = lightstep.NewTracer(lightstep.Options{ | |
88 | AccessToken: *lightstepToken, | |
89 | }) | |
90 | defer lightstep.FlushLightStepTracer(otTracer) | |
91 | } else if *appdashAddr != "" { | |
92 | otTracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr)) | |
93 | } else { | |
94 | otTracer = stdopentracing.GlobalTracer() // no-op | |
95 | } | |
96 | } | |
97 | ||
98 | // This is a demonstration client, which supports multiple transports. | |
99 | // Your clients will probably just define and stick with 1 transport. | |
100 | var ( | |
101 | svc addservice.Service | |
102 | err error | |
103 | ) | |
104 | if *httpAddr != "" { | |
105 | svc, err = addtransport.NewHTTPClient(*httpAddr, otTracer, zipkinTracer, log.NewNopLogger()) | |
106 | } else if *grpcAddr != "" { | |
107 | conn, err := grpc.Dial(*grpcAddr, grpc.WithInsecure(), grpc.WithTimeout(time.Second)) | |
108 | if err != nil { | |
109 | fmt.Fprintf(os.Stderr, "error: %v", err) | |
110 | os.Exit(1) | |
111 | } | |
112 | defer conn.Close() | |
113 | svc = addtransport.NewGRPCClient(conn, otTracer, zipkinTracer, log.NewNopLogger()) | |
114 | } else if *jsonRPCAddr != "" { | |
115 | svc, err = addtransport.NewJSONRPCClient(*jsonRPCAddr, otTracer, log.NewNopLogger()) | |
116 | } else if *thriftAddr != "" { | |
117 | // It's necessary to do all of this construction in the func main, | |
118 | // because (among other reasons) we need to control the lifecycle of the | |
119 | // Thrift transport, i.e. close it eventually. | |
120 | var protocolFactory thrift.TProtocolFactory | |
121 | switch *thriftProtocol { | |
122 | case "compact": | |
123 | protocolFactory = thrift.NewTCompactProtocolFactory() | |
124 | case "simplejson": | |
125 | protocolFactory = thrift.NewTSimpleJSONProtocolFactory() | |
126 | case "json": | |
127 | protocolFactory = thrift.NewTJSONProtocolFactory() | |
128 | case "binary", "": | |
129 | protocolFactory = thrift.NewTBinaryProtocolFactoryDefault() | |
130 | default: | |
131 | fmt.Fprintf(os.Stderr, "error: invalid protocol %q\n", *thriftProtocol) | |
132 | os.Exit(1) | |
133 | } | |
134 | var transportFactory thrift.TTransportFactory | |
135 | if *thriftBuffer > 0 { | |
136 | transportFactory = thrift.NewTBufferedTransportFactory(*thriftBuffer) | |
137 | } else { | |
138 | transportFactory = thrift.NewTTransportFactory() | |
139 | } | |
140 | if *thriftFramed { | |
141 | transportFactory = thrift.NewTFramedTransportFactory(transportFactory) | |
142 | } | |
143 | transportSocket, err := thrift.NewTSocket(*thriftAddr) | |
144 | if err != nil { | |
145 | fmt.Fprintf(os.Stderr, "error: %v\n", err) | |
146 | os.Exit(1) | |
147 | } | |
148 | transport, err := transportFactory.GetTransport(transportSocket) | |
149 | if err != nil { | |
150 | fmt.Fprintf(os.Stderr, "error: %v\n", err) | |
151 | os.Exit(1) | |
152 | } | |
153 | if err := transport.Open(); err != nil { | |
154 | fmt.Fprintf(os.Stderr, "error: %v\n", err) | |
155 | os.Exit(1) | |
156 | } | |
157 | defer transport.Close() | |
158 | client := addthrift.NewAddServiceClientFactory(transport, protocolFactory) | |
159 | svc = addtransport.NewThriftClient(client) | |
160 | } else { | |
161 | fmt.Fprintf(os.Stderr, "error: no remote address specified\n") | |
162 | os.Exit(1) | |
163 | } | |
164 | if err != nil { | |
165 | fmt.Fprintf(os.Stderr, "error: %v\n", err) | |
166 | os.Exit(1) | |
167 | } | |
168 | ||
169 | switch *method { | |
170 | case "sum": | |
171 | a, _ := strconv.ParseInt(fs.Args()[0], 10, 64) | |
172 | b, _ := strconv.ParseInt(fs.Args()[1], 10, 64) | |
173 | v, err := svc.Sum(context.Background(), int(a), int(b)) | |
174 | if err != nil { | |
175 | fmt.Fprintf(os.Stderr, "error: %v\n", err) | |
176 | os.Exit(1) | |
177 | } | |
178 | fmt.Fprintf(os.Stdout, "%d + %d = %d\n", a, b, v) | |
179 | ||
180 | case "concat": | |
181 | a := fs.Args()[0] | |
182 | b := fs.Args()[1] | |
183 | v, err := svc.Concat(context.Background(), a, b) | |
184 | if err != nil { | |
185 | fmt.Fprintf(os.Stderr, "error: %v\n", err) | |
186 | os.Exit(1) | |
187 | } | |
188 | fmt.Fprintf(os.Stdout, "%q + %q = %q\n", a, b, v) | |
189 | ||
190 | default: | |
191 | fmt.Fprintf(os.Stderr, "error: invalid method %q\n", *method) | |
192 | os.Exit(1) | |
193 | } | |
194 | } | |
195 | ||
196 | func usageFor(fs *flag.FlagSet, short string) func() { | |
197 | return func() { | |
198 | fmt.Fprintf(os.Stderr, "USAGE\n") | |
199 | fmt.Fprintf(os.Stderr, " %s\n", short) | |
200 | fmt.Fprintf(os.Stderr, "\n") | |
201 | fmt.Fprintf(os.Stderr, "FLAGS\n") | |
202 | w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0) | |
203 | fs.VisitAll(func(f *flag.Flag) { | |
204 | fmt.Fprintf(w, "\t-%s %s\t%s\n", f.Name, f.DefValue, f.Usage) | |
205 | }) | |
206 | w.Flush() | |
207 | fmt.Fprintf(os.Stderr, "\n") | |
208 | } | |
209 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "flag" | |
4 | "fmt" | |
5 | "net" | |
6 | "net/http" | |
7 | "os" | |
8 | "os/signal" | |
9 | "syscall" | |
10 | "text/tabwriter" | |
11 | ||
12 | "github.com/apache/thrift/lib/go/thrift" | |
13 | lightstep "github.com/lightstep/lightstep-tracer-go" | |
14 | "github.com/oklog/oklog/pkg/group" | |
15 | stdopentracing "github.com/opentracing/opentracing-go" | |
16 | zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing" | |
17 | zipkin "github.com/openzipkin/zipkin-go" | |
18 | zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http" | |
19 | stdprometheus "github.com/prometheus/client_golang/prometheus" | |
20 | "github.com/prometheus/client_golang/prometheus/promhttp" | |
21 | "google.golang.org/grpc" | |
22 | "sourcegraph.com/sourcegraph/appdash" | |
23 | appdashot "sourcegraph.com/sourcegraph/appdash/opentracing" | |
24 | ||
25 | "github.com/go-kit/kit/log" | |
26 | "github.com/go-kit/kit/metrics" | |
27 | "github.com/go-kit/kit/metrics/prometheus" | |
28 | kitgrpc "github.com/go-kit/kit/transport/grpc" | |
29 | ||
30 | addpb "github.com/go-kit/kit/examples/addsvc/pb" | |
31 | "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint" | |
32 | "github.com/go-kit/kit/examples/addsvc/pkg/addservice" | |
33 | "github.com/go-kit/kit/examples/addsvc/pkg/addtransport" | |
34 | addthrift "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc" | |
35 | ) | |
36 | ||
37 | func main() { | |
38 | // Define our flags. Your service probably won't need to bind listeners for | |
39 | // *all* supported transports, or support both Zipkin and LightStep, and so | |
40 | // on, but we do it here for demonstration purposes. | |
41 | fs := flag.NewFlagSet("addsvc", flag.ExitOnError) | |
42 | var ( | |
43 | debugAddr = fs.String("debug.addr", ":8080", "Debug and metrics listen address") | |
44 | httpAddr = fs.String("http-addr", ":8081", "HTTP listen address") | |
45 | grpcAddr = fs.String("grpc-addr", ":8082", "gRPC listen address") | |
46 | thriftAddr = fs.String("thrift-addr", ":8083", "Thrift listen address") | |
47 | jsonRPCAddr = fs.String("jsonrpc-addr", ":8084", "JSON RPC listen address") | |
48 | thriftProtocol = fs.String("thrift-protocol", "binary", "binary, compact, json, simplejson") | |
49 | thriftBuffer = fs.Int("thrift-buffer", 0, "0 for unbuffered") | |
50 | thriftFramed = fs.Bool("thrift-framed", false, "true to enable framing") | |
51 | zipkinURL = fs.String("zipkin-url", "", "Enable Zipkin tracing via HTTP reporter URL e.g. http://localhost:9411/api/v2/spans") | |
52 | zipkinBridge = fs.Bool("zipkin-ot-bridge", false, "Use Zipkin OpenTracing bridge instead of native implementation") | |
53 | lightstepToken = fs.String("lightstep-token", "", "Enable LightStep tracing via a LightStep access token") | |
54 | appdashAddr = fs.String("appdash-addr", "", "Enable Appdash tracing via an Appdash server host:port") | |
55 | ) | |
56 | fs.Usage = usageFor(fs, os.Args[0]+" [flags]") | |
57 | fs.Parse(os.Args[1:]) | |
58 | ||
59 | // Create a single logger, which we'll use and give to other components. | |
60 | var logger log.Logger | |
61 | { | |
62 | logger = log.NewLogfmtLogger(os.Stderr) | |
63 | logger = log.With(logger, "ts", log.DefaultTimestampUTC) | |
64 | logger = log.With(logger, "caller", log.DefaultCaller) | |
65 | } | |
66 | ||
67 | var zipkinTracer *zipkin.Tracer | |
68 | { | |
69 | if *zipkinURL != "" { | |
70 | var ( | |
71 | err error | |
72 | hostPort = "localhost:80" | |
73 | serviceName = "addsvc" | |
74 | reporter = zipkinhttp.NewReporter(*zipkinURL) | |
75 | ) | |
76 | defer reporter.Close() | |
77 | zEP, _ := zipkin.NewEndpoint(serviceName, hostPort) | |
78 | zipkinTracer, err = zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(zEP)) | |
79 | if err != nil { | |
80 | logger.Log("err", err) | |
81 | os.Exit(1) | |
82 | } | |
83 | if !(*zipkinBridge) { | |
84 | logger.Log("tracer", "Zipkin", "type", "Native", "URL", *zipkinURL) | |
85 | } | |
86 | } | |
87 | } | |
88 | ||
89 | // Determine which OpenTracing tracer to use. We'll pass the tracer to all the | |
90 | // components that use it, as a dependency. | |
91 | var tracer stdopentracing.Tracer | |
92 | { | |
93 | if *zipkinBridge && zipkinTracer != nil { | |
94 | logger.Log("tracer", "Zipkin", "type", "OpenTracing", "URL", *zipkinURL) | |
95 | tracer = zipkinot.Wrap(zipkinTracer) | |
96 | zipkinTracer = nil // do not instrument with both native tracer and opentracing bridge | |
97 | } else if *lightstepToken != "" { | |
98 | logger.Log("tracer", "LightStep") // probably don't want to print out the token :) | |
99 | tracer = lightstep.NewTracer(lightstep.Options{ | |
100 | AccessToken: *lightstepToken, | |
101 | }) | |
102 | defer lightstep.FlushLightStepTracer(tracer) | |
103 | } else if *appdashAddr != "" { | |
104 | logger.Log("tracer", "Appdash", "addr", *appdashAddr) | |
105 | tracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr)) | |
106 | } else { | |
107 | tracer = stdopentracing.GlobalTracer() // no-op | |
108 | } | |
109 | } | |
110 | ||
111 | // Create the (sparse) metrics we'll use in the service. They, too, are | |
112 | // dependencies that we pass to components that use them. | |
113 | var ints, chars metrics.Counter | |
114 | { | |
115 | // Business-level metrics. | |
116 | ints = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ | |
117 | Namespace: "example", | |
118 | Subsystem: "addsvc", | |
119 | Name: "integers_summed", | |
120 | Help: "Total count of integers summed via the Sum method.", | |
121 | }, []string{}) | |
122 | chars = prometheus.NewCounterFrom(stdprometheus.CounterOpts{ | |
123 | Namespace: "example", | |
124 | Subsystem: "addsvc", | |
125 | Name: "characters_concatenated", | |
126 | Help: "Total count of characters concatenated via the Concat method.", | |
127 | }, []string{}) | |
128 | } | |
129 | var duration metrics.Histogram | |
130 | { | |
131 | // Endpoint-level metrics. | |
132 | duration = prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ | |
133 | Namespace: "example", | |
134 | Subsystem: "addsvc", | |
135 | Name: "request_duration_seconds", | |
136 | Help: "Request duration in seconds.", | |
137 | }, []string{"method", "success"}) | |
138 | } | |
139 | http.DefaultServeMux.Handle("/metrics", promhttp.Handler()) | |
140 | ||
141 | // Build the layers of the service "onion" from the inside out. First, the | |
142 | // business logic service; then, the set of endpoints that wrap the service; | |
143 | // and finally, a series of concrete transport adapters. The adapters, like | |
144 | // the HTTP handler or the gRPC server, are the bridge between Go kit and | |
145 | // the interfaces that the transports expect. Note that we're not binding | |
146 | // them to ports or anything yet; we'll do that next. | |
147 | var ( | |
148 | service = addservice.New(logger, ints, chars) | |
149 | endpoints = addendpoint.New(service, logger, duration, tracer, zipkinTracer) | |
150 | httpHandler = addtransport.NewHTTPHandler(endpoints, tracer, zipkinTracer, logger) | |
151 | grpcServer = addtransport.NewGRPCServer(endpoints, tracer, zipkinTracer, logger) | |
152 | thriftServer = addtransport.NewThriftServer(endpoints) | |
153 | jsonrpcHandler = addtransport.NewJSONRPCHandler(endpoints, logger) | |
154 | ) | |
155 | ||
156 | // Now we're to the part of the func main where we want to start actually | |
157 | // running things, like servers bound to listeners to receive connections. | |
158 | // | |
159 | // The method is the same for each component: add a new actor to the group | |
160 | // struct, which is a combination of 2 anonymous functions: the first | |
161 | // function actually runs the component, and the second function should | |
162 | // interrupt the first function and cause it to return. It's in these | |
163 | // functions that we actually bind the Go kit server/handler structs to the | |
164 | // concrete transports and run them. | |
165 | // | |
166 | // Putting each component into its own block is mostly for aesthetics: it | |
167 | // clearly demarcates the scope in which each listener/socket may be used. | |
168 | var g group.Group | |
169 | { | |
170 | // The debug listener mounts the http.DefaultServeMux, and serves up | |
171 | // stuff like the Prometheus metrics route, the Go debug and profiling | |
172 | // routes, and so on. | |
173 | debugListener, err := net.Listen("tcp", *debugAddr) | |
174 | if err != nil { | |
175 | logger.Log("transport", "debug/HTTP", "during", "Listen", "err", err) | |
176 | os.Exit(1) | |
177 | } | |
178 | g.Add(func() error { | |
179 | logger.Log("transport", "debug/HTTP", "addr", *debugAddr) | |
180 | return http.Serve(debugListener, http.DefaultServeMux) | |
181 | }, func(error) { | |
182 | debugListener.Close() | |
183 | }) | |
184 | } | |
185 | { | |
186 | // The HTTP listener mounts the Go kit HTTP handler we created. | |
187 | httpListener, err := net.Listen("tcp", *httpAddr) | |
188 | if err != nil { | |
189 | logger.Log("transport", "HTTP", "during", "Listen", "err", err) | |
190 | os.Exit(1) | |
191 | } | |
192 | g.Add(func() error { | |
193 | logger.Log("transport", "HTTP", "addr", *httpAddr) | |
194 | return http.Serve(httpListener, httpHandler) | |
195 | }, func(error) { | |
196 | httpListener.Close() | |
197 | }) | |
198 | } | |
199 | { | |
200 | // The gRPC listener mounts the Go kit gRPC server we created. | |
201 | grpcListener, err := net.Listen("tcp", *grpcAddr) | |
202 | if err != nil { | |
203 | logger.Log("transport", "gRPC", "during", "Listen", "err", err) | |
204 | os.Exit(1) | |
205 | } | |
206 | g.Add(func() error { | |
207 | logger.Log("transport", "gRPC", "addr", *grpcAddr) | |
208 | // we add the Go Kit gRPC Interceptor to our gRPC service as it is used by | |
209 | // the here demonstrated zipkin tracing middleware. | |
210 | baseServer := grpc.NewServer(grpc.UnaryInterceptor(kitgrpc.Interceptor)) | |
211 | addpb.RegisterAddServer(baseServer, grpcServer) | |
212 | return baseServer.Serve(grpcListener) | |
213 | }, func(error) { | |
214 | grpcListener.Close() | |
215 | }) | |
216 | } | |
217 | { | |
218 | // The Thrift socket mounts the Go kit Thrift server we created earlier. | |
219 | // There's a lot of boilerplate involved here, related to configuring | |
220 | // the protocol and transport; blame Thrift. | |
221 | thriftSocket, err := thrift.NewTServerSocket(*thriftAddr) | |
222 | if err != nil { | |
223 | logger.Log("transport", "Thrift", "during", "Listen", "err", err) | |
224 | os.Exit(1) | |
225 | } | |
226 | g.Add(func() error { | |
227 | logger.Log("transport", "Thrift", "addr", *thriftAddr) | |
228 | var protocolFactory thrift.TProtocolFactory | |
229 | switch *thriftProtocol { | |
230 | case "binary": | |
231 | protocolFactory = thrift.NewTBinaryProtocolFactoryDefault() | |
232 | case "compact": | |
233 | protocolFactory = thrift.NewTCompactProtocolFactory() | |
234 | case "json": | |
235 | protocolFactory = thrift.NewTJSONProtocolFactory() | |
236 | case "simplejson": | |
237 | protocolFactory = thrift.NewTSimpleJSONProtocolFactory() | |
238 | default: | |
239 | return fmt.Errorf("invalid Thrift protocol %q", *thriftProtocol) | |
240 | } | |
241 | var transportFactory thrift.TTransportFactory | |
242 | if *thriftBuffer > 0 { | |
243 | transportFactory = thrift.NewTBufferedTransportFactory(*thriftBuffer) | |
244 | } else { | |
245 | transportFactory = thrift.NewTTransportFactory() | |
246 | } | |
247 | if *thriftFramed { | |
248 | transportFactory = thrift.NewTFramedTransportFactory(transportFactory) | |
249 | } | |
250 | return thrift.NewTSimpleServer4( | |
251 | addthrift.NewAddServiceProcessor(thriftServer), | |
252 | thriftSocket, | |
253 | transportFactory, | |
254 | protocolFactory, | |
255 | ).Serve() | |
256 | }, func(error) { | |
257 | thriftSocket.Close() | |
258 | }) | |
259 | } | |
260 | { | |
261 | httpListener, err := net.Listen("tcp", *jsonRPCAddr) | |
262 | if err != nil { | |
263 | logger.Log("transport", "JSONRPC over HTTP", "during", "Listen", "err", err) | |
264 | os.Exit(1) | |
265 | } | |
266 | g.Add(func() error { | |
267 | logger.Log("transport", "JSONRPC over HTTP", "addr", *jsonRPCAddr) | |
268 | return http.Serve(httpListener, jsonrpcHandler) | |
269 | }, func(error) { | |
270 | httpListener.Close() | |
271 | }) | |
272 | } | |
273 | { | |
274 | // This function just sits and waits for ctrl-C. | |
275 | cancelInterrupt := make(chan struct{}) | |
276 | g.Add(func() error { | |
277 | c := make(chan os.Signal, 1) | |
278 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) | |
279 | select { | |
280 | case sig := <-c: | |
281 | return fmt.Errorf("received signal %s", sig) | |
282 | case <-cancelInterrupt: | |
283 | return nil | |
284 | } | |
285 | }, func(error) { | |
286 | close(cancelInterrupt) | |
287 | }) | |
288 | } | |
289 | logger.Log("exit", g.Run()) | |
290 | } | |
291 | ||
292 | func usageFor(fs *flag.FlagSet, short string) func() { | |
293 | return func() { | |
294 | fmt.Fprintf(os.Stderr, "USAGE\n") | |
295 | fmt.Fprintf(os.Stderr, " %s\n", short) | |
296 | fmt.Fprintf(os.Stderr, "\n") | |
297 | fmt.Fprintf(os.Stderr, "FLAGS\n") | |
298 | w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0) | |
299 | fs.VisitAll(func(f *flag.Flag) { | |
300 | fmt.Fprintf(w, "\t-%s %s\t%s\n", f.Name, f.DefValue, f.Usage) | |
301 | }) | |
302 | w.Flush() | |
303 | fmt.Fprintf(os.Stderr, "\n") | |
304 | } | |
305 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "net/http" | |
5 | "os" | |
6 | "strings" | |
7 | "testing" | |
8 | ||
9 | "github.com/pact-foundation/pact-go/dsl" | |
10 | ) | |
11 | ||
12 | func TestPactStringsvcUppercase(t *testing.T) { | |
13 | if os.Getenv("WRITE_PACTS") == "" { | |
14 | t.Skip("skipping Pact contracts; set WRITE_PACTS environment variable to enable") | |
15 | } | |
16 | ||
17 | pact := dsl.Pact{ | |
18 | Consumer: "addsvc", | |
19 | Provider: "stringsvc", | |
20 | } | |
21 | defer pact.Teardown() | |
22 | ||
23 | pact.AddInteraction(). | |
24 | UponReceiving("stringsvc uppercase"). | |
25 | WithRequest(dsl.Request{ | |
26 | Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/json; charset=utf-8")}, | |
27 | Method: "POST", | |
28 | Path: dsl.String("/uppercase"), | |
29 | Body: `{"s":"foo"}`, | |
30 | }). | |
31 | WillRespondWith(dsl.Response{ | |
32 | Status: 200, | |
33 | Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/json; charset=utf-8")}, | |
34 | Body: `{"v":"FOO"}`, | |
35 | }) | |
36 | ||
37 | if err := pact.Verify(func() error { | |
38 | u := fmt.Sprintf("http://localhost:%d/uppercase", pact.Server.Port) | |
39 | req, err := http.NewRequest("POST", u, strings.NewReader(`{"s":"foo"}`)) | |
40 | if err != nil { | |
41 | return err | |
42 | } | |
43 | req.Header.Set("Content-Type", "application/json; charset=utf-8") | |
44 | if _, err = http.DefaultClient.Do(req); err != nil { | |
45 | return err | |
46 | } | |
47 | return nil | |
48 | }); err != nil { | |
49 | t.Fatal(err) | |
50 | } | |
51 | ||
52 | pact.WritePact() | |
53 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "io/ioutil" | |
4 | "net/http" | |
5 | "net/http/httptest" | |
6 | "strings" | |
7 | "testing" | |
8 | ||
9 | "github.com/opentracing/opentracing-go" | |
10 | zipkin "github.com/openzipkin/zipkin-go" | |
11 | ||
12 | "github.com/go-kit/kit/log" | |
13 | "github.com/go-kit/kit/metrics/discard" | |
14 | ||
15 | "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint" | |
16 | "github.com/go-kit/kit/examples/addsvc/pkg/addservice" | |
17 | "github.com/go-kit/kit/examples/addsvc/pkg/addtransport" | |
18 | ) | |
19 | ||
20 | func TestHTTP(t *testing.T) { | |
21 | zkt, _ := zipkin.NewTracer(nil, zipkin.WithNoopTracer(true)) | |
22 | svc := addservice.New(log.NewNopLogger(), discard.NewCounter(), discard.NewCounter()) | |
23 | eps := addendpoint.New(svc, log.NewNopLogger(), discard.NewHistogram(), opentracing.GlobalTracer(), zkt) | |
24 | mux := addtransport.NewHTTPHandler(eps, opentracing.GlobalTracer(), zkt, log.NewNopLogger()) | |
25 | srv := httptest.NewServer(mux) | |
26 | defer srv.Close() | |
27 | ||
28 | for _, testcase := range []struct { | |
29 | method, url, body, want string | |
30 | }{ | |
31 | {"GET", srv.URL + "/concat", `{"a":"1","b":"2"}`, `{"v":"12"}`}, | |
32 | {"GET", srv.URL + "/sum", `{"a":1,"b":2}`, `{"v":3}`}, | |
33 | } { | |
34 | req, _ := http.NewRequest(testcase.method, testcase.url, strings.NewReader(testcase.body)) | |
35 | resp, _ := http.DefaultClient.Do(req) | |
36 | body, _ := ioutil.ReadAll(resp.Body) | |
37 | if want, have := testcase.want, strings.TrimSpace(string(body)); want != have { | |
38 | t.Errorf("%s %s %s: want %q, have %q", testcase.method, testcase.url, testcase.body, want, have) | |
39 | } | |
40 | } | |
41 | } |
0 | // Code generated by protoc-gen-go. DO NOT EDIT. | |
1 | // source: addsvc.proto | |
2 | ||
3 | package pb | |
4 | ||
5 | import ( | |
6 | context "context" | |
7 | fmt "fmt" | |
8 | proto "github.com/golang/protobuf/proto" | |
9 | grpc "google.golang.org/grpc" | |
10 | math "math" | |
11 | ) | |
12 | ||
13 | // Reference imports to suppress errors if they are not otherwise used. | |
14 | var _ = proto.Marshal | |
15 | var _ = fmt.Errorf | |
16 | var _ = math.Inf | |
17 | ||
18 | // This is a compile-time assertion to ensure that this generated file | |
19 | // is compatible with the proto package it is being compiled against. | |
20 | // A compilation error at this line likely means your copy of the | |
21 | // proto package needs to be updated. | |
22 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package | |
23 | ||
24 | // The sum request contains two parameters. | |
25 | type SumRequest struct { | |
26 | A int64 `protobuf:"varint,1,opt,name=a,proto3" json:"a,omitempty"` | |
27 | B int64 `protobuf:"varint,2,opt,name=b,proto3" json:"b,omitempty"` | |
28 | XXX_NoUnkeyedLiteral struct{} `json:"-"` | |
29 | XXX_unrecognized []byte `json:"-"` | |
30 | XXX_sizecache int32 `json:"-"` | |
31 | } | |
32 | ||
33 | func (m *SumRequest) Reset() { *m = SumRequest{} } | |
34 | func (m *SumRequest) String() string { return proto.CompactTextString(m) } | |
35 | func (*SumRequest) ProtoMessage() {} | |
36 | func (*SumRequest) Descriptor() ([]byte, []int) { | |
37 | return fileDescriptor_174367f558d60c26, []int{0} | |
38 | } | |
39 | ||
40 | func (m *SumRequest) XXX_Unmarshal(b []byte) error { | |
41 | return xxx_messageInfo_SumRequest.Unmarshal(m, b) | |
42 | } | |
43 | func (m *SumRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | |
44 | return xxx_messageInfo_SumRequest.Marshal(b, m, deterministic) | |
45 | } | |
46 | func (m *SumRequest) XXX_Merge(src proto.Message) { | |
47 | xxx_messageInfo_SumRequest.Merge(m, src) | |
48 | } | |
49 | func (m *SumRequest) XXX_Size() int { | |
50 | return xxx_messageInfo_SumRequest.Size(m) | |
51 | } | |
52 | func (m *SumRequest) XXX_DiscardUnknown() { | |
53 | xxx_messageInfo_SumRequest.DiscardUnknown(m) | |
54 | } | |
55 | ||
56 | var xxx_messageInfo_SumRequest proto.InternalMessageInfo | |
57 | ||
58 | func (m *SumRequest) GetA() int64 { | |
59 | if m != nil { | |
60 | return m.A | |
61 | } | |
62 | return 0 | |
63 | } | |
64 | ||
65 | func (m *SumRequest) GetB() int64 { | |
66 | if m != nil { | |
67 | return m.B | |
68 | } | |
69 | return 0 | |
70 | } | |
71 | ||
72 | // The sum response contains the result of the calculation. | |
73 | type SumReply struct { | |
74 | V int64 `protobuf:"varint,1,opt,name=v,proto3" json:"v,omitempty"` | |
75 | Err string `protobuf:"bytes,2,opt,name=err,proto3" json:"err,omitempty"` | |
76 | XXX_NoUnkeyedLiteral struct{} `json:"-"` | |
77 | XXX_unrecognized []byte `json:"-"` | |
78 | XXX_sizecache int32 `json:"-"` | |
79 | } | |
80 | ||
81 | func (m *SumReply) Reset() { *m = SumReply{} } | |
82 | func (m *SumReply) String() string { return proto.CompactTextString(m) } | |
83 | func (*SumReply) ProtoMessage() {} | |
84 | func (*SumReply) Descriptor() ([]byte, []int) { | |
85 | return fileDescriptor_174367f558d60c26, []int{1} | |
86 | } | |
87 | ||
88 | func (m *SumReply) XXX_Unmarshal(b []byte) error { | |
89 | return xxx_messageInfo_SumReply.Unmarshal(m, b) | |
90 | } | |
91 | func (m *SumReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | |
92 | return xxx_messageInfo_SumReply.Marshal(b, m, deterministic) | |
93 | } | |
94 | func (m *SumReply) XXX_Merge(src proto.Message) { | |
95 | xxx_messageInfo_SumReply.Merge(m, src) | |
96 | } | |
97 | func (m *SumReply) XXX_Size() int { | |
98 | return xxx_messageInfo_SumReply.Size(m) | |
99 | } | |
100 | func (m *SumReply) XXX_DiscardUnknown() { | |
101 | xxx_messageInfo_SumReply.DiscardUnknown(m) | |
102 | } | |
103 | ||
104 | var xxx_messageInfo_SumReply proto.InternalMessageInfo | |
105 | ||
106 | func (m *SumReply) GetV() int64 { | |
107 | if m != nil { | |
108 | return m.V | |
109 | } | |
110 | return 0 | |
111 | } | |
112 | ||
113 | func (m *SumReply) GetErr() string { | |
114 | if m != nil { | |
115 | return m.Err | |
116 | } | |
117 | return "" | |
118 | } | |
119 | ||
120 | // The Concat request contains two parameters. | |
121 | type ConcatRequest struct { | |
122 | A string `protobuf:"bytes,1,opt,name=a,proto3" json:"a,omitempty"` | |
123 | B string `protobuf:"bytes,2,opt,name=b,proto3" json:"b,omitempty"` | |
124 | XXX_NoUnkeyedLiteral struct{} `json:"-"` | |
125 | XXX_unrecognized []byte `json:"-"` | |
126 | XXX_sizecache int32 `json:"-"` | |
127 | } | |
128 | ||
129 | func (m *ConcatRequest) Reset() { *m = ConcatRequest{} } | |
130 | func (m *ConcatRequest) String() string { return proto.CompactTextString(m) } | |
131 | func (*ConcatRequest) ProtoMessage() {} | |
132 | func (*ConcatRequest) Descriptor() ([]byte, []int) { | |
133 | return fileDescriptor_174367f558d60c26, []int{2} | |
134 | } | |
135 | ||
136 | func (m *ConcatRequest) XXX_Unmarshal(b []byte) error { | |
137 | return xxx_messageInfo_ConcatRequest.Unmarshal(m, b) | |
138 | } | |
139 | func (m *ConcatRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | |
140 | return xxx_messageInfo_ConcatRequest.Marshal(b, m, deterministic) | |
141 | } | |
142 | func (m *ConcatRequest) XXX_Merge(src proto.Message) { | |
143 | xxx_messageInfo_ConcatRequest.Merge(m, src) | |
144 | } | |
145 | func (m *ConcatRequest) XXX_Size() int { | |
146 | return xxx_messageInfo_ConcatRequest.Size(m) | |
147 | } | |
148 | func (m *ConcatRequest) XXX_DiscardUnknown() { | |
149 | xxx_messageInfo_ConcatRequest.DiscardUnknown(m) | |
150 | } | |
151 | ||
152 | var xxx_messageInfo_ConcatRequest proto.InternalMessageInfo | |
153 | ||
154 | func (m *ConcatRequest) GetA() string { | |
155 | if m != nil { | |
156 | return m.A | |
157 | } | |
158 | return "" | |
159 | } | |
160 | ||
161 | func (m *ConcatRequest) GetB() string { | |
162 | if m != nil { | |
163 | return m.B | |
164 | } | |
165 | return "" | |
166 | } | |
167 | ||
168 | // The Concat response contains the result of the concatenation. | |
169 | type ConcatReply struct { | |
170 | V string `protobuf:"bytes,1,opt,name=v,proto3" json:"v,omitempty"` | |
171 | Err string `protobuf:"bytes,2,opt,name=err,proto3" json:"err,omitempty"` | |
172 | XXX_NoUnkeyedLiteral struct{} `json:"-"` | |
173 | XXX_unrecognized []byte `json:"-"` | |
174 | XXX_sizecache int32 `json:"-"` | |
175 | } | |
176 | ||
177 | func (m *ConcatReply) Reset() { *m = ConcatReply{} } | |
178 | func (m *ConcatReply) String() string { return proto.CompactTextString(m) } | |
179 | func (*ConcatReply) ProtoMessage() {} | |
180 | func (*ConcatReply) Descriptor() ([]byte, []int) { | |
181 | return fileDescriptor_174367f558d60c26, []int{3} | |
182 | } | |
183 | ||
184 | func (m *ConcatReply) XXX_Unmarshal(b []byte) error { | |
185 | return xxx_messageInfo_ConcatReply.Unmarshal(m, b) | |
186 | } | |
187 | func (m *ConcatReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { | |
188 | return xxx_messageInfo_ConcatReply.Marshal(b, m, deterministic) | |
189 | } | |
190 | func (m *ConcatReply) XXX_Merge(src proto.Message) { | |
191 | xxx_messageInfo_ConcatReply.Merge(m, src) | |
192 | } | |
193 | func (m *ConcatReply) XXX_Size() int { | |
194 | return xxx_messageInfo_ConcatReply.Size(m) | |
195 | } | |
196 | func (m *ConcatReply) XXX_DiscardUnknown() { | |
197 | xxx_messageInfo_ConcatReply.DiscardUnknown(m) | |
198 | } | |
199 | ||
200 | var xxx_messageInfo_ConcatReply proto.InternalMessageInfo | |
201 | ||
202 | func (m *ConcatReply) GetV() string { | |
203 | if m != nil { | |
204 | return m.V | |
205 | } | |
206 | return "" | |
207 | } | |
208 | ||
209 | func (m *ConcatReply) GetErr() string { | |
210 | if m != nil { | |
211 | return m.Err | |
212 | } | |
213 | return "" | |
214 | } | |
215 | ||
216 | func init() { | |
217 | proto.RegisterType((*SumRequest)(nil), "pb.SumRequest") | |
218 | proto.RegisterType((*SumReply)(nil), "pb.SumReply") | |
219 | proto.RegisterType((*ConcatRequest)(nil), "pb.ConcatRequest") | |
220 | proto.RegisterType((*ConcatReply)(nil), "pb.ConcatReply") | |
221 | } | |
222 | ||
223 | func init() { proto.RegisterFile("addsvc.proto", fileDescriptor_174367f558d60c26) } | |
224 | ||
225 | var fileDescriptor_174367f558d60c26 = []byte{ | |
226 | // 189 bytes of a gzipped FileDescriptorProto | |
227 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x4c, 0x49, 0x29, | |
228 | 0x2e, 0x4b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x48, 0x52, 0xd2, 0xe0, 0xe2, | |
229 | 0x0a, 0x2e, 0xcd, 0x0d, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0xe2, 0xe1, 0x62, 0x4c, 0x94, | |
230 | 0x60, 0x54, 0x60, 0xd4, 0x60, 0x0e, 0x62, 0x4c, 0x04, 0xf1, 0x92, 0x24, 0x98, 0x20, 0xbc, 0x24, | |
231 | 0x25, 0x2d, 0x2e, 0x0e, 0xb0, 0xca, 0x82, 0x9c, 0x4a, 0x90, 0x4c, 0x19, 0x4c, 0x5d, 0x99, 0x90, | |
232 | 0x00, 0x17, 0x73, 0x6a, 0x51, 0x11, 0x58, 0x25, 0x67, 0x10, 0x88, 0xa9, 0xa4, 0xcd, 0xc5, 0xeb, | |
233 | 0x9c, 0x9f, 0x97, 0x9c, 0x58, 0x82, 0x61, 0x30, 0x27, 0x8a, 0xc1, 0x9c, 0x20, 0x83, 0x75, 0xb9, | |
234 | 0xb8, 0x61, 0x8a, 0x51, 0xcc, 0xe6, 0xc4, 0x6a, 0xb6, 0x51, 0x0c, 0x17, 0xb3, 0x63, 0x4a, 0x8a, | |
235 | 0x90, 0x2a, 0x17, 0x73, 0x70, 0x69, 0xae, 0x10, 0x9f, 0x5e, 0x41, 0x92, 0x1e, 0xc2, 0x07, 0x52, | |
236 | 0x3c, 0x70, 0x7e, 0x41, 0x4e, 0xa5, 0x12, 0x83, 0x90, 0x1e, 0x17, 0x1b, 0xc4, 0x70, 0x21, 0x41, | |
237 | 0x90, 0x0c, 0x8a, 0xab, 0xa4, 0xf8, 0x91, 0x85, 0xc0, 0xea, 0x93, 0xd8, 0xc0, 0x41, 0x63, 0x0c, | |
238 | 0x08, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x37, 0x81, 0x99, 0x2a, 0x01, 0x00, 0x00, | |
239 | } | |
240 | ||
241 | // Reference imports to suppress errors if they are not otherwise used. | |
242 | var _ context.Context | |
243 | var _ grpc.ClientConn | |
244 | ||
245 | // This is a compile-time assertion to ensure that this generated file | |
246 | // is compatible with the grpc package it is being compiled against. | |
247 | const _ = grpc.SupportPackageIsVersion4 | |
248 | ||
249 | // AddClient is the client API for Add service. | |
250 | // | |
251 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. | |
252 | type AddClient interface { | |
253 | // Sums two integers. | |
254 | Sum(ctx context.Context, in *SumRequest, opts ...grpc.CallOption) (*SumReply, error) | |
255 | // Concatenates two strings | |
256 | Concat(ctx context.Context, in *ConcatRequest, opts ...grpc.CallOption) (*ConcatReply, error) | |
257 | } | |
258 | ||
259 | type addClient struct { | |
260 | cc *grpc.ClientConn | |
261 | } | |
262 | ||
263 | func NewAddClient(cc *grpc.ClientConn) AddClient { | |
264 | return &addClient{cc} | |
265 | } | |
266 | ||
267 | func (c *addClient) Sum(ctx context.Context, in *SumRequest, opts ...grpc.CallOption) (*SumReply, error) { | |
268 | out := new(SumReply) | |
269 | err := c.cc.Invoke(ctx, "/pb.Add/Sum", in, out, opts...) | |
270 | if err != nil { | |
271 | return nil, err | |
272 | } | |
273 | return out, nil | |
274 | } | |
275 | ||
276 | func (c *addClient) Concat(ctx context.Context, in *ConcatRequest, opts ...grpc.CallOption) (*ConcatReply, error) { | |
277 | out := new(ConcatReply) | |
278 | err := c.cc.Invoke(ctx, "/pb.Add/Concat", in, out, opts...) | |
279 | if err != nil { | |
280 | return nil, err | |
281 | } | |
282 | return out, nil | |
283 | } | |
284 | ||
285 | // AddServer is the server API for Add service. | |
286 | type AddServer interface { | |
287 | // Sums two integers. | |
288 | Sum(context.Context, *SumRequest) (*SumReply, error) | |
289 | // Concatenates two strings | |
290 | Concat(context.Context, *ConcatRequest) (*ConcatReply, error) | |
291 | } | |
292 | ||
293 | func RegisterAddServer(s *grpc.Server, srv AddServer) { | |
294 | s.RegisterService(&_Add_serviceDesc, srv) | |
295 | } | |
296 | ||
297 | func _Add_Sum_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | |
298 | in := new(SumRequest) | |
299 | if err := dec(in); err != nil { | |
300 | return nil, err | |
301 | } | |
302 | if interceptor == nil { | |
303 | return srv.(AddServer).Sum(ctx, in) | |
304 | } | |
305 | info := &grpc.UnaryServerInfo{ | |
306 | Server: srv, | |
307 | FullMethod: "/pb.Add/Sum", | |
308 | } | |
309 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { | |
310 | return srv.(AddServer).Sum(ctx, req.(*SumRequest)) | |
311 | } | |
312 | return interceptor(ctx, in, info, handler) | |
313 | } | |
314 | ||
315 | func _Add_Concat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | |
316 | in := new(ConcatRequest) | |
317 | if err := dec(in); err != nil { | |
318 | return nil, err | |
319 | } | |
320 | if interceptor == nil { | |
321 | return srv.(AddServer).Concat(ctx, in) | |
322 | } | |
323 | info := &grpc.UnaryServerInfo{ | |
324 | Server: srv, | |
325 | FullMethod: "/pb.Add/Concat", | |
326 | } | |
327 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { | |
328 | return srv.(AddServer).Concat(ctx, req.(*ConcatRequest)) | |
329 | } | |
330 | return interceptor(ctx, in, info, handler) | |
331 | } | |
332 | ||
333 | var _Add_serviceDesc = grpc.ServiceDesc{ | |
334 | ServiceName: "pb.Add", | |
335 | HandlerType: (*AddServer)(nil), | |
336 | Methods: []grpc.MethodDesc{ | |
337 | { | |
338 | MethodName: "Sum", | |
339 | Handler: _Add_Sum_Handler, | |
340 | }, | |
341 | { | |
342 | MethodName: "Concat", | |
343 | Handler: _Add_Concat_Handler, | |
344 | }, | |
345 | }, | |
346 | Streams: []grpc.StreamDesc{}, | |
347 | Metadata: "addsvc.proto", | |
348 | } |
0 | syntax = "proto3"; | |
1 | ||
2 | package pb; | |
3 | ||
4 | // The Add service definition. | |
5 | service Add { | |
6 | // Sums two integers. | |
7 | rpc Sum (SumRequest) returns (SumReply) {} | |
8 | ||
9 | // Concatenates two strings | |
10 | rpc Concat (ConcatRequest) returns (ConcatReply) {} | |
11 | } | |
12 | ||
13 | // The sum request contains two parameters. | |
14 | message SumRequest { | |
15 | int64 a = 1; | |
16 | int64 b = 2; | |
17 | } | |
18 | ||
19 | // The sum response contains the result of the calculation. | |
20 | message SumReply { | |
21 | int64 v = 1; | |
22 | string err = 2; | |
23 | } | |
24 | ||
25 | // The Concat request contains two parameters. | |
26 | message ConcatRequest { | |
27 | string a = 1; | |
28 | string b = 2; | |
29 | } | |
30 | ||
31 | // The Concat response contains the result of the concatenation. | |
32 | message ConcatReply { | |
33 | string v = 1; | |
34 | string err = 2; | |
35 | } |
0 | #!/usr/bin/env sh | |
1 | ||
2 | # Install proto3 from source | |
3 | # brew install autoconf automake libtool | |
4 | # git clone https://github.com/google/protobuf | |
5 | # ./autogen.sh ; ./configure ; make ; make install | |
6 | # | |
7 | # Update protoc Go bindings via | |
8 | # go get -u github.com/golang/protobuf/{proto,protoc-gen-go} | |
9 | # | |
10 | # See also | |
11 | # https://github.com/grpc/grpc-go/tree/master/examples | |
12 | ||
13 | protoc addsvc.proto --go_out=plugins=grpc:. |
0 | package addendpoint | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "fmt" | |
5 | "time" | |
6 | ||
7 | "github.com/go-kit/kit/endpoint" | |
8 | "github.com/go-kit/kit/log" | |
9 | "github.com/go-kit/kit/metrics" | |
10 | ) | |
11 | ||
12 | // InstrumentingMiddleware returns an endpoint middleware that records | |
13 | // the duration of each invocation to the passed histogram. The middleware adds | |
14 | // a single field: "success", which is "true" if no error is returned, and | |
15 | // "false" otherwise. | |
16 | func InstrumentingMiddleware(duration metrics.Histogram) endpoint.Middleware { | |
17 | return func(next endpoint.Endpoint) endpoint.Endpoint { | |
18 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
19 | ||
20 | defer func(begin time.Time) { | |
21 | duration.With("success", fmt.Sprint(err == nil)).Observe(time.Since(begin).Seconds()) | |
22 | }(time.Now()) | |
23 | return next(ctx, request) | |
24 | ||
25 | } | |
26 | } | |
27 | } | |
28 | ||
29 | // LoggingMiddleware returns an endpoint middleware that logs the | |
30 | // duration of each invocation, and the resulting error, if any. | |
31 | func LoggingMiddleware(logger log.Logger) endpoint.Middleware { | |
32 | return func(next endpoint.Endpoint) endpoint.Endpoint { | |
33 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
34 | ||
35 | defer func(begin time.Time) { | |
36 | logger.Log("transport_error", err, "took", time.Since(begin)) | |
37 | }(time.Now()) | |
38 | return next(ctx, request) | |
39 | ||
40 | } | |
41 | } | |
42 | } |
0 | package addendpoint | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "time" | |
5 | ||
6 | "golang.org/x/time/rate" | |
7 | ||
8 | stdopentracing "github.com/opentracing/opentracing-go" | |
9 | stdzipkin "github.com/openzipkin/zipkin-go" | |
10 | "github.com/sony/gobreaker" | |
11 | ||
12 | "github.com/go-kit/kit/circuitbreaker" | |
13 | "github.com/go-kit/kit/endpoint" | |
14 | "github.com/go-kit/kit/log" | |
15 | "github.com/go-kit/kit/metrics" | |
16 | "github.com/go-kit/kit/ratelimit" | |
17 | "github.com/go-kit/kit/tracing/opentracing" | |
18 | "github.com/go-kit/kit/tracing/zipkin" | |
19 | ||
20 | "github.com/go-kit/kit/examples/addsvc/pkg/addservice" | |
21 | ) | |
22 | ||
23 | // Set collects all of the endpoints that compose an add service. It's meant to | |
24 | // be used as a helper struct, to collect all of the endpoints into a single | |
25 | // parameter. | |
26 | type Set struct { | |
27 | SumEndpoint endpoint.Endpoint | |
28 | ConcatEndpoint endpoint.Endpoint | |
29 | } | |
30 | ||
31 | // New returns a Set that wraps the provided server, and wires in all of the | |
32 | // expected endpoint middlewares via the various parameters. | |
33 | func New(svc addservice.Service, logger log.Logger, duration metrics.Histogram, otTracer stdopentracing.Tracer, zipkinTracer *stdzipkin.Tracer) Set { | |
34 | var sumEndpoint endpoint.Endpoint | |
35 | { | |
36 | sumEndpoint = MakeSumEndpoint(svc) | |
37 | // Sum is limited to 1 request per second with burst of 1 request. | |
38 | // Note, rate is defined as a time interval between requests. | |
39 | sumEndpoint = ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), 1))(sumEndpoint) | |
40 | sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(sumEndpoint) | |
41 | sumEndpoint = opentracing.TraceServer(otTracer, "Sum")(sumEndpoint) | |
42 | if zipkinTracer != nil { | |
43 | sumEndpoint = zipkin.TraceEndpoint(zipkinTracer, "Sum")(sumEndpoint) | |
44 | } | |
45 | sumEndpoint = LoggingMiddleware(log.With(logger, "method", "Sum"))(sumEndpoint) | |
46 | sumEndpoint = InstrumentingMiddleware(duration.With("method", "Sum"))(sumEndpoint) | |
47 | } | |
48 | var concatEndpoint endpoint.Endpoint | |
49 | { | |
50 | concatEndpoint = MakeConcatEndpoint(svc) | |
51 | // Concat is limited to 1 request per second with burst of 100 requests. | |
52 | // Note, rate is defined as a number of requests per second. | |
53 | concatEndpoint = ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Limit(1), 100))(concatEndpoint) | |
54 | concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(concatEndpoint) | |
55 | concatEndpoint = opentracing.TraceServer(otTracer, "Concat")(concatEndpoint) | |
56 | if zipkinTracer != nil { | |
57 | concatEndpoint = zipkin.TraceEndpoint(zipkinTracer, "Concat")(concatEndpoint) | |
58 | } | |
59 | concatEndpoint = LoggingMiddleware(log.With(logger, "method", "Concat"))(concatEndpoint) | |
60 | concatEndpoint = InstrumentingMiddleware(duration.With("method", "Concat"))(concatEndpoint) | |
61 | } | |
62 | return Set{ | |
63 | SumEndpoint: sumEndpoint, | |
64 | ConcatEndpoint: concatEndpoint, | |
65 | } | |
66 | } | |
67 | ||
68 | // Sum implements the service interface, so Set may be used as a service. | |
69 | // This is primarily useful in the context of a client library. | |
70 | func (s Set) Sum(ctx context.Context, a, b int) (int, error) { | |
71 | resp, err := s.SumEndpoint(ctx, SumRequest{A: a, B: b}) | |
72 | if err != nil { | |
73 | return 0, err | |
74 | } | |
75 | response := resp.(SumResponse) | |
76 | return response.V, response.Err | |
77 | } | |
78 | ||
79 | // Concat implements the service interface, so Set may be used as a | |
80 | // service. This is primarily useful in the context of a client library. | |
81 | func (s Set) Concat(ctx context.Context, a, b string) (string, error) { | |
82 | resp, err := s.ConcatEndpoint(ctx, ConcatRequest{A: a, B: b}) | |
83 | if err != nil { | |
84 | return "", err | |
85 | } | |
86 | response := resp.(ConcatResponse) | |
87 | return response.V, response.Err | |
88 | } | |
89 | ||
90 | // MakeSumEndpoint constructs a Sum endpoint wrapping the service. | |
91 | func MakeSumEndpoint(s addservice.Service) endpoint.Endpoint { | |
92 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
93 | req := request.(SumRequest) | |
94 | v, err := s.Sum(ctx, req.A, req.B) | |
95 | return SumResponse{V: v, Err: err}, nil | |
96 | } | |
97 | } | |
98 | ||
99 | // MakeConcatEndpoint constructs a Concat endpoint wrapping the service. | |
100 | func MakeConcatEndpoint(s addservice.Service) endpoint.Endpoint { | |
101 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
102 | req := request.(ConcatRequest) | |
103 | v, err := s.Concat(ctx, req.A, req.B) | |
104 | return ConcatResponse{V: v, Err: err}, nil | |
105 | } | |
106 | } | |
107 | ||
108 | // compile time assertions for our response types implementing endpoint.Failer. | |
109 | var ( | |
110 | _ endpoint.Failer = SumResponse{} | |
111 | _ endpoint.Failer = ConcatResponse{} | |
112 | ) | |
113 | ||
114 | // SumRequest collects the request parameters for the Sum method. | |
115 | type SumRequest struct { | |
116 | A, B int | |
117 | } | |
118 | ||
119 | // SumResponse collects the response values for the Sum method. | |
120 | type SumResponse struct { | |
121 | V int `json:"v"` | |
122 | Err error `json:"-"` // should be intercepted by Failed/errorEncoder | |
123 | } | |
124 | ||
125 | // Failed implements endpoint.Failer. | |
126 | func (r SumResponse) Failed() error { return r.Err } | |
127 | ||
128 | // ConcatRequest collects the request parameters for the Concat method. | |
129 | type ConcatRequest struct { | |
130 | A, B string | |
131 | } | |
132 | ||
133 | // ConcatResponse collects the response values for the Concat method. | |
134 | type ConcatResponse struct { | |
135 | V string `json:"v"` | |
136 | Err error `json:"-"` | |
137 | } | |
138 | ||
139 | // Failed implements endpoint.Failer. | |
140 | func (r ConcatResponse) Failed() error { return r.Err } |
0 | package addservice | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | ||
5 | "github.com/go-kit/kit/log" | |
6 | "github.com/go-kit/kit/metrics" | |
7 | ) | |
8 | ||
9 | // Middleware describes a service (as opposed to endpoint) middleware. | |
10 | type Middleware func(Service) Service | |
11 | ||
12 | // LoggingMiddleware takes a logger as a dependency | |
13 | // and returns a service Middleware. | |
14 | func LoggingMiddleware(logger log.Logger) Middleware { | |
15 | return func(next Service) Service { | |
16 | return loggingMiddleware{logger, next} | |
17 | } | |
18 | } | |
19 | ||
20 | type loggingMiddleware struct { | |
21 | logger log.Logger | |
22 | next Service | |
23 | } | |
24 | ||
25 | func (mw loggingMiddleware) Sum(ctx context.Context, a, b int) (v int, err error) { | |
26 | defer func() { | |
27 | mw.logger.Log("method", "Sum", "a", a, "b", b, "v", v, "err", err) | |
28 | }() | |
29 | return mw.next.Sum(ctx, a, b) | |
30 | } | |
31 | ||
32 | func (mw loggingMiddleware) Concat(ctx context.Context, a, b string) (v string, err error) { | |
33 | defer func() { | |
34 | mw.logger.Log("method", "Concat", "a", a, "b", b, "v", v, "err", err) | |
35 | }() | |
36 | return mw.next.Concat(ctx, a, b) | |
37 | } | |
38 | ||
39 | // InstrumentingMiddleware returns a service middleware that instruments | |
40 | // the number of integers summed and characters concatenated over the lifetime of | |
41 | // the service. | |
42 | func InstrumentingMiddleware(ints, chars metrics.Counter) Middleware { | |
43 | return func(next Service) Service { | |
44 | return instrumentingMiddleware{ | |
45 | ints: ints, | |
46 | chars: chars, | |
47 | next: next, | |
48 | } | |
49 | } | |
50 | } | |
51 | ||
52 | type instrumentingMiddleware struct { | |
53 | ints metrics.Counter | |
54 | chars metrics.Counter | |
55 | next Service | |
56 | } | |
57 | ||
58 | func (mw instrumentingMiddleware) Sum(ctx context.Context, a, b int) (int, error) { | |
59 | v, err := mw.next.Sum(ctx, a, b) | |
60 | mw.ints.Add(float64(v)) | |
61 | return v, err | |
62 | } | |
63 | ||
64 | func (mw instrumentingMiddleware) Concat(ctx context.Context, a, b string) (string, error) { | |
65 | v, err := mw.next.Concat(ctx, a, b) | |
66 | mw.chars.Add(float64(len(v))) | |
67 | return v, err | |
68 | } |
0 | package addservice | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "errors" | |
5 | ||
6 | "github.com/go-kit/kit/log" | |
7 | "github.com/go-kit/kit/metrics" | |
8 | ) | |
9 | ||
10 | // Service describes a service that adds things together. | |
11 | type Service interface { | |
12 | Sum(ctx context.Context, a, b int) (int, error) | |
13 | Concat(ctx context.Context, a, b string) (string, error) | |
14 | } | |
15 | ||
16 | // New returns a basic Service with all of the expected middlewares wired in. | |
17 | func New(logger log.Logger, ints, chars metrics.Counter) Service { | |
18 | var svc Service | |
19 | { | |
20 | svc = NewBasicService() | |
21 | svc = LoggingMiddleware(logger)(svc) | |
22 | svc = InstrumentingMiddleware(ints, chars)(svc) | |
23 | } | |
24 | return svc | |
25 | } | |
26 | ||
27 | var ( | |
28 | // ErrTwoZeroes is an arbitrary business rule for the Add method. | |
29 | ErrTwoZeroes = errors.New("can't sum two zeroes") | |
30 | ||
31 | // ErrIntOverflow protects the Add method. We've decided that this error | |
32 | // indicates a misbehaving service and should count against e.g. circuit | |
33 | // breakers. So, we return it directly in endpoints, to illustrate the | |
34 | // difference. In a real service, this probably wouldn't be the case. | |
35 | ErrIntOverflow = errors.New("integer overflow") | |
36 | ||
37 | // ErrMaxSizeExceeded protects the Concat method. | |
38 | ErrMaxSizeExceeded = errors.New("result exceeds maximum size") | |
39 | ) | |
40 | ||
41 | // NewBasicService returns a naïve, stateless implementation of Service. | |
42 | func NewBasicService() Service { | |
43 | return basicService{} | |
44 | } | |
45 | ||
46 | type basicService struct{} | |
47 | ||
48 | const ( | |
49 | intMax = 1<<31 - 1 | |
50 | intMin = -(intMax + 1) | |
51 | maxLen = 10 | |
52 | ) | |
53 | ||
54 | func (s basicService) Sum(_ context.Context, a, b int) (int, error) { | |
55 | if a == 0 && b == 0 { | |
56 | return 0, ErrTwoZeroes | |
57 | } | |
58 | if (b > 0 && a > (intMax-b)) || (b < 0 && a < (intMin-b)) { | |
59 | return 0, ErrIntOverflow | |
60 | } | |
61 | return a + b, nil | |
62 | } | |
63 | ||
64 | // Concat implements Service. | |
65 | func (s basicService) Concat(_ context.Context, a, b string) (string, error) { | |
66 | if len(a)+len(b) > maxLen { | |
67 | return "", ErrMaxSizeExceeded | |
68 | } | |
69 | return a + b, nil | |
70 | } |
0 | package addtransport | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "errors" | |
5 | "time" | |
6 | ||
7 | "google.golang.org/grpc" | |
8 | ||
9 | stdopentracing "github.com/opentracing/opentracing-go" | |
10 | stdzipkin "github.com/openzipkin/zipkin-go" | |
11 | "github.com/sony/gobreaker" | |
12 | "golang.org/x/time/rate" | |
13 | ||
14 | "github.com/go-kit/kit/circuitbreaker" | |
15 | "github.com/go-kit/kit/endpoint" | |
16 | "github.com/go-kit/kit/log" | |
17 | "github.com/go-kit/kit/ratelimit" | |
18 | "github.com/go-kit/kit/tracing/opentracing" | |
19 | "github.com/go-kit/kit/tracing/zipkin" | |
20 | "github.com/go-kit/kit/transport" | |
21 | grpctransport "github.com/go-kit/kit/transport/grpc" | |
22 | ||
23 | "github.com/go-kit/kit/examples/addsvc/pb" | |
24 | "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint" | |
25 | "github.com/go-kit/kit/examples/addsvc/pkg/addservice" | |
26 | ) | |
27 | ||
28 | type grpcServer struct { | |
29 | sum grpctransport.Handler | |
30 | concat grpctransport.Handler | |
31 | } | |
32 | ||
33 | // NewGRPCServer makes a set of endpoints available as a gRPC AddServer. | |
34 | func NewGRPCServer(endpoints addendpoint.Set, otTracer stdopentracing.Tracer, zipkinTracer *stdzipkin.Tracer, logger log.Logger) pb.AddServer { | |
35 | options := []grpctransport.ServerOption{ | |
36 | grpctransport.ServerErrorHandler(transport.NewLogErrorHandler(logger)), | |
37 | } | |
38 | ||
39 | if zipkinTracer != nil { | |
40 | // Zipkin GRPC Server Trace can either be instantiated per gRPC method with a | |
41 | // provided operation name or a global tracing service can be instantiated | |
42 | // without an operation name and fed to each Go kit gRPC server as a | |
43 | // ServerOption. | |
44 | // In the latter case, the operation name will be the endpoint's grpc method | |
45 | // path if used in combination with the Go kit gRPC Interceptor. | |
46 | // | |
47 | // In this example, we demonstrate a global Zipkin tracing service with | |
48 | // Go kit gRPC Interceptor. | |
49 | options = append(options, zipkin.GRPCServerTrace(zipkinTracer)) | |
50 | } | |
51 | ||
52 | return &grpcServer{ | |
53 | sum: grpctransport.NewServer( | |
54 | endpoints.SumEndpoint, | |
55 | decodeGRPCSumRequest, | |
56 | encodeGRPCSumResponse, | |
57 | append(options, grpctransport.ServerBefore(opentracing.GRPCToContext(otTracer, "Sum", logger)))..., | |
58 | ), | |
59 | concat: grpctransport.NewServer( | |
60 | endpoints.ConcatEndpoint, | |
61 | decodeGRPCConcatRequest, | |
62 | encodeGRPCConcatResponse, | |
63 | append(options, grpctransport.ServerBefore(opentracing.GRPCToContext(otTracer, "Concat", logger)))..., | |
64 | ), | |
65 | } | |
66 | } | |
67 | ||
68 | func (s *grpcServer) Sum(ctx context.Context, req *pb.SumRequest) (*pb.SumReply, error) { | |
69 | _, rep, err := s.sum.ServeGRPC(ctx, req) | |
70 | if err != nil { | |
71 | return nil, err | |
72 | } | |
73 | return rep.(*pb.SumReply), nil | |
74 | } | |
75 | ||
76 | func (s *grpcServer) Concat(ctx context.Context, req *pb.ConcatRequest) (*pb.ConcatReply, error) { | |
77 | _, rep, err := s.concat.ServeGRPC(ctx, req) | |
78 | if err != nil { | |
79 | return nil, err | |
80 | } | |
81 | return rep.(*pb.ConcatReply), nil | |
82 | } | |
83 | ||
84 | // NewGRPCClient returns an AddService backed by a gRPC server at the other end | |
85 | // of the conn. The caller is responsible for constructing the conn, and | |
86 | // eventually closing the underlying transport. We bake-in certain middlewares, | |
87 | // implementing the client library pattern. | |
88 | func NewGRPCClient(conn *grpc.ClientConn, otTracer stdopentracing.Tracer, zipkinTracer *stdzipkin.Tracer, logger log.Logger) addservice.Service { | |
89 | // We construct a single ratelimiter middleware, to limit the total outgoing | |
90 | // QPS from this client to all methods on the remote instance. We also | |
91 | // construct per-endpoint circuitbreaker middlewares to demonstrate how | |
92 | // that's done, although they could easily be combined into a single breaker | |
93 | // for the entire remote instance, too. | |
94 | limiter := ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), 100)) | |
95 | ||
96 | // global client middlewares | |
97 | var options []grpctransport.ClientOption | |
98 | ||
99 | if zipkinTracer != nil { | |
100 | // Zipkin GRPC Client Trace can either be instantiated per gRPC method with a | |
101 | // provided operation name or a global tracing client can be instantiated | |
102 | // without an operation name and fed to each Go kit client as ClientOption. | |
103 | // In the latter case, the operation name will be the endpoint's grpc method | |
104 | // path. | |
105 | // | |
106 | // In this example, we demonstrace a global tracing client. | |
107 | options = append(options, zipkin.GRPCClientTrace(zipkinTracer)) | |
108 | ||
109 | } | |
110 | // Each individual endpoint is an grpc/transport.Client (which implements | |
111 | // endpoint.Endpoint) that gets wrapped with various middlewares. If you | |
112 | // made your own client library, you'd do this work there, so your server | |
113 | // could rely on a consistent set of client behavior. | |
114 | var sumEndpoint endpoint.Endpoint | |
115 | { | |
116 | sumEndpoint = grpctransport.NewClient( | |
117 | conn, | |
118 | "pb.Add", | |
119 | "Sum", | |
120 | encodeGRPCSumRequest, | |
121 | decodeGRPCSumResponse, | |
122 | pb.SumReply{}, | |
123 | append(options, grpctransport.ClientBefore(opentracing.ContextToGRPC(otTracer, logger)))..., | |
124 | ).Endpoint() | |
125 | sumEndpoint = opentracing.TraceClient(otTracer, "Sum")(sumEndpoint) | |
126 | sumEndpoint = limiter(sumEndpoint) | |
127 | sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ | |
128 | Name: "Sum", | |
129 | Timeout: 30 * time.Second, | |
130 | }))(sumEndpoint) | |
131 | } | |
132 | ||
133 | // The Concat endpoint is the same thing, with slightly different | |
134 | // middlewares to demonstrate how to specialize per-endpoint. | |
135 | var concatEndpoint endpoint.Endpoint | |
136 | { | |
137 | concatEndpoint = grpctransport.NewClient( | |
138 | conn, | |
139 | "pb.Add", | |
140 | "Concat", | |
141 | encodeGRPCConcatRequest, | |
142 | decodeGRPCConcatResponse, | |
143 | pb.ConcatReply{}, | |
144 | append(options, grpctransport.ClientBefore(opentracing.ContextToGRPC(otTracer, logger)))..., | |
145 | ).Endpoint() | |
146 | concatEndpoint = opentracing.TraceClient(otTracer, "Concat")(concatEndpoint) | |
147 | concatEndpoint = limiter(concatEndpoint) | |
148 | concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ | |
149 | Name: "Concat", | |
150 | Timeout: 10 * time.Second, | |
151 | }))(concatEndpoint) | |
152 | } | |
153 | ||
154 | // Returning the endpoint.Set as a service.Service relies on the | |
155 | // endpoint.Set implementing the Service methods. That's just a simple bit | |
156 | // of glue code. | |
157 | return addendpoint.Set{ | |
158 | SumEndpoint: sumEndpoint, | |
159 | ConcatEndpoint: concatEndpoint, | |
160 | } | |
161 | } | |
162 | ||
163 | // decodeGRPCSumRequest is a transport/grpc.DecodeRequestFunc that converts a | |
164 | // gRPC sum request to a user-domain sum request. Primarily useful in a server. | |
165 | func decodeGRPCSumRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { | |
166 | req := grpcReq.(*pb.SumRequest) | |
167 | return addendpoint.SumRequest{A: int(req.A), B: int(req.B)}, nil | |
168 | } | |
169 | ||
170 | // decodeGRPCConcatRequest is a transport/grpc.DecodeRequestFunc that converts a | |
171 | // gRPC concat request to a user-domain concat request. Primarily useful in a | |
172 | // server. | |
173 | func decodeGRPCConcatRequest(_ context.Context, grpcReq interface{}) (interface{}, error) { | |
174 | req := grpcReq.(*pb.ConcatRequest) | |
175 | return addendpoint.ConcatRequest{A: req.A, B: req.B}, nil | |
176 | } | |
177 | ||
178 | // decodeGRPCSumResponse is a transport/grpc.DecodeResponseFunc that converts a | |
179 | // gRPC sum reply to a user-domain sum response. Primarily useful in a client. | |
180 | func decodeGRPCSumResponse(_ context.Context, grpcReply interface{}) (interface{}, error) { | |
181 | reply := grpcReply.(*pb.SumReply) | |
182 | return addendpoint.SumResponse{V: int(reply.V), Err: str2err(reply.Err)}, nil | |
183 | } | |
184 | ||
185 | // decodeGRPCConcatResponse is a transport/grpc.DecodeResponseFunc that converts | |
186 | // a gRPC concat reply to a user-domain concat response. Primarily useful in a | |
187 | // client. | |
188 | func decodeGRPCConcatResponse(_ context.Context, grpcReply interface{}) (interface{}, error) { | |
189 | reply := grpcReply.(*pb.ConcatReply) | |
190 | return addendpoint.ConcatResponse{V: reply.V, Err: str2err(reply.Err)}, nil | |
191 | } | |
192 | ||
193 | // encodeGRPCSumResponse is a transport/grpc.EncodeResponseFunc that converts a | |
194 | // user-domain sum response to a gRPC sum reply. Primarily useful in a server. | |
195 | func encodeGRPCSumResponse(_ context.Context, response interface{}) (interface{}, error) { | |
196 | resp := response.(addendpoint.SumResponse) | |
197 | return &pb.SumReply{V: int64(resp.V), Err: err2str(resp.Err)}, nil | |
198 | } | |
199 | ||
200 | // encodeGRPCConcatResponse is a transport/grpc.EncodeResponseFunc that converts | |
201 | // a user-domain concat response to a gRPC concat reply. Primarily useful in a | |
202 | // server. | |
203 | func encodeGRPCConcatResponse(_ context.Context, response interface{}) (interface{}, error) { | |
204 | resp := response.(addendpoint.ConcatResponse) | |
205 | return &pb.ConcatReply{V: resp.V, Err: err2str(resp.Err)}, nil | |
206 | } | |
207 | ||
208 | // encodeGRPCSumRequest is a transport/grpc.EncodeRequestFunc that converts a | |
209 | // user-domain sum request to a gRPC sum request. Primarily useful in a client. | |
210 | func encodeGRPCSumRequest(_ context.Context, request interface{}) (interface{}, error) { | |
211 | req := request.(addendpoint.SumRequest) | |
212 | return &pb.SumRequest{A: int64(req.A), B: int64(req.B)}, nil | |
213 | } | |
214 | ||
215 | // encodeGRPCConcatRequest is a transport/grpc.EncodeRequestFunc that converts a | |
216 | // user-domain concat request to a gRPC concat request. Primarily useful in a | |
217 | // client. | |
218 | func encodeGRPCConcatRequest(_ context.Context, request interface{}) (interface{}, error) { | |
219 | req := request.(addendpoint.ConcatRequest) | |
220 | return &pb.ConcatRequest{A: req.A, B: req.B}, nil | |
221 | } | |
222 | ||
223 | // These annoying helper functions are required to translate Go error types to | |
224 | // and from strings, which is the type we use in our IDLs to represent errors. | |
225 | // There is special casing to treat empty strings as nil errors. | |
226 | ||
227 | func str2err(s string) error { | |
228 | if s == "" { | |
229 | return nil | |
230 | } | |
231 | return errors.New(s) | |
232 | } | |
233 | ||
234 | func err2str(err error) string { | |
235 | if err == nil { | |
236 | return "" | |
237 | } | |
238 | return err.Error() | |
239 | } |
0 | package addtransport | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "context" | |
5 | "encoding/json" | |
6 | "errors" | |
7 | "io/ioutil" | |
8 | "net/http" | |
9 | "net/url" | |
10 | "strings" | |
11 | "time" | |
12 | ||
13 | "golang.org/x/time/rate" | |
14 | ||
15 | stdopentracing "github.com/opentracing/opentracing-go" | |
16 | stdzipkin "github.com/openzipkin/zipkin-go" | |
17 | "github.com/sony/gobreaker" | |
18 | ||
19 | "github.com/go-kit/kit/circuitbreaker" | |
20 | "github.com/go-kit/kit/endpoint" | |
21 | "github.com/go-kit/kit/log" | |
22 | "github.com/go-kit/kit/ratelimit" | |
23 | "github.com/go-kit/kit/tracing/opentracing" | |
24 | "github.com/go-kit/kit/tracing/zipkin" | |
25 | "github.com/go-kit/kit/transport" | |
26 | httptransport "github.com/go-kit/kit/transport/http" | |
27 | ||
28 | "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint" | |
29 | "github.com/go-kit/kit/examples/addsvc/pkg/addservice" | |
30 | ) | |
31 | ||
32 | // NewHTTPHandler returns an HTTP handler that makes a set of endpoints | |
33 | // available on predefined paths. | |
34 | func NewHTTPHandler(endpoints addendpoint.Set, otTracer stdopentracing.Tracer, zipkinTracer *stdzipkin.Tracer, logger log.Logger) http.Handler { | |
35 | options := []httptransport.ServerOption{ | |
36 | httptransport.ServerErrorEncoder(errorEncoder), | |
37 | httptransport.ServerErrorHandler(transport.NewLogErrorHandler(logger)), | |
38 | } | |
39 | ||
40 | if zipkinTracer != nil { | |
41 | // Zipkin HTTP Server Trace can either be instantiated per endpoint with a | |
42 | // provided operation name or a global tracing service can be instantiated | |
43 | // without an operation name and fed to each Go kit endpoint as ServerOption. | |
44 | // In the latter case, the operation name will be the endpoint's http method. | |
45 | // We demonstrate a global tracing service here. | |
46 | options = append(options, zipkin.HTTPServerTrace(zipkinTracer)) | |
47 | } | |
48 | ||
49 | m := http.NewServeMux() | |
50 | m.Handle("/sum", httptransport.NewServer( | |
51 | endpoints.SumEndpoint, | |
52 | decodeHTTPSumRequest, | |
53 | encodeHTTPGenericResponse, | |
54 | append(options, httptransport.ServerBefore(opentracing.HTTPToContext(otTracer, "Sum", logger)))..., | |
55 | )) | |
56 | m.Handle("/concat", httptransport.NewServer( | |
57 | endpoints.ConcatEndpoint, | |
58 | decodeHTTPConcatRequest, | |
59 | encodeHTTPGenericResponse, | |
60 | append(options, httptransport.ServerBefore(opentracing.HTTPToContext(otTracer, "Concat", logger)))..., | |
61 | )) | |
62 | return m | |
63 | } | |
64 | ||
65 | // NewHTTPClient returns an AddService backed by an HTTP server living at the | |
66 | // remote instance. We expect instance to come from a service discovery system, | |
67 | // so likely of the form "host:port". We bake-in certain middlewares, | |
68 | // implementing the client library pattern. | |
69 | func NewHTTPClient(instance string, otTracer stdopentracing.Tracer, zipkinTracer *stdzipkin.Tracer, logger log.Logger) (addservice.Service, error) { | |
70 | // Quickly sanitize the instance string. | |
71 | if !strings.HasPrefix(instance, "http") { | |
72 | instance = "http://" + instance | |
73 | } | |
74 | u, err := url.Parse(instance) | |
75 | if err != nil { | |
76 | return nil, err | |
77 | } | |
78 | ||
79 | // We construct a single ratelimiter middleware, to limit the total outgoing | |
80 | // QPS from this client to all methods on the remote instance. We also | |
81 | // construct per-endpoint circuitbreaker middlewares to demonstrate how | |
82 | // that's done, although they could easily be combined into a single breaker | |
83 | // for the entire remote instance, too. | |
84 | limiter := ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), 100)) | |
85 | ||
86 | // global client middlewares | |
87 | var options []httptransport.ClientOption | |
88 | ||
89 | if zipkinTracer != nil { | |
90 | // Zipkin HTTP Client Trace can either be instantiated per endpoint with a | |
91 | // provided operation name or a global tracing client can be instantiated | |
92 | // without an operation name and fed to each Go kit endpoint as ClientOption. | |
93 | // In the latter case, the operation name will be the endpoint's http method. | |
94 | options = append(options, zipkin.HTTPClientTrace(zipkinTracer)) | |
95 | } | |
96 | ||
97 | // Each individual endpoint is an http/transport.Client (which implements | |
98 | // endpoint.Endpoint) that gets wrapped with various middlewares. If you | |
99 | // made your own client library, you'd do this work there, so your server | |
100 | // could rely on a consistent set of client behavior. | |
101 | var sumEndpoint endpoint.Endpoint | |
102 | { | |
103 | sumEndpoint = httptransport.NewClient( | |
104 | "POST", | |
105 | copyURL(u, "/sum"), | |
106 | encodeHTTPGenericRequest, | |
107 | decodeHTTPSumResponse, | |
108 | append(options, httptransport.ClientBefore(opentracing.ContextToHTTP(otTracer, logger)))..., | |
109 | ).Endpoint() | |
110 | sumEndpoint = opentracing.TraceClient(otTracer, "Sum")(sumEndpoint) | |
111 | if zipkinTracer != nil { | |
112 | sumEndpoint = zipkin.TraceEndpoint(zipkinTracer, "Sum")(sumEndpoint) | |
113 | } | |
114 | sumEndpoint = limiter(sumEndpoint) | |
115 | sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ | |
116 | Name: "Sum", | |
117 | Timeout: 30 * time.Second, | |
118 | }))(sumEndpoint) | |
119 | } | |
120 | ||
121 | // The Concat endpoint is the same thing, with slightly different | |
122 | // middlewares to demonstrate how to specialize per-endpoint. | |
123 | var concatEndpoint endpoint.Endpoint | |
124 | { | |
125 | concatEndpoint = httptransport.NewClient( | |
126 | "POST", | |
127 | copyURL(u, "/concat"), | |
128 | encodeHTTPGenericRequest, | |
129 | decodeHTTPConcatResponse, | |
130 | append(options, httptransport.ClientBefore(opentracing.ContextToHTTP(otTracer, logger)))..., | |
131 | ).Endpoint() | |
132 | concatEndpoint = opentracing.TraceClient(otTracer, "Concat")(concatEndpoint) | |
133 | if zipkinTracer != nil { | |
134 | concatEndpoint = zipkin.TraceEndpoint(zipkinTracer, "Concat")(concatEndpoint) | |
135 | } | |
136 | concatEndpoint = limiter(concatEndpoint) | |
137 | concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ | |
138 | Name: "Concat", | |
139 | Timeout: 10 * time.Second, | |
140 | }))(concatEndpoint) | |
141 | } | |
142 | ||
143 | // Returning the endpoint.Set as a service.Service relies on the | |
144 | // endpoint.Set implementing the Service methods. That's just a simple bit | |
145 | // of glue code. | |
146 | return addendpoint.Set{ | |
147 | SumEndpoint: sumEndpoint, | |
148 | ConcatEndpoint: concatEndpoint, | |
149 | }, nil | |
150 | } | |
151 | ||
152 | func copyURL(base *url.URL, path string) *url.URL { | |
153 | next := *base | |
154 | next.Path = path | |
155 | return &next | |
156 | } | |
157 | ||
158 | func errorEncoder(_ context.Context, err error, w http.ResponseWriter) { | |
159 | w.WriteHeader(err2code(err)) | |
160 | json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()}) | |
161 | } | |
162 | ||
163 | func err2code(err error) int { | |
164 | switch err { | |
165 | case addservice.ErrTwoZeroes, addservice.ErrMaxSizeExceeded, addservice.ErrIntOverflow: | |
166 | return http.StatusBadRequest | |
167 | } | |
168 | return http.StatusInternalServerError | |
169 | } | |
170 | ||
171 | func errorDecoder(r *http.Response) error { | |
172 | var w errorWrapper | |
173 | if err := json.NewDecoder(r.Body).Decode(&w); err != nil { | |
174 | return err | |
175 | } | |
176 | return errors.New(w.Error) | |
177 | } | |
178 | ||
179 | type errorWrapper struct { | |
180 | Error string `json:"error"` | |
181 | } | |
182 | ||
183 | // decodeHTTPSumRequest is a transport/http.DecodeRequestFunc that decodes a | |
184 | // JSON-encoded sum request from the HTTP request body. Primarily useful in a | |
185 | // server. | |
186 | func decodeHTTPSumRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
187 | var req addendpoint.SumRequest | |
188 | err := json.NewDecoder(r.Body).Decode(&req) | |
189 | return req, err | |
190 | } | |
191 | ||
192 | // decodeHTTPConcatRequest is a transport/http.DecodeRequestFunc that decodes a | |
193 | // JSON-encoded concat request from the HTTP request body. Primarily useful in a | |
194 | // server. | |
195 | func decodeHTTPConcatRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
196 | var req addendpoint.ConcatRequest | |
197 | err := json.NewDecoder(r.Body).Decode(&req) | |
198 | return req, err | |
199 | } | |
200 | ||
201 | // decodeHTTPSumResponse is a transport/http.DecodeResponseFunc that decodes a | |
202 | // JSON-encoded sum response from the HTTP response body. If the response has a | |
203 | // non-200 status code, we will interpret that as an error and attempt to decode | |
204 | // the specific error message from the response body. Primarily useful in a | |
205 | // client. | |
206 | func decodeHTTPSumResponse(_ context.Context, r *http.Response) (interface{}, error) { | |
207 | if r.StatusCode != http.StatusOK { | |
208 | return nil, errors.New(r.Status) | |
209 | } | |
210 | var resp addendpoint.SumResponse | |
211 | err := json.NewDecoder(r.Body).Decode(&resp) | |
212 | return resp, err | |
213 | } | |
214 | ||
215 | // decodeHTTPConcatResponse is a transport/http.DecodeResponseFunc that decodes | |
216 | // a JSON-encoded concat response from the HTTP response body. If the response | |
217 | // has a non-200 status code, we will interpret that as an error and attempt to | |
218 | // decode the specific error message from the response body. Primarily useful in | |
219 | // a client. | |
220 | func decodeHTTPConcatResponse(_ context.Context, r *http.Response) (interface{}, error) { | |
221 | if r.StatusCode != http.StatusOK { | |
222 | return nil, errors.New(r.Status) | |
223 | } | |
224 | var resp addendpoint.ConcatResponse | |
225 | err := json.NewDecoder(r.Body).Decode(&resp) | |
226 | return resp, err | |
227 | } | |
228 | ||
229 | // encodeHTTPGenericRequest is a transport/http.EncodeRequestFunc that | |
230 | // JSON-encodes any request to the request body. Primarily useful in a client. | |
231 | func encodeHTTPGenericRequest(_ context.Context, r *http.Request, request interface{}) error { | |
232 | var buf bytes.Buffer | |
233 | if err := json.NewEncoder(&buf).Encode(request); err != nil { | |
234 | return err | |
235 | } | |
236 | r.Body = ioutil.NopCloser(&buf) | |
237 | return nil | |
238 | } | |
239 | ||
240 | // encodeHTTPGenericResponse is a transport/http.EncodeResponseFunc that encodes | |
241 | // the response as JSON to the response writer. Primarily useful in a server. | |
242 | func encodeHTTPGenericResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { | |
243 | if f, ok := response.(endpoint.Failer); ok && f.Failed() != nil { | |
244 | errorEncoder(ctx, f.Failed(), w) | |
245 | return nil | |
246 | } | |
247 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
248 | return json.NewEncoder(w).Encode(response) | |
249 | } |
0 | package addtransport | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "net/url" | |
7 | "strings" | |
8 | "time" | |
9 | ||
10 | "golang.org/x/time/rate" | |
11 | ||
12 | "github.com/go-kit/kit/circuitbreaker" | |
13 | "github.com/go-kit/kit/endpoint" | |
14 | "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint" | |
15 | "github.com/go-kit/kit/examples/addsvc/pkg/addservice" | |
16 | "github.com/go-kit/kit/log" | |
17 | "github.com/go-kit/kit/ratelimit" | |
18 | "github.com/go-kit/kit/tracing/opentracing" | |
19 | "github.com/go-kit/kit/transport/http/jsonrpc" | |
20 | stdopentracing "github.com/opentracing/opentracing-go" | |
21 | "github.com/sony/gobreaker" | |
22 | ) | |
23 | ||
24 | // NewJSONRPCHandler returns a JSON RPC Server/Handler that can be passed to http.Handle() | |
25 | func NewJSONRPCHandler(endpoints addendpoint.Set, logger log.Logger) *jsonrpc.Server { | |
26 | handler := jsonrpc.NewServer( | |
27 | makeEndpointCodecMap(endpoints), | |
28 | jsonrpc.ServerErrorLogger(logger), | |
29 | ) | |
30 | return handler | |
31 | } | |
32 | ||
33 | // NewJSONRPCClient returns an addservice backed by a JSON RPC over HTTP server | |
34 | // living at the remote instance. We expect instance to come from a service | |
35 | // discovery system, so likely of the form "host:port". We bake-in certain | |
36 | // middlewares, implementing the client library pattern. | |
37 | func NewJSONRPCClient(instance string, tracer stdopentracing.Tracer, logger log.Logger) (addservice.Service, error) { | |
38 | // Quickly sanitize the instance string. | |
39 | if !strings.HasPrefix(instance, "http") { | |
40 | instance = "http://" + instance | |
41 | } | |
42 | u, err := url.Parse(instance) | |
43 | if err != nil { | |
44 | return nil, err | |
45 | } | |
46 | ||
47 | // We construct a single ratelimiter middleware, to limit the total outgoing | |
48 | // QPS from this client to all methods on the remote instance. We also | |
49 | // construct per-endpoint circuitbreaker middlewares to demonstrate how | |
50 | // that's done, although they could easily be combined into a single breaker | |
51 | // for the entire remote instance, too. | |
52 | limiter := ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), 100)) | |
53 | ||
54 | var sumEndpoint endpoint.Endpoint | |
55 | { | |
56 | sumEndpoint = jsonrpc.NewClient( | |
57 | u, | |
58 | "sum", | |
59 | jsonrpc.ClientRequestEncoder(encodeSumRequest), | |
60 | jsonrpc.ClientResponseDecoder(decodeSumResponse), | |
61 | ).Endpoint() | |
62 | sumEndpoint = opentracing.TraceClient(tracer, "Sum")(sumEndpoint) | |
63 | sumEndpoint = limiter(sumEndpoint) | |
64 | sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ | |
65 | Name: "Sum", | |
66 | Timeout: 30 * time.Second, | |
67 | }))(sumEndpoint) | |
68 | } | |
69 | ||
70 | var concatEndpoint endpoint.Endpoint | |
71 | { | |
72 | concatEndpoint = jsonrpc.NewClient( | |
73 | u, | |
74 | "concat", | |
75 | jsonrpc.ClientRequestEncoder(encodeConcatRequest), | |
76 | jsonrpc.ClientResponseDecoder(decodeConcatResponse), | |
77 | ).Endpoint() | |
78 | concatEndpoint = opentracing.TraceClient(tracer, "Concat")(concatEndpoint) | |
79 | concatEndpoint = limiter(concatEndpoint) | |
80 | concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ | |
81 | Name: "Concat", | |
82 | Timeout: 30 * time.Second, | |
83 | }))(concatEndpoint) | |
84 | } | |
85 | ||
86 | // Returning the endpoint.Set as a service.Service relies on the | |
87 | // endpoint.Set implementing the Service methods. That's just a simple bit | |
88 | // of glue code. | |
89 | return addendpoint.Set{ | |
90 | SumEndpoint: sumEndpoint, | |
91 | ConcatEndpoint: concatEndpoint, | |
92 | }, nil | |
93 | ||
94 | } | |
95 | ||
96 | // makeEndpointCodecMap returns a codec map configured for the addsvc. | |
97 | func makeEndpointCodecMap(endpoints addendpoint.Set) jsonrpc.EndpointCodecMap { | |
98 | return jsonrpc.EndpointCodecMap{ | |
99 | "sum": jsonrpc.EndpointCodec{ | |
100 | Endpoint: endpoints.SumEndpoint, | |
101 | Decode: decodeSumRequest, | |
102 | Encode: encodeSumResponse, | |
103 | }, | |
104 | "concat": jsonrpc.EndpointCodec{ | |
105 | Endpoint: endpoints.ConcatEndpoint, | |
106 | Decode: decodeConcatRequest, | |
107 | Encode: encodeConcatResponse, | |
108 | }, | |
109 | } | |
110 | } | |
111 | ||
112 | func decodeSumRequest(_ context.Context, msg json.RawMessage) (interface{}, error) { | |
113 | var req addendpoint.SumRequest | |
114 | err := json.Unmarshal(msg, &req) | |
115 | if err != nil { | |
116 | return nil, &jsonrpc.Error{ | |
117 | Code: -32000, | |
118 | Message: fmt.Sprintf("couldn't unmarshal body to sum request: %s", err), | |
119 | } | |
120 | } | |
121 | return req, nil | |
122 | } | |
123 | ||
124 | func encodeSumResponse(_ context.Context, obj interface{}) (json.RawMessage, error) { | |
125 | res, ok := obj.(addendpoint.SumResponse) | |
126 | if !ok { | |
127 | return nil, &jsonrpc.Error{ | |
128 | Code: -32000, | |
129 | Message: fmt.Sprintf("Asserting result to *SumResponse failed. Got %T, %+v", obj, obj), | |
130 | } | |
131 | } | |
132 | b, err := json.Marshal(res) | |
133 | if err != nil { | |
134 | return nil, fmt.Errorf("couldn't marshal response: %s", err) | |
135 | } | |
136 | return b, nil | |
137 | } | |
138 | ||
139 | func decodeSumResponse(_ context.Context, res jsonrpc.Response) (interface{}, error) { | |
140 | if res.Error != nil { | |
141 | return nil, *res.Error | |
142 | } | |
143 | var sumres addendpoint.SumResponse | |
144 | err := json.Unmarshal(res.Result, &sumres) | |
145 | if err != nil { | |
146 | return nil, fmt.Errorf("couldn't unmarshal body to SumResponse: %s", err) | |
147 | } | |
148 | return sumres, nil | |
149 | } | |
150 | ||
151 | func encodeSumRequest(_ context.Context, obj interface{}) (json.RawMessage, error) { | |
152 | req, ok := obj.(addendpoint.SumRequest) | |
153 | if !ok { | |
154 | return nil, fmt.Errorf("couldn't assert request as SumRequest, got %T", obj) | |
155 | } | |
156 | b, err := json.Marshal(req) | |
157 | if err != nil { | |
158 | return nil, fmt.Errorf("couldn't marshal request: %s", err) | |
159 | } | |
160 | return b, nil | |
161 | } | |
162 | ||
163 | func decodeConcatRequest(_ context.Context, msg json.RawMessage) (interface{}, error) { | |
164 | var req addendpoint.ConcatRequest | |
165 | err := json.Unmarshal(msg, &req) | |
166 | if err != nil { | |
167 | return nil, &jsonrpc.Error{ | |
168 | Code: -32000, | |
169 | Message: fmt.Sprintf("couldn't unmarshal body to concat request: %s", err), | |
170 | } | |
171 | } | |
172 | return req, nil | |
173 | } | |
174 | ||
175 | func encodeConcatResponse(_ context.Context, obj interface{}) (json.RawMessage, error) { | |
176 | res, ok := obj.(addendpoint.ConcatResponse) | |
177 | if !ok { | |
178 | return nil, &jsonrpc.Error{ | |
179 | Code: -32000, | |
180 | Message: fmt.Sprintf("Asserting result to *ConcatResponse failed. Got %T, %+v", obj, obj), | |
181 | } | |
182 | } | |
183 | b, err := json.Marshal(res) | |
184 | if err != nil { | |
185 | return nil, fmt.Errorf("couldn't marshal response: %s", err) | |
186 | } | |
187 | return b, nil | |
188 | } | |
189 | ||
190 | func decodeConcatResponse(_ context.Context, res jsonrpc.Response) (interface{}, error) { | |
191 | if res.Error != nil { | |
192 | return nil, *res.Error | |
193 | } | |
194 | var concatres addendpoint.ConcatResponse | |
195 | err := json.Unmarshal(res.Result, &concatres) | |
196 | if err != nil { | |
197 | return nil, fmt.Errorf("couldn't unmarshal body to ConcatResponse: %s", err) | |
198 | } | |
199 | return concatres, nil | |
200 | } | |
201 | ||
202 | func encodeConcatRequest(_ context.Context, obj interface{}) (json.RawMessage, error) { | |
203 | req, ok := obj.(addendpoint.ConcatRequest) | |
204 | if !ok { | |
205 | return nil, fmt.Errorf("couldn't assert request as ConcatRequest, got %T", obj) | |
206 | } | |
207 | b, err := json.Marshal(req) | |
208 | if err != nil { | |
209 | return nil, fmt.Errorf("couldn't marshal request: %s", err) | |
210 | } | |
211 | return b, nil | |
212 | } |
0 | package addtransport | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "time" | |
5 | ||
6 | "golang.org/x/time/rate" | |
7 | ||
8 | "github.com/sony/gobreaker" | |
9 | ||
10 | "github.com/go-kit/kit/circuitbreaker" | |
11 | "github.com/go-kit/kit/endpoint" | |
12 | "github.com/go-kit/kit/ratelimit" | |
13 | ||
14 | "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint" | |
15 | "github.com/go-kit/kit/examples/addsvc/pkg/addservice" | |
16 | addthrift "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc" | |
17 | ) | |
18 | ||
19 | type thriftServer struct { | |
20 | ctx context.Context | |
21 | endpoints addendpoint.Set | |
22 | } | |
23 | ||
24 | // NewThriftServer makes a set of endpoints available as a Thrift service. | |
25 | func NewThriftServer(endpoints addendpoint.Set) addthrift.AddService { | |
26 | return &thriftServer{ | |
27 | endpoints: endpoints, | |
28 | } | |
29 | } | |
30 | ||
31 | func (s *thriftServer) Sum(ctx context.Context, a int64, b int64) (*addthrift.SumReply, error) { | |
32 | request := addendpoint.SumRequest{A: int(a), B: int(b)} | |
33 | response, err := s.endpoints.SumEndpoint(ctx, request) | |
34 | if err != nil { | |
35 | return nil, err | |
36 | } | |
37 | resp := response.(addendpoint.SumResponse) | |
38 | return &addthrift.SumReply{Value: int64(resp.V), Err: err2str(resp.Err)}, nil | |
39 | } | |
40 | ||
41 | func (s *thriftServer) Concat(ctx context.Context, a string, b string) (*addthrift.ConcatReply, error) { | |
42 | request := addendpoint.ConcatRequest{A: a, B: b} | |
43 | response, err := s.endpoints.ConcatEndpoint(ctx, request) | |
44 | if err != nil { | |
45 | return nil, err | |
46 | } | |
47 | resp := response.(addendpoint.ConcatResponse) | |
48 | return &addthrift.ConcatReply{Value: resp.V, Err: err2str(resp.Err)}, nil | |
49 | } | |
50 | ||
51 | // NewThriftClient returns an AddService backed by a Thrift server described by | |
52 | // the provided client. The caller is responsible for constructing the client, | |
53 | // and eventually closing the underlying transport. We bake-in certain middlewares, | |
54 | // implementing the client library pattern. | |
55 | func NewThriftClient(client *addthrift.AddServiceClient) addservice.Service { | |
56 | // We construct a single ratelimiter middleware, to limit the total outgoing | |
57 | // QPS from this client to all methods on the remote instance. We also | |
58 | // construct per-endpoint circuitbreaker middlewares to demonstrate how | |
59 | // that's done, although they could easily be combined into a single breaker | |
60 | // for the entire remote instance, too. | |
61 | limiter := ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), 100)) | |
62 | ||
63 | // Each individual endpoint is an http/transport.Client (which implements | |
64 | // endpoint.Endpoint) that gets wrapped with various middlewares. If you | |
65 | // could rely on a consistent set of client behavior. | |
66 | var sumEndpoint endpoint.Endpoint | |
67 | { | |
68 | sumEndpoint = MakeThriftSumEndpoint(client) | |
69 | sumEndpoint = limiter(sumEndpoint) | |
70 | sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ | |
71 | Name: "Sum", | |
72 | Timeout: 30 * time.Second, | |
73 | }))(sumEndpoint) | |
74 | } | |
75 | ||
76 | // The Concat endpoint is the same thing, with slightly different | |
77 | // middlewares to demonstrate how to specialize per-endpoint. | |
78 | var concatEndpoint endpoint.Endpoint | |
79 | { | |
80 | concatEndpoint = MakeThriftConcatEndpoint(client) | |
81 | concatEndpoint = limiter(concatEndpoint) | |
82 | concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ | |
83 | Name: "Concat", | |
84 | Timeout: 10 * time.Second, | |
85 | }))(concatEndpoint) | |
86 | } | |
87 | ||
88 | // Returning the endpoint.Set as a service.Service relies on the | |
89 | // endpoint.Set implementing the Service methods. That's just a simple bit | |
90 | // of glue code. | |
91 | return addendpoint.Set{ | |
92 | SumEndpoint: sumEndpoint, | |
93 | ConcatEndpoint: concatEndpoint, | |
94 | } | |
95 | } | |
96 | ||
97 | // MakeThriftSumEndpoint returns an endpoint that invokes the passed Thrift client. | |
98 | // Useful only in clients, and only until a proper transport/thrift.Client exists. | |
99 | func MakeThriftSumEndpoint(client *addthrift.AddServiceClient) endpoint.Endpoint { | |
100 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
101 | req := request.(addendpoint.SumRequest) | |
102 | reply, err := client.Sum(ctx, int64(req.A), int64(req.B)) | |
103 | if err == addservice.ErrIntOverflow { | |
104 | return nil, err // special case; see comment on ErrIntOverflow | |
105 | } | |
106 | return addendpoint.SumResponse{V: int(reply.Value), Err: err}, nil | |
107 | } | |
108 | } | |
109 | ||
110 | // MakeThriftConcatEndpoint returns an endpoint that invokes the passed Thrift | |
111 | // client. Useful only in clients, and only until a proper | |
112 | // transport/thrift.Client exists. | |
113 | func MakeThriftConcatEndpoint(client *addthrift.AddServiceClient) endpoint.Endpoint { | |
114 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
115 | req := request.(addendpoint.ConcatRequest) | |
116 | reply, err := client.Concat(ctx, req.A, req.B) | |
117 | return addendpoint.ConcatResponse{V: reply.Value, Err: err}, nil | |
118 | } | |
119 | } |
0 | struct SumReply { | |
1 | 1: i64 value | |
2 | 2: string err | |
3 | } | |
4 | ||
5 | struct ConcatReply { | |
6 | 1: string value | |
7 | 2: string err | |
8 | } | |
9 | ||
10 | service AddService { | |
11 | SumReply Sum(1: i64 a, 2: i64 b) | |
12 | ConcatReply Concat(1: string a, 2: string b) | |
13 | } |
0 | #!/usr/bin/env sh | |
1 | ||
2 | # See also https://thrift.apache.org/tutorial/go. | |
3 | # | |
4 | # An old version can be obtained via `brew install thrift`. | |
5 | # For the latest, here's the annoying dance: | |
6 | # | |
7 | # brew install automake bison pkg-config openssl | |
8 | # ln -s /usr/local/opt/openssl/include/openssl /usr/local/include # if it isn't already | |
9 | # git clone git@github.com:apache/thrift | |
10 | # ./bootstrap.sh | |
11 | # bash | |
12 | # export PATH=/usr/local/Cellar/bison/*/bin:$PATH | |
13 | # ./configure ./configure --without-qt4 --without-qt5 --without-c_glib --without-csharp --without-java --without-erlang --without-nodejs --without-lua --without-python --without-perl --without-php --without-php_extension --without-dart --without-ruby --without-haskell --without-rs --without-cl --without-haxe --without-dotnetcore --without-d | |
14 | # make | |
15 | # sudo make install | |
16 | ||
17 | thrift -r --gen "go:package_prefix=github.com/go-kit/kit/examples/addsvc/thrift/gen-go/,thrift_import=github.com/apache/thrift/lib/go/thrift" addsvc.thrift |
0 | // Code generated by Thrift Compiler (0.14.1). DO NOT EDIT. | |
1 | ||
2 | package addsvc | |
3 | ||
4 | var GoUnusedProtection__ int |
0 | // Code generated by Thrift Compiler (0.14.1). DO NOT EDIT. | |
1 | ||
2 | package main | |
3 | ||
4 | import ( | |
5 | "context" | |
6 | "flag" | |
7 | "fmt" | |
8 | "github.com/apache/thrift/lib/go/thrift" | |
9 | "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc" | |
10 | "math" | |
11 | "net" | |
12 | "net/url" | |
13 | "os" | |
14 | "strconv" | |
15 | "strings" | |
16 | ) | |
17 | ||
18 | var _ = addsvc.GoUnusedProtection__ | |
19 | ||
20 | func Usage() { | |
21 | fmt.Fprintln(os.Stderr, "Usage of ", os.Args[0], " [-h host:port] [-u url] [-f[ramed]] function [arg1 [arg2...]]:") | |
22 | flag.PrintDefaults() | |
23 | fmt.Fprintln(os.Stderr, "\nFunctions:") | |
24 | fmt.Fprintln(os.Stderr, " SumReply Sum(i64 a, i64 b)") | |
25 | fmt.Fprintln(os.Stderr, " ConcatReply Concat(string a, string b)") | |
26 | fmt.Fprintln(os.Stderr) | |
27 | os.Exit(0) | |
28 | } | |
29 | ||
30 | type httpHeaders map[string]string | |
31 | ||
32 | func (h httpHeaders) String() string { | |
33 | var m map[string]string = h | |
34 | return fmt.Sprintf("%s", m) | |
35 | } | |
36 | ||
37 | func (h httpHeaders) Set(value string) error { | |
38 | parts := strings.Split(value, ": ") | |
39 | if len(parts) != 2 { | |
40 | return fmt.Errorf("header should be of format 'Key: Value'") | |
41 | } | |
42 | h[parts[0]] = parts[1] | |
43 | return nil | |
44 | } | |
45 | ||
46 | func main() { | |
47 | flag.Usage = Usage | |
48 | var host string | |
49 | var port int | |
50 | var protocol string | |
51 | var urlString string | |
52 | var framed bool | |
53 | var useHttp bool | |
54 | headers := make(httpHeaders) | |
55 | var parsedUrl *url.URL | |
56 | var trans thrift.TTransport | |
57 | _ = strconv.Atoi | |
58 | _ = math.Abs | |
59 | flag.Usage = Usage | |
60 | flag.StringVar(&host, "h", "localhost", "Specify host and port") | |
61 | flag.IntVar(&port, "p", 9090, "Specify port") | |
62 | flag.StringVar(&protocol, "P", "binary", "Specify the protocol (binary, compact, simplejson, json)") | |
63 | flag.StringVar(&urlString, "u", "", "Specify the url") | |
64 | flag.BoolVar(&framed, "framed", false, "Use framed transport") | |
65 | flag.BoolVar(&useHttp, "http", false, "Use http") | |
66 | flag.Var(headers, "H", "Headers to set on the http(s) request (e.g. -H \"Key: Value\")") | |
67 | flag.Parse() | |
68 | ||
69 | if len(urlString) > 0 { | |
70 | var err error | |
71 | parsedUrl, err = url.Parse(urlString) | |
72 | if err != nil { | |
73 | fmt.Fprintln(os.Stderr, "Error parsing URL: ", err) | |
74 | flag.Usage() | |
75 | } | |
76 | host = parsedUrl.Host | |
77 | useHttp = len(parsedUrl.Scheme) <= 0 || parsedUrl.Scheme == "http" || parsedUrl.Scheme == "https" | |
78 | } else if useHttp { | |
79 | _, err := url.Parse(fmt.Sprint("http://", host, ":", port)) | |
80 | if err != nil { | |
81 | fmt.Fprintln(os.Stderr, "Error parsing URL: ", err) | |
82 | flag.Usage() | |
83 | } | |
84 | } | |
85 | ||
86 | cmd := flag.Arg(0) | |
87 | var err error | |
88 | if useHttp { | |
89 | trans, err = thrift.NewTHttpClient(parsedUrl.String()) | |
90 | if len(headers) > 0 { | |
91 | httptrans := trans.(*thrift.THttpClient) | |
92 | for key, value := range headers { | |
93 | httptrans.SetHeader(key, value) | |
94 | } | |
95 | } | |
96 | } else { | |
97 | portStr := fmt.Sprint(port) | |
98 | if strings.Contains(host, ":") { | |
99 | host, portStr, err = net.SplitHostPort(host) | |
100 | if err != nil { | |
101 | fmt.Fprintln(os.Stderr, "error with host:", err) | |
102 | os.Exit(1) | |
103 | } | |
104 | } | |
105 | trans, err = thrift.NewTSocket(net.JoinHostPort(host, portStr)) | |
106 | if err != nil { | |
107 | fmt.Fprintln(os.Stderr, "error resolving address:", err) | |
108 | os.Exit(1) | |
109 | } | |
110 | if framed { | |
111 | trans = thrift.NewTFramedTransport(trans) | |
112 | } | |
113 | } | |
114 | if err != nil { | |
115 | fmt.Fprintln(os.Stderr, "Error creating transport", err) | |
116 | os.Exit(1) | |
117 | } | |
118 | defer trans.Close() | |
119 | var protocolFactory thrift.TProtocolFactory | |
120 | switch protocol { | |
121 | case "compact": | |
122 | protocolFactory = thrift.NewTCompactProtocolFactory() | |
123 | break | |
124 | case "simplejson": | |
125 | protocolFactory = thrift.NewTSimpleJSONProtocolFactory() | |
126 | break | |
127 | case "json": | |
128 | protocolFactory = thrift.NewTJSONProtocolFactory() | |
129 | break | |
130 | case "binary", "": | |
131 | protocolFactory = thrift.NewTBinaryProtocolFactoryDefault() | |
132 | break | |
133 | default: | |
134 | fmt.Fprintln(os.Stderr, "Invalid protocol specified: ", protocol) | |
135 | Usage() | |
136 | os.Exit(1) | |
137 | } | |
138 | iprot := protocolFactory.GetProtocol(trans) | |
139 | oprot := protocolFactory.GetProtocol(trans) | |
140 | client := addsvc.NewAddServiceClient(thrift.NewTStandardClient(iprot, oprot)) | |
141 | if err := trans.Open(); err != nil { | |
142 | fmt.Fprintln(os.Stderr, "Error opening socket to ", host, ":", port, " ", err) | |
143 | os.Exit(1) | |
144 | } | |
145 | ||
146 | switch cmd { | |
147 | case "Sum": | |
148 | if flag.NArg()-1 != 2 { | |
149 | fmt.Fprintln(os.Stderr, "Sum requires 2 args") | |
150 | flag.Usage() | |
151 | } | |
152 | argvalue0, err8 := (strconv.ParseInt(flag.Arg(1), 10, 64)) | |
153 | if err8 != nil { | |
154 | Usage() | |
155 | return | |
156 | } | |
157 | value0 := argvalue0 | |
158 | argvalue1, err9 := (strconv.ParseInt(flag.Arg(2), 10, 64)) | |
159 | if err9 != nil { | |
160 | Usage() | |
161 | return | |
162 | } | |
163 | value1 := argvalue1 | |
164 | fmt.Print(client.Sum(context.Background(), value0, value1)) | |
165 | fmt.Print("\n") | |
166 | break | |
167 | case "Concat": | |
168 | if flag.NArg()-1 != 2 { | |
169 | fmt.Fprintln(os.Stderr, "Concat requires 2 args") | |
170 | flag.Usage() | |
171 | } | |
172 | argvalue0 := flag.Arg(1) | |
173 | value0 := argvalue0 | |
174 | argvalue1 := flag.Arg(2) | |
175 | value1 := argvalue1 | |
176 | fmt.Print(client.Concat(context.Background(), value0, value1)) | |
177 | fmt.Print("\n") | |
178 | break | |
179 | case "": | |
180 | Usage() | |
181 | break | |
182 | default: | |
183 | fmt.Fprintln(os.Stderr, "Invalid function ", cmd) | |
184 | } | |
185 | } |
0 | // Code generated by Thrift Compiler (0.14.1). DO NOT EDIT. | |
1 | ||
2 | package addsvc | |
3 | ||
4 | import ( | |
5 | "bytes" | |
6 | "context" | |
7 | "fmt" | |
8 | "github.com/apache/thrift/lib/go/thrift" | |
9 | "time" | |
10 | ) | |
11 | ||
12 | // (needed to ensure safety because of naive import list construction.) | |
13 | var _ = thrift.ZERO | |
14 | var _ = fmt.Printf | |
15 | var _ = context.Background | |
16 | var _ = time.Now | |
17 | var _ = bytes.Equal | |
18 | ||
19 | func init() { | |
20 | } |
0 | // Code generated by Thrift Compiler (0.14.1). DO NOT EDIT. | |
1 | ||
2 | package addsvc | |
3 | ||
4 | import ( | |
5 | "bytes" | |
6 | "context" | |
7 | "fmt" | |
8 | "github.com/apache/thrift/lib/go/thrift" | |
9 | "time" | |
10 | ) | |
11 | ||
12 | // (needed to ensure safety because of naive import list construction.) | |
13 | var _ = thrift.ZERO | |
14 | var _ = fmt.Printf | |
15 | var _ = context.Background | |
16 | var _ = time.Now | |
17 | var _ = bytes.Equal | |
18 | ||
19 | // Attributes: | |
20 | // - Value | |
21 | // - Err | |
22 | type SumReply struct { | |
23 | Value int64 `thrift:"value,1" db:"value" json:"value"` | |
24 | Err string `thrift:"err,2" db:"err" json:"err"` | |
25 | } | |
26 | ||
27 | func NewSumReply() *SumReply { | |
28 | return &SumReply{} | |
29 | } | |
30 | ||
31 | func (p *SumReply) GetValue() int64 { | |
32 | return p.Value | |
33 | } | |
34 | ||
35 | func (p *SumReply) GetErr() string { | |
36 | return p.Err | |
37 | } | |
38 | func (p *SumReply) Read(ctx context.Context, iprot thrift.TProtocol) error { | |
39 | if _, err := iprot.ReadStructBegin(ctx); err != nil { | |
40 | return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) | |
41 | } | |
42 | ||
43 | for { | |
44 | _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) | |
45 | if err != nil { | |
46 | return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) | |
47 | } | |
48 | if fieldTypeId == thrift.STOP { | |
49 | break | |
50 | } | |
51 | switch fieldId { | |
52 | case 1: | |
53 | if fieldTypeId == thrift.I64 { | |
54 | if err := p.ReadField1(ctx, iprot); err != nil { | |
55 | return err | |
56 | } | |
57 | } else { | |
58 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
59 | return err | |
60 | } | |
61 | } | |
62 | case 2: | |
63 | if fieldTypeId == thrift.STRING { | |
64 | if err := p.ReadField2(ctx, iprot); err != nil { | |
65 | return err | |
66 | } | |
67 | } else { | |
68 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
69 | return err | |
70 | } | |
71 | } | |
72 | default: | |
73 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
74 | return err | |
75 | } | |
76 | } | |
77 | if err := iprot.ReadFieldEnd(ctx); err != nil { | |
78 | return err | |
79 | } | |
80 | } | |
81 | if err := iprot.ReadStructEnd(ctx); err != nil { | |
82 | return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) | |
83 | } | |
84 | return nil | |
85 | } | |
86 | ||
87 | func (p *SumReply) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { | |
88 | if v, err := iprot.ReadI64(ctx); err != nil { | |
89 | return thrift.PrependError("error reading field 1: ", err) | |
90 | } else { | |
91 | p.Value = v | |
92 | } | |
93 | return nil | |
94 | } | |
95 | ||
96 | func (p *SumReply) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { | |
97 | if v, err := iprot.ReadString(ctx); err != nil { | |
98 | return thrift.PrependError("error reading field 2: ", err) | |
99 | } else { | |
100 | p.Err = v | |
101 | } | |
102 | return nil | |
103 | } | |
104 | ||
105 | func (p *SumReply) Write(ctx context.Context, oprot thrift.TProtocol) error { | |
106 | if err := oprot.WriteStructBegin(ctx, "SumReply"); err != nil { | |
107 | return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) | |
108 | } | |
109 | if p != nil { | |
110 | if err := p.writeField1(ctx, oprot); err != nil { | |
111 | return err | |
112 | } | |
113 | if err := p.writeField2(ctx, oprot); err != nil { | |
114 | return err | |
115 | } | |
116 | } | |
117 | if err := oprot.WriteFieldStop(ctx); err != nil { | |
118 | return thrift.PrependError("write field stop error: ", err) | |
119 | } | |
120 | if err := oprot.WriteStructEnd(ctx); err != nil { | |
121 | return thrift.PrependError("write struct stop error: ", err) | |
122 | } | |
123 | return nil | |
124 | } | |
125 | ||
126 | func (p *SumReply) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
127 | if err := oprot.WriteFieldBegin(ctx, "value", thrift.I64, 1); err != nil { | |
128 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:value: ", p), err) | |
129 | } | |
130 | if err := oprot.WriteI64(ctx, int64(p.Value)); err != nil { | |
131 | return thrift.PrependError(fmt.Sprintf("%T.value (1) field write error: ", p), err) | |
132 | } | |
133 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
134 | return thrift.PrependError(fmt.Sprintf("%T write field end error 1:value: ", p), err) | |
135 | } | |
136 | return err | |
137 | } | |
138 | ||
139 | func (p *SumReply) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
140 | if err := oprot.WriteFieldBegin(ctx, "err", thrift.STRING, 2); err != nil { | |
141 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:err: ", p), err) | |
142 | } | |
143 | if err := oprot.WriteString(ctx, string(p.Err)); err != nil { | |
144 | return thrift.PrependError(fmt.Sprintf("%T.err (2) field write error: ", p), err) | |
145 | } | |
146 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
147 | return thrift.PrependError(fmt.Sprintf("%T write field end error 2:err: ", p), err) | |
148 | } | |
149 | return err | |
150 | } | |
151 | ||
152 | func (p *SumReply) Equals(other *SumReply) bool { | |
153 | if p == other { | |
154 | return true | |
155 | } else if p == nil || other == nil { | |
156 | return false | |
157 | } | |
158 | if p.Value != other.Value { | |
159 | return false | |
160 | } | |
161 | if p.Err != other.Err { | |
162 | return false | |
163 | } | |
164 | return true | |
165 | } | |
166 | ||
167 | func (p *SumReply) String() string { | |
168 | if p == nil { | |
169 | return "<nil>" | |
170 | } | |
171 | return fmt.Sprintf("SumReply(%+v)", *p) | |
172 | } | |
173 | ||
174 | // Attributes: | |
175 | // - Value | |
176 | // - Err | |
177 | type ConcatReply struct { | |
178 | Value string `thrift:"value,1" db:"value" json:"value"` | |
179 | Err string `thrift:"err,2" db:"err" json:"err"` | |
180 | } | |
181 | ||
182 | func NewConcatReply() *ConcatReply { | |
183 | return &ConcatReply{} | |
184 | } | |
185 | ||
186 | func (p *ConcatReply) GetValue() string { | |
187 | return p.Value | |
188 | } | |
189 | ||
190 | func (p *ConcatReply) GetErr() string { | |
191 | return p.Err | |
192 | } | |
193 | func (p *ConcatReply) Read(ctx context.Context, iprot thrift.TProtocol) error { | |
194 | if _, err := iprot.ReadStructBegin(ctx); err != nil { | |
195 | return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) | |
196 | } | |
197 | ||
198 | for { | |
199 | _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) | |
200 | if err != nil { | |
201 | return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) | |
202 | } | |
203 | if fieldTypeId == thrift.STOP { | |
204 | break | |
205 | } | |
206 | switch fieldId { | |
207 | case 1: | |
208 | if fieldTypeId == thrift.STRING { | |
209 | if err := p.ReadField1(ctx, iprot); err != nil { | |
210 | return err | |
211 | } | |
212 | } else { | |
213 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
214 | return err | |
215 | } | |
216 | } | |
217 | case 2: | |
218 | if fieldTypeId == thrift.STRING { | |
219 | if err := p.ReadField2(ctx, iprot); err != nil { | |
220 | return err | |
221 | } | |
222 | } else { | |
223 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
224 | return err | |
225 | } | |
226 | } | |
227 | default: | |
228 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
229 | return err | |
230 | } | |
231 | } | |
232 | if err := iprot.ReadFieldEnd(ctx); err != nil { | |
233 | return err | |
234 | } | |
235 | } | |
236 | if err := iprot.ReadStructEnd(ctx); err != nil { | |
237 | return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) | |
238 | } | |
239 | return nil | |
240 | } | |
241 | ||
242 | func (p *ConcatReply) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { | |
243 | if v, err := iprot.ReadString(ctx); err != nil { | |
244 | return thrift.PrependError("error reading field 1: ", err) | |
245 | } else { | |
246 | p.Value = v | |
247 | } | |
248 | return nil | |
249 | } | |
250 | ||
251 | func (p *ConcatReply) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { | |
252 | if v, err := iprot.ReadString(ctx); err != nil { | |
253 | return thrift.PrependError("error reading field 2: ", err) | |
254 | } else { | |
255 | p.Err = v | |
256 | } | |
257 | return nil | |
258 | } | |
259 | ||
260 | func (p *ConcatReply) Write(ctx context.Context, oprot thrift.TProtocol) error { | |
261 | if err := oprot.WriteStructBegin(ctx, "ConcatReply"); err != nil { | |
262 | return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) | |
263 | } | |
264 | if p != nil { | |
265 | if err := p.writeField1(ctx, oprot); err != nil { | |
266 | return err | |
267 | } | |
268 | if err := p.writeField2(ctx, oprot); err != nil { | |
269 | return err | |
270 | } | |
271 | } | |
272 | if err := oprot.WriteFieldStop(ctx); err != nil { | |
273 | return thrift.PrependError("write field stop error: ", err) | |
274 | } | |
275 | if err := oprot.WriteStructEnd(ctx); err != nil { | |
276 | return thrift.PrependError("write struct stop error: ", err) | |
277 | } | |
278 | return nil | |
279 | } | |
280 | ||
281 | func (p *ConcatReply) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
282 | if err := oprot.WriteFieldBegin(ctx, "value", thrift.STRING, 1); err != nil { | |
283 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:value: ", p), err) | |
284 | } | |
285 | if err := oprot.WriteString(ctx, string(p.Value)); err != nil { | |
286 | return thrift.PrependError(fmt.Sprintf("%T.value (1) field write error: ", p), err) | |
287 | } | |
288 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
289 | return thrift.PrependError(fmt.Sprintf("%T write field end error 1:value: ", p), err) | |
290 | } | |
291 | return err | |
292 | } | |
293 | ||
294 | func (p *ConcatReply) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
295 | if err := oprot.WriteFieldBegin(ctx, "err", thrift.STRING, 2); err != nil { | |
296 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:err: ", p), err) | |
297 | } | |
298 | if err := oprot.WriteString(ctx, string(p.Err)); err != nil { | |
299 | return thrift.PrependError(fmt.Sprintf("%T.err (2) field write error: ", p), err) | |
300 | } | |
301 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
302 | return thrift.PrependError(fmt.Sprintf("%T write field end error 2:err: ", p), err) | |
303 | } | |
304 | return err | |
305 | } | |
306 | ||
307 | func (p *ConcatReply) Equals(other *ConcatReply) bool { | |
308 | if p == other { | |
309 | return true | |
310 | } else if p == nil || other == nil { | |
311 | return false | |
312 | } | |
313 | if p.Value != other.Value { | |
314 | return false | |
315 | } | |
316 | if p.Err != other.Err { | |
317 | return false | |
318 | } | |
319 | return true | |
320 | } | |
321 | ||
322 | func (p *ConcatReply) String() string { | |
323 | if p == nil { | |
324 | return "<nil>" | |
325 | } | |
326 | return fmt.Sprintf("ConcatReply(%+v)", *p) | |
327 | } | |
328 | ||
329 | type AddService interface { | |
330 | // Parameters: | |
331 | // - A | |
332 | // - B | |
333 | Sum(ctx context.Context, a int64, b int64) (_r *SumReply, _err error) | |
334 | // Parameters: | |
335 | // - A | |
336 | // - B | |
337 | Concat(ctx context.Context, a string, b string) (_r *ConcatReply, _err error) | |
338 | } | |
339 | ||
340 | type AddServiceClient struct { | |
341 | c thrift.TClient | |
342 | meta thrift.ResponseMeta | |
343 | } | |
344 | ||
345 | func NewAddServiceClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *AddServiceClient { | |
346 | return &AddServiceClient{ | |
347 | c: thrift.NewTStandardClient(f.GetProtocol(t), f.GetProtocol(t)), | |
348 | } | |
349 | } | |
350 | ||
351 | func NewAddServiceClientProtocol(t thrift.TTransport, iprot thrift.TProtocol, oprot thrift.TProtocol) *AddServiceClient { | |
352 | return &AddServiceClient{ | |
353 | c: thrift.NewTStandardClient(iprot, oprot), | |
354 | } | |
355 | } | |
356 | ||
357 | func NewAddServiceClient(c thrift.TClient) *AddServiceClient { | |
358 | return &AddServiceClient{ | |
359 | c: c, | |
360 | } | |
361 | } | |
362 | ||
363 | func (p *AddServiceClient) Client_() thrift.TClient { | |
364 | return p.c | |
365 | } | |
366 | ||
367 | func (p *AddServiceClient) LastResponseMeta_() thrift.ResponseMeta { | |
368 | return p.meta | |
369 | } | |
370 | ||
371 | func (p *AddServiceClient) SetLastResponseMeta_(meta thrift.ResponseMeta) { | |
372 | p.meta = meta | |
373 | } | |
374 | ||
375 | // Parameters: | |
376 | // - A | |
377 | // - B | |
378 | func (p *AddServiceClient) Sum(ctx context.Context, a int64, b int64) (_r *SumReply, _err error) { | |
379 | var _args0 AddServiceSumArgs | |
380 | _args0.A = a | |
381 | _args0.B = b | |
382 | var _result2 AddServiceSumResult | |
383 | var _meta1 thrift.ResponseMeta | |
384 | _meta1, _err = p.Client_().Call(ctx, "Sum", &_args0, &_result2) | |
385 | p.SetLastResponseMeta_(_meta1) | |
386 | if _err != nil { | |
387 | return | |
388 | } | |
389 | return _result2.GetSuccess(), nil | |
390 | } | |
391 | ||
392 | // Parameters: | |
393 | // - A | |
394 | // - B | |
395 | func (p *AddServiceClient) Concat(ctx context.Context, a string, b string) (_r *ConcatReply, _err error) { | |
396 | var _args3 AddServiceConcatArgs | |
397 | _args3.A = a | |
398 | _args3.B = b | |
399 | var _result5 AddServiceConcatResult | |
400 | var _meta4 thrift.ResponseMeta | |
401 | _meta4, _err = p.Client_().Call(ctx, "Concat", &_args3, &_result5) | |
402 | p.SetLastResponseMeta_(_meta4) | |
403 | if _err != nil { | |
404 | return | |
405 | } | |
406 | return _result5.GetSuccess(), nil | |
407 | } | |
408 | ||
409 | type AddServiceProcessor struct { | |
410 | processorMap map[string]thrift.TProcessorFunction | |
411 | handler AddService | |
412 | } | |
413 | ||
414 | func (p *AddServiceProcessor) AddToProcessorMap(key string, processor thrift.TProcessorFunction) { | |
415 | p.processorMap[key] = processor | |
416 | } | |
417 | ||
418 | func (p *AddServiceProcessor) GetProcessorFunction(key string) (processor thrift.TProcessorFunction, ok bool) { | |
419 | processor, ok = p.processorMap[key] | |
420 | return processor, ok | |
421 | } | |
422 | ||
423 | func (p *AddServiceProcessor) ProcessorMap() map[string]thrift.TProcessorFunction { | |
424 | return p.processorMap | |
425 | } | |
426 | ||
427 | func NewAddServiceProcessor(handler AddService) *AddServiceProcessor { | |
428 | ||
429 | self6 := &AddServiceProcessor{handler: handler, processorMap: make(map[string]thrift.TProcessorFunction)} | |
430 | self6.processorMap["Sum"] = &addServiceProcessorSum{handler: handler} | |
431 | self6.processorMap["Concat"] = &addServiceProcessorConcat{handler: handler} | |
432 | return self6 | |
433 | } | |
434 | ||
435 | func (p *AddServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { | |
436 | name, _, seqId, err2 := iprot.ReadMessageBegin(ctx) | |
437 | if err2 != nil { | |
438 | return false, thrift.WrapTException(err2) | |
439 | } | |
440 | if processor, ok := p.GetProcessorFunction(name); ok { | |
441 | return processor.Process(ctx, seqId, iprot, oprot) | |
442 | } | |
443 | iprot.Skip(ctx, thrift.STRUCT) | |
444 | iprot.ReadMessageEnd(ctx) | |
445 | x7 := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, "Unknown function "+name) | |
446 | oprot.WriteMessageBegin(ctx, name, thrift.EXCEPTION, seqId) | |
447 | x7.Write(ctx, oprot) | |
448 | oprot.WriteMessageEnd(ctx) | |
449 | oprot.Flush(ctx) | |
450 | return false, x7 | |
451 | ||
452 | } | |
453 | ||
454 | type addServiceProcessorSum struct { | |
455 | handler AddService | |
456 | } | |
457 | ||
458 | func (p *addServiceProcessorSum) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { | |
459 | args := AddServiceSumArgs{} | |
460 | var err2 error | |
461 | if err2 = args.Read(ctx, iprot); err2 != nil { | |
462 | iprot.ReadMessageEnd(ctx) | |
463 | x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) | |
464 | oprot.WriteMessageBegin(ctx, "Sum", thrift.EXCEPTION, seqId) | |
465 | x.Write(ctx, oprot) | |
466 | oprot.WriteMessageEnd(ctx) | |
467 | oprot.Flush(ctx) | |
468 | return false, thrift.WrapTException(err2) | |
469 | } | |
470 | iprot.ReadMessageEnd(ctx) | |
471 | ||
472 | tickerCancel := func() {} | |
473 | // Start a goroutine to do server side connectivity check. | |
474 | if thrift.ServerConnectivityCheckInterval > 0 { | |
475 | var cancel context.CancelFunc | |
476 | ctx, cancel = context.WithCancel(ctx) | |
477 | defer cancel() | |
478 | var tickerCtx context.Context | |
479 | tickerCtx, tickerCancel = context.WithCancel(context.Background()) | |
480 | defer tickerCancel() | |
481 | go func(ctx context.Context, cancel context.CancelFunc) { | |
482 | ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) | |
483 | defer ticker.Stop() | |
484 | for { | |
485 | select { | |
486 | case <-ctx.Done(): | |
487 | return | |
488 | case <-ticker.C: | |
489 | if !iprot.Transport().IsOpen() { | |
490 | cancel() | |
491 | return | |
492 | } | |
493 | } | |
494 | } | |
495 | }(tickerCtx, cancel) | |
496 | } | |
497 | ||
498 | result := AddServiceSumResult{} | |
499 | var retval *SumReply | |
500 | if retval, err2 = p.handler.Sum(ctx, args.A, args.B); err2 != nil { | |
501 | tickerCancel() | |
502 | if err2 == thrift.ErrAbandonRequest { | |
503 | return false, thrift.WrapTException(err2) | |
504 | } | |
505 | x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Sum: "+err2.Error()) | |
506 | oprot.WriteMessageBegin(ctx, "Sum", thrift.EXCEPTION, seqId) | |
507 | x.Write(ctx, oprot) | |
508 | oprot.WriteMessageEnd(ctx) | |
509 | oprot.Flush(ctx) | |
510 | return true, thrift.WrapTException(err2) | |
511 | } else { | |
512 | result.Success = retval | |
513 | } | |
514 | tickerCancel() | |
515 | if err2 = oprot.WriteMessageBegin(ctx, "Sum", thrift.REPLY, seqId); err2 != nil { | |
516 | err = thrift.WrapTException(err2) | |
517 | } | |
518 | if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { | |
519 | err = thrift.WrapTException(err2) | |
520 | } | |
521 | if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { | |
522 | err = thrift.WrapTException(err2) | |
523 | } | |
524 | if err2 = oprot.Flush(ctx); err == nil && err2 != nil { | |
525 | err = thrift.WrapTException(err2) | |
526 | } | |
527 | if err != nil { | |
528 | return | |
529 | } | |
530 | return true, err | |
531 | } | |
532 | ||
533 | type addServiceProcessorConcat struct { | |
534 | handler AddService | |
535 | } | |
536 | ||
537 | func (p *addServiceProcessorConcat) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { | |
538 | args := AddServiceConcatArgs{} | |
539 | var err2 error | |
540 | if err2 = args.Read(ctx, iprot); err2 != nil { | |
541 | iprot.ReadMessageEnd(ctx) | |
542 | x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err2.Error()) | |
543 | oprot.WriteMessageBegin(ctx, "Concat", thrift.EXCEPTION, seqId) | |
544 | x.Write(ctx, oprot) | |
545 | oprot.WriteMessageEnd(ctx) | |
546 | oprot.Flush(ctx) | |
547 | return false, thrift.WrapTException(err2) | |
548 | } | |
549 | iprot.ReadMessageEnd(ctx) | |
550 | ||
551 | tickerCancel := func() {} | |
552 | // Start a goroutine to do server side connectivity check. | |
553 | if thrift.ServerConnectivityCheckInterval > 0 { | |
554 | var cancel context.CancelFunc | |
555 | ctx, cancel = context.WithCancel(ctx) | |
556 | defer cancel() | |
557 | var tickerCtx context.Context | |
558 | tickerCtx, tickerCancel = context.WithCancel(context.Background()) | |
559 | defer tickerCancel() | |
560 | go func(ctx context.Context, cancel context.CancelFunc) { | |
561 | ticker := time.NewTicker(thrift.ServerConnectivityCheckInterval) | |
562 | defer ticker.Stop() | |
563 | for { | |
564 | select { | |
565 | case <-ctx.Done(): | |
566 | return | |
567 | case <-ticker.C: | |
568 | if !iprot.Transport().IsOpen() { | |
569 | cancel() | |
570 | return | |
571 | } | |
572 | } | |
573 | } | |
574 | }(tickerCtx, cancel) | |
575 | } | |
576 | ||
577 | result := AddServiceConcatResult{} | |
578 | var retval *ConcatReply | |
579 | if retval, err2 = p.handler.Concat(ctx, args.A, args.B); err2 != nil { | |
580 | tickerCancel() | |
581 | if err2 == thrift.ErrAbandonRequest { | |
582 | return false, thrift.WrapTException(err2) | |
583 | } | |
584 | x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Concat: "+err2.Error()) | |
585 | oprot.WriteMessageBegin(ctx, "Concat", thrift.EXCEPTION, seqId) | |
586 | x.Write(ctx, oprot) | |
587 | oprot.WriteMessageEnd(ctx) | |
588 | oprot.Flush(ctx) | |
589 | return true, thrift.WrapTException(err2) | |
590 | } else { | |
591 | result.Success = retval | |
592 | } | |
593 | tickerCancel() | |
594 | if err2 = oprot.WriteMessageBegin(ctx, "Concat", thrift.REPLY, seqId); err2 != nil { | |
595 | err = thrift.WrapTException(err2) | |
596 | } | |
597 | if err2 = result.Write(ctx, oprot); err == nil && err2 != nil { | |
598 | err = thrift.WrapTException(err2) | |
599 | } | |
600 | if err2 = oprot.WriteMessageEnd(ctx); err == nil && err2 != nil { | |
601 | err = thrift.WrapTException(err2) | |
602 | } | |
603 | if err2 = oprot.Flush(ctx); err == nil && err2 != nil { | |
604 | err = thrift.WrapTException(err2) | |
605 | } | |
606 | if err != nil { | |
607 | return | |
608 | } | |
609 | return true, err | |
610 | } | |
611 | ||
612 | // HELPER FUNCTIONS AND STRUCTURES | |
613 | ||
614 | // Attributes: | |
615 | // - A | |
616 | // - B | |
617 | type AddServiceSumArgs struct { | |
618 | A int64 `thrift:"a,1" db:"a" json:"a"` | |
619 | B int64 `thrift:"b,2" db:"b" json:"b"` | |
620 | } | |
621 | ||
622 | func NewAddServiceSumArgs() *AddServiceSumArgs { | |
623 | return &AddServiceSumArgs{} | |
624 | } | |
625 | ||
626 | func (p *AddServiceSumArgs) GetA() int64 { | |
627 | return p.A | |
628 | } | |
629 | ||
630 | func (p *AddServiceSumArgs) GetB() int64 { | |
631 | return p.B | |
632 | } | |
633 | func (p *AddServiceSumArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { | |
634 | if _, err := iprot.ReadStructBegin(ctx); err != nil { | |
635 | return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) | |
636 | } | |
637 | ||
638 | for { | |
639 | _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) | |
640 | if err != nil { | |
641 | return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) | |
642 | } | |
643 | if fieldTypeId == thrift.STOP { | |
644 | break | |
645 | } | |
646 | switch fieldId { | |
647 | case 1: | |
648 | if fieldTypeId == thrift.I64 { | |
649 | if err := p.ReadField1(ctx, iprot); err != nil { | |
650 | return err | |
651 | } | |
652 | } else { | |
653 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
654 | return err | |
655 | } | |
656 | } | |
657 | case 2: | |
658 | if fieldTypeId == thrift.I64 { | |
659 | if err := p.ReadField2(ctx, iprot); err != nil { | |
660 | return err | |
661 | } | |
662 | } else { | |
663 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
664 | return err | |
665 | } | |
666 | } | |
667 | default: | |
668 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
669 | return err | |
670 | } | |
671 | } | |
672 | if err := iprot.ReadFieldEnd(ctx); err != nil { | |
673 | return err | |
674 | } | |
675 | } | |
676 | if err := iprot.ReadStructEnd(ctx); err != nil { | |
677 | return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) | |
678 | } | |
679 | return nil | |
680 | } | |
681 | ||
682 | func (p *AddServiceSumArgs) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { | |
683 | if v, err := iprot.ReadI64(ctx); err != nil { | |
684 | return thrift.PrependError("error reading field 1: ", err) | |
685 | } else { | |
686 | p.A = v | |
687 | } | |
688 | return nil | |
689 | } | |
690 | ||
691 | func (p *AddServiceSumArgs) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { | |
692 | if v, err := iprot.ReadI64(ctx); err != nil { | |
693 | return thrift.PrependError("error reading field 2: ", err) | |
694 | } else { | |
695 | p.B = v | |
696 | } | |
697 | return nil | |
698 | } | |
699 | ||
700 | func (p *AddServiceSumArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { | |
701 | if err := oprot.WriteStructBegin(ctx, "Sum_args"); err != nil { | |
702 | return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) | |
703 | } | |
704 | if p != nil { | |
705 | if err := p.writeField1(ctx, oprot); err != nil { | |
706 | return err | |
707 | } | |
708 | if err := p.writeField2(ctx, oprot); err != nil { | |
709 | return err | |
710 | } | |
711 | } | |
712 | if err := oprot.WriteFieldStop(ctx); err != nil { | |
713 | return thrift.PrependError("write field stop error: ", err) | |
714 | } | |
715 | if err := oprot.WriteStructEnd(ctx); err != nil { | |
716 | return thrift.PrependError("write struct stop error: ", err) | |
717 | } | |
718 | return nil | |
719 | } | |
720 | ||
721 | func (p *AddServiceSumArgs) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
722 | if err := oprot.WriteFieldBegin(ctx, "a", thrift.I64, 1); err != nil { | |
723 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:a: ", p), err) | |
724 | } | |
725 | if err := oprot.WriteI64(ctx, int64(p.A)); err != nil { | |
726 | return thrift.PrependError(fmt.Sprintf("%T.a (1) field write error: ", p), err) | |
727 | } | |
728 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
729 | return thrift.PrependError(fmt.Sprintf("%T write field end error 1:a: ", p), err) | |
730 | } | |
731 | return err | |
732 | } | |
733 | ||
734 | func (p *AddServiceSumArgs) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
735 | if err := oprot.WriteFieldBegin(ctx, "b", thrift.I64, 2); err != nil { | |
736 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:b: ", p), err) | |
737 | } | |
738 | if err := oprot.WriteI64(ctx, int64(p.B)); err != nil { | |
739 | return thrift.PrependError(fmt.Sprintf("%T.b (2) field write error: ", p), err) | |
740 | } | |
741 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
742 | return thrift.PrependError(fmt.Sprintf("%T write field end error 2:b: ", p), err) | |
743 | } | |
744 | return err | |
745 | } | |
746 | ||
747 | func (p *AddServiceSumArgs) String() string { | |
748 | if p == nil { | |
749 | return "<nil>" | |
750 | } | |
751 | return fmt.Sprintf("AddServiceSumArgs(%+v)", *p) | |
752 | } | |
753 | ||
754 | // Attributes: | |
755 | // - Success | |
756 | type AddServiceSumResult struct { | |
757 | Success *SumReply `thrift:"success,0" db:"success" json:"success,omitempty"` | |
758 | } | |
759 | ||
760 | func NewAddServiceSumResult() *AddServiceSumResult { | |
761 | return &AddServiceSumResult{} | |
762 | } | |
763 | ||
764 | var AddServiceSumResult_Success_DEFAULT *SumReply | |
765 | ||
766 | func (p *AddServiceSumResult) GetSuccess() *SumReply { | |
767 | if !p.IsSetSuccess() { | |
768 | return AddServiceSumResult_Success_DEFAULT | |
769 | } | |
770 | return p.Success | |
771 | } | |
772 | func (p *AddServiceSumResult) IsSetSuccess() bool { | |
773 | return p.Success != nil | |
774 | } | |
775 | ||
776 | func (p *AddServiceSumResult) Read(ctx context.Context, iprot thrift.TProtocol) error { | |
777 | if _, err := iprot.ReadStructBegin(ctx); err != nil { | |
778 | return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) | |
779 | } | |
780 | ||
781 | for { | |
782 | _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) | |
783 | if err != nil { | |
784 | return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) | |
785 | } | |
786 | if fieldTypeId == thrift.STOP { | |
787 | break | |
788 | } | |
789 | switch fieldId { | |
790 | case 0: | |
791 | if fieldTypeId == thrift.STRUCT { | |
792 | if err := p.ReadField0(ctx, iprot); err != nil { | |
793 | return err | |
794 | } | |
795 | } else { | |
796 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
797 | return err | |
798 | } | |
799 | } | |
800 | default: | |
801 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
802 | return err | |
803 | } | |
804 | } | |
805 | if err := iprot.ReadFieldEnd(ctx); err != nil { | |
806 | return err | |
807 | } | |
808 | } | |
809 | if err := iprot.ReadStructEnd(ctx); err != nil { | |
810 | return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) | |
811 | } | |
812 | return nil | |
813 | } | |
814 | ||
815 | func (p *AddServiceSumResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { | |
816 | p.Success = &SumReply{} | |
817 | if err := p.Success.Read(ctx, iprot); err != nil { | |
818 | return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err) | |
819 | } | |
820 | return nil | |
821 | } | |
822 | ||
823 | func (p *AddServiceSumResult) Write(ctx context.Context, oprot thrift.TProtocol) error { | |
824 | if err := oprot.WriteStructBegin(ctx, "Sum_result"); err != nil { | |
825 | return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) | |
826 | } | |
827 | if p != nil { | |
828 | if err := p.writeField0(ctx, oprot); err != nil { | |
829 | return err | |
830 | } | |
831 | } | |
832 | if err := oprot.WriteFieldStop(ctx); err != nil { | |
833 | return thrift.PrependError("write field stop error: ", err) | |
834 | } | |
835 | if err := oprot.WriteStructEnd(ctx); err != nil { | |
836 | return thrift.PrependError("write struct stop error: ", err) | |
837 | } | |
838 | return nil | |
839 | } | |
840 | ||
841 | func (p *AddServiceSumResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
842 | if p.IsSetSuccess() { | |
843 | if err := oprot.WriteFieldBegin(ctx, "success", thrift.STRUCT, 0); err != nil { | |
844 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) | |
845 | } | |
846 | if err := p.Success.Write(ctx, oprot); err != nil { | |
847 | return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err) | |
848 | } | |
849 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
850 | return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) | |
851 | } | |
852 | } | |
853 | return err | |
854 | } | |
855 | ||
856 | func (p *AddServiceSumResult) String() string { | |
857 | if p == nil { | |
858 | return "<nil>" | |
859 | } | |
860 | return fmt.Sprintf("AddServiceSumResult(%+v)", *p) | |
861 | } | |
862 | ||
863 | // Attributes: | |
864 | // - A | |
865 | // - B | |
866 | type AddServiceConcatArgs struct { | |
867 | A string `thrift:"a,1" db:"a" json:"a"` | |
868 | B string `thrift:"b,2" db:"b" json:"b"` | |
869 | } | |
870 | ||
871 | func NewAddServiceConcatArgs() *AddServiceConcatArgs { | |
872 | return &AddServiceConcatArgs{} | |
873 | } | |
874 | ||
875 | func (p *AddServiceConcatArgs) GetA() string { | |
876 | return p.A | |
877 | } | |
878 | ||
879 | func (p *AddServiceConcatArgs) GetB() string { | |
880 | return p.B | |
881 | } | |
882 | func (p *AddServiceConcatArgs) Read(ctx context.Context, iprot thrift.TProtocol) error { | |
883 | if _, err := iprot.ReadStructBegin(ctx); err != nil { | |
884 | return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) | |
885 | } | |
886 | ||
887 | for { | |
888 | _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) | |
889 | if err != nil { | |
890 | return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) | |
891 | } | |
892 | if fieldTypeId == thrift.STOP { | |
893 | break | |
894 | } | |
895 | switch fieldId { | |
896 | case 1: | |
897 | if fieldTypeId == thrift.STRING { | |
898 | if err := p.ReadField1(ctx, iprot); err != nil { | |
899 | return err | |
900 | } | |
901 | } else { | |
902 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
903 | return err | |
904 | } | |
905 | } | |
906 | case 2: | |
907 | if fieldTypeId == thrift.STRING { | |
908 | if err := p.ReadField2(ctx, iprot); err != nil { | |
909 | return err | |
910 | } | |
911 | } else { | |
912 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
913 | return err | |
914 | } | |
915 | } | |
916 | default: | |
917 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
918 | return err | |
919 | } | |
920 | } | |
921 | if err := iprot.ReadFieldEnd(ctx); err != nil { | |
922 | return err | |
923 | } | |
924 | } | |
925 | if err := iprot.ReadStructEnd(ctx); err != nil { | |
926 | return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) | |
927 | } | |
928 | return nil | |
929 | } | |
930 | ||
931 | func (p *AddServiceConcatArgs) ReadField1(ctx context.Context, iprot thrift.TProtocol) error { | |
932 | if v, err := iprot.ReadString(ctx); err != nil { | |
933 | return thrift.PrependError("error reading field 1: ", err) | |
934 | } else { | |
935 | p.A = v | |
936 | } | |
937 | return nil | |
938 | } | |
939 | ||
940 | func (p *AddServiceConcatArgs) ReadField2(ctx context.Context, iprot thrift.TProtocol) error { | |
941 | if v, err := iprot.ReadString(ctx); err != nil { | |
942 | return thrift.PrependError("error reading field 2: ", err) | |
943 | } else { | |
944 | p.B = v | |
945 | } | |
946 | return nil | |
947 | } | |
948 | ||
949 | func (p *AddServiceConcatArgs) Write(ctx context.Context, oprot thrift.TProtocol) error { | |
950 | if err := oprot.WriteStructBegin(ctx, "Concat_args"); err != nil { | |
951 | return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) | |
952 | } | |
953 | if p != nil { | |
954 | if err := p.writeField1(ctx, oprot); err != nil { | |
955 | return err | |
956 | } | |
957 | if err := p.writeField2(ctx, oprot); err != nil { | |
958 | return err | |
959 | } | |
960 | } | |
961 | if err := oprot.WriteFieldStop(ctx); err != nil { | |
962 | return thrift.PrependError("write field stop error: ", err) | |
963 | } | |
964 | if err := oprot.WriteStructEnd(ctx); err != nil { | |
965 | return thrift.PrependError("write struct stop error: ", err) | |
966 | } | |
967 | return nil | |
968 | } | |
969 | ||
970 | func (p *AddServiceConcatArgs) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
971 | if err := oprot.WriteFieldBegin(ctx, "a", thrift.STRING, 1); err != nil { | |
972 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:a: ", p), err) | |
973 | } | |
974 | if err := oprot.WriteString(ctx, string(p.A)); err != nil { | |
975 | return thrift.PrependError(fmt.Sprintf("%T.a (1) field write error: ", p), err) | |
976 | } | |
977 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
978 | return thrift.PrependError(fmt.Sprintf("%T write field end error 1:a: ", p), err) | |
979 | } | |
980 | return err | |
981 | } | |
982 | ||
983 | func (p *AddServiceConcatArgs) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
984 | if err := oprot.WriteFieldBegin(ctx, "b", thrift.STRING, 2); err != nil { | |
985 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:b: ", p), err) | |
986 | } | |
987 | if err := oprot.WriteString(ctx, string(p.B)); err != nil { | |
988 | return thrift.PrependError(fmt.Sprintf("%T.b (2) field write error: ", p), err) | |
989 | } | |
990 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
991 | return thrift.PrependError(fmt.Sprintf("%T write field end error 2:b: ", p), err) | |
992 | } | |
993 | return err | |
994 | } | |
995 | ||
996 | func (p *AddServiceConcatArgs) String() string { | |
997 | if p == nil { | |
998 | return "<nil>" | |
999 | } | |
1000 | return fmt.Sprintf("AddServiceConcatArgs(%+v)", *p) | |
1001 | } | |
1002 | ||
1003 | // Attributes: | |
1004 | // - Success | |
1005 | type AddServiceConcatResult struct { | |
1006 | Success *ConcatReply `thrift:"success,0" db:"success" json:"success,omitempty"` | |
1007 | } | |
1008 | ||
1009 | func NewAddServiceConcatResult() *AddServiceConcatResult { | |
1010 | return &AddServiceConcatResult{} | |
1011 | } | |
1012 | ||
1013 | var AddServiceConcatResult_Success_DEFAULT *ConcatReply | |
1014 | ||
1015 | func (p *AddServiceConcatResult) GetSuccess() *ConcatReply { | |
1016 | if !p.IsSetSuccess() { | |
1017 | return AddServiceConcatResult_Success_DEFAULT | |
1018 | } | |
1019 | return p.Success | |
1020 | } | |
1021 | func (p *AddServiceConcatResult) IsSetSuccess() bool { | |
1022 | return p.Success != nil | |
1023 | } | |
1024 | ||
1025 | func (p *AddServiceConcatResult) Read(ctx context.Context, iprot thrift.TProtocol) error { | |
1026 | if _, err := iprot.ReadStructBegin(ctx); err != nil { | |
1027 | return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) | |
1028 | } | |
1029 | ||
1030 | for { | |
1031 | _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx) | |
1032 | if err != nil { | |
1033 | return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err) | |
1034 | } | |
1035 | if fieldTypeId == thrift.STOP { | |
1036 | break | |
1037 | } | |
1038 | switch fieldId { | |
1039 | case 0: | |
1040 | if fieldTypeId == thrift.STRUCT { | |
1041 | if err := p.ReadField0(ctx, iprot); err != nil { | |
1042 | return err | |
1043 | } | |
1044 | } else { | |
1045 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
1046 | return err | |
1047 | } | |
1048 | } | |
1049 | default: | |
1050 | if err := iprot.Skip(ctx, fieldTypeId); err != nil { | |
1051 | return err | |
1052 | } | |
1053 | } | |
1054 | if err := iprot.ReadFieldEnd(ctx); err != nil { | |
1055 | return err | |
1056 | } | |
1057 | } | |
1058 | if err := iprot.ReadStructEnd(ctx); err != nil { | |
1059 | return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) | |
1060 | } | |
1061 | return nil | |
1062 | } | |
1063 | ||
1064 | func (p *AddServiceConcatResult) ReadField0(ctx context.Context, iprot thrift.TProtocol) error { | |
1065 | p.Success = &ConcatReply{} | |
1066 | if err := p.Success.Read(ctx, iprot); err != nil { | |
1067 | return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err) | |
1068 | } | |
1069 | return nil | |
1070 | } | |
1071 | ||
1072 | func (p *AddServiceConcatResult) Write(ctx context.Context, oprot thrift.TProtocol) error { | |
1073 | if err := oprot.WriteStructBegin(ctx, "Concat_result"); err != nil { | |
1074 | return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) | |
1075 | } | |
1076 | if p != nil { | |
1077 | if err := p.writeField0(ctx, oprot); err != nil { | |
1078 | return err | |
1079 | } | |
1080 | } | |
1081 | if err := oprot.WriteFieldStop(ctx); err != nil { | |
1082 | return thrift.PrependError("write field stop error: ", err) | |
1083 | } | |
1084 | if err := oprot.WriteStructEnd(ctx); err != nil { | |
1085 | return thrift.PrependError("write struct stop error: ", err) | |
1086 | } | |
1087 | return nil | |
1088 | } | |
1089 | ||
1090 | func (p *AddServiceConcatResult) writeField0(ctx context.Context, oprot thrift.TProtocol) (err error) { | |
1091 | if p.IsSetSuccess() { | |
1092 | if err := oprot.WriteFieldBegin(ctx, "success", thrift.STRUCT, 0); err != nil { | |
1093 | return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) | |
1094 | } | |
1095 | if err := p.Success.Write(ctx, oprot); err != nil { | |
1096 | return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err) | |
1097 | } | |
1098 | if err := oprot.WriteFieldEnd(ctx); err != nil { | |
1099 | return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) | |
1100 | } | |
1101 | } | |
1102 | return err | |
1103 | } | |
1104 | ||
1105 | func (p *AddServiceConcatResult) String() string { | |
1106 | if p == nil { | |
1107 | return "<nil>" | |
1108 | } | |
1109 | return fmt.Sprintf("AddServiceConcatResult(%+v)", *p) | |
1110 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "context" | |
5 | "encoding/json" | |
6 | "flag" | |
7 | "fmt" | |
8 | "io" | |
9 | "io/ioutil" | |
10 | "net/http" | |
11 | "net/url" | |
12 | "os" | |
13 | "os/signal" | |
14 | "strings" | |
15 | "syscall" | |
16 | "time" | |
17 | ||
18 | consulsd "github.com/go-kit/kit/sd/consul" | |
19 | "github.com/gorilla/mux" | |
20 | "github.com/hashicorp/consul/api" | |
21 | stdopentracing "github.com/opentracing/opentracing-go" | |
22 | stdzipkin "github.com/openzipkin/zipkin-go" | |
23 | "google.golang.org/grpc" | |
24 | ||
25 | "github.com/go-kit/kit/endpoint" | |
26 | "github.com/go-kit/kit/log" | |
27 | "github.com/go-kit/kit/sd" | |
28 | "github.com/go-kit/kit/sd/lb" | |
29 | httptransport "github.com/go-kit/kit/transport/http" | |
30 | ||
31 | "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint" | |
32 | "github.com/go-kit/kit/examples/addsvc/pkg/addservice" | |
33 | "github.com/go-kit/kit/examples/addsvc/pkg/addtransport" | |
34 | ) | |
35 | ||
36 | func main() { | |
37 | var ( | |
38 | httpAddr = flag.String("http.addr", ":8000", "Address for HTTP (JSON) server") | |
39 | consulAddr = flag.String("consul.addr", "", "Consul agent address") | |
40 | retryMax = flag.Int("retry.max", 3, "per-request retries to different instances") | |
41 | retryTimeout = flag.Duration("retry.timeout", 500*time.Millisecond, "per-request timeout, including retries") | |
42 | ) | |
43 | flag.Parse() | |
44 | ||
45 | // Logging domain. | |
46 | var logger log.Logger | |
47 | { | |
48 | logger = log.NewLogfmtLogger(os.Stderr) | |
49 | logger = log.With(logger, "ts", log.DefaultTimestampUTC) | |
50 | logger = log.With(logger, "caller", log.DefaultCaller) | |
51 | } | |
52 | ||
53 | // Service discovery domain. In this example we use Consul. | |
54 | var client consulsd.Client | |
55 | { | |
56 | consulConfig := api.DefaultConfig() | |
57 | if len(*consulAddr) > 0 { | |
58 | consulConfig.Address = *consulAddr | |
59 | } | |
60 | consulClient, err := api.NewClient(consulConfig) | |
61 | if err != nil { | |
62 | logger.Log("err", err) | |
63 | os.Exit(1) | |
64 | } | |
65 | client = consulsd.NewClient(consulClient) | |
66 | } | |
67 | ||
68 | // Transport domain. | |
69 | tracer := stdopentracing.GlobalTracer() // no-op | |
70 | zipkinTracer, _ := stdzipkin.NewTracer(nil, stdzipkin.WithNoopTracer(true)) | |
71 | ctx := context.Background() | |
72 | r := mux.NewRouter() | |
73 | ||
74 | // Now we begin installing the routes. Each route corresponds to a single | |
75 | // method: sum, concat, uppercase, and count. | |
76 | ||
77 | // addsvc routes. | |
78 | { | |
79 | // Each method gets constructed with a factory. Factories take an | |
80 | // instance string, and return a specific endpoint. In the factory we | |
81 | // dial the instance string we get from Consul, and then leverage an | |
82 | // addsvc client package to construct a complete service. We can then | |
83 | // leverage the addsvc.Make{Sum,Concat}Endpoint constructors to convert | |
84 | // the complete service to specific endpoint. | |
85 | var ( | |
86 | tags = []string{} | |
87 | passingOnly = true | |
88 | endpoints = addendpoint.Set{} | |
89 | instancer = consulsd.NewInstancer(client, logger, "addsvc", tags, passingOnly) | |
90 | ) | |
91 | { | |
92 | factory := addsvcFactory(addendpoint.MakeSumEndpoint, tracer, zipkinTracer, logger) | |
93 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
94 | balancer := lb.NewRoundRobin(endpointer) | |
95 | retry := lb.Retry(*retryMax, *retryTimeout, balancer) | |
96 | endpoints.SumEndpoint = retry | |
97 | } | |
98 | { | |
99 | factory := addsvcFactory(addendpoint.MakeConcatEndpoint, tracer, zipkinTracer, logger) | |
100 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
101 | balancer := lb.NewRoundRobin(endpointer) | |
102 | retry := lb.Retry(*retryMax, *retryTimeout, balancer) | |
103 | endpoints.ConcatEndpoint = retry | |
104 | } | |
105 | ||
106 | // Here we leverage the fact that addsvc comes with a constructor for an | |
107 | // HTTP handler, and just install it under a particular path prefix in | |
108 | // our router. | |
109 | ||
110 | r.PathPrefix("/addsvc").Handler(http.StripPrefix("/addsvc", addtransport.NewHTTPHandler(endpoints, tracer, zipkinTracer, logger))) | |
111 | } | |
112 | ||
113 | // stringsvc routes. | |
114 | { | |
115 | // addsvc had lots of nice importable Go packages we could leverage. | |
116 | // With stringsvc we are not so fortunate, it just has some endpoints | |
117 | // that we assume will exist. So we have to write that logic here. This | |
118 | // is by design, so you can see two totally different methods of | |
119 | // proxying to a remote service. | |
120 | ||
121 | var ( | |
122 | tags = []string{} | |
123 | passingOnly = true | |
124 | uppercase endpoint.Endpoint | |
125 | count endpoint.Endpoint | |
126 | instancer = consulsd.NewInstancer(client, logger, "stringsvc", tags, passingOnly) | |
127 | ) | |
128 | { | |
129 | factory := stringsvcFactory(ctx, "GET", "/uppercase") | |
130 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
131 | balancer := lb.NewRoundRobin(endpointer) | |
132 | retry := lb.Retry(*retryMax, *retryTimeout, balancer) | |
133 | uppercase = retry | |
134 | } | |
135 | { | |
136 | factory := stringsvcFactory(ctx, "GET", "/count") | |
137 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
138 | balancer := lb.NewRoundRobin(endpointer) | |
139 | retry := lb.Retry(*retryMax, *retryTimeout, balancer) | |
140 | count = retry | |
141 | } | |
142 | ||
143 | // We can use the transport/http.Server to act as our handler, all we | |
144 | // have to do provide it with the encode and decode functions for our | |
145 | // stringsvc methods. | |
146 | ||
147 | r.Handle("/stringsvc/uppercase", httptransport.NewServer(uppercase, decodeUppercaseRequest, encodeJSONResponse)) | |
148 | r.Handle("/stringsvc/count", httptransport.NewServer(count, decodeCountRequest, encodeJSONResponse)) | |
149 | } | |
150 | ||
151 | // Interrupt handler. | |
152 | errc := make(chan error) | |
153 | go func() { | |
154 | c := make(chan os.Signal) | |
155 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) | |
156 | errc <- fmt.Errorf("%s", <-c) | |
157 | }() | |
158 | ||
159 | // HTTP transport. | |
160 | go func() { | |
161 | logger.Log("transport", "HTTP", "addr", *httpAddr) | |
162 | errc <- http.ListenAndServe(*httpAddr, r) | |
163 | }() | |
164 | ||
165 | // Run! | |
166 | logger.Log("exit", <-errc) | |
167 | } | |
168 | ||
169 | func addsvcFactory(makeEndpoint func(addservice.Service) endpoint.Endpoint, tracer stdopentracing.Tracer, zipkinTracer *stdzipkin.Tracer, logger log.Logger) sd.Factory { | |
170 | return func(instance string) (endpoint.Endpoint, io.Closer, error) { | |
171 | // We could just as easily use the HTTP or Thrift client package to make | |
172 | // the connection to addsvc. We've chosen gRPC arbitrarily. Note that | |
173 | // the transport is an implementation detail: it doesn't leak out of | |
174 | // this function. Nice! | |
175 | ||
176 | conn, err := grpc.Dial(instance, grpc.WithInsecure()) | |
177 | if err != nil { | |
178 | return nil, nil, err | |
179 | } | |
180 | service := addtransport.NewGRPCClient(conn, tracer, zipkinTracer, logger) | |
181 | endpoint := makeEndpoint(service) | |
182 | ||
183 | // Notice that the addsvc gRPC client converts the connection to a | |
184 | // complete addsvc, and we just throw away everything except the method | |
185 | // we're interested in. A smarter factory would mux multiple methods | |
186 | // over the same connection. But that would require more work to manage | |
187 | // the returned io.Closer, e.g. reference counting. Since this is for | |
188 | // the purposes of demonstration, we'll just keep it simple. | |
189 | ||
190 | return endpoint, conn, nil | |
191 | } | |
192 | } | |
193 | ||
194 | func stringsvcFactory(ctx context.Context, method, path string) sd.Factory { | |
195 | return func(instance string) (endpoint.Endpoint, io.Closer, error) { | |
196 | if !strings.HasPrefix(instance, "http") { | |
197 | instance = "http://" + instance | |
198 | } | |
199 | tgt, err := url.Parse(instance) | |
200 | if err != nil { | |
201 | return nil, nil, err | |
202 | } | |
203 | tgt.Path = path | |
204 | ||
205 | // Since stringsvc doesn't have any kind of package we can import, or | |
206 | // any formal spec, we are forced to just assert where the endpoints | |
207 | // live, and write our own code to encode and decode requests and | |
208 | // responses. Ideally, if you write the service, you will want to | |
209 | // provide stronger guarantees to your clients. | |
210 | ||
211 | var ( | |
212 | enc httptransport.EncodeRequestFunc | |
213 | dec httptransport.DecodeResponseFunc | |
214 | ) | |
215 | switch path { | |
216 | case "/uppercase": | |
217 | enc, dec = encodeJSONRequest, decodeUppercaseResponse | |
218 | case "/count": | |
219 | enc, dec = encodeJSONRequest, decodeCountResponse | |
220 | default: | |
221 | return nil, nil, fmt.Errorf("unknown stringsvc path %q", path) | |
222 | } | |
223 | ||
224 | return httptransport.NewClient(method, tgt, enc, dec).Endpoint(), nil, nil | |
225 | } | |
226 | } | |
227 | ||
228 | func encodeJSONRequest(_ context.Context, req *http.Request, request interface{}) error { | |
229 | // Both uppercase and count requests are encoded in the same way: | |
230 | // simple JSON serialization to the request body. | |
231 | var buf bytes.Buffer | |
232 | if err := json.NewEncoder(&buf).Encode(request); err != nil { | |
233 | return err | |
234 | } | |
235 | req.Body = ioutil.NopCloser(&buf) | |
236 | return nil | |
237 | } | |
238 | ||
239 | func encodeJSONResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { | |
240 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
241 | return json.NewEncoder(w).Encode(response) | |
242 | } | |
243 | ||
244 | // I've just copied these functions from stringsvc3/transport.go, inlining the | |
245 | // struct definitions. | |
246 | ||
247 | func decodeUppercaseResponse(ctx context.Context, resp *http.Response) (interface{}, error) { | |
248 | var response struct { | |
249 | V string `json:"v"` | |
250 | Err string `json:"err,omitempty"` | |
251 | } | |
252 | if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { | |
253 | return nil, err | |
254 | } | |
255 | return response, nil | |
256 | } | |
257 | ||
258 | func decodeCountResponse(ctx context.Context, resp *http.Response) (interface{}, error) { | |
259 | var response struct { | |
260 | V int `json:"v"` | |
261 | } | |
262 | if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { | |
263 | return nil, err | |
264 | } | |
265 | return response, nil | |
266 | } | |
267 | ||
268 | func decodeUppercaseRequest(ctx context.Context, req *http.Request) (interface{}, error) { | |
269 | var request struct { | |
270 | S string `json:"s"` | |
271 | } | |
272 | if err := json.NewDecoder(req.Body).Decode(&request); err != nil { | |
273 | return nil, err | |
274 | } | |
275 | return request, nil | |
276 | } | |
277 | ||
278 | func decodeCountRequest(ctx context.Context, req *http.Request) (interface{}, error) { | |
279 | var request struct { | |
280 | S string `json:"s"` | |
281 | } | |
282 | if err := json.NewDecoder(req.Body).Decode(&request); err != nil { | |
283 | return nil, err | |
284 | } | |
285 | return request, nil | |
286 | } |
0 | module github.com/go-kit/kit/examples | |
1 | ||
2 | go 1.16 | |
3 | ||
4 | require ( | |
5 | github.com/apache/thrift v0.14.1 | |
6 | github.com/go-kit/kit v0.10.0 | |
7 | github.com/golang/protobuf v1.5.2 | |
8 | github.com/gorilla/mux v1.7.3 | |
9 | github.com/hashicorp/consul/api v1.3.0 | |
10 | github.com/hashicorp/go-version v1.3.0 // indirect | |
11 | github.com/lightstep/lightstep-tracer-go v0.22.0 | |
12 | github.com/nats-io/nats.go v1.11.0 | |
13 | github.com/oklog/oklog v0.3.2 | |
14 | github.com/oklog/run v1.1.0 // indirect | |
15 | github.com/opentracing/basictracer-go v1.1.0 // indirect | |
16 | github.com/opentracing/opentracing-go v1.2.0 | |
17 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 | |
18 | github.com/openzipkin/zipkin-go v0.2.2 | |
19 | github.com/pact-foundation/pact-go v1.0.4 | |
20 | github.com/pborman/uuid v1.2.0 | |
21 | github.com/prometheus/client_golang v1.5.1 | |
22 | github.com/sony/gobreaker v0.4.1 | |
23 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 | |
24 | google.golang.org/grpc v1.37.0 | |
25 | sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 | |
26 | ) | |
27 | ||
28 | replace github.com/go-kit/kit => ../ |
0 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |
1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | |
3 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= | |
4 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= | |
5 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= | |
6 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= | |
7 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= | |
8 | github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= | |
9 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= | |
10 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= | |
11 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= | |
12 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | |
13 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | |
14 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | |
15 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | |
16 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= | |
17 | github.com/apache/thrift v0.14.1 h1:Yh8v0hpCj63p5edXOLaqTJW0IJ1p+eMW6+YSOqw1d6s= | |
18 | github.com/apache/thrift v0.14.1/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= | |
19 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= | |
20 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= | |
21 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= | |
22 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= | |
23 | github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= | |
24 | github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= | |
25 | github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= | |
26 | github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= | |
27 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= | |
28 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= | |
29 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | |
30 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | |
31 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= | |
32 | github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= | |
33 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= | |
34 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | |
35 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= | |
36 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | |
37 | github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= | |
38 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | |
39 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= | |
40 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= | |
41 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= | |
42 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | |
43 | github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= | |
44 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= | |
45 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
46 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
47 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
48 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | |
49 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | |
50 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= | |
51 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= | |
52 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= | |
53 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= | |
54 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= | |
55 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | |
56 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | |
57 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= | |
58 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= | |
59 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | |
60 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | |
61 | github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= | |
62 | github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= | |
63 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | |
64 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | |
65 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | |
66 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | |
67 | github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= | |
68 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= | |
69 | github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= | |
70 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= | |
71 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | |
72 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= | |
73 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | |
74 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | |
75 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= | |
76 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | |
77 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | |
78 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= | |
79 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= | |
80 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | |
81 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | |
82 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | |
83 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | |
84 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | |
85 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | |
86 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | |
87 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | |
88 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | |
89 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= | |
90 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= | |
91 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= | |
92 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= | |
93 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | |
94 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= | |
95 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | |
96 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | |
97 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= | |
98 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | |
99 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | |
100 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | |
101 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= | |
102 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | |
103 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | |
104 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | |
105 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | |
106 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | |
107 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | |
108 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= | |
109 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | |
110 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | |
111 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | |
112 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= | |
113 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | |
114 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= | |
115 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | |
116 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= | |
117 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | |
118 | github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= | |
119 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | |
120 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= | |
121 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= | |
122 | github.com/hashicorp/consul/api v1.3.0 h1:HXNYlRkkM/t+Y/Yhxtwcy02dlYwIaoxzvxPnS+cqy78= | |
123 | github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= | |
124 | github.com/hashicorp/consul/sdk v0.3.0 h1:UOxjlb4xVNF93jak1mzzoBatyFju9nrkxpVwIp/QqxQ= | |
125 | github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= | |
126 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= | |
127 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | |
128 | github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= | |
129 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= | |
130 | github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= | |
131 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= | |
132 | github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= | |
133 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= | |
134 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= | |
135 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= | |
136 | github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= | |
137 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= | |
138 | github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= | |
139 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= | |
140 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= | |
141 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | |
142 | github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= | |
143 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | |
144 | github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= | |
145 | github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | |
146 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= | |
147 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | |
148 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= | |
149 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | |
150 | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= | |
151 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= | |
152 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= | |
153 | github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= | |
154 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= | |
155 | github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= | |
156 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= | |
157 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= | |
158 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | |
159 | github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= | |
160 | github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= | |
161 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= | |
162 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= | |
163 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | |
164 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | |
165 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | |
166 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | |
167 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= | |
168 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= | |
169 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= | |
170 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= | |
171 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | |
172 | github.com/klauspost/compress v1.11.12 h1:famVnQVu7QwryBN4jNseQdUKES71ZAOnB6UQQJPZvqk= | |
173 | github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= | |
174 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | |
175 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | |
176 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | |
177 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |
178 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | |
179 | github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718 h1:lrdADj7ifyBpqGJ+cT4vE5ztUoAF87uUf76+epwPViY= | |
180 | github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= | |
181 | github.com/lightstep/lightstep-tracer-go v0.22.0 h1:Yy1G8UDT1Ayx010fji4IaTIsmijil5xijvwJ1OoypDk= | |
182 | github.com/lightstep/lightstep-tracer-go v0.22.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A= | |
183 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= | |
184 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= | |
185 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= | |
186 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | |
187 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | |
188 | github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= | |
189 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= | |
190 | github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= | |
191 | github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= | |
192 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= | |
193 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= | |
194 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | |
195 | github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= | |
196 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= | |
197 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= | |
198 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= | |
199 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | |
200 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= | |
201 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | |
202 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | |
203 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | |
204 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | |
205 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | |
206 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | |
207 | github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= | |
208 | github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= | |
209 | github.com/nats-io/jwt/v2 v2.0.2 h1:ejVCLO8gu6/4bOKIHQpmB5UhhUJfAQw55yvLWpfmKjI= | |
210 | github.com/nats-io/jwt/v2 v2.0.2/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= | |
211 | github.com/nats-io/nats-server/v2 v2.2.6 h1:FPK9wWx9pagxcw14s8W9rlfzfyHm61uNLnJyybZbn48= | |
212 | github.com/nats-io/nats-server/v2 v2.2.6/go.mod h1:sEnFaxqe09cDmfMgACxZbziXnhQFhwk+aKkZjBBRYrI= | |
213 | github.com/nats-io/nats.go v1.11.0 h1:L263PZkrmkRJRJT2YHU8GwWWvEvmr9/LUKuJTXsF32k= | |
214 | github.com/nats-io/nats.go v1.11.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= | |
215 | github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= | |
216 | github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= | |
217 | github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= | |
218 | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= | |
219 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= | |
220 | github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= | |
221 | github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= | |
222 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= | |
223 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= | |
224 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | |
225 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= | |
226 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | |
227 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= | |
228 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | |
229 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= | |
230 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= | |
231 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= | |
232 | github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0= | |
233 | github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc= | |
234 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= | |
235 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= | |
236 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= | |
237 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= | |
238 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU= | |
239 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= | |
240 | github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= | |
241 | github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= | |
242 | github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= | |
243 | github.com/pact-foundation/pact-go v1.0.4 h1:OYkFijGHoZAYbOIb1LWXrwKQbMMRUv1oQ89blD2Mh2Q= | |
244 | github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= | |
245 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= | |
246 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= | |
247 | github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= | |
248 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= | |
249 | github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= | |
250 | github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= | |
251 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |
252 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | |
253 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |
254 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= | |
255 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
256 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |
257 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= | |
258 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= | |
259 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= | |
260 | github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= | |
261 | github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= | |
262 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | |
263 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | |
264 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | |
265 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= | |
266 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | |
267 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | |
268 | github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= | |
269 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= | |
270 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | |
271 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | |
272 | github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= | |
273 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= | |
274 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= | |
275 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= | |
276 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | |
277 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= | |
278 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= | |
279 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= | |
280 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= | |
281 | github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY= | |
282 | github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= | |
283 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | |
284 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | |
285 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | |
286 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= | |
287 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | |
288 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= | |
289 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | |
290 | github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= | |
291 | github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= | |
292 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= | |
293 | github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= | |
294 | github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a h1:AhmOdSHeswKHBjhsLs/7+1voOxT+LLrSk/Nxvk35fug= | |
295 | github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= | |
296 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |
297 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |
298 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |
299 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | |
300 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | |
301 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= | |
302 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | |
303 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | |
304 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |
305 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |
306 | go.etcd.io/etcd/api/v3 v3.5.0-beta.4/go.mod h1:yF0YUmBghT48aC0/eTFrhULo+uKQAr5spQQ6sRhPauE= | |
307 | go.etcd.io/etcd/client/pkg/v3 v3.5.0-beta.4/go.mod h1:a+pbz+UrcOpvve1Qxf6tGovi15PjgtRhi0QTO2Nlc4U= | |
308 | go.etcd.io/etcd/client/v2 v2.305.0-beta.4/go.mod h1:PImE2qIZFEHFgOrFrvOX3bJvS2xc4Q9SRpCnuCZTNAE= | |
309 | go.etcd.io/etcd/client/v3 v3.5.0-beta.4/go.mod h1:0L1RulN1QSXq6uKPMUSX+OTAYyFkapMK7iUHXXIH/1E= | |
310 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | |
311 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | |
312 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= | |
313 | go.uber.org/zap v1.16.1-0.20210329175301-c23abee72d19/go.mod h1:aMfIlz3TDBfB0BwTCKFU1XbEmj9zevr5S5LcBr85MXw= | |
314 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | |
315 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | |
316 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |
317 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | |
318 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |
319 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |
320 | golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= | |
321 | golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | |
322 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | |
323 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | |
324 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | |
325 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | |
326 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= | |
327 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | |
328 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | |
329 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | |
330 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
331 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
332 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
333 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
334 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
335 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
336 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
337 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |
338 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |
339 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |
340 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | |
341 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |
342 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |
343 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |
344 | golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | |
345 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | |
346 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | |
347 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= | |
348 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | |
349 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | |
350 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | |
351 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
352 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
353 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
354 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
355 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
356 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
357 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |
358 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
359 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
360 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
361 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
362 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
363 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
364 | golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
365 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
366 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
367 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
368 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
369 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
370 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
371 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
372 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
373 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= | |
374 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
375 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |
376 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |
377 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | |
378 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= | |
379 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | |
380 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= | |
381 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | |
382 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
383 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
384 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
385 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | |
386 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= | |
387 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | |
388 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | |
389 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | |
390 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | |
391 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | |
392 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | |
393 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | |
394 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |
395 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |
396 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |
397 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= | |
398 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |
399 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | |
400 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | |
401 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | |
402 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | |
403 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | |
404 | google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= | |
405 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | |
406 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | |
407 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= | |
408 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= | |
409 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | |
410 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= | |
411 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | |
412 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | |
413 | google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | |
414 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | |
415 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= | |
416 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | |
417 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= | |
418 | google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= | |
419 | google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= | |
420 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | |
421 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | |
422 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | |
423 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= | |
424 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= | |
425 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | |
426 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | |
427 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | |
428 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= | |
429 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | |
430 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= | |
431 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | |
432 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | |
433 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
434 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
435 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= | |
436 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | |
437 | gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= | |
438 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= | |
439 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | |
440 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= | |
441 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
442 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
443 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
444 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
445 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
446 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= | |
447 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |
448 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | |
449 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | |
450 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= | |
451 | sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 h1:ucqkfpjg9WzSUubAO62csmucvxl4/JeW3F4I4909XkM= | |
452 | sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= |
0 | # profilesvc | |
1 | ||
2 | This example demonstrates how to use Go kit to implement a REST-y HTTP service. | |
3 | It leverages the excellent [gorilla mux package](https://github.com/gorilla/mux) for routing. | |
4 | ||
5 | Run the example with the optional port address for the service: | |
6 | ||
7 | ```bash | |
8 | $ go run ./cmd/profilesvc/main.go -http.addr :8080 | |
9 | ts=2018-05-01T16:13:12.849086255Z caller=main.go:47 transport=HTTP addr=:8080 | |
10 | ``` | |
11 | ||
12 | Create a Profile: | |
13 | ||
14 | ```bash | |
15 | $ curl -d '{"id":"1234","Name":"Go Kit"}' -H "Content-Type: application/json" -X POST http://localhost:8080/profiles/ | |
16 | {} | |
17 | ``` | |
18 | ||
19 | Get the profile you just created | |
20 | ||
21 | ```bash | |
22 | $ curl localhost:8080/profiles/1234 | |
23 | {"profile":{"id":"1234","name":"Go Kit"}} | |
24 | ``` |
0 | // Package client provides a profilesvc client based on a predefined Consul | |
1 | // service name and relevant tags. Users must only provide the address of a | |
2 | // Consul server. | |
3 | package client | |
4 | ||
5 | import ( | |
6 | "io" | |
7 | "time" | |
8 | ||
9 | consulapi "github.com/hashicorp/consul/api" | |
10 | ||
11 | "github.com/go-kit/kit/endpoint" | |
12 | "github.com/go-kit/kit/examples/profilesvc" | |
13 | "github.com/go-kit/kit/log" | |
14 | "github.com/go-kit/kit/sd" | |
15 | "github.com/go-kit/kit/sd/consul" | |
16 | "github.com/go-kit/kit/sd/lb" | |
17 | ) | |
18 | ||
19 | // New returns a service that's load-balanced over instances of profilesvc found | |
20 | // in the provided Consul server. The mechanism of looking up profilesvc | |
21 | // instances in Consul is hard-coded into the client. | |
22 | func New(consulAddr string, logger log.Logger) (profilesvc.Service, error) { | |
23 | apiclient, err := consulapi.NewClient(&consulapi.Config{ | |
24 | Address: consulAddr, | |
25 | }) | |
26 | if err != nil { | |
27 | return nil, err | |
28 | } | |
29 | ||
30 | // As the implementer of profilesvc, we declare and enforce these | |
31 | // parameters for all of the profilesvc consumers. | |
32 | var ( | |
33 | consulService = "profilesvc" | |
34 | consulTags = []string{"prod"} | |
35 | passingOnly = true | |
36 | retryMax = 3 | |
37 | retryTimeout = 500 * time.Millisecond | |
38 | ) | |
39 | ||
40 | var ( | |
41 | sdclient = consul.NewClient(apiclient) | |
42 | instancer = consul.NewInstancer(sdclient, logger, consulService, consulTags, passingOnly) | |
43 | endpoints profilesvc.Endpoints | |
44 | ) | |
45 | { | |
46 | factory := factoryFor(profilesvc.MakePostProfileEndpoint) | |
47 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
48 | balancer := lb.NewRoundRobin(endpointer) | |
49 | retry := lb.Retry(retryMax, retryTimeout, balancer) | |
50 | endpoints.PostProfileEndpoint = retry | |
51 | } | |
52 | { | |
53 | factory := factoryFor(profilesvc.MakeGetProfileEndpoint) | |
54 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
55 | balancer := lb.NewRoundRobin(endpointer) | |
56 | retry := lb.Retry(retryMax, retryTimeout, balancer) | |
57 | endpoints.GetProfileEndpoint = retry | |
58 | } | |
59 | { | |
60 | factory := factoryFor(profilesvc.MakePutProfileEndpoint) | |
61 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
62 | balancer := lb.NewRoundRobin(endpointer) | |
63 | retry := lb.Retry(retryMax, retryTimeout, balancer) | |
64 | endpoints.PutProfileEndpoint = retry | |
65 | } | |
66 | { | |
67 | factory := factoryFor(profilesvc.MakePatchProfileEndpoint) | |
68 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
69 | balancer := lb.NewRoundRobin(endpointer) | |
70 | retry := lb.Retry(retryMax, retryTimeout, balancer) | |
71 | endpoints.PatchProfileEndpoint = retry | |
72 | } | |
73 | { | |
74 | factory := factoryFor(profilesvc.MakeDeleteProfileEndpoint) | |
75 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
76 | balancer := lb.NewRoundRobin(endpointer) | |
77 | retry := lb.Retry(retryMax, retryTimeout, balancer) | |
78 | endpoints.DeleteProfileEndpoint = retry | |
79 | } | |
80 | { | |
81 | factory := factoryFor(profilesvc.MakeGetAddressesEndpoint) | |
82 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
83 | balancer := lb.NewRoundRobin(endpointer) | |
84 | retry := lb.Retry(retryMax, retryTimeout, balancer) | |
85 | endpoints.GetAddressesEndpoint = retry | |
86 | } | |
87 | { | |
88 | factory := factoryFor(profilesvc.MakeGetAddressEndpoint) | |
89 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
90 | balancer := lb.NewRoundRobin(endpointer) | |
91 | retry := lb.Retry(retryMax, retryTimeout, balancer) | |
92 | endpoints.GetAddressEndpoint = retry | |
93 | } | |
94 | { | |
95 | factory := factoryFor(profilesvc.MakePostAddressEndpoint) | |
96 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
97 | balancer := lb.NewRoundRobin(endpointer) | |
98 | retry := lb.Retry(retryMax, retryTimeout, balancer) | |
99 | endpoints.PostAddressEndpoint = retry | |
100 | } | |
101 | { | |
102 | factory := factoryFor(profilesvc.MakeDeleteAddressEndpoint) | |
103 | endpointer := sd.NewEndpointer(instancer, factory, logger) | |
104 | balancer := lb.NewRoundRobin(endpointer) | |
105 | retry := lb.Retry(retryMax, retryTimeout, balancer) | |
106 | endpoints.DeleteAddressEndpoint = retry | |
107 | } | |
108 | ||
109 | return endpoints, nil | |
110 | } | |
111 | ||
112 | func factoryFor(makeEndpoint func(profilesvc.Service) endpoint.Endpoint) sd.Factory { | |
113 | return func(instance string) (endpoint.Endpoint, io.Closer, error) { | |
114 | service, err := profilesvc.MakeClientEndpoints(instance) | |
115 | if err != nil { | |
116 | return nil, nil, err | |
117 | } | |
118 | return makeEndpoint(service), nil, nil | |
119 | } | |
120 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "flag" | |
4 | "fmt" | |
5 | "net/http" | |
6 | "os" | |
7 | "os/signal" | |
8 | "syscall" | |
9 | ||
10 | "github.com/go-kit/kit/examples/profilesvc" | |
11 | "github.com/go-kit/kit/log" | |
12 | ) | |
13 | ||
14 | func main() { | |
15 | var ( | |
16 | httpAddr = flag.String("http.addr", ":8080", "HTTP listen address") | |
17 | ) | |
18 | flag.Parse() | |
19 | ||
20 | var logger log.Logger | |
21 | { | |
22 | logger = log.NewLogfmtLogger(os.Stderr) | |
23 | logger = log.With(logger, "ts", log.DefaultTimestampUTC) | |
24 | logger = log.With(logger, "caller", log.DefaultCaller) | |
25 | } | |
26 | ||
27 | var s profilesvc.Service | |
28 | { | |
29 | s = profilesvc.NewInmemService() | |
30 | s = profilesvc.LoggingMiddleware(logger)(s) | |
31 | } | |
32 | ||
33 | var h http.Handler | |
34 | { | |
35 | h = profilesvc.MakeHTTPHandler(s, log.With(logger, "component", "HTTP")) | |
36 | } | |
37 | ||
38 | errs := make(chan error) | |
39 | go func() { | |
40 | c := make(chan os.Signal) | |
41 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) | |
42 | errs <- fmt.Errorf("%s", <-c) | |
43 | }() | |
44 | ||
45 | go func() { | |
46 | logger.Log("transport", "HTTP", "addr", *httpAddr) | |
47 | errs <- http.ListenAndServe(*httpAddr, h) | |
48 | }() | |
49 | ||
50 | logger.Log("exit", <-errs) | |
51 | } |
0 | package profilesvc | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "net/url" | |
5 | "strings" | |
6 | ||
7 | "github.com/go-kit/kit/endpoint" | |
8 | httptransport "github.com/go-kit/kit/transport/http" | |
9 | ) | |
10 | ||
11 | // Endpoints collects all of the endpoints that compose a profile service. It's | |
12 | // meant to be used as a helper struct, to collect all of the endpoints into a | |
13 | // single parameter. | |
14 | // | |
15 | // In a server, it's useful for functions that need to operate on a per-endpoint | |
16 | // basis. For example, you might pass an Endpoints to a function that produces | |
17 | // an http.Handler, with each method (endpoint) wired up to a specific path. (It | |
18 | // is probably a mistake in design to invoke the Service methods on the | |
19 | // Endpoints struct in a server.) | |
20 | // | |
21 | // In a client, it's useful to collect individually constructed endpoints into a | |
22 | // single type that implements the Service interface. For example, you might | |
23 | // construct individual endpoints using transport/http.NewClient, combine them | |
24 | // into an Endpoints, and return it to the caller as a Service. | |
25 | type Endpoints struct { | |
26 | PostProfileEndpoint endpoint.Endpoint | |
27 | GetProfileEndpoint endpoint.Endpoint | |
28 | PutProfileEndpoint endpoint.Endpoint | |
29 | PatchProfileEndpoint endpoint.Endpoint | |
30 | DeleteProfileEndpoint endpoint.Endpoint | |
31 | GetAddressesEndpoint endpoint.Endpoint | |
32 | GetAddressEndpoint endpoint.Endpoint | |
33 | PostAddressEndpoint endpoint.Endpoint | |
34 | DeleteAddressEndpoint endpoint.Endpoint | |
35 | } | |
36 | ||
37 | // MakeServerEndpoints returns an Endpoints struct where each endpoint invokes | |
38 | // the corresponding method on the provided service. Useful in a profilesvc | |
39 | // server. | |
40 | func MakeServerEndpoints(s Service) Endpoints { | |
41 | return Endpoints{ | |
42 | PostProfileEndpoint: MakePostProfileEndpoint(s), | |
43 | GetProfileEndpoint: MakeGetProfileEndpoint(s), | |
44 | PutProfileEndpoint: MakePutProfileEndpoint(s), | |
45 | PatchProfileEndpoint: MakePatchProfileEndpoint(s), | |
46 | DeleteProfileEndpoint: MakeDeleteProfileEndpoint(s), | |
47 | GetAddressesEndpoint: MakeGetAddressesEndpoint(s), | |
48 | GetAddressEndpoint: MakeGetAddressEndpoint(s), | |
49 | PostAddressEndpoint: MakePostAddressEndpoint(s), | |
50 | DeleteAddressEndpoint: MakeDeleteAddressEndpoint(s), | |
51 | } | |
52 | } | |
53 | ||
54 | // MakeClientEndpoints returns an Endpoints struct where each endpoint invokes | |
55 | // the corresponding method on the remote instance, via a transport/http.Client. | |
56 | // Useful in a profilesvc client. | |
57 | func MakeClientEndpoints(instance string) (Endpoints, error) { | |
58 | if !strings.HasPrefix(instance, "http") { | |
59 | instance = "http://" + instance | |
60 | } | |
61 | tgt, err := url.Parse(instance) | |
62 | if err != nil { | |
63 | return Endpoints{}, err | |
64 | } | |
65 | tgt.Path = "" | |
66 | ||
67 | options := []httptransport.ClientOption{} | |
68 | ||
69 | // Note that the request encoders need to modify the request URL, changing | |
70 | // the path. That's fine: we simply need to provide specific encoders for | |
71 | // each endpoint. | |
72 | ||
73 | return Endpoints{ | |
74 | PostProfileEndpoint: httptransport.NewClient("POST", tgt, encodePostProfileRequest, decodePostProfileResponse, options...).Endpoint(), | |
75 | GetProfileEndpoint: httptransport.NewClient("GET", tgt, encodeGetProfileRequest, decodeGetProfileResponse, options...).Endpoint(), | |
76 | PutProfileEndpoint: httptransport.NewClient("PUT", tgt, encodePutProfileRequest, decodePutProfileResponse, options...).Endpoint(), | |
77 | PatchProfileEndpoint: httptransport.NewClient("PATCH", tgt, encodePatchProfileRequest, decodePatchProfileResponse, options...).Endpoint(), | |
78 | DeleteProfileEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteProfileRequest, decodeDeleteProfileResponse, options...).Endpoint(), | |
79 | GetAddressesEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressesRequest, decodeGetAddressesResponse, options...).Endpoint(), | |
80 | GetAddressEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressRequest, decodeGetAddressResponse, options...).Endpoint(), | |
81 | PostAddressEndpoint: httptransport.NewClient("POST", tgt, encodePostAddressRequest, decodePostAddressResponse, options...).Endpoint(), | |
82 | DeleteAddressEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteAddressRequest, decodeDeleteAddressResponse, options...).Endpoint(), | |
83 | }, nil | |
84 | } | |
85 | ||
86 | // PostProfile implements Service. Primarily useful in a client. | |
87 | func (e Endpoints) PostProfile(ctx context.Context, p Profile) error { | |
88 | request := postProfileRequest{Profile: p} | |
89 | response, err := e.PostProfileEndpoint(ctx, request) | |
90 | if err != nil { | |
91 | return err | |
92 | } | |
93 | resp := response.(postProfileResponse) | |
94 | return resp.Err | |
95 | } | |
96 | ||
97 | // GetProfile implements Service. Primarily useful in a client. | |
98 | func (e Endpoints) GetProfile(ctx context.Context, id string) (Profile, error) { | |
99 | request := getProfileRequest{ID: id} | |
100 | response, err := e.GetProfileEndpoint(ctx, request) | |
101 | if err != nil { | |
102 | return Profile{}, err | |
103 | } | |
104 | resp := response.(getProfileResponse) | |
105 | return resp.Profile, resp.Err | |
106 | } | |
107 | ||
108 | // PutProfile implements Service. Primarily useful in a client. | |
109 | func (e Endpoints) PutProfile(ctx context.Context, id string, p Profile) error { | |
110 | request := putProfileRequest{ID: id, Profile: p} | |
111 | response, err := e.PutProfileEndpoint(ctx, request) | |
112 | if err != nil { | |
113 | return err | |
114 | } | |
115 | resp := response.(putProfileResponse) | |
116 | return resp.Err | |
117 | } | |
118 | ||
119 | // PatchProfile implements Service. Primarily useful in a client. | |
120 | func (e Endpoints) PatchProfile(ctx context.Context, id string, p Profile) error { | |
121 | request := patchProfileRequest{ID: id, Profile: p} | |
122 | response, err := e.PatchProfileEndpoint(ctx, request) | |
123 | if err != nil { | |
124 | return err | |
125 | } | |
126 | resp := response.(patchProfileResponse) | |
127 | return resp.Err | |
128 | } | |
129 | ||
130 | // DeleteProfile implements Service. Primarily useful in a client. | |
131 | func (e Endpoints) DeleteProfile(ctx context.Context, id string) error { | |
132 | request := deleteProfileRequest{ID: id} | |
133 | response, err := e.DeleteProfileEndpoint(ctx, request) | |
134 | if err != nil { | |
135 | return err | |
136 | } | |
137 | resp := response.(deleteProfileResponse) | |
138 | return resp.Err | |
139 | } | |
140 | ||
141 | // GetAddresses implements Service. Primarily useful in a client. | |
142 | func (e Endpoints) GetAddresses(ctx context.Context, profileID string) ([]Address, error) { | |
143 | request := getAddressesRequest{ProfileID: profileID} | |
144 | response, err := e.GetAddressesEndpoint(ctx, request) | |
145 | if err != nil { | |
146 | return nil, err | |
147 | } | |
148 | resp := response.(getAddressesResponse) | |
149 | return resp.Addresses, resp.Err | |
150 | } | |
151 | ||
152 | // GetAddress implements Service. Primarily useful in a client. | |
153 | func (e Endpoints) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) { | |
154 | request := getAddressRequest{ProfileID: profileID, AddressID: addressID} | |
155 | response, err := e.GetAddressEndpoint(ctx, request) | |
156 | if err != nil { | |
157 | return Address{}, err | |
158 | } | |
159 | resp := response.(getAddressResponse) | |
160 | return resp.Address, resp.Err | |
161 | } | |
162 | ||
163 | // PostAddress implements Service. Primarily useful in a client. | |
164 | func (e Endpoints) PostAddress(ctx context.Context, profileID string, a Address) error { | |
165 | request := postAddressRequest{ProfileID: profileID, Address: a} | |
166 | response, err := e.PostAddressEndpoint(ctx, request) | |
167 | if err != nil { | |
168 | return err | |
169 | } | |
170 | resp := response.(postAddressResponse) | |
171 | return resp.Err | |
172 | } | |
173 | ||
174 | // DeleteAddress implements Service. Primarily useful in a client. | |
175 | func (e Endpoints) DeleteAddress(ctx context.Context, profileID string, addressID string) error { | |
176 | request := deleteAddressRequest{ProfileID: profileID, AddressID: addressID} | |
177 | response, err := e.DeleteAddressEndpoint(ctx, request) | |
178 | if err != nil { | |
179 | return err | |
180 | } | |
181 | resp := response.(deleteAddressResponse) | |
182 | return resp.Err | |
183 | } | |
184 | ||
185 | // MakePostProfileEndpoint returns an endpoint via the passed service. | |
186 | // Primarily useful in a server. | |
187 | func MakePostProfileEndpoint(s Service) endpoint.Endpoint { | |
188 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
189 | req := request.(postProfileRequest) | |
190 | e := s.PostProfile(ctx, req.Profile) | |
191 | return postProfileResponse{Err: e}, nil | |
192 | } | |
193 | } | |
194 | ||
195 | // MakeGetProfileEndpoint returns an endpoint via the passed service. | |
196 | // Primarily useful in a server. | |
197 | func MakeGetProfileEndpoint(s Service) endpoint.Endpoint { | |
198 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
199 | req := request.(getProfileRequest) | |
200 | p, e := s.GetProfile(ctx, req.ID) | |
201 | return getProfileResponse{Profile: p, Err: e}, nil | |
202 | } | |
203 | } | |
204 | ||
205 | // MakePutProfileEndpoint returns an endpoint via the passed service. | |
206 | // Primarily useful in a server. | |
207 | func MakePutProfileEndpoint(s Service) endpoint.Endpoint { | |
208 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
209 | req := request.(putProfileRequest) | |
210 | e := s.PutProfile(ctx, req.ID, req.Profile) | |
211 | return putProfileResponse{Err: e}, nil | |
212 | } | |
213 | } | |
214 | ||
215 | // MakePatchProfileEndpoint returns an endpoint via the passed service. | |
216 | // Primarily useful in a server. | |
217 | func MakePatchProfileEndpoint(s Service) endpoint.Endpoint { | |
218 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
219 | req := request.(patchProfileRequest) | |
220 | e := s.PatchProfile(ctx, req.ID, req.Profile) | |
221 | return patchProfileResponse{Err: e}, nil | |
222 | } | |
223 | } | |
224 | ||
225 | // MakeDeleteProfileEndpoint returns an endpoint via the passed service. | |
226 | // Primarily useful in a server. | |
227 | func MakeDeleteProfileEndpoint(s Service) endpoint.Endpoint { | |
228 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
229 | req := request.(deleteProfileRequest) | |
230 | e := s.DeleteProfile(ctx, req.ID) | |
231 | return deleteProfileResponse{Err: e}, nil | |
232 | } | |
233 | } | |
234 | ||
235 | // MakeGetAddressesEndpoint returns an endpoint via the passed service. | |
236 | // Primarily useful in a server. | |
237 | func MakeGetAddressesEndpoint(s Service) endpoint.Endpoint { | |
238 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
239 | req := request.(getAddressesRequest) | |
240 | a, e := s.GetAddresses(ctx, req.ProfileID) | |
241 | return getAddressesResponse{Addresses: a, Err: e}, nil | |
242 | } | |
243 | } | |
244 | ||
245 | // MakeGetAddressEndpoint returns an endpoint via the passed service. | |
246 | // Primarily useful in a server. | |
247 | func MakeGetAddressEndpoint(s Service) endpoint.Endpoint { | |
248 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
249 | req := request.(getAddressRequest) | |
250 | a, e := s.GetAddress(ctx, req.ProfileID, req.AddressID) | |
251 | return getAddressResponse{Address: a, Err: e}, nil | |
252 | } | |
253 | } | |
254 | ||
255 | // MakePostAddressEndpoint returns an endpoint via the passed service. | |
256 | // Primarily useful in a server. | |
257 | func MakePostAddressEndpoint(s Service) endpoint.Endpoint { | |
258 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
259 | req := request.(postAddressRequest) | |
260 | e := s.PostAddress(ctx, req.ProfileID, req.Address) | |
261 | return postAddressResponse{Err: e}, nil | |
262 | } | |
263 | } | |
264 | ||
265 | // MakeDeleteAddressEndpoint returns an endpoint via the passed service. | |
266 | // Primarily useful in a server. | |
267 | func MakeDeleteAddressEndpoint(s Service) endpoint.Endpoint { | |
268 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { | |
269 | req := request.(deleteAddressRequest) | |
270 | e := s.DeleteAddress(ctx, req.ProfileID, req.AddressID) | |
271 | return deleteAddressResponse{Err: e}, nil | |
272 | } | |
273 | } | |
274 | ||
275 | // We have two options to return errors from the business logic. | |
276 | // | |
277 | // We could return the error via the endpoint itself. That makes certain things | |
278 | // a little bit easier, like providing non-200 HTTP responses to the client. But | |
279 | // Go kit assumes that endpoint errors are (or may be treated as) | |
280 | // transport-domain errors. For example, an endpoint error will count against a | |
281 | // circuit breaker error count. | |
282 | // | |
283 | // Therefore, it's often better to return service (business logic) errors in the | |
284 | // response object. This means we have to do a bit more work in the HTTP | |
285 | // response encoder to detect e.g. a not-found error and provide a proper HTTP | |
286 | // status code. That work is done with the errorer interface, in transport.go. | |
287 | // Response types that may contain business-logic errors implement that | |
288 | // interface. | |
289 | ||
290 | type postProfileRequest struct { | |
291 | Profile Profile | |
292 | } | |
293 | ||
294 | type postProfileResponse struct { | |
295 | Err error `json:"err,omitempty"` | |
296 | } | |
297 | ||
298 | func (r postProfileResponse) error() error { return r.Err } | |
299 | ||
300 | type getProfileRequest struct { | |
301 | ID string | |
302 | } | |
303 | ||
304 | type getProfileResponse struct { | |
305 | Profile Profile `json:"profile,omitempty"` | |
306 | Err error `json:"err,omitempty"` | |
307 | } | |
308 | ||
309 | func (r getProfileResponse) error() error { return r.Err } | |
310 | ||
311 | type putProfileRequest struct { | |
312 | ID string | |
313 | Profile Profile | |
314 | } | |
315 | ||
316 | type putProfileResponse struct { | |
317 | Err error `json:"err,omitempty"` | |
318 | } | |
319 | ||
320 | func (r putProfileResponse) error() error { return nil } | |
321 | ||
322 | type patchProfileRequest struct { | |
323 | ID string | |
324 | Profile Profile | |
325 | } | |
326 | ||
327 | type patchProfileResponse struct { | |
328 | Err error `json:"err,omitempty"` | |
329 | } | |
330 | ||
331 | func (r patchProfileResponse) error() error { return r.Err } | |
332 | ||
333 | type deleteProfileRequest struct { | |
334 | ID string | |
335 | } | |
336 | ||
337 | type deleteProfileResponse struct { | |
338 | Err error `json:"err,omitempty"` | |
339 | } | |
340 | ||
341 | func (r deleteProfileResponse) error() error { return r.Err } | |
342 | ||
343 | type getAddressesRequest struct { | |
344 | ProfileID string | |
345 | } | |
346 | ||
347 | type getAddressesResponse struct { | |
348 | Addresses []Address `json:"addresses,omitempty"` | |
349 | Err error `json:"err,omitempty"` | |
350 | } | |
351 | ||
352 | func (r getAddressesResponse) error() error { return r.Err } | |
353 | ||
354 | type getAddressRequest struct { | |
355 | ProfileID string | |
356 | AddressID string | |
357 | } | |
358 | ||
359 | type getAddressResponse struct { | |
360 | Address Address `json:"address,omitempty"` | |
361 | Err error `json:"err,omitempty"` | |
362 | } | |
363 | ||
364 | func (r getAddressResponse) error() error { return r.Err } | |
365 | ||
366 | type postAddressRequest struct { | |
367 | ProfileID string | |
368 | Address Address | |
369 | } | |
370 | ||
371 | type postAddressResponse struct { | |
372 | Err error `json:"err,omitempty"` | |
373 | } | |
374 | ||
375 | func (r postAddressResponse) error() error { return r.Err } | |
376 | ||
377 | type deleteAddressRequest struct { | |
378 | ProfileID string | |
379 | AddressID string | |
380 | } | |
381 | ||
382 | type deleteAddressResponse struct { | |
383 | Err error `json:"err,omitempty"` | |
384 | } | |
385 | ||
386 | func (r deleteAddressResponse) error() error { return r.Err } |
0 | package profilesvc | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "time" | |
5 | ||
6 | "github.com/go-kit/kit/log" | |
7 | ) | |
8 | ||
9 | // Middleware describes a service (as opposed to endpoint) middleware. | |
10 | type Middleware func(Service) Service | |
11 | ||
12 | func LoggingMiddleware(logger log.Logger) Middleware { | |
13 | return func(next Service) Service { | |
14 | return &loggingMiddleware{ | |
15 | next: next, | |
16 | logger: logger, | |
17 | } | |
18 | } | |
19 | } | |
20 | ||
21 | type loggingMiddleware struct { | |
22 | next Service | |
23 | logger log.Logger | |
24 | } | |
25 | ||
26 | func (mw loggingMiddleware) PostProfile(ctx context.Context, p Profile) (err error) { | |
27 | defer func(begin time.Time) { | |
28 | mw.logger.Log("method", "PostProfile", "id", p.ID, "took", time.Since(begin), "err", err) | |
29 | }(time.Now()) | |
30 | return mw.next.PostProfile(ctx, p) | |
31 | } | |
32 | ||
33 | func (mw loggingMiddleware) GetProfile(ctx context.Context, id string) (p Profile, err error) { | |
34 | defer func(begin time.Time) { | |
35 | mw.logger.Log("method", "GetProfile", "id", id, "took", time.Since(begin), "err", err) | |
36 | }(time.Now()) | |
37 | return mw.next.GetProfile(ctx, id) | |
38 | } | |
39 | ||
40 | func (mw loggingMiddleware) PutProfile(ctx context.Context, id string, p Profile) (err error) { | |
41 | defer func(begin time.Time) { | |
42 | mw.logger.Log("method", "PutProfile", "id", id, "took", time.Since(begin), "err", err) | |
43 | }(time.Now()) | |
44 | return mw.next.PutProfile(ctx, id, p) | |
45 | } | |
46 | ||
47 | func (mw loggingMiddleware) PatchProfile(ctx context.Context, id string, p Profile) (err error) { | |
48 | defer func(begin time.Time) { | |
49 | mw.logger.Log("method", "PatchProfile", "id", id, "took", time.Since(begin), "err", err) | |
50 | }(time.Now()) | |
51 | return mw.next.PatchProfile(ctx, id, p) | |
52 | } | |
53 | ||
54 | func (mw loggingMiddleware) DeleteProfile(ctx context.Context, id string) (err error) { | |
55 | defer func(begin time.Time) { | |
56 | mw.logger.Log("method", "DeleteProfile", "id", id, "took", time.Since(begin), "err", err) | |
57 | }(time.Now()) | |
58 | return mw.next.DeleteProfile(ctx, id) | |
59 | } | |
60 | ||
61 | func (mw loggingMiddleware) GetAddresses(ctx context.Context, profileID string) (addresses []Address, err error) { | |
62 | defer func(begin time.Time) { | |
63 | mw.logger.Log("method", "GetAddresses", "profileID", profileID, "took", time.Since(begin), "err", err) | |
64 | }(time.Now()) | |
65 | return mw.next.GetAddresses(ctx, profileID) | |
66 | } | |
67 | ||
68 | func (mw loggingMiddleware) GetAddress(ctx context.Context, profileID string, addressID string) (a Address, err error) { | |
69 | defer func(begin time.Time) { | |
70 | mw.logger.Log("method", "GetAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err) | |
71 | }(time.Now()) | |
72 | return mw.next.GetAddress(ctx, profileID, addressID) | |
73 | } | |
74 | ||
75 | func (mw loggingMiddleware) PostAddress(ctx context.Context, profileID string, a Address) (err error) { | |
76 | defer func(begin time.Time) { | |
77 | mw.logger.Log("method", "PostAddress", "profileID", profileID, "took", time.Since(begin), "err", err) | |
78 | }(time.Now()) | |
79 | return mw.next.PostAddress(ctx, profileID, a) | |
80 | } | |
81 | ||
82 | func (mw loggingMiddleware) DeleteAddress(ctx context.Context, profileID string, addressID string) (err error) { | |
83 | defer func(begin time.Time) { | |
84 | mw.logger.Log("method", "DeleteAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err) | |
85 | }(time.Now()) | |
86 | return mw.next.DeleteAddress(ctx, profileID, addressID) | |
87 | } |
0 | package profilesvc | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "errors" | |
5 | "sync" | |
6 | ) | |
7 | ||
8 | // Service is a simple CRUD interface for user profiles. | |
9 | type Service interface { | |
10 | PostProfile(ctx context.Context, p Profile) error | |
11 | GetProfile(ctx context.Context, id string) (Profile, error) | |
12 | PutProfile(ctx context.Context, id string, p Profile) error | |
13 | PatchProfile(ctx context.Context, id string, p Profile) error | |
14 | DeleteProfile(ctx context.Context, id string) error | |
15 | GetAddresses(ctx context.Context, profileID string) ([]Address, error) | |
16 | GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) | |
17 | PostAddress(ctx context.Context, profileID string, a Address) error | |
18 | DeleteAddress(ctx context.Context, profileID string, addressID string) error | |
19 | } | |
20 | ||
21 | // Profile represents a single user profile. | |
22 | // ID should be globally unique. | |
23 | type Profile struct { | |
24 | ID string `json:"id"` | |
25 | Name string `json:"name,omitempty"` | |
26 | Addresses []Address `json:"addresses,omitempty"` | |
27 | } | |
28 | ||
29 | // Address is a field of a user profile. | |
30 | // ID should be unique within the profile (at a minimum). | |
31 | type Address struct { | |
32 | ID string `json:"id"` | |
33 | Location string `json:"location,omitempty"` | |
34 | } | |
35 | ||
36 | var ( | |
37 | ErrInconsistentIDs = errors.New("inconsistent IDs") | |
38 | ErrAlreadyExists = errors.New("already exists") | |
39 | ErrNotFound = errors.New("not found") | |
40 | ) | |
41 | ||
42 | type inmemService struct { | |
43 | mtx sync.RWMutex | |
44 | m map[string]Profile | |
45 | } | |
46 | ||
47 | func NewInmemService() Service { | |
48 | return &inmemService{ | |
49 | m: map[string]Profile{}, | |
50 | } | |
51 | } | |
52 | ||
53 | func (s *inmemService) PostProfile(ctx context.Context, p Profile) error { | |
54 | s.mtx.Lock() | |
55 | defer s.mtx.Unlock() | |
56 | if _, ok := s.m[p.ID]; ok { | |
57 | return ErrAlreadyExists // POST = create, don't overwrite | |
58 | } | |
59 | s.m[p.ID] = p | |
60 | return nil | |
61 | } | |
62 | ||
63 | func (s *inmemService) GetProfile(ctx context.Context, id string) (Profile, error) { | |
64 | s.mtx.RLock() | |
65 | defer s.mtx.RUnlock() | |
66 | p, ok := s.m[id] | |
67 | if !ok { | |
68 | return Profile{}, ErrNotFound | |
69 | } | |
70 | return p, nil | |
71 | } | |
72 | ||
73 | func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error { | |
74 | if id != p.ID { | |
75 | return ErrInconsistentIDs | |
76 | } | |
77 | s.mtx.Lock() | |
78 | defer s.mtx.Unlock() | |
79 | s.m[id] = p // PUT = create or update | |
80 | return nil | |
81 | } | |
82 | ||
83 | func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error { | |
84 | if p.ID != "" && id != p.ID { | |
85 | return ErrInconsistentIDs | |
86 | } | |
87 | ||
88 | s.mtx.Lock() | |
89 | defer s.mtx.Unlock() | |
90 | ||
91 | existing, ok := s.m[id] | |
92 | if !ok { | |
93 | return ErrNotFound // PATCH = update existing, don't create | |
94 | } | |
95 | ||
96 | // We assume that it's not possible to PATCH the ID, and that it's not | |
97 | // possible to PATCH any field to its zero value. That is, the zero value | |
98 | // means not specified. The way around this is to use e.g. Name *string in | |
99 | // the Profile definition. But since this is just a demonstrative example, | |
100 | // I'm leaving that out. | |
101 | ||
102 | if p.Name != "" { | |
103 | existing.Name = p.Name | |
104 | } | |
105 | if len(p.Addresses) > 0 { | |
106 | existing.Addresses = p.Addresses | |
107 | } | |
108 | s.m[id] = existing | |
109 | return nil | |
110 | } | |
111 | ||
112 | func (s *inmemService) DeleteProfile(ctx context.Context, id string) error { | |
113 | s.mtx.Lock() | |
114 | defer s.mtx.Unlock() | |
115 | if _, ok := s.m[id]; !ok { | |
116 | return ErrNotFound | |
117 | } | |
118 | delete(s.m, id) | |
119 | return nil | |
120 | } | |
121 | ||
122 | func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) { | |
123 | s.mtx.RLock() | |
124 | defer s.mtx.RUnlock() | |
125 | p, ok := s.m[profileID] | |
126 | if !ok { | |
127 | return []Address{}, ErrNotFound | |
128 | } | |
129 | return p.Addresses, nil | |
130 | } | |
131 | ||
132 | func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) { | |
133 | s.mtx.RLock() | |
134 | defer s.mtx.RUnlock() | |
135 | p, ok := s.m[profileID] | |
136 | if !ok { | |
137 | return Address{}, ErrNotFound | |
138 | } | |
139 | for _, address := range p.Addresses { | |
140 | if address.ID == addressID { | |
141 | return address, nil | |
142 | } | |
143 | } | |
144 | return Address{}, ErrNotFound | |
145 | } | |
146 | ||
147 | func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error { | |
148 | s.mtx.Lock() | |
149 | defer s.mtx.Unlock() | |
150 | p, ok := s.m[profileID] | |
151 | if !ok { | |
152 | return ErrNotFound | |
153 | } | |
154 | for _, address := range p.Addresses { | |
155 | if address.ID == a.ID { | |
156 | return ErrAlreadyExists | |
157 | } | |
158 | } | |
159 | p.Addresses = append(p.Addresses, a) | |
160 | s.m[profileID] = p | |
161 | return nil | |
162 | } | |
163 | ||
164 | func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error { | |
165 | s.mtx.Lock() | |
166 | defer s.mtx.Unlock() | |
167 | p, ok := s.m[profileID] | |
168 | if !ok { | |
169 | return ErrNotFound | |
170 | } | |
171 | newAddresses := make([]Address, 0, len(p.Addresses)) | |
172 | for _, address := range p.Addresses { | |
173 | if address.ID == addressID { | |
174 | continue // delete | |
175 | } | |
176 | newAddresses = append(newAddresses, address) | |
177 | } | |
178 | if len(newAddresses) == len(p.Addresses) { | |
179 | return ErrNotFound | |
180 | } | |
181 | p.Addresses = newAddresses | |
182 | s.m[profileID] = p | |
183 | return nil | |
184 | } |
0 | package profilesvc | |
1 | ||
2 | // The profilesvc is just over HTTP, so we just have a single transport.go. | |
3 | ||
4 | import ( | |
5 | "bytes" | |
6 | "context" | |
7 | "encoding/json" | |
8 | "errors" | |
9 | "io/ioutil" | |
10 | "net/http" | |
11 | "net/url" | |
12 | ||
13 | "github.com/gorilla/mux" | |
14 | ||
15 | "github.com/go-kit/kit/log" | |
16 | "github.com/go-kit/kit/transport" | |
17 | httptransport "github.com/go-kit/kit/transport/http" | |
18 | ) | |
19 | ||
20 | var ( | |
21 | // ErrBadRouting is returned when an expected path variable is missing. | |
22 | // It always indicates programmer error. | |
23 | ErrBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)") | |
24 | ) | |
25 | ||
26 | // MakeHTTPHandler mounts all of the service endpoints into an http.Handler. | |
27 | // Useful in a profilesvc server. | |
28 | func MakeHTTPHandler(s Service, logger log.Logger) http.Handler { | |
29 | r := mux.NewRouter() | |
30 | e := MakeServerEndpoints(s) | |
31 | options := []httptransport.ServerOption{ | |
32 | httptransport.ServerErrorHandler(transport.NewLogErrorHandler(logger)), | |
33 | httptransport.ServerErrorEncoder(encodeError), | |
34 | } | |
35 | ||
36 | // POST /profiles/ adds another profile | |
37 | // GET /profiles/:id retrieves the given profile by id | |
38 | // PUT /profiles/:id post updated profile information about the profile | |
39 | // PATCH /profiles/:id partial updated profile information | |
40 | // DELETE /profiles/:id remove the given profile | |
41 | // GET /profiles/:id/addresses/ retrieve addresses associated with the profile | |
42 | // GET /profiles/:id/addresses/:addressID retrieve a particular profile address | |
43 | // POST /profiles/:id/addresses/ add a new address | |
44 | // DELETE /profiles/:id/addresses/:addressID remove an address | |
45 | ||
46 | r.Methods("POST").Path("/profiles/").Handler(httptransport.NewServer( | |
47 | e.PostProfileEndpoint, | |
48 | decodePostProfileRequest, | |
49 | encodeResponse, | |
50 | options..., | |
51 | )) | |
52 | r.Methods("GET").Path("/profiles/{id}").Handler(httptransport.NewServer( | |
53 | e.GetProfileEndpoint, | |
54 | decodeGetProfileRequest, | |
55 | encodeResponse, | |
56 | options..., | |
57 | )) | |
58 | r.Methods("PUT").Path("/profiles/{id}").Handler(httptransport.NewServer( | |
59 | e.PutProfileEndpoint, | |
60 | decodePutProfileRequest, | |
61 | encodeResponse, | |
62 | options..., | |
63 | )) | |
64 | r.Methods("PATCH").Path("/profiles/{id}").Handler(httptransport.NewServer( | |
65 | e.PatchProfileEndpoint, | |
66 | decodePatchProfileRequest, | |
67 | encodeResponse, | |
68 | options..., | |
69 | )) | |
70 | r.Methods("DELETE").Path("/profiles/{id}").Handler(httptransport.NewServer( | |
71 | e.DeleteProfileEndpoint, | |
72 | decodeDeleteProfileRequest, | |
73 | encodeResponse, | |
74 | options..., | |
75 | )) | |
76 | r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer( | |
77 | e.GetAddressesEndpoint, | |
78 | decodeGetAddressesRequest, | |
79 | encodeResponse, | |
80 | options..., | |
81 | )) | |
82 | r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer( | |
83 | e.GetAddressEndpoint, | |
84 | decodeGetAddressRequest, | |
85 | encodeResponse, | |
86 | options..., | |
87 | )) | |
88 | r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer( | |
89 | e.PostAddressEndpoint, | |
90 | decodePostAddressRequest, | |
91 | encodeResponse, | |
92 | options..., | |
93 | )) | |
94 | r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer( | |
95 | e.DeleteAddressEndpoint, | |
96 | decodeDeleteAddressRequest, | |
97 | encodeResponse, | |
98 | options..., | |
99 | )) | |
100 | return r | |
101 | } | |
102 | ||
103 | func decodePostProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
104 | var req postProfileRequest | |
105 | if e := json.NewDecoder(r.Body).Decode(&req.Profile); e != nil { | |
106 | return nil, e | |
107 | } | |
108 | return req, nil | |
109 | } | |
110 | ||
111 | func decodeGetProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
112 | vars := mux.Vars(r) | |
113 | id, ok := vars["id"] | |
114 | if !ok { | |
115 | return nil, ErrBadRouting | |
116 | } | |
117 | return getProfileRequest{ID: id}, nil | |
118 | } | |
119 | ||
120 | func decodePutProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
121 | vars := mux.Vars(r) | |
122 | id, ok := vars["id"] | |
123 | if !ok { | |
124 | return nil, ErrBadRouting | |
125 | } | |
126 | var profile Profile | |
127 | if err := json.NewDecoder(r.Body).Decode(&profile); err != nil { | |
128 | return nil, err | |
129 | } | |
130 | return putProfileRequest{ | |
131 | ID: id, | |
132 | Profile: profile, | |
133 | }, nil | |
134 | } | |
135 | ||
136 | func decodePatchProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
137 | vars := mux.Vars(r) | |
138 | id, ok := vars["id"] | |
139 | if !ok { | |
140 | return nil, ErrBadRouting | |
141 | } | |
142 | var profile Profile | |
143 | if err := json.NewDecoder(r.Body).Decode(&profile); err != nil { | |
144 | return nil, err | |
145 | } | |
146 | return patchProfileRequest{ | |
147 | ID: id, | |
148 | Profile: profile, | |
149 | }, nil | |
150 | } | |
151 | ||
152 | func decodeDeleteProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
153 | vars := mux.Vars(r) | |
154 | id, ok := vars["id"] | |
155 | if !ok { | |
156 | return nil, ErrBadRouting | |
157 | } | |
158 | return deleteProfileRequest{ID: id}, nil | |
159 | } | |
160 | ||
161 | func decodeGetAddressesRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
162 | vars := mux.Vars(r) | |
163 | id, ok := vars["id"] | |
164 | if !ok { | |
165 | return nil, ErrBadRouting | |
166 | } | |
167 | return getAddressesRequest{ProfileID: id}, nil | |
168 | } | |
169 | ||
170 | func decodeGetAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
171 | vars := mux.Vars(r) | |
172 | id, ok := vars["id"] | |
173 | if !ok { | |
174 | return nil, ErrBadRouting | |
175 | } | |
176 | addressID, ok := vars["addressID"] | |
177 | if !ok { | |
178 | return nil, ErrBadRouting | |
179 | } | |
180 | return getAddressRequest{ | |
181 | ProfileID: id, | |
182 | AddressID: addressID, | |
183 | }, nil | |
184 | } | |
185 | ||
186 | func decodePostAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
187 | vars := mux.Vars(r) | |
188 | id, ok := vars["id"] | |
189 | if !ok { | |
190 | return nil, ErrBadRouting | |
191 | } | |
192 | var address Address | |
193 | if err := json.NewDecoder(r.Body).Decode(&address); err != nil { | |
194 | return nil, err | |
195 | } | |
196 | return postAddressRequest{ | |
197 | ProfileID: id, | |
198 | Address: address, | |
199 | }, nil | |
200 | } | |
201 | ||
202 | func decodeDeleteAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
203 | vars := mux.Vars(r) | |
204 | id, ok := vars["id"] | |
205 | if !ok { | |
206 | return nil, ErrBadRouting | |
207 | } | |
208 | addressID, ok := vars["addressID"] | |
209 | if !ok { | |
210 | return nil, ErrBadRouting | |
211 | } | |
212 | return deleteAddressRequest{ | |
213 | ProfileID: id, | |
214 | AddressID: addressID, | |
215 | }, nil | |
216 | } | |
217 | ||
218 | func encodePostProfileRequest(ctx context.Context, req *http.Request, request interface{}) error { | |
219 | // r.Methods("POST").Path("/profiles/") | |
220 | req.URL.Path = "/profiles/" | |
221 | return encodeRequest(ctx, req, request) | |
222 | } | |
223 | ||
224 | func encodeGetProfileRequest(ctx context.Context, req *http.Request, request interface{}) error { | |
225 | // r.Methods("GET").Path("/profiles/{id}") | |
226 | r := request.(getProfileRequest) | |
227 | profileID := url.QueryEscape(r.ID) | |
228 | req.URL.Path = "/profiles/" + profileID | |
229 | return encodeRequest(ctx, req, request) | |
230 | } | |
231 | ||
232 | func encodePutProfileRequest(ctx context.Context, req *http.Request, request interface{}) error { | |
233 | // r.Methods("PUT").Path("/profiles/{id}") | |
234 | r := request.(putProfileRequest) | |
235 | profileID := url.QueryEscape(r.ID) | |
236 | req.URL.Path = "/profiles/" + profileID | |
237 | return encodeRequest(ctx, req, request) | |
238 | } | |
239 | ||
240 | func encodePatchProfileRequest(ctx context.Context, req *http.Request, request interface{}) error { | |
241 | // r.Methods("PATCH").Path("/profiles/{id}") | |
242 | r := request.(patchProfileRequest) | |
243 | profileID := url.QueryEscape(r.ID) | |
244 | req.URL.Path = "/profiles/" + profileID | |
245 | return encodeRequest(ctx, req, request) | |
246 | } | |
247 | ||
248 | func encodeDeleteProfileRequest(ctx context.Context, req *http.Request, request interface{}) error { | |
249 | // r.Methods("DELETE").Path("/profiles/{id}") | |
250 | r := request.(deleteProfileRequest) | |
251 | profileID := url.QueryEscape(r.ID) | |
252 | req.URL.Path = "/profiles/" + profileID | |
253 | return encodeRequest(ctx, req, request) | |
254 | } | |
255 | ||
256 | func encodeGetAddressesRequest(ctx context.Context, req *http.Request, request interface{}) error { | |
257 | // r.Methods("GET").Path("/profiles/{id}/addresses/") | |
258 | r := request.(getAddressesRequest) | |
259 | profileID := url.QueryEscape(r.ProfileID) | |
260 | req.URL.Path = "/profiles/" + profileID + "/addresses/" | |
261 | return encodeRequest(ctx, req, request) | |
262 | } | |
263 | ||
264 | func encodeGetAddressRequest(ctx context.Context, req *http.Request, request interface{}) error { | |
265 | // r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}") | |
266 | r := request.(getAddressRequest) | |
267 | profileID := url.QueryEscape(r.ProfileID) | |
268 | addressID := url.QueryEscape(r.AddressID) | |
269 | req.URL.Path = "/profiles/" + profileID + "/addresses/" + addressID | |
270 | return encodeRequest(ctx, req, request) | |
271 | } | |
272 | ||
273 | func encodePostAddressRequest(ctx context.Context, req *http.Request, request interface{}) error { | |
274 | // r.Methods("POST").Path("/profiles/{id}/addresses/") | |
275 | r := request.(postAddressRequest) | |
276 | profileID := url.QueryEscape(r.ProfileID) | |
277 | req.URL.Path = "/profiles/" + profileID + "/addresses/" | |
278 | return encodeRequest(ctx, req, request) | |
279 | } | |
280 | ||
281 | func encodeDeleteAddressRequest(ctx context.Context, req *http.Request, request interface{}) error { | |
282 | // r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}") | |
283 | r := request.(deleteAddressRequest) | |
284 | profileID := url.QueryEscape(r.ProfileID) | |
285 | addressID := url.QueryEscape(r.AddressID) | |
286 | req.URL.Path = "/profiles/" + profileID + "/addresses/" + addressID | |
287 | return encodeRequest(ctx, req, request) | |
288 | } | |
289 | ||
290 | func decodePostProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
291 | var response postProfileResponse | |
292 | err := json.NewDecoder(resp.Body).Decode(&response) | |
293 | return response, err | |
294 | } | |
295 | ||
296 | func decodeGetProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
297 | var response getProfileResponse | |
298 | err := json.NewDecoder(resp.Body).Decode(&response) | |
299 | return response, err | |
300 | } | |
301 | ||
302 | func decodePutProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
303 | var response putProfileResponse | |
304 | err := json.NewDecoder(resp.Body).Decode(&response) | |
305 | return response, err | |
306 | } | |
307 | ||
308 | func decodePatchProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
309 | var response patchProfileResponse | |
310 | err := json.NewDecoder(resp.Body).Decode(&response) | |
311 | return response, err | |
312 | } | |
313 | ||
314 | func decodeDeleteProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
315 | var response deleteProfileResponse | |
316 | err := json.NewDecoder(resp.Body).Decode(&response) | |
317 | return response, err | |
318 | } | |
319 | ||
320 | func decodeGetAddressesResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
321 | var response getAddressesResponse | |
322 | err := json.NewDecoder(resp.Body).Decode(&response) | |
323 | return response, err | |
324 | } | |
325 | ||
326 | func decodeGetAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
327 | var response getAddressResponse | |
328 | err := json.NewDecoder(resp.Body).Decode(&response) | |
329 | return response, err | |
330 | } | |
331 | ||
332 | func decodePostAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
333 | var response postAddressResponse | |
334 | err := json.NewDecoder(resp.Body).Decode(&response) | |
335 | return response, err | |
336 | } | |
337 | ||
338 | func decodeDeleteAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
339 | var response deleteAddressResponse | |
340 | err := json.NewDecoder(resp.Body).Decode(&response) | |
341 | return response, err | |
342 | } | |
343 | ||
344 | // errorer is implemented by all concrete response types that may contain | |
345 | // errors. It allows us to change the HTTP response code without needing to | |
346 | // trigger an endpoint (transport-level) error. For more information, read the | |
347 | // big comment in endpoints.go. | |
348 | type errorer interface { | |
349 | error() error | |
350 | } | |
351 | ||
352 | // encodeResponse is the common method to encode all response types to the | |
353 | // client. I chose to do it this way because, since we're using JSON, there's no | |
354 | // reason to provide anything more specific. It's certainly possible to | |
355 | // specialize on a per-response (per-method) basis. | |
356 | func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { | |
357 | if e, ok := response.(errorer); ok && e.error() != nil { | |
358 | // Not a Go kit transport error, but a business-logic error. | |
359 | // Provide those as HTTP errors. | |
360 | encodeError(ctx, e.error(), w) | |
361 | return nil | |
362 | } | |
363 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
364 | return json.NewEncoder(w).Encode(response) | |
365 | } | |
366 | ||
367 | // encodeRequest likewise JSON-encodes the request to the HTTP request body. | |
368 | // Don't use it directly as a transport/http.Client EncodeRequestFunc: | |
369 | // profilesvc endpoints require mutating the HTTP method and request path. | |
370 | func encodeRequest(_ context.Context, req *http.Request, request interface{}) error { | |
371 | var buf bytes.Buffer | |
372 | err := json.NewEncoder(&buf).Encode(request) | |
373 | if err != nil { | |
374 | return err | |
375 | } | |
376 | req.Body = ioutil.NopCloser(&buf) | |
377 | return nil | |
378 | } | |
379 | ||
380 | func encodeError(_ context.Context, err error, w http.ResponseWriter) { | |
381 | if err == nil { | |
382 | panic("encodeError with nil error") | |
383 | } | |
384 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
385 | w.WriteHeader(codeFrom(err)) | |
386 | json.NewEncoder(w).Encode(map[string]interface{}{ | |
387 | "error": err.Error(), | |
388 | }) | |
389 | } | |
390 | ||
391 | func codeFrom(err error) int { | |
392 | switch err { | |
393 | case ErrNotFound: | |
394 | return http.StatusNotFound | |
395 | case ErrAlreadyExists, ErrInconsistentIDs: | |
396 | return http.StatusBadRequest | |
397 | default: | |
398 | return http.StatusInternalServerError | |
399 | } | |
400 | } |
0 | # shipping | |
1 | ||
2 | This example demonstrates a more real-world application consisting of multiple services. | |
3 | ||
4 | ## Description | |
5 | ||
6 | The implementation is based on the container shipping domain from the [Domain Driven Design](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) book by Eric Evans, which was [originally](http://dddsample.sourceforge.net/) implemented in Java but has since been ported to Go. This example is a somewhat stripped down version to demonstrate the use of Go kit. The [original Go application](https://github.com/marcusolsson/goddd) is maintained separately and accompanied by an [AngularJS application](https://github.com/marcusolsson/dddelivery-angularjs) as well as a mock [routing service](https://github.com/marcusolsson/pathfinder). | |
7 | ||
8 | ### Organization | |
9 | ||
10 | The application consists of three application services, `booking`, `handling` and `tracking`. Each of these is an individual Go kit service as seen in previous examples. | |
11 | ||
12 | - __booking__ - used by the shipping company to book and route cargos. | |
13 | - __handling__ - used by our staff around the world to register whenever the cargo has been received, loaded etc. | |
14 | - __tracking__ - used by the customer to track the cargo along the route | |
15 | ||
16 | There are also a few pure domain packages that contain some intricate business-logic. They provide domain objects and services that are used by each application service to provide interesting use-cases for the user. | |
17 | ||
18 | `inmem` contains in-memory implementations for the repositories found in the domain packages. | |
19 | ||
20 | The `routing` package provides a _domain service_ that is used to query an external application for possible routes. | |
21 | ||
22 | ## Contributing | |
23 | ||
24 | As with all Go kit examples you are more than welcome to contribute. If you do however, please consider contributing back to the original project as well. |
0 | package booking | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "time" | |
5 | ||
6 | "github.com/go-kit/kit/endpoint" | |
7 | ||
8 | "github.com/go-kit/kit/examples/shipping/cargo" | |
9 | "github.com/go-kit/kit/examples/shipping/location" | |
10 | ) | |
11 | ||
12 | type bookCargoRequest struct { | |
13 | Origin location.UNLocode | |
14 | Destination location.UNLocode | |
15 | ArrivalDeadline time.Time | |
16 | } | |
17 | ||
18 | type bookCargoResponse struct { | |
19 | ID cargo.TrackingID `json:"tracking_id,omitempty"` | |
20 | Err error `json:"error,omitempty"` | |
21 | } | |
22 | ||
23 | func (r bookCargoResponse) error() error { return r.Err } | |
24 | ||
25 | func makeBookCargoEndpoint(s Service) endpoint.Endpoint { | |
26 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
27 | req := request.(bookCargoRequest) | |
28 | id, err := s.BookNewCargo(req.Origin, req.Destination, req.ArrivalDeadline) | |
29 | return bookCargoResponse{ID: id, Err: err}, nil | |
30 | } | |
31 | } | |
32 | ||
33 | type loadCargoRequest struct { | |
34 | ID cargo.TrackingID | |
35 | } | |
36 | ||
37 | type loadCargoResponse struct { | |
38 | Cargo *Cargo `json:"cargo,omitempty"` | |
39 | Err error `json:"error,omitempty"` | |
40 | } | |
41 | ||
42 | func (r loadCargoResponse) error() error { return r.Err } | |
43 | ||
44 | func makeLoadCargoEndpoint(s Service) endpoint.Endpoint { | |
45 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
46 | req := request.(loadCargoRequest) | |
47 | c, err := s.LoadCargo(req.ID) | |
48 | return loadCargoResponse{Cargo: &c, Err: err}, nil | |
49 | } | |
50 | } | |
51 | ||
52 | type requestRoutesRequest struct { | |
53 | ID cargo.TrackingID | |
54 | } | |
55 | ||
56 | type requestRoutesResponse struct { | |
57 | Routes []cargo.Itinerary `json:"routes,omitempty"` | |
58 | Err error `json:"error,omitempty"` | |
59 | } | |
60 | ||
61 | func (r requestRoutesResponse) error() error { return r.Err } | |
62 | ||
63 | func makeRequestRoutesEndpoint(s Service) endpoint.Endpoint { | |
64 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
65 | req := request.(requestRoutesRequest) | |
66 | itin := s.RequestPossibleRoutesForCargo(req.ID) | |
67 | return requestRoutesResponse{Routes: itin, Err: nil}, nil | |
68 | } | |
69 | } | |
70 | ||
71 | type assignToRouteRequest struct { | |
72 | ID cargo.TrackingID | |
73 | Itinerary cargo.Itinerary | |
74 | } | |
75 | ||
76 | type assignToRouteResponse struct { | |
77 | Err error `json:"error,omitempty"` | |
78 | } | |
79 | ||
80 | func (r assignToRouteResponse) error() error { return r.Err } | |
81 | ||
82 | func makeAssignToRouteEndpoint(s Service) endpoint.Endpoint { | |
83 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
84 | req := request.(assignToRouteRequest) | |
85 | err := s.AssignCargoToRoute(req.ID, req.Itinerary) | |
86 | return assignToRouteResponse{Err: err}, nil | |
87 | } | |
88 | } | |
89 | ||
90 | type changeDestinationRequest struct { | |
91 | ID cargo.TrackingID | |
92 | Destination location.UNLocode | |
93 | } | |
94 | ||
95 | type changeDestinationResponse struct { | |
96 | Err error `json:"error,omitempty"` | |
97 | } | |
98 | ||
99 | func (r changeDestinationResponse) error() error { return r.Err } | |
100 | ||
101 | func makeChangeDestinationEndpoint(s Service) endpoint.Endpoint { | |
102 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
103 | req := request.(changeDestinationRequest) | |
104 | err := s.ChangeDestination(req.ID, req.Destination) | |
105 | return changeDestinationResponse{Err: err}, nil | |
106 | } | |
107 | } | |
108 | ||
109 | type listCargosRequest struct{} | |
110 | ||
111 | type listCargosResponse struct { | |
112 | Cargos []Cargo `json:"cargos,omitempty"` | |
113 | Err error `json:"error,omitempty"` | |
114 | } | |
115 | ||
116 | func (r listCargosResponse) error() error { return r.Err } | |
117 | ||
118 | func makeListCargosEndpoint(s Service) endpoint.Endpoint { | |
119 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
120 | _ = request.(listCargosRequest) | |
121 | return listCargosResponse{Cargos: s.Cargos(), Err: nil}, nil | |
122 | } | |
123 | } | |
124 | ||
125 | type listLocationsRequest struct { | |
126 | } | |
127 | ||
128 | type listLocationsResponse struct { | |
129 | Locations []Location `json:"locations,omitempty"` | |
130 | Err error `json:"error,omitempty"` | |
131 | } | |
132 | ||
133 | func makeListLocationsEndpoint(s Service) endpoint.Endpoint { | |
134 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
135 | _ = request.(listLocationsRequest) | |
136 | return listLocationsResponse{Locations: s.Locations(), Err: nil}, nil | |
137 | } | |
138 | } |
0 | package booking | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/metrics" | |
6 | ||
7 | "github.com/go-kit/kit/examples/shipping/cargo" | |
8 | "github.com/go-kit/kit/examples/shipping/location" | |
9 | ) | |
10 | ||
11 | type instrumentingService struct { | |
12 | requestCount metrics.Counter | |
13 | requestLatency metrics.Histogram | |
14 | Service | |
15 | } | |
16 | ||
17 | // NewInstrumentingService returns an instance of an instrumenting Service. | |
18 | func NewInstrumentingService(counter metrics.Counter, latency metrics.Histogram, s Service) Service { | |
19 | return &instrumentingService{ | |
20 | requestCount: counter, | |
21 | requestLatency: latency, | |
22 | Service: s, | |
23 | } | |
24 | } | |
25 | ||
26 | func (s *instrumentingService) BookNewCargo(origin, destination location.UNLocode, deadline time.Time) (cargo.TrackingID, error) { | |
27 | defer func(begin time.Time) { | |
28 | s.requestCount.With("method", "book").Add(1) | |
29 | s.requestLatency.With("method", "book").Observe(time.Since(begin).Seconds()) | |
30 | }(time.Now()) | |
31 | ||
32 | return s.Service.BookNewCargo(origin, destination, deadline) | |
33 | } | |
34 | ||
35 | func (s *instrumentingService) LoadCargo(id cargo.TrackingID) (c Cargo, err error) { | |
36 | defer func(begin time.Time) { | |
37 | s.requestCount.With("method", "load").Add(1) | |
38 | s.requestLatency.With("method", "load").Observe(time.Since(begin).Seconds()) | |
39 | }(time.Now()) | |
40 | ||
41 | return s.Service.LoadCargo(id) | |
42 | } | |
43 | ||
44 | func (s *instrumentingService) RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary { | |
45 | defer func(begin time.Time) { | |
46 | s.requestCount.With("method", "request_routes").Add(1) | |
47 | s.requestLatency.With("method", "request_routes").Observe(time.Since(begin).Seconds()) | |
48 | }(time.Now()) | |
49 | ||
50 | return s.Service.RequestPossibleRoutesForCargo(id) | |
51 | } | |
52 | ||
53 | func (s *instrumentingService) AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) (err error) { | |
54 | defer func(begin time.Time) { | |
55 | s.requestCount.With("method", "assign_to_route").Add(1) | |
56 | s.requestLatency.With("method", "assign_to_route").Observe(time.Since(begin).Seconds()) | |
57 | }(time.Now()) | |
58 | ||
59 | return s.Service.AssignCargoToRoute(id, itinerary) | |
60 | } | |
61 | ||
62 | func (s *instrumentingService) ChangeDestination(id cargo.TrackingID, l location.UNLocode) (err error) { | |
63 | defer func(begin time.Time) { | |
64 | s.requestCount.With("method", "change_destination").Add(1) | |
65 | s.requestLatency.With("method", "change_destination").Observe(time.Since(begin).Seconds()) | |
66 | }(time.Now()) | |
67 | ||
68 | return s.Service.ChangeDestination(id, l) | |
69 | } | |
70 | ||
71 | func (s *instrumentingService) Cargos() []Cargo { | |
72 | defer func(begin time.Time) { | |
73 | s.requestCount.With("method", "list_cargos").Add(1) | |
74 | s.requestLatency.With("method", "list_cargos").Observe(time.Since(begin).Seconds()) | |
75 | }(time.Now()) | |
76 | ||
77 | return s.Service.Cargos() | |
78 | } | |
79 | ||
80 | func (s *instrumentingService) Locations() []Location { | |
81 | defer func(begin time.Time) { | |
82 | s.requestCount.With("method", "list_locations").Add(1) | |
83 | s.requestLatency.With("method", "list_locations").Observe(time.Since(begin).Seconds()) | |
84 | }(time.Now()) | |
85 | ||
86 | return s.Service.Locations() | |
87 | } |
0 | package booking | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/log" | |
6 | ||
7 | "github.com/go-kit/kit/examples/shipping/cargo" | |
8 | "github.com/go-kit/kit/examples/shipping/location" | |
9 | ) | |
10 | ||
11 | type loggingService struct { | |
12 | logger log.Logger | |
13 | Service | |
14 | } | |
15 | ||
16 | // NewLoggingService returns a new instance of a logging Service. | |
17 | func NewLoggingService(logger log.Logger, s Service) Service { | |
18 | return &loggingService{logger, s} | |
19 | } | |
20 | ||
21 | func (s *loggingService) BookNewCargo(origin location.UNLocode, destination location.UNLocode, deadline time.Time) (id cargo.TrackingID, err error) { | |
22 | defer func(begin time.Time) { | |
23 | s.logger.Log( | |
24 | "method", "book", | |
25 | "origin", origin, | |
26 | "destination", destination, | |
27 | "arrival_deadline", deadline, | |
28 | "took", time.Since(begin), | |
29 | "err", err, | |
30 | ) | |
31 | }(time.Now()) | |
32 | return s.Service.BookNewCargo(origin, destination, deadline) | |
33 | } | |
34 | ||
35 | func (s *loggingService) LoadCargo(id cargo.TrackingID) (c Cargo, err error) { | |
36 | defer func(begin time.Time) { | |
37 | s.logger.Log( | |
38 | "method", "load", | |
39 | "tracking_id", id, | |
40 | "took", time.Since(begin), | |
41 | "err", err, | |
42 | ) | |
43 | }(time.Now()) | |
44 | return s.Service.LoadCargo(id) | |
45 | } | |
46 | ||
47 | func (s *loggingService) RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary { | |
48 | defer func(begin time.Time) { | |
49 | s.logger.Log( | |
50 | "method", "request_routes", | |
51 | "tracking_id", id, | |
52 | "took", time.Since(begin), | |
53 | ) | |
54 | }(time.Now()) | |
55 | return s.Service.RequestPossibleRoutesForCargo(id) | |
56 | } | |
57 | ||
58 | func (s *loggingService) AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) (err error) { | |
59 | defer func(begin time.Time) { | |
60 | s.logger.Log( | |
61 | "method", "assign_to_route", | |
62 | "tracking_id", id, | |
63 | "took", time.Since(begin), | |
64 | "err", err, | |
65 | ) | |
66 | }(time.Now()) | |
67 | return s.Service.AssignCargoToRoute(id, itinerary) | |
68 | } | |
69 | ||
70 | func (s *loggingService) ChangeDestination(id cargo.TrackingID, l location.UNLocode) (err error) { | |
71 | defer func(begin time.Time) { | |
72 | s.logger.Log( | |
73 | "method", "change_destination", | |
74 | "tracking_id", id, | |
75 | "destination", l, | |
76 | "took", time.Since(begin), | |
77 | "err", err, | |
78 | ) | |
79 | }(time.Now()) | |
80 | return s.Service.ChangeDestination(id, l) | |
81 | } | |
82 | ||
83 | func (s *loggingService) Cargos() []Cargo { | |
84 | defer func(begin time.Time) { | |
85 | s.logger.Log( | |
86 | "method", "list_cargos", | |
87 | "took", time.Since(begin), | |
88 | ) | |
89 | }(time.Now()) | |
90 | return s.Service.Cargos() | |
91 | } | |
92 | ||
93 | func (s *loggingService) Locations() []Location { | |
94 | defer func(begin time.Time) { | |
95 | s.logger.Log( | |
96 | "method", "list_locations", | |
97 | "took", time.Since(begin), | |
98 | ) | |
99 | }(time.Now()) | |
100 | return s.Service.Locations() | |
101 | } |
0 | // Package booking provides the use-case of booking a cargo. Used by views | |
1 | // facing an administrator. | |
2 | package booking | |
3 | ||
4 | import ( | |
5 | "errors" | |
6 | "time" | |
7 | ||
8 | "github.com/go-kit/kit/examples/shipping/cargo" | |
9 | "github.com/go-kit/kit/examples/shipping/location" | |
10 | "github.com/go-kit/kit/examples/shipping/routing" | |
11 | ) | |
12 | ||
13 | // ErrInvalidArgument is returned when one or more arguments are invalid. | |
14 | var ErrInvalidArgument = errors.New("invalid argument") | |
15 | ||
16 | // Service is the interface that provides booking methods. | |
17 | type Service interface { | |
18 | // BookNewCargo registers a new cargo in the tracking system, not yet | |
19 | // routed. | |
20 | BookNewCargo(origin location.UNLocode, destination location.UNLocode, deadline time.Time) (cargo.TrackingID, error) | |
21 | ||
22 | // LoadCargo returns a read model of a cargo. | |
23 | LoadCargo(id cargo.TrackingID) (Cargo, error) | |
24 | ||
25 | // RequestPossibleRoutesForCargo requests a list of itineraries describing | |
26 | // possible routes for this cargo. | |
27 | RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary | |
28 | ||
29 | // AssignCargoToRoute assigns a cargo to the route specified by the | |
30 | // itinerary. | |
31 | AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) error | |
32 | ||
33 | // ChangeDestination changes the destination of a cargo. | |
34 | ChangeDestination(id cargo.TrackingID, destination location.UNLocode) error | |
35 | ||
36 | // Cargos returns a list of all cargos that have been booked. | |
37 | Cargos() []Cargo | |
38 | ||
39 | // Locations returns a list of registered locations. | |
40 | Locations() []Location | |
41 | } | |
42 | ||
43 | type service struct { | |
44 | cargos cargo.Repository | |
45 | locations location.Repository | |
46 | handlingEvents cargo.HandlingEventRepository | |
47 | routingService routing.Service | |
48 | } | |
49 | ||
50 | func (s *service) AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) error { | |
51 | if id == "" || len(itinerary.Legs) == 0 { | |
52 | return ErrInvalidArgument | |
53 | } | |
54 | ||
55 | c, err := s.cargos.Find(id) | |
56 | if err != nil { | |
57 | return err | |
58 | } | |
59 | ||
60 | c.AssignToRoute(itinerary) | |
61 | ||
62 | return s.cargos.Store(c) | |
63 | } | |
64 | ||
65 | func (s *service) BookNewCargo(origin, destination location.UNLocode, deadline time.Time) (cargo.TrackingID, error) { | |
66 | if origin == "" || destination == "" || deadline.IsZero() { | |
67 | return "", ErrInvalidArgument | |
68 | } | |
69 | ||
70 | id := cargo.NextTrackingID() | |
71 | rs := cargo.RouteSpecification{ | |
72 | Origin: origin, | |
73 | Destination: destination, | |
74 | ArrivalDeadline: deadline, | |
75 | } | |
76 | ||
77 | c := cargo.New(id, rs) | |
78 | ||
79 | if err := s.cargos.Store(c); err != nil { | |
80 | return "", err | |
81 | } | |
82 | ||
83 | return c.TrackingID, nil | |
84 | } | |
85 | ||
86 | func (s *service) LoadCargo(id cargo.TrackingID) (Cargo, error) { | |
87 | if id == "" { | |
88 | return Cargo{}, ErrInvalidArgument | |
89 | } | |
90 | ||
91 | c, err := s.cargos.Find(id) | |
92 | if err != nil { | |
93 | return Cargo{}, err | |
94 | } | |
95 | ||
96 | return assemble(c, s.handlingEvents), nil | |
97 | } | |
98 | ||
99 | func (s *service) ChangeDestination(id cargo.TrackingID, destination location.UNLocode) error { | |
100 | if id == "" || destination == "" { | |
101 | return ErrInvalidArgument | |
102 | } | |
103 | ||
104 | c, err := s.cargos.Find(id) | |
105 | if err != nil { | |
106 | return err | |
107 | } | |
108 | ||
109 | l, err := s.locations.Find(destination) | |
110 | if err != nil { | |
111 | return err | |
112 | } | |
113 | ||
114 | c.SpecifyNewRoute(cargo.RouteSpecification{ | |
115 | Origin: c.Origin, | |
116 | Destination: l.UNLocode, | |
117 | ArrivalDeadline: c.RouteSpecification.ArrivalDeadline, | |
118 | }) | |
119 | ||
120 | if err := s.cargos.Store(c); err != nil { | |
121 | return err | |
122 | } | |
123 | ||
124 | return nil | |
125 | } | |
126 | ||
127 | func (s *service) RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary { | |
128 | if id == "" { | |
129 | return nil | |
130 | } | |
131 | ||
132 | c, err := s.cargos.Find(id) | |
133 | if err != nil { | |
134 | return []cargo.Itinerary{} | |
135 | } | |
136 | ||
137 | return s.routingService.FetchRoutesForSpecification(c.RouteSpecification) | |
138 | } | |
139 | ||
140 | func (s *service) Cargos() []Cargo { | |
141 | var result []Cargo | |
142 | for _, c := range s.cargos.FindAll() { | |
143 | result = append(result, assemble(c, s.handlingEvents)) | |
144 | } | |
145 | return result | |
146 | } | |
147 | ||
148 | func (s *service) Locations() []Location { | |
149 | var result []Location | |
150 | for _, v := range s.locations.FindAll() { | |
151 | result = append(result, Location{ | |
152 | UNLocode: string(v.UNLocode), | |
153 | Name: v.Name, | |
154 | }) | |
155 | } | |
156 | return result | |
157 | } | |
158 | ||
159 | // NewService creates a booking service with necessary dependencies. | |
160 | func NewService(cargos cargo.Repository, locations location.Repository, events cargo.HandlingEventRepository, rs routing.Service) Service { | |
161 | return &service{ | |
162 | cargos: cargos, | |
163 | locations: locations, | |
164 | handlingEvents: events, | |
165 | routingService: rs, | |
166 | } | |
167 | } | |
168 | ||
169 | // Location is a read model for booking views. | |
170 | type Location struct { | |
171 | UNLocode string `json:"locode"` | |
172 | Name string `json:"name"` | |
173 | } | |
174 | ||
175 | // Cargo is a read model for booking views. | |
176 | type Cargo struct { | |
177 | ArrivalDeadline time.Time `json:"arrival_deadline"` | |
178 | Destination string `json:"destination"` | |
179 | Legs []cargo.Leg `json:"legs,omitempty"` | |
180 | Misrouted bool `json:"misrouted"` | |
181 | Origin string `json:"origin"` | |
182 | Routed bool `json:"routed"` | |
183 | TrackingID string `json:"tracking_id"` | |
184 | } | |
185 | ||
186 | func assemble(c *cargo.Cargo, events cargo.HandlingEventRepository) Cargo { | |
187 | return Cargo{ | |
188 | TrackingID: string(c.TrackingID), | |
189 | Origin: string(c.Origin), | |
190 | Destination: string(c.RouteSpecification.Destination), | |
191 | Misrouted: c.Delivery.RoutingStatus == cargo.Misrouted, | |
192 | Routed: !c.Itinerary.IsEmpty(), | |
193 | ArrivalDeadline: c.RouteSpecification.ArrivalDeadline, | |
194 | Legs: c.Itinerary.Legs, | |
195 | } | |
196 | } |
0 | package booking | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "encoding/json" | |
5 | "errors" | |
6 | "net/http" | |
7 | "time" | |
8 | ||
9 | "github.com/gorilla/mux" | |
10 | ||
11 | kitlog "github.com/go-kit/kit/log" | |
12 | "github.com/go-kit/kit/transport" | |
13 | kithttp "github.com/go-kit/kit/transport/http" | |
14 | ||
15 | "github.com/go-kit/kit/examples/shipping/cargo" | |
16 | "github.com/go-kit/kit/examples/shipping/location" | |
17 | ) | |
18 | ||
19 | // MakeHandler returns a handler for the booking service. | |
20 | func MakeHandler(bs Service, logger kitlog.Logger) http.Handler { | |
21 | opts := []kithttp.ServerOption{ | |
22 | kithttp.ServerErrorHandler(transport.NewLogErrorHandler(logger)), | |
23 | kithttp.ServerErrorEncoder(encodeError), | |
24 | } | |
25 | ||
26 | bookCargoHandler := kithttp.NewServer( | |
27 | makeBookCargoEndpoint(bs), | |
28 | decodeBookCargoRequest, | |
29 | encodeResponse, | |
30 | opts..., | |
31 | ) | |
32 | loadCargoHandler := kithttp.NewServer( | |
33 | makeLoadCargoEndpoint(bs), | |
34 | decodeLoadCargoRequest, | |
35 | encodeResponse, | |
36 | opts..., | |
37 | ) | |
38 | requestRoutesHandler := kithttp.NewServer( | |
39 | makeRequestRoutesEndpoint(bs), | |
40 | decodeRequestRoutesRequest, | |
41 | encodeResponse, | |
42 | opts..., | |
43 | ) | |
44 | assignToRouteHandler := kithttp.NewServer( | |
45 | makeAssignToRouteEndpoint(bs), | |
46 | decodeAssignToRouteRequest, | |
47 | encodeResponse, | |
48 | opts..., | |
49 | ) | |
50 | changeDestinationHandler := kithttp.NewServer( | |
51 | makeChangeDestinationEndpoint(bs), | |
52 | decodeChangeDestinationRequest, | |
53 | encodeResponse, | |
54 | opts..., | |
55 | ) | |
56 | listCargosHandler := kithttp.NewServer( | |
57 | makeListCargosEndpoint(bs), | |
58 | decodeListCargosRequest, | |
59 | encodeResponse, | |
60 | opts..., | |
61 | ) | |
62 | listLocationsHandler := kithttp.NewServer( | |
63 | makeListLocationsEndpoint(bs), | |
64 | decodeListLocationsRequest, | |
65 | encodeResponse, | |
66 | opts..., | |
67 | ) | |
68 | ||
69 | r := mux.NewRouter() | |
70 | ||
71 | r.Handle("/booking/v1/cargos", bookCargoHandler).Methods("POST") | |
72 | r.Handle("/booking/v1/cargos", listCargosHandler).Methods("GET") | |
73 | r.Handle("/booking/v1/cargos/{id}", loadCargoHandler).Methods("GET") | |
74 | r.Handle("/booking/v1/cargos/{id}/request_routes", requestRoutesHandler).Methods("GET") | |
75 | r.Handle("/booking/v1/cargos/{id}/assign_to_route", assignToRouteHandler).Methods("POST") | |
76 | r.Handle("/booking/v1/cargos/{id}/change_destination", changeDestinationHandler).Methods("POST") | |
77 | r.Handle("/booking/v1/locations", listLocationsHandler).Methods("GET") | |
78 | ||
79 | return r | |
80 | } | |
81 | ||
82 | var errBadRoute = errors.New("bad route") | |
83 | ||
84 | func decodeBookCargoRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
85 | var body struct { | |
86 | Origin string `json:"origin"` | |
87 | Destination string `json:"destination"` | |
88 | ArrivalDeadline time.Time `json:"arrival_deadline"` | |
89 | } | |
90 | ||
91 | if err := json.NewDecoder(r.Body).Decode(&body); err != nil { | |
92 | return nil, err | |
93 | } | |
94 | ||
95 | return bookCargoRequest{ | |
96 | Origin: location.UNLocode(body.Origin), | |
97 | Destination: location.UNLocode(body.Destination), | |
98 | ArrivalDeadline: body.ArrivalDeadline, | |
99 | }, nil | |
100 | } | |
101 | ||
102 | func decodeLoadCargoRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
103 | vars := mux.Vars(r) | |
104 | id, ok := vars["id"] | |
105 | if !ok { | |
106 | return nil, errBadRoute | |
107 | } | |
108 | return loadCargoRequest{ID: cargo.TrackingID(id)}, nil | |
109 | } | |
110 | ||
111 | func decodeRequestRoutesRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
112 | vars := mux.Vars(r) | |
113 | id, ok := vars["id"] | |
114 | if !ok { | |
115 | return nil, errBadRoute | |
116 | } | |
117 | return requestRoutesRequest{ID: cargo.TrackingID(id)}, nil | |
118 | } | |
119 | ||
120 | func decodeAssignToRouteRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
121 | vars := mux.Vars(r) | |
122 | id, ok := vars["id"] | |
123 | if !ok { | |
124 | return nil, errBadRoute | |
125 | } | |
126 | ||
127 | var itinerary cargo.Itinerary | |
128 | if err := json.NewDecoder(r.Body).Decode(&itinerary); err != nil { | |
129 | return nil, err | |
130 | } | |
131 | ||
132 | return assignToRouteRequest{ | |
133 | ID: cargo.TrackingID(id), | |
134 | Itinerary: itinerary, | |
135 | }, nil | |
136 | } | |
137 | ||
138 | func decodeChangeDestinationRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
139 | vars := mux.Vars(r) | |
140 | id, ok := vars["id"] | |
141 | if !ok { | |
142 | return nil, errBadRoute | |
143 | } | |
144 | ||
145 | var body struct { | |
146 | Destination string `json:"destination"` | |
147 | } | |
148 | ||
149 | if err := json.NewDecoder(r.Body).Decode(&body); err != nil { | |
150 | return nil, err | |
151 | } | |
152 | ||
153 | return changeDestinationRequest{ | |
154 | ID: cargo.TrackingID(id), | |
155 | Destination: location.UNLocode(body.Destination), | |
156 | }, nil | |
157 | } | |
158 | ||
159 | func decodeListCargosRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
160 | return listCargosRequest{}, nil | |
161 | } | |
162 | ||
163 | func decodeListLocationsRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
164 | return listLocationsRequest{}, nil | |
165 | } | |
166 | ||
167 | func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { | |
168 | if e, ok := response.(errorer); ok && e.error() != nil { | |
169 | encodeError(ctx, e.error(), w) | |
170 | return nil | |
171 | } | |
172 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
173 | return json.NewEncoder(w).Encode(response) | |
174 | } | |
175 | ||
176 | type errorer interface { | |
177 | error() error | |
178 | } | |
179 | ||
180 | // encode errors from business-logic | |
181 | func encodeError(_ context.Context, err error, w http.ResponseWriter) { | |
182 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
183 | switch err { | |
184 | case cargo.ErrUnknown: | |
185 | w.WriteHeader(http.StatusNotFound) | |
186 | case ErrInvalidArgument: | |
187 | w.WriteHeader(http.StatusBadRequest) | |
188 | default: | |
189 | w.WriteHeader(http.StatusInternalServerError) | |
190 | } | |
191 | json.NewEncoder(w).Encode(map[string]interface{}{ | |
192 | "error": err.Error(), | |
193 | }) | |
194 | } |
0 | // Package cargo contains the heart of the domain model. | |
1 | package cargo | |
2 | ||
3 | import ( | |
4 | "errors" | |
5 | "strings" | |
6 | "time" | |
7 | ||
8 | "github.com/pborman/uuid" | |
9 | ||
10 | "github.com/go-kit/kit/examples/shipping/location" | |
11 | ) | |
12 | ||
13 | // TrackingID uniquely identifies a particular cargo. | |
14 | type TrackingID string | |
15 | ||
16 | // Cargo is the central class in the domain model. | |
17 | type Cargo struct { | |
18 | TrackingID TrackingID | |
19 | Origin location.UNLocode | |
20 | RouteSpecification RouteSpecification | |
21 | Itinerary Itinerary | |
22 | Delivery Delivery | |
23 | } | |
24 | ||
25 | // SpecifyNewRoute specifies a new route for this cargo. | |
26 | func (c *Cargo) SpecifyNewRoute(rs RouteSpecification) { | |
27 | c.RouteSpecification = rs | |
28 | c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary) | |
29 | } | |
30 | ||
31 | // AssignToRoute attaches a new itinerary to this cargo. | |
32 | func (c *Cargo) AssignToRoute(itinerary Itinerary) { | |
33 | c.Itinerary = itinerary | |
34 | c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary) | |
35 | } | |
36 | ||
37 | // DeriveDeliveryProgress updates all aspects of the cargo aggregate status | |
38 | // based on the current route specification, itinerary and handling of the cargo. | |
39 | func (c *Cargo) DeriveDeliveryProgress(history HandlingHistory) { | |
40 | c.Delivery = DeriveDeliveryFrom(c.RouteSpecification, c.Itinerary, history) | |
41 | } | |
42 | ||
43 | // New creates a new, unrouted cargo. | |
44 | func New(id TrackingID, rs RouteSpecification) *Cargo { | |
45 | itinerary := Itinerary{} | |
46 | history := HandlingHistory{make([]HandlingEvent, 0)} | |
47 | ||
48 | return &Cargo{ | |
49 | TrackingID: id, | |
50 | Origin: rs.Origin, | |
51 | RouteSpecification: rs, | |
52 | Delivery: DeriveDeliveryFrom(rs, itinerary, history), | |
53 | } | |
54 | } | |
55 | ||
56 | // Repository provides access to a cargo store. | |
57 | type Repository interface { | |
58 | Store(cargo *Cargo) error | |
59 | Find(id TrackingID) (*Cargo, error) | |
60 | FindAll() []*Cargo | |
61 | } | |
62 | ||
63 | // ErrUnknown is used when a cargo could not be found. | |
64 | var ErrUnknown = errors.New("unknown cargo") | |
65 | ||
66 | // NextTrackingID generates a new tracking ID. | |
67 | // TODO: Move to infrastructure(?) | |
68 | func NextTrackingID() TrackingID { | |
69 | return TrackingID(strings.Split(strings.ToUpper(uuid.New()), "-")[0]) | |
70 | } | |
71 | ||
72 | // RouteSpecification Contains information about a route: its origin, | |
73 | // destination and arrival deadline. | |
74 | type RouteSpecification struct { | |
75 | Origin location.UNLocode | |
76 | Destination location.UNLocode | |
77 | ArrivalDeadline time.Time | |
78 | } | |
79 | ||
80 | // IsSatisfiedBy checks whether provided itinerary satisfies this | |
81 | // specification. | |
82 | func (s RouteSpecification) IsSatisfiedBy(itinerary Itinerary) bool { | |
83 | return itinerary.Legs != nil && | |
84 | s.Origin == itinerary.InitialDepartureLocation() && | |
85 | s.Destination == itinerary.FinalArrivalLocation() | |
86 | } | |
87 | ||
88 | // RoutingStatus describes status of cargo routing. | |
89 | type RoutingStatus int | |
90 | ||
91 | // Valid routing statuses. | |
92 | const ( | |
93 | NotRouted RoutingStatus = iota | |
94 | Misrouted | |
95 | Routed | |
96 | ) | |
97 | ||
98 | func (s RoutingStatus) String() string { | |
99 | switch s { | |
100 | case NotRouted: | |
101 | return "Not routed" | |
102 | case Misrouted: | |
103 | return "Misrouted" | |
104 | case Routed: | |
105 | return "Routed" | |
106 | } | |
107 | return "" | |
108 | } | |
109 | ||
110 | // TransportStatus describes status of cargo transportation. | |
111 | type TransportStatus int | |
112 | ||
113 | // Valid transport statuses. | |
114 | const ( | |
115 | NotReceived TransportStatus = iota | |
116 | InPort | |
117 | OnboardCarrier | |
118 | Claimed | |
119 | Unknown | |
120 | ) | |
121 | ||
122 | func (s TransportStatus) String() string { | |
123 | switch s { | |
124 | case NotReceived: | |
125 | return "Not received" | |
126 | case InPort: | |
127 | return "In port" | |
128 | case OnboardCarrier: | |
129 | return "Onboard carrier" | |
130 | case Claimed: | |
131 | return "Claimed" | |
132 | case Unknown: | |
133 | return "Unknown" | |
134 | } | |
135 | return "" | |
136 | } |
0 | package cargo | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/examples/shipping/location" | |
6 | "github.com/go-kit/kit/examples/shipping/voyage" | |
7 | ) | |
8 | ||
9 | // Delivery is the actual transportation of the cargo, as opposed to the | |
10 | // customer requirement (RouteSpecification) and the plan (Itinerary). | |
11 | type Delivery struct { | |
12 | Itinerary Itinerary | |
13 | RouteSpecification RouteSpecification | |
14 | RoutingStatus RoutingStatus | |
15 | TransportStatus TransportStatus | |
16 | NextExpectedActivity HandlingActivity | |
17 | LastEvent HandlingEvent | |
18 | LastKnownLocation location.UNLocode | |
19 | CurrentVoyage voyage.Number | |
20 | ETA time.Time | |
21 | IsMisdirected bool | |
22 | IsUnloadedAtDestination bool | |
23 | } | |
24 | ||
25 | // UpdateOnRouting creates a new delivery snapshot to reflect changes in | |
26 | // routing, i.e. when the route specification or the itinerary has changed but | |
27 | // no additional handling of the cargo has been performed. | |
28 | func (d Delivery) UpdateOnRouting(rs RouteSpecification, itinerary Itinerary) Delivery { | |
29 | return newDelivery(d.LastEvent, itinerary, rs) | |
30 | } | |
31 | ||
32 | // IsOnTrack checks if the delivery is on track. | |
33 | func (d Delivery) IsOnTrack() bool { | |
34 | return d.RoutingStatus == Routed && !d.IsMisdirected | |
35 | } | |
36 | ||
37 | // DeriveDeliveryFrom creates a new delivery snapshot based on the complete | |
38 | // handling history of a cargo, as well as its route specification and | |
39 | // itinerary. | |
40 | func DeriveDeliveryFrom(rs RouteSpecification, itinerary Itinerary, history HandlingHistory) Delivery { | |
41 | lastEvent, _ := history.MostRecentlyCompletedEvent() | |
42 | return newDelivery(lastEvent, itinerary, rs) | |
43 | } | |
44 | ||
45 | // newDelivery creates a up-to-date delivery based on an handling event, | |
46 | // itinerary and a route specification. | |
47 | func newDelivery(lastEvent HandlingEvent, itinerary Itinerary, rs RouteSpecification) Delivery { | |
48 | var ( | |
49 | routingStatus = calculateRoutingStatus(itinerary, rs) | |
50 | transportStatus = calculateTransportStatus(lastEvent) | |
51 | lastKnownLocation = calculateLastKnownLocation(lastEvent) | |
52 | isMisdirected = calculateMisdirectedStatus(lastEvent, itinerary) | |
53 | isUnloadedAtDestination = calculateUnloadedAtDestination(lastEvent, rs) | |
54 | currentVoyage = calculateCurrentVoyage(transportStatus, lastEvent) | |
55 | ) | |
56 | ||
57 | d := Delivery{ | |
58 | LastEvent: lastEvent, | |
59 | Itinerary: itinerary, | |
60 | RouteSpecification: rs, | |
61 | RoutingStatus: routingStatus, | |
62 | TransportStatus: transportStatus, | |
63 | LastKnownLocation: lastKnownLocation, | |
64 | IsMisdirected: isMisdirected, | |
65 | IsUnloadedAtDestination: isUnloadedAtDestination, | |
66 | CurrentVoyage: currentVoyage, | |
67 | } | |
68 | ||
69 | d.NextExpectedActivity = calculateNextExpectedActivity(d) | |
70 | d.ETA = calculateETA(d) | |
71 | ||
72 | return d | |
73 | } | |
74 | ||
75 | // Below are internal functions used when creating a new delivery. | |
76 | ||
77 | func calculateRoutingStatus(itinerary Itinerary, rs RouteSpecification) RoutingStatus { | |
78 | if itinerary.Legs == nil { | |
79 | return NotRouted | |
80 | } | |
81 | ||
82 | if rs.IsSatisfiedBy(itinerary) { | |
83 | return Routed | |
84 | } | |
85 | ||
86 | return Misrouted | |
87 | } | |
88 | ||
89 | func calculateMisdirectedStatus(event HandlingEvent, itinerary Itinerary) bool { | |
90 | if event.Activity.Type == NotHandled { | |
91 | return false | |
92 | } | |
93 | ||
94 | return !itinerary.IsExpected(event) | |
95 | } | |
96 | ||
97 | func calculateUnloadedAtDestination(event HandlingEvent, rs RouteSpecification) bool { | |
98 | if event.Activity.Type == NotHandled { | |
99 | return false | |
100 | } | |
101 | ||
102 | return event.Activity.Type == Unload && rs.Destination == event.Activity.Location | |
103 | } | |
104 | ||
105 | func calculateTransportStatus(event HandlingEvent) TransportStatus { | |
106 | switch event.Activity.Type { | |
107 | case NotHandled: | |
108 | return NotReceived | |
109 | case Load: | |
110 | return OnboardCarrier | |
111 | case Unload: | |
112 | return InPort | |
113 | case Receive: | |
114 | return InPort | |
115 | case Customs: | |
116 | return InPort | |
117 | case Claim: | |
118 | return Claimed | |
119 | } | |
120 | return Unknown | |
121 | } | |
122 | ||
123 | func calculateLastKnownLocation(event HandlingEvent) location.UNLocode { | |
124 | return event.Activity.Location | |
125 | } | |
126 | ||
127 | func calculateNextExpectedActivity(d Delivery) HandlingActivity { | |
128 | if !d.IsOnTrack() { | |
129 | return HandlingActivity{} | |
130 | } | |
131 | ||
132 | switch d.LastEvent.Activity.Type { | |
133 | case NotHandled: | |
134 | return HandlingActivity{Type: Receive, Location: d.RouteSpecification.Origin} | |
135 | case Receive: | |
136 | l := d.Itinerary.Legs[0] | |
137 | return HandlingActivity{Type: Load, Location: l.LoadLocation, VoyageNumber: l.VoyageNumber} | |
138 | case Load: | |
139 | for _, l := range d.Itinerary.Legs { | |
140 | if l.LoadLocation == d.LastEvent.Activity.Location { | |
141 | return HandlingActivity{Type: Unload, Location: l.UnloadLocation, VoyageNumber: l.VoyageNumber} | |
142 | } | |
143 | } | |
144 | case Unload: | |
145 | for i, l := range d.Itinerary.Legs { | |
146 | if l.UnloadLocation == d.LastEvent.Activity.Location { | |
147 | if i < len(d.Itinerary.Legs)-1 { | |
148 | return HandlingActivity{Type: Load, Location: d.Itinerary.Legs[i+1].LoadLocation, VoyageNumber: d.Itinerary.Legs[i+1].VoyageNumber} | |
149 | } | |
150 | ||
151 | return HandlingActivity{Type: Claim, Location: l.UnloadLocation} | |
152 | } | |
153 | } | |
154 | } | |
155 | ||
156 | return HandlingActivity{} | |
157 | } | |
158 | ||
159 | func calculateCurrentVoyage(transportStatus TransportStatus, event HandlingEvent) voyage.Number { | |
160 | if transportStatus == OnboardCarrier && event.Activity.Type != NotHandled { | |
161 | return event.Activity.VoyageNumber | |
162 | } | |
163 | ||
164 | return voyage.Number("") | |
165 | } | |
166 | ||
167 | func calculateETA(d Delivery) time.Time { | |
168 | if !d.IsOnTrack() { | |
169 | return time.Time{} | |
170 | } | |
171 | ||
172 | return d.Itinerary.FinalArrivalTime() | |
173 | } |
0 | package cargo | |
1 | ||
2 | // TODO: It would make sense to have this in its own package. Unfortunately, | |
3 | // then there would be a circular dependency between the cargo and handling | |
4 | // packages since cargo.Delivery would use handling.HandlingEvent and | |
5 | // handling.HandlingEvent would use cargo.TrackingID. Also, | |
6 | // HandlingEventFactory depends on the cargo repository. | |
7 | // | |
8 | // It would make sense not having the cargo package depend on handling. | |
9 | ||
10 | import ( | |
11 | "errors" | |
12 | "time" | |
13 | ||
14 | "github.com/go-kit/kit/examples/shipping/location" | |
15 | "github.com/go-kit/kit/examples/shipping/voyage" | |
16 | ) | |
17 | ||
18 | // HandlingActivity represents how and where a cargo can be handled, and can | |
19 | // be used to express predictions about what is expected to happen to a cargo | |
20 | // in the future. | |
21 | type HandlingActivity struct { | |
22 | Type HandlingEventType | |
23 | Location location.UNLocode | |
24 | VoyageNumber voyage.Number | |
25 | } | |
26 | ||
27 | // HandlingEvent is used to register the event when, for instance, a cargo is | |
28 | // unloaded from a carrier at a some location at a given time. | |
29 | type HandlingEvent struct { | |
30 | TrackingID TrackingID | |
31 | Activity HandlingActivity | |
32 | } | |
33 | ||
34 | // HandlingEventType describes type of a handling event. | |
35 | type HandlingEventType int | |
36 | ||
37 | // Valid handling event types. | |
38 | const ( | |
39 | NotHandled HandlingEventType = iota | |
40 | Load | |
41 | Unload | |
42 | Receive | |
43 | Claim | |
44 | Customs | |
45 | ) | |
46 | ||
47 | func (t HandlingEventType) String() string { | |
48 | switch t { | |
49 | case NotHandled: | |
50 | return "Not Handled" | |
51 | case Load: | |
52 | return "Load" | |
53 | case Unload: | |
54 | return "Unload" | |
55 | case Receive: | |
56 | return "Receive" | |
57 | case Claim: | |
58 | return "Claim" | |
59 | case Customs: | |
60 | return "Customs" | |
61 | } | |
62 | ||
63 | return "" | |
64 | } | |
65 | ||
66 | // HandlingHistory is the handling history of a cargo. | |
67 | type HandlingHistory struct { | |
68 | HandlingEvents []HandlingEvent | |
69 | } | |
70 | ||
71 | // MostRecentlyCompletedEvent returns most recently completed handling event. | |
72 | func (h HandlingHistory) MostRecentlyCompletedEvent() (HandlingEvent, error) { | |
73 | if len(h.HandlingEvents) == 0 { | |
74 | return HandlingEvent{}, errors.New("delivery history is empty") | |
75 | } | |
76 | ||
77 | return h.HandlingEvents[len(h.HandlingEvents)-1], nil | |
78 | } | |
79 | ||
80 | // HandlingEventRepository provides access a handling event store. | |
81 | type HandlingEventRepository interface { | |
82 | Store(e HandlingEvent) | |
83 | QueryHandlingHistory(TrackingID) HandlingHistory | |
84 | } | |
85 | ||
86 | // HandlingEventFactory creates handling events. | |
87 | type HandlingEventFactory struct { | |
88 | CargoRepository Repository | |
89 | VoyageRepository voyage.Repository | |
90 | LocationRepository location.Repository | |
91 | } | |
92 | ||
93 | // CreateHandlingEvent creates a validated handling event. | |
94 | func (f *HandlingEventFactory) CreateHandlingEvent(registered time.Time, completed time.Time, id TrackingID, | |
95 | voyageNumber voyage.Number, unLocode location.UNLocode, eventType HandlingEventType) (HandlingEvent, error) { | |
96 | ||
97 | if _, err := f.CargoRepository.Find(id); err != nil { | |
98 | return HandlingEvent{}, err | |
99 | } | |
100 | ||
101 | if _, err := f.VoyageRepository.Find(voyageNumber); err != nil { | |
102 | // TODO: This is pretty ugly, but when creating a Receive event, the voyage number is not known. | |
103 | if len(voyageNumber) > 0 { | |
104 | return HandlingEvent{}, err | |
105 | } | |
106 | } | |
107 | ||
108 | if _, err := f.LocationRepository.Find(unLocode); err != nil { | |
109 | return HandlingEvent{}, err | |
110 | } | |
111 | ||
112 | return HandlingEvent{ | |
113 | TrackingID: id, | |
114 | Activity: HandlingActivity{ | |
115 | Type: eventType, | |
116 | Location: unLocode, | |
117 | VoyageNumber: voyageNumber, | |
118 | }, | |
119 | }, nil | |
120 | } |
0 | package cargo | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/examples/shipping/location" | |
6 | "github.com/go-kit/kit/examples/shipping/voyage" | |
7 | ) | |
8 | ||
9 | // Leg describes the transportation between two locations on a voyage. | |
10 | type Leg struct { | |
11 | VoyageNumber voyage.Number `json:"voyage_number"` | |
12 | LoadLocation location.UNLocode `json:"from"` | |
13 | UnloadLocation location.UNLocode `json:"to"` | |
14 | LoadTime time.Time `json:"load_time"` | |
15 | UnloadTime time.Time `json:"unload_time"` | |
16 | } | |
17 | ||
18 | // NewLeg creates a new itinerary leg. | |
19 | func NewLeg(voyageNumber voyage.Number, loadLocation, unloadLocation location.UNLocode, loadTime, unloadTime time.Time) Leg { | |
20 | return Leg{ | |
21 | VoyageNumber: voyageNumber, | |
22 | LoadLocation: loadLocation, | |
23 | UnloadLocation: unloadLocation, | |
24 | LoadTime: loadTime, | |
25 | UnloadTime: unloadTime, | |
26 | } | |
27 | } | |
28 | ||
29 | // Itinerary specifies steps required to transport a cargo from its origin to | |
30 | // destination. | |
31 | type Itinerary struct { | |
32 | Legs []Leg `json:"legs"` | |
33 | } | |
34 | ||
35 | // InitialDepartureLocation returns the start of the itinerary. | |
36 | func (i Itinerary) InitialDepartureLocation() location.UNLocode { | |
37 | if i.IsEmpty() { | |
38 | return location.UNLocode("") | |
39 | } | |
40 | return i.Legs[0].LoadLocation | |
41 | } | |
42 | ||
43 | // FinalArrivalLocation returns the end of the itinerary. | |
44 | func (i Itinerary) FinalArrivalLocation() location.UNLocode { | |
45 | if i.IsEmpty() { | |
46 | return location.UNLocode("") | |
47 | } | |
48 | return i.Legs[len(i.Legs)-1].UnloadLocation | |
49 | } | |
50 | ||
51 | // FinalArrivalTime returns the expected arrival time at final destination. | |
52 | func (i Itinerary) FinalArrivalTime() time.Time { | |
53 | return i.Legs[len(i.Legs)-1].UnloadTime | |
54 | } | |
55 | ||
56 | // IsEmpty checks if the itinerary contains at least one leg. | |
57 | func (i Itinerary) IsEmpty() bool { | |
58 | return i.Legs == nil || len(i.Legs) == 0 | |
59 | } | |
60 | ||
61 | // IsExpected checks if the given handling event is expected when executing | |
62 | // this itinerary. | |
63 | func (i Itinerary) IsExpected(event HandlingEvent) bool { | |
64 | if i.IsEmpty() { | |
65 | return true | |
66 | } | |
67 | ||
68 | switch event.Activity.Type { | |
69 | case Receive: | |
70 | return i.InitialDepartureLocation() == event.Activity.Location | |
71 | case Load: | |
72 | for _, l := range i.Legs { | |
73 | if l.LoadLocation == event.Activity.Location && l.VoyageNumber == event.Activity.VoyageNumber { | |
74 | return true | |
75 | } | |
76 | } | |
77 | return false | |
78 | case Unload: | |
79 | for _, l := range i.Legs { | |
80 | if l.UnloadLocation == event.Activity.Location && l.VoyageNumber == event.Activity.VoyageNumber { | |
81 | return true | |
82 | } | |
83 | } | |
84 | return false | |
85 | case Claim: | |
86 | return i.FinalArrivalLocation() == event.Activity.Location | |
87 | } | |
88 | ||
89 | return true | |
90 | } |
0 | package handling | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "time" | |
5 | ||
6 | "github.com/go-kit/kit/endpoint" | |
7 | ||
8 | "github.com/go-kit/kit/examples/shipping/cargo" | |
9 | "github.com/go-kit/kit/examples/shipping/location" | |
10 | "github.com/go-kit/kit/examples/shipping/voyage" | |
11 | ) | |
12 | ||
13 | type registerIncidentRequest struct { | |
14 | ID cargo.TrackingID | |
15 | Location location.UNLocode | |
16 | Voyage voyage.Number | |
17 | EventType cargo.HandlingEventType | |
18 | CompletionTime time.Time | |
19 | } | |
20 | ||
21 | type registerIncidentResponse struct { | |
22 | Err error `json:"error,omitempty"` | |
23 | } | |
24 | ||
25 | func (r registerIncidentResponse) error() error { return r.Err } | |
26 | ||
27 | func makeRegisterIncidentEndpoint(hs Service) endpoint.Endpoint { | |
28 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
29 | req := request.(registerIncidentRequest) | |
30 | err := hs.RegisterHandlingEvent(req.CompletionTime, req.ID, req.Voyage, req.Location, req.EventType) | |
31 | return registerIncidentResponse{Err: err}, nil | |
32 | } | |
33 | } |
0 | package handling | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/metrics" | |
6 | ||
7 | "github.com/go-kit/kit/examples/shipping/cargo" | |
8 | "github.com/go-kit/kit/examples/shipping/location" | |
9 | "github.com/go-kit/kit/examples/shipping/voyage" | |
10 | ) | |
11 | ||
12 | type instrumentingService struct { | |
13 | requestCount metrics.Counter | |
14 | requestLatency metrics.Histogram | |
15 | Service | |
16 | } | |
17 | ||
18 | // NewInstrumentingService returns an instance of an instrumenting Service. | |
19 | func NewInstrumentingService(counter metrics.Counter, latency metrics.Histogram, s Service) Service { | |
20 | return &instrumentingService{ | |
21 | requestCount: counter, | |
22 | requestLatency: latency, | |
23 | Service: s, | |
24 | } | |
25 | } | |
26 | ||
27 | func (s *instrumentingService) RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number, | |
28 | loc location.UNLocode, eventType cargo.HandlingEventType) error { | |
29 | ||
30 | defer func(begin time.Time) { | |
31 | s.requestCount.With("method", "register_incident").Add(1) | |
32 | s.requestLatency.With("method", "register_incident").Observe(time.Since(begin).Seconds()) | |
33 | }(time.Now()) | |
34 | ||
35 | return s.Service.RegisterHandlingEvent(completed, id, voyageNumber, loc, eventType) | |
36 | } |
0 | package handling | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/log" | |
6 | ||
7 | "github.com/go-kit/kit/examples/shipping/cargo" | |
8 | "github.com/go-kit/kit/examples/shipping/location" | |
9 | "github.com/go-kit/kit/examples/shipping/voyage" | |
10 | ) | |
11 | ||
12 | type loggingService struct { | |
13 | logger log.Logger | |
14 | Service | |
15 | } | |
16 | ||
17 | // NewLoggingService returns a new instance of a logging Service. | |
18 | func NewLoggingService(logger log.Logger, s Service) Service { | |
19 | return &loggingService{logger, s} | |
20 | } | |
21 | ||
22 | func (s *loggingService) RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number, | |
23 | unLocode location.UNLocode, eventType cargo.HandlingEventType) (err error) { | |
24 | defer func(begin time.Time) { | |
25 | s.logger.Log( | |
26 | "method", "register_incident", | |
27 | "tracking_id", id, | |
28 | "location", unLocode, | |
29 | "voyage", voyageNumber, | |
30 | "event_type", eventType, | |
31 | "completion_time", completed, | |
32 | "took", time.Since(begin), | |
33 | "err", err, | |
34 | ) | |
35 | }(time.Now()) | |
36 | return s.Service.RegisterHandlingEvent(completed, id, voyageNumber, unLocode, eventType) | |
37 | } |
0 | // Package handling provides the use-case for registering incidents. Used by | |
1 | // views facing the people handling the cargo along its route. | |
2 | package handling | |
3 | ||
4 | import ( | |
5 | "errors" | |
6 | "time" | |
7 | ||
8 | "github.com/go-kit/kit/examples/shipping/cargo" | |
9 | "github.com/go-kit/kit/examples/shipping/inspection" | |
10 | "github.com/go-kit/kit/examples/shipping/location" | |
11 | "github.com/go-kit/kit/examples/shipping/voyage" | |
12 | ) | |
13 | ||
14 | // ErrInvalidArgument is returned when one or more arguments are invalid. | |
15 | var ErrInvalidArgument = errors.New("invalid argument") | |
16 | ||
17 | // EventHandler provides a means of subscribing to registered handling events. | |
18 | type EventHandler interface { | |
19 | CargoWasHandled(cargo.HandlingEvent) | |
20 | } | |
21 | ||
22 | // Service provides handling operations. | |
23 | type Service interface { | |
24 | // RegisterHandlingEvent registers a handling event in the system, and | |
25 | // notifies interested parties that a cargo has been handled. | |
26 | RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number, | |
27 | unLocode location.UNLocode, eventType cargo.HandlingEventType) error | |
28 | } | |
29 | ||
30 | type service struct { | |
31 | handlingEventRepository cargo.HandlingEventRepository | |
32 | handlingEventFactory cargo.HandlingEventFactory | |
33 | handlingEventHandler EventHandler | |
34 | } | |
35 | ||
36 | func (s *service) RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number, | |
37 | loc location.UNLocode, eventType cargo.HandlingEventType) error { | |
38 | if completed.IsZero() || id == "" || loc == "" || eventType == cargo.NotHandled { | |
39 | return ErrInvalidArgument | |
40 | } | |
41 | ||
42 | e, err := s.handlingEventFactory.CreateHandlingEvent(time.Now(), completed, id, voyageNumber, loc, eventType) | |
43 | if err != nil { | |
44 | return err | |
45 | } | |
46 | ||
47 | s.handlingEventRepository.Store(e) | |
48 | s.handlingEventHandler.CargoWasHandled(e) | |
49 | ||
50 | return nil | |
51 | } | |
52 | ||
53 | // NewService creates a handling event service with necessary dependencies. | |
54 | func NewService(r cargo.HandlingEventRepository, f cargo.HandlingEventFactory, h EventHandler) Service { | |
55 | return &service{ | |
56 | handlingEventRepository: r, | |
57 | handlingEventFactory: f, | |
58 | handlingEventHandler: h, | |
59 | } | |
60 | } | |
61 | ||
62 | type handlingEventHandler struct { | |
63 | InspectionService inspection.Service | |
64 | } | |
65 | ||
66 | func (h *handlingEventHandler) CargoWasHandled(event cargo.HandlingEvent) { | |
67 | h.InspectionService.InspectCargo(event.TrackingID) | |
68 | } | |
69 | ||
70 | // NewEventHandler returns a new instance of a EventHandler. | |
71 | func NewEventHandler(s inspection.Service) EventHandler { | |
72 | return &handlingEventHandler{ | |
73 | InspectionService: s, | |
74 | } | |
75 | } |
0 | package handling | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "encoding/json" | |
5 | "net/http" | |
6 | "time" | |
7 | ||
8 | "github.com/gorilla/mux" | |
9 | ||
10 | kitlog "github.com/go-kit/kit/log" | |
11 | "github.com/go-kit/kit/transport" | |
12 | kithttp "github.com/go-kit/kit/transport/http" | |
13 | ||
14 | "github.com/go-kit/kit/examples/shipping/cargo" | |
15 | "github.com/go-kit/kit/examples/shipping/location" | |
16 | "github.com/go-kit/kit/examples/shipping/voyage" | |
17 | ) | |
18 | ||
19 | // MakeHandler returns a handler for the handling service. | |
20 | func MakeHandler(hs Service, logger kitlog.Logger) http.Handler { | |
21 | r := mux.NewRouter() | |
22 | ||
23 | opts := []kithttp.ServerOption{ | |
24 | kithttp.ServerErrorHandler(transport.NewLogErrorHandler(logger)), | |
25 | kithttp.ServerErrorEncoder(encodeError), | |
26 | } | |
27 | ||
28 | registerIncidentHandler := kithttp.NewServer( | |
29 | makeRegisterIncidentEndpoint(hs), | |
30 | decodeRegisterIncidentRequest, | |
31 | encodeResponse, | |
32 | opts..., | |
33 | ) | |
34 | ||
35 | r.Handle("/handling/v1/incidents", registerIncidentHandler).Methods("POST") | |
36 | ||
37 | return r | |
38 | } | |
39 | ||
40 | func decodeRegisterIncidentRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
41 | var body struct { | |
42 | CompletionTime time.Time `json:"completion_time"` | |
43 | TrackingID string `json:"tracking_id"` | |
44 | VoyageNumber string `json:"voyage"` | |
45 | Location string `json:"location"` | |
46 | EventType string `json:"event_type"` | |
47 | } | |
48 | ||
49 | if err := json.NewDecoder(r.Body).Decode(&body); err != nil { | |
50 | return nil, err | |
51 | } | |
52 | ||
53 | return registerIncidentRequest{ | |
54 | CompletionTime: body.CompletionTime, | |
55 | ID: cargo.TrackingID(body.TrackingID), | |
56 | Voyage: voyage.Number(body.VoyageNumber), | |
57 | Location: location.UNLocode(body.Location), | |
58 | EventType: stringToEventType(body.EventType), | |
59 | }, nil | |
60 | } | |
61 | ||
62 | func stringToEventType(s string) cargo.HandlingEventType { | |
63 | types := map[string]cargo.HandlingEventType{ | |
64 | cargo.Receive.String(): cargo.Receive, | |
65 | cargo.Load.String(): cargo.Load, | |
66 | cargo.Unload.String(): cargo.Unload, | |
67 | cargo.Customs.String(): cargo.Customs, | |
68 | cargo.Claim.String(): cargo.Claim, | |
69 | } | |
70 | return types[s] | |
71 | } | |
72 | ||
73 | func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { | |
74 | if e, ok := response.(errorer); ok && e.error() != nil { | |
75 | encodeError(ctx, e.error(), w) | |
76 | return nil | |
77 | } | |
78 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
79 | return json.NewEncoder(w).Encode(response) | |
80 | } | |
81 | ||
82 | type errorer interface { | |
83 | error() error | |
84 | } | |
85 | ||
86 | // encode errors from business-logic | |
87 | func encodeError(_ context.Context, err error, w http.ResponseWriter) { | |
88 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
89 | switch err { | |
90 | case cargo.ErrUnknown: | |
91 | w.WriteHeader(http.StatusNotFound) | |
92 | case ErrInvalidArgument: | |
93 | w.WriteHeader(http.StatusBadRequest) | |
94 | default: | |
95 | w.WriteHeader(http.StatusInternalServerError) | |
96 | } | |
97 | json.NewEncoder(w).Encode(map[string]interface{}{ | |
98 | "error": err.Error(), | |
99 | }) | |
100 | } |
0 | // Package inmem provides in-memory implementations of all the domain repositories. | |
1 | package inmem | |
2 | ||
3 | import ( | |
4 | "sync" | |
5 | ||
6 | "github.com/go-kit/kit/examples/shipping/cargo" | |
7 | "github.com/go-kit/kit/examples/shipping/location" | |
8 | "github.com/go-kit/kit/examples/shipping/voyage" | |
9 | ) | |
10 | ||
11 | type cargoRepository struct { | |
12 | mtx sync.RWMutex | |
13 | cargos map[cargo.TrackingID]*cargo.Cargo | |
14 | } | |
15 | ||
16 | func (r *cargoRepository) Store(c *cargo.Cargo) error { | |
17 | r.mtx.Lock() | |
18 | defer r.mtx.Unlock() | |
19 | r.cargos[c.TrackingID] = c | |
20 | return nil | |
21 | } | |
22 | ||
23 | func (r *cargoRepository) Find(id cargo.TrackingID) (*cargo.Cargo, error) { | |
24 | r.mtx.RLock() | |
25 | defer r.mtx.RUnlock() | |
26 | if val, ok := r.cargos[id]; ok { | |
27 | return val, nil | |
28 | } | |
29 | return nil, cargo.ErrUnknown | |
30 | } | |
31 | ||
32 | func (r *cargoRepository) FindAll() []*cargo.Cargo { | |
33 | r.mtx.RLock() | |
34 | defer r.mtx.RUnlock() | |
35 | c := make([]*cargo.Cargo, 0, len(r.cargos)) | |
36 | for _, val := range r.cargos { | |
37 | c = append(c, val) | |
38 | } | |
39 | return c | |
40 | } | |
41 | ||
42 | // NewCargoRepository returns a new instance of a in-memory cargo repository. | |
43 | func NewCargoRepository() cargo.Repository { | |
44 | return &cargoRepository{ | |
45 | cargos: make(map[cargo.TrackingID]*cargo.Cargo), | |
46 | } | |
47 | } | |
48 | ||
49 | type locationRepository struct { | |
50 | locations map[location.UNLocode]*location.Location | |
51 | } | |
52 | ||
53 | func (r *locationRepository) Find(locode location.UNLocode) (*location.Location, error) { | |
54 | if l, ok := r.locations[locode]; ok { | |
55 | return l, nil | |
56 | } | |
57 | return nil, location.ErrUnknown | |
58 | } | |
59 | ||
60 | func (r *locationRepository) FindAll() []*location.Location { | |
61 | l := make([]*location.Location, 0, len(r.locations)) | |
62 | for _, val := range r.locations { | |
63 | l = append(l, val) | |
64 | } | |
65 | return l | |
66 | } | |
67 | ||
68 | // NewLocationRepository returns a new instance of a in-memory location repository. | |
69 | func NewLocationRepository() location.Repository { | |
70 | r := &locationRepository{ | |
71 | locations: make(map[location.UNLocode]*location.Location), | |
72 | } | |
73 | ||
74 | r.locations[location.SESTO] = location.Stockholm | |
75 | r.locations[location.AUMEL] = location.Melbourne | |
76 | r.locations[location.CNHKG] = location.Hongkong | |
77 | r.locations[location.JNTKO] = location.Tokyo | |
78 | r.locations[location.NLRTM] = location.Rotterdam | |
79 | r.locations[location.DEHAM] = location.Hamburg | |
80 | ||
81 | return r | |
82 | } | |
83 | ||
84 | type voyageRepository struct { | |
85 | voyages map[voyage.Number]*voyage.Voyage | |
86 | } | |
87 | ||
88 | func (r *voyageRepository) Find(voyageNumber voyage.Number) (*voyage.Voyage, error) { | |
89 | if v, ok := r.voyages[voyageNumber]; ok { | |
90 | return v, nil | |
91 | } | |
92 | ||
93 | return nil, voyage.ErrUnknown | |
94 | } | |
95 | ||
96 | // NewVoyageRepository returns a new instance of a in-memory voyage repository. | |
97 | func NewVoyageRepository() voyage.Repository { | |
98 | r := &voyageRepository{ | |
99 | voyages: make(map[voyage.Number]*voyage.Voyage), | |
100 | } | |
101 | ||
102 | r.voyages[voyage.V100.Number] = voyage.V100 | |
103 | r.voyages[voyage.V300.Number] = voyage.V300 | |
104 | r.voyages[voyage.V400.Number] = voyage.V400 | |
105 | ||
106 | r.voyages[voyage.V0100S.Number] = voyage.V0100S | |
107 | r.voyages[voyage.V0200T.Number] = voyage.V0200T | |
108 | r.voyages[voyage.V0300A.Number] = voyage.V0300A | |
109 | r.voyages[voyage.V0301S.Number] = voyage.V0301S | |
110 | r.voyages[voyage.V0400S.Number] = voyage.V0400S | |
111 | ||
112 | return r | |
113 | } | |
114 | ||
115 | type handlingEventRepository struct { | |
116 | mtx sync.RWMutex | |
117 | events map[cargo.TrackingID][]cargo.HandlingEvent | |
118 | } | |
119 | ||
120 | func (r *handlingEventRepository) Store(e cargo.HandlingEvent) { | |
121 | r.mtx.Lock() | |
122 | defer r.mtx.Unlock() | |
123 | // Make array if it's the first event with this tracking ID. | |
124 | if _, ok := r.events[e.TrackingID]; !ok { | |
125 | r.events[e.TrackingID] = make([]cargo.HandlingEvent, 0) | |
126 | } | |
127 | r.events[e.TrackingID] = append(r.events[e.TrackingID], e) | |
128 | } | |
129 | ||
130 | func (r *handlingEventRepository) QueryHandlingHistory(id cargo.TrackingID) cargo.HandlingHistory { | |
131 | r.mtx.RLock() | |
132 | defer r.mtx.RUnlock() | |
133 | return cargo.HandlingHistory{HandlingEvents: r.events[id]} | |
134 | } | |
135 | ||
136 | // NewHandlingEventRepository returns a new instance of a in-memory handling event repository. | |
137 | func NewHandlingEventRepository() cargo.HandlingEventRepository { | |
138 | return &handlingEventRepository{ | |
139 | events: make(map[cargo.TrackingID][]cargo.HandlingEvent), | |
140 | } | |
141 | } |
0 | // Package inspection provides means to inspect cargos. | |
1 | package inspection | |
2 | ||
3 | import ( | |
4 | "github.com/go-kit/kit/examples/shipping/cargo" | |
5 | ) | |
6 | ||
7 | // EventHandler provides means of subscribing to inspection events. | |
8 | type EventHandler interface { | |
9 | CargoWasMisdirected(*cargo.Cargo) | |
10 | CargoHasArrived(*cargo.Cargo) | |
11 | } | |
12 | ||
13 | // Service provides cargo inspection operations. | |
14 | type Service interface { | |
15 | // InspectCargo inspects cargo and send relevant notifications to | |
16 | // interested parties, for example if a cargo has been misdirected, or | |
17 | // unloaded at the final destination. | |
18 | InspectCargo(id cargo.TrackingID) | |
19 | } | |
20 | ||
21 | type service struct { | |
22 | cargos cargo.Repository | |
23 | events cargo.HandlingEventRepository | |
24 | handler EventHandler | |
25 | } | |
26 | ||
27 | // TODO: Should be transactional | |
28 | func (s *service) InspectCargo(id cargo.TrackingID) { | |
29 | c, err := s.cargos.Find(id) | |
30 | if err != nil { | |
31 | return | |
32 | } | |
33 | ||
34 | h := s.events.QueryHandlingHistory(id) | |
35 | ||
36 | c.DeriveDeliveryProgress(h) | |
37 | ||
38 | if c.Delivery.IsMisdirected { | |
39 | s.handler.CargoWasMisdirected(c) | |
40 | } | |
41 | ||
42 | if c.Delivery.IsUnloadedAtDestination { | |
43 | s.handler.CargoHasArrived(c) | |
44 | } | |
45 | ||
46 | s.cargos.Store(c) | |
47 | } | |
48 | ||
49 | // NewService creates a inspection service with necessary dependencies. | |
50 | func NewService(cargos cargo.Repository, events cargo.HandlingEventRepository, handler EventHandler) Service { | |
51 | return &service{cargos, events, handler} | |
52 | } |
0 | // Package location provides the Location aggregate. | |
1 | package location | |
2 | ||
3 | import ( | |
4 | "errors" | |
5 | ) | |
6 | ||
7 | // UNLocode is the United Nations location code that uniquely identifies a | |
8 | // particular location. | |
9 | // | |
10 | // http://www.unece.org/cefact/locode/ | |
11 | // http://www.unece.org/cefact/locode/DocColumnDescription.htm#LOCODE | |
12 | type UNLocode string | |
13 | ||
14 | // Location is a location is our model is stops on a journey, such as cargo | |
15 | // origin or destination, or carrier movement endpoints. | |
16 | type Location struct { | |
17 | UNLocode UNLocode | |
18 | Name string | |
19 | } | |
20 | ||
21 | // ErrUnknown is used when a location could not be found. | |
22 | var ErrUnknown = errors.New("unknown location") | |
23 | ||
24 | // Repository provides access a location store. | |
25 | type Repository interface { | |
26 | Find(locode UNLocode) (*Location, error) | |
27 | FindAll() []*Location | |
28 | } |
0 | package location | |
1 | ||
2 | // Sample UN locodes. | |
3 | var ( | |
4 | SESTO UNLocode = "SESTO" | |
5 | AUMEL UNLocode = "AUMEL" | |
6 | CNHKG UNLocode = "CNHKG" | |
7 | USNYC UNLocode = "USNYC" | |
8 | USCHI UNLocode = "USCHI" | |
9 | JNTKO UNLocode = "JNTKO" | |
10 | DEHAM UNLocode = "DEHAM" | |
11 | NLRTM UNLocode = "NLRTM" | |
12 | FIHEL UNLocode = "FIHEL" | |
13 | ) | |
14 | ||
15 | // Sample locations. | |
16 | var ( | |
17 | Stockholm = &Location{SESTO, "Stockholm"} | |
18 | Melbourne = &Location{AUMEL, "Melbourne"} | |
19 | Hongkong = &Location{CNHKG, "Hongkong"} | |
20 | NewYork = &Location{USNYC, "New York"} | |
21 | Chicago = &Location{USCHI, "Chicago"} | |
22 | Tokyo = &Location{JNTKO, "Tokyo"} | |
23 | Hamburg = &Location{DEHAM, "Hamburg"} | |
24 | Rotterdam = &Location{NLRTM, "Rotterdam"} | |
25 | Helsinki = &Location{FIHEL, "Helsinki"} | |
26 | ) |
0 | package main | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "flag" | |
5 | "fmt" | |
6 | "net/http" | |
7 | "os" | |
8 | "os/signal" | |
9 | "syscall" | |
10 | "time" | |
11 | ||
12 | stdprometheus "github.com/prometheus/client_golang/prometheus" | |
13 | "github.com/prometheus/client_golang/prometheus/promhttp" | |
14 | ||
15 | "github.com/go-kit/kit/log" | |
16 | kitprometheus "github.com/go-kit/kit/metrics/prometheus" | |
17 | ||
18 | "github.com/go-kit/kit/examples/shipping/booking" | |
19 | "github.com/go-kit/kit/examples/shipping/cargo" | |
20 | "github.com/go-kit/kit/examples/shipping/handling" | |
21 | "github.com/go-kit/kit/examples/shipping/inmem" | |
22 | "github.com/go-kit/kit/examples/shipping/inspection" | |
23 | "github.com/go-kit/kit/examples/shipping/location" | |
24 | "github.com/go-kit/kit/examples/shipping/routing" | |
25 | "github.com/go-kit/kit/examples/shipping/tracking" | |
26 | ) | |
27 | ||
28 | const ( | |
29 | defaultPort = "8080" | |
30 | defaultRoutingServiceURL = "http://localhost:7878" | |
31 | ) | |
32 | ||
33 | func main() { | |
34 | var ( | |
35 | addr = envString("PORT", defaultPort) | |
36 | rsurl = envString("ROUTINGSERVICE_URL", defaultRoutingServiceURL) | |
37 | ||
38 | httpAddr = flag.String("http.addr", ":"+addr, "HTTP listen address") | |
39 | routingServiceURL = flag.String("service.routing", rsurl, "routing service URL") | |
40 | ||
41 | ctx = context.Background() | |
42 | ) | |
43 | ||
44 | flag.Parse() | |
45 | ||
46 | var logger log.Logger | |
47 | logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) | |
48 | logger = log.With(logger, "ts", log.DefaultTimestampUTC) | |
49 | ||
50 | var ( | |
51 | cargos = inmem.NewCargoRepository() | |
52 | locations = inmem.NewLocationRepository() | |
53 | voyages = inmem.NewVoyageRepository() | |
54 | handlingEvents = inmem.NewHandlingEventRepository() | |
55 | ) | |
56 | ||
57 | // Configure some questionable dependencies. | |
58 | var ( | |
59 | handlingEventFactory = cargo.HandlingEventFactory{ | |
60 | CargoRepository: cargos, | |
61 | VoyageRepository: voyages, | |
62 | LocationRepository: locations, | |
63 | } | |
64 | handlingEventHandler = handling.NewEventHandler( | |
65 | inspection.NewService(cargos, handlingEvents, nil), | |
66 | ) | |
67 | ) | |
68 | ||
69 | // Facilitate testing by adding some cargos. | |
70 | storeTestData(cargos) | |
71 | ||
72 | fieldKeys := []string{"method"} | |
73 | ||
74 | var rs routing.Service | |
75 | rs = routing.NewProxyingMiddleware(ctx, *routingServiceURL)(rs) | |
76 | ||
77 | var bs booking.Service | |
78 | bs = booking.NewService(cargos, locations, handlingEvents, rs) | |
79 | bs = booking.NewLoggingService(log.With(logger, "component", "booking"), bs) | |
80 | bs = booking.NewInstrumentingService( | |
81 | kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ | |
82 | Namespace: "api", | |
83 | Subsystem: "booking_service", | |
84 | Name: "request_count", | |
85 | Help: "Number of requests received.", | |
86 | }, fieldKeys), | |
87 | kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ | |
88 | Namespace: "api", | |
89 | Subsystem: "booking_service", | |
90 | Name: "request_latency_microseconds", | |
91 | Help: "Total duration of requests in microseconds.", | |
92 | }, fieldKeys), | |
93 | bs, | |
94 | ) | |
95 | ||
96 | var ts tracking.Service | |
97 | ts = tracking.NewService(cargos, handlingEvents) | |
98 | ts = tracking.NewLoggingService(log.With(logger, "component", "tracking"), ts) | |
99 | ts = tracking.NewInstrumentingService( | |
100 | kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ | |
101 | Namespace: "api", | |
102 | Subsystem: "tracking_service", | |
103 | Name: "request_count", | |
104 | Help: "Number of requests received.", | |
105 | }, fieldKeys), | |
106 | kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ | |
107 | Namespace: "api", | |
108 | Subsystem: "tracking_service", | |
109 | Name: "request_latency_microseconds", | |
110 | Help: "Total duration of requests in microseconds.", | |
111 | }, fieldKeys), | |
112 | ts, | |
113 | ) | |
114 | ||
115 | var hs handling.Service | |
116 | hs = handling.NewService(handlingEvents, handlingEventFactory, handlingEventHandler) | |
117 | hs = handling.NewLoggingService(log.With(logger, "component", "handling"), hs) | |
118 | hs = handling.NewInstrumentingService( | |
119 | kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ | |
120 | Namespace: "api", | |
121 | Subsystem: "handling_service", | |
122 | Name: "request_count", | |
123 | Help: "Number of requests received.", | |
124 | }, fieldKeys), | |
125 | kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ | |
126 | Namespace: "api", | |
127 | Subsystem: "handling_service", | |
128 | Name: "request_latency_microseconds", | |
129 | Help: "Total duration of requests in microseconds.", | |
130 | }, fieldKeys), | |
131 | hs, | |
132 | ) | |
133 | ||
134 | httpLogger := log.With(logger, "component", "http") | |
135 | ||
136 | mux := http.NewServeMux() | |
137 | ||
138 | mux.Handle("/booking/v1/", booking.MakeHandler(bs, httpLogger)) | |
139 | mux.Handle("/tracking/v1/", tracking.MakeHandler(ts, httpLogger)) | |
140 | mux.Handle("/handling/v1/", handling.MakeHandler(hs, httpLogger)) | |
141 | ||
142 | http.Handle("/", accessControl(mux)) | |
143 | http.Handle("/metrics", promhttp.Handler()) | |
144 | ||
145 | errs := make(chan error, 2) | |
146 | go func() { | |
147 | logger.Log("transport", "http", "address", *httpAddr, "msg", "listening") | |
148 | errs <- http.ListenAndServe(*httpAddr, nil) | |
149 | }() | |
150 | go func() { | |
151 | c := make(chan os.Signal) | |
152 | signal.Notify(c, syscall.SIGINT) | |
153 | errs <- fmt.Errorf("%s", <-c) | |
154 | }() | |
155 | ||
156 | logger.Log("terminated", <-errs) | |
157 | } | |
158 | ||
159 | func accessControl(h http.Handler) http.Handler { | |
160 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
161 | w.Header().Set("Access-Control-Allow-Origin", "*") | |
162 | w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") | |
163 | w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type") | |
164 | ||
165 | if r.Method == "OPTIONS" { | |
166 | return | |
167 | } | |
168 | ||
169 | h.ServeHTTP(w, r) | |
170 | }) | |
171 | } | |
172 | ||
173 | func envString(env, fallback string) string { | |
174 | e := os.Getenv(env) | |
175 | if e == "" { | |
176 | return fallback | |
177 | } | |
178 | return e | |
179 | } | |
180 | ||
181 | func storeTestData(r cargo.Repository) { | |
182 | test1 := cargo.New("FTL456", cargo.RouteSpecification{ | |
183 | Origin: location.AUMEL, | |
184 | Destination: location.SESTO, | |
185 | ArrivalDeadline: time.Now().AddDate(0, 0, 7), | |
186 | }) | |
187 | if err := r.Store(test1); err != nil { | |
188 | panic(err) | |
189 | } | |
190 | ||
191 | test2 := cargo.New("ABC123", cargo.RouteSpecification{ | |
192 | Origin: location.SESTO, | |
193 | Destination: location.CNHKG, | |
194 | ArrivalDeadline: time.Now().AddDate(0, 0, 14), | |
195 | }) | |
196 | if err := r.Store(test2); err != nil { | |
197 | panic(err) | |
198 | } | |
199 | } |
0 | package routing | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "encoding/json" | |
5 | "net/http" | |
6 | "net/url" | |
7 | "time" | |
8 | ||
9 | "github.com/go-kit/kit/circuitbreaker" | |
10 | "github.com/go-kit/kit/endpoint" | |
11 | kithttp "github.com/go-kit/kit/transport/http" | |
12 | ||
13 | "github.com/go-kit/kit/examples/shipping/cargo" | |
14 | "github.com/go-kit/kit/examples/shipping/location" | |
15 | "github.com/go-kit/kit/examples/shipping/voyage" | |
16 | ) | |
17 | ||
18 | type proxyService struct { | |
19 | context.Context | |
20 | FetchRoutesEndpoint endpoint.Endpoint | |
21 | Service | |
22 | } | |
23 | ||
24 | func (s proxyService) FetchRoutesForSpecification(rs cargo.RouteSpecification) []cargo.Itinerary { | |
25 | response, err := s.FetchRoutesEndpoint(s.Context, fetchRoutesRequest{ | |
26 | From: string(rs.Origin), | |
27 | To: string(rs.Destination), | |
28 | }) | |
29 | if err != nil { | |
30 | return []cargo.Itinerary{} | |
31 | } | |
32 | ||
33 | resp := response.(fetchRoutesResponse) | |
34 | ||
35 | var itineraries []cargo.Itinerary | |
36 | for _, r := range resp.Paths { | |
37 | var legs []cargo.Leg | |
38 | for _, e := range r.Edges { | |
39 | legs = append(legs, cargo.Leg{ | |
40 | VoyageNumber: voyage.Number(e.Voyage), | |
41 | LoadLocation: location.UNLocode(e.Origin), | |
42 | UnloadLocation: location.UNLocode(e.Destination), | |
43 | LoadTime: e.Departure, | |
44 | UnloadTime: e.Arrival, | |
45 | }) | |
46 | } | |
47 | ||
48 | itineraries = append(itineraries, cargo.Itinerary{Legs: legs}) | |
49 | } | |
50 | ||
51 | return itineraries | |
52 | } | |
53 | ||
54 | // ServiceMiddleware defines a middleware for a routing service. | |
55 | type ServiceMiddleware func(Service) Service | |
56 | ||
57 | // NewProxyingMiddleware returns a new instance of a proxying middleware. | |
58 | func NewProxyingMiddleware(ctx context.Context, proxyURL string) ServiceMiddleware { | |
59 | return func(next Service) Service { | |
60 | var e endpoint.Endpoint | |
61 | e = makeFetchRoutesEndpoint(ctx, proxyURL) | |
62 | e = circuitbreaker.Hystrix("fetch-routes")(e) | |
63 | return proxyService{ctx, e, next} | |
64 | } | |
65 | } | |
66 | ||
67 | type fetchRoutesRequest struct { | |
68 | From string | |
69 | To string | |
70 | } | |
71 | ||
72 | type fetchRoutesResponse struct { | |
73 | Paths []struct { | |
74 | Edges []struct { | |
75 | Origin string `json:"origin"` | |
76 | Destination string `json:"destination"` | |
77 | Voyage string `json:"voyage"` | |
78 | Departure time.Time `json:"departure"` | |
79 | Arrival time.Time `json:"arrival"` | |
80 | } `json:"edges"` | |
81 | } `json:"paths"` | |
82 | } | |
83 | ||
84 | func makeFetchRoutesEndpoint(ctx context.Context, instance string) endpoint.Endpoint { | |
85 | u, err := url.Parse(instance) | |
86 | if err != nil { | |
87 | panic(err) | |
88 | } | |
89 | if u.Path == "" { | |
90 | u.Path = "/paths" | |
91 | } | |
92 | return kithttp.NewClient( | |
93 | "GET", u, | |
94 | encodeFetchRoutesRequest, | |
95 | decodeFetchRoutesResponse, | |
96 | ).Endpoint() | |
97 | } | |
98 | ||
99 | func decodeFetchRoutesResponse(_ context.Context, resp *http.Response) (interface{}, error) { | |
100 | var response fetchRoutesResponse | |
101 | if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { | |
102 | return nil, err | |
103 | } | |
104 | return response, nil | |
105 | } | |
106 | ||
107 | func encodeFetchRoutesRequest(_ context.Context, r *http.Request, request interface{}) error { | |
108 | req := request.(fetchRoutesRequest) | |
109 | ||
110 | vals := r.URL.Query() | |
111 | vals.Add("from", req.From) | |
112 | vals.Add("to", req.To) | |
113 | r.URL.RawQuery = vals.Encode() | |
114 | ||
115 | return nil | |
116 | } |
0 | // Package routing provides the routing domain service. It does not actually | |
1 | // implement the routing service but merely acts as a proxy for a separate | |
2 | // bounded context. | |
3 | package routing | |
4 | ||
5 | import ( | |
6 | "github.com/go-kit/kit/examples/shipping/cargo" | |
7 | ) | |
8 | ||
9 | // Service provides access to an external routing service. | |
10 | type Service interface { | |
11 | // FetchRoutesForSpecification finds all possible routes that satisfy a | |
12 | // given specification. | |
13 | FetchRoutesForSpecification(rs cargo.RouteSpecification) []cargo.Itinerary | |
14 | } |
0 | package tracking | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | ||
5 | "github.com/go-kit/kit/endpoint" | |
6 | ) | |
7 | ||
8 | type trackCargoRequest struct { | |
9 | ID string | |
10 | } | |
11 | ||
12 | type trackCargoResponse struct { | |
13 | Cargo *Cargo `json:"cargo,omitempty"` | |
14 | Err error `json:"error,omitempty"` | |
15 | } | |
16 | ||
17 | func (r trackCargoResponse) error() error { return r.Err } | |
18 | ||
19 | func makeTrackCargoEndpoint(ts Service) endpoint.Endpoint { | |
20 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
21 | req := request.(trackCargoRequest) | |
22 | c, err := ts.Track(req.ID) | |
23 | return trackCargoResponse{Cargo: &c, Err: err}, nil | |
24 | } | |
25 | } |
0 | package tracking | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/metrics" | |
6 | ) | |
7 | ||
8 | type instrumentingService struct { | |
9 | requestCount metrics.Counter | |
10 | requestLatency metrics.Histogram | |
11 | Service | |
12 | } | |
13 | ||
14 | // NewInstrumentingService returns an instance of an instrumenting Service. | |
15 | func NewInstrumentingService(counter metrics.Counter, latency metrics.Histogram, s Service) Service { | |
16 | return &instrumentingService{ | |
17 | requestCount: counter, | |
18 | requestLatency: latency, | |
19 | Service: s, | |
20 | } | |
21 | } | |
22 | ||
23 | func (s *instrumentingService) Track(id string) (Cargo, error) { | |
24 | defer func(begin time.Time) { | |
25 | s.requestCount.With("method", "track").Add(1) | |
26 | s.requestLatency.With("method", "track").Observe(time.Since(begin).Seconds()) | |
27 | }(time.Now()) | |
28 | ||
29 | return s.Service.Track(id) | |
30 | } |
0 | package tracking | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/log" | |
6 | ) | |
7 | ||
8 | type loggingService struct { | |
9 | logger log.Logger | |
10 | Service | |
11 | } | |
12 | ||
13 | // NewLoggingService returns a new instance of a logging Service. | |
14 | func NewLoggingService(logger log.Logger, s Service) Service { | |
15 | return &loggingService{logger, s} | |
16 | } | |
17 | ||
18 | func (s *loggingService) Track(id string) (c Cargo, err error) { | |
19 | defer func(begin time.Time) { | |
20 | s.logger.Log("method", "track", "tracking_id", id, "took", time.Since(begin), "err", err) | |
21 | }(time.Now()) | |
22 | return s.Service.Track(id) | |
23 | } |
0 | // Package tracking provides the use-case of tracking a cargo. Used by views | |
1 | // facing the end-user. | |
2 | package tracking | |
3 | ||
4 | import ( | |
5 | "errors" | |
6 | "fmt" | |
7 | "strings" | |
8 | "time" | |
9 | ||
10 | "github.com/go-kit/kit/examples/shipping/cargo" | |
11 | ) | |
12 | ||
13 | // ErrInvalidArgument is returned when one or more arguments are invalid. | |
14 | var ErrInvalidArgument = errors.New("invalid argument") | |
15 | ||
16 | // Service is the interface that provides the basic Track method. | |
17 | type Service interface { | |
18 | // Track returns a cargo matching a tracking ID. | |
19 | Track(id string) (Cargo, error) | |
20 | } | |
21 | ||
22 | type service struct { | |
23 | cargos cargo.Repository | |
24 | handlingEvents cargo.HandlingEventRepository | |
25 | } | |
26 | ||
27 | func (s *service) Track(id string) (Cargo, error) { | |
28 | if id == "" { | |
29 | return Cargo{}, ErrInvalidArgument | |
30 | } | |
31 | c, err := s.cargos.Find(cargo.TrackingID(id)) | |
32 | if err != nil { | |
33 | return Cargo{}, err | |
34 | } | |
35 | return assemble(c, s.handlingEvents), nil | |
36 | } | |
37 | ||
38 | // NewService returns a new instance of the default Service. | |
39 | func NewService(cargos cargo.Repository, events cargo.HandlingEventRepository) Service { | |
40 | return &service{ | |
41 | cargos: cargos, | |
42 | handlingEvents: events, | |
43 | } | |
44 | } | |
45 | ||
46 | // Cargo is a read model for tracking views. | |
47 | type Cargo struct { | |
48 | TrackingID string `json:"tracking_id"` | |
49 | StatusText string `json:"status_text"` | |
50 | Origin string `json:"origin"` | |
51 | Destination string `json:"destination"` | |
52 | ETA time.Time `json:"eta"` | |
53 | NextExpectedActivity string `json:"next_expected_activity"` | |
54 | ArrivalDeadline time.Time `json:"arrival_deadline"` | |
55 | Events []Event `json:"events"` | |
56 | } | |
57 | ||
58 | // Leg is a read model for booking views. | |
59 | type Leg struct { | |
60 | VoyageNumber string `json:"voyage_number"` | |
61 | From string `json:"from"` | |
62 | To string `json:"to"` | |
63 | LoadTime time.Time `json:"load_time"` | |
64 | UnloadTime time.Time `json:"unload_time"` | |
65 | } | |
66 | ||
67 | // Event is a read model for tracking views. | |
68 | type Event struct { | |
69 | Description string `json:"description"` | |
70 | Expected bool `json:"expected"` | |
71 | } | |
72 | ||
73 | func assemble(c *cargo.Cargo, events cargo.HandlingEventRepository) Cargo { | |
74 | return Cargo{ | |
75 | TrackingID: string(c.TrackingID), | |
76 | Origin: string(c.Origin), | |
77 | Destination: string(c.RouteSpecification.Destination), | |
78 | ETA: c.Delivery.ETA, | |
79 | NextExpectedActivity: nextExpectedActivity(c), | |
80 | ArrivalDeadline: c.RouteSpecification.ArrivalDeadline, | |
81 | StatusText: assembleStatusText(c), | |
82 | Events: assembleEvents(c, events), | |
83 | } | |
84 | } | |
85 | ||
86 | func assembleLegs(c cargo.Cargo) []Leg { | |
87 | var legs []Leg | |
88 | for _, l := range c.Itinerary.Legs { | |
89 | legs = append(legs, Leg{ | |
90 | VoyageNumber: string(l.VoyageNumber), | |
91 | From: string(l.LoadLocation), | |
92 | To: string(l.UnloadLocation), | |
93 | LoadTime: l.LoadTime, | |
94 | UnloadTime: l.UnloadTime, | |
95 | }) | |
96 | } | |
97 | return legs | |
98 | } | |
99 | ||
100 | func nextExpectedActivity(c *cargo.Cargo) string { | |
101 | a := c.Delivery.NextExpectedActivity | |
102 | prefix := "Next expected activity is to" | |
103 | ||
104 | switch a.Type { | |
105 | case cargo.Load: | |
106 | return fmt.Sprintf("%s %s cargo onto voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location) | |
107 | case cargo.Unload: | |
108 | return fmt.Sprintf("%s %s cargo off of voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location) | |
109 | case cargo.NotHandled: | |
110 | return "There are currently no expected activities for this cargo." | |
111 | } | |
112 | ||
113 | return fmt.Sprintf("%s %s cargo in %s.", prefix, strings.ToLower(a.Type.String()), a.Location) | |
114 | } | |
115 | ||
116 | func assembleStatusText(c *cargo.Cargo) string { | |
117 | switch c.Delivery.TransportStatus { | |
118 | case cargo.NotReceived: | |
119 | return "Not received" | |
120 | case cargo.InPort: | |
121 | return fmt.Sprintf("In port %s", c.Delivery.LastKnownLocation) | |
122 | case cargo.OnboardCarrier: | |
123 | return fmt.Sprintf("Onboard voyage %s", c.Delivery.CurrentVoyage) | |
124 | case cargo.Claimed: | |
125 | return "Claimed" | |
126 | default: | |
127 | return "Unknown" | |
128 | } | |
129 | } | |
130 | ||
131 | func assembleEvents(c *cargo.Cargo, handlingEvents cargo.HandlingEventRepository) []Event { | |
132 | h := handlingEvents.QueryHandlingHistory(c.TrackingID) | |
133 | ||
134 | var events []Event | |
135 | for _, e := range h.HandlingEvents { | |
136 | var description string | |
137 | ||
138 | switch e.Activity.Type { | |
139 | case cargo.NotHandled: | |
140 | description = "Cargo has not yet been received." | |
141 | case cargo.Receive: | |
142 | description = fmt.Sprintf("Received in %s, at %s", e.Activity.Location, time.Now().Format(time.RFC3339)) | |
143 | case cargo.Load: | |
144 | description = fmt.Sprintf("Loaded onto voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339)) | |
145 | case cargo.Unload: | |
146 | description = fmt.Sprintf("Unloaded off voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339)) | |
147 | case cargo.Claim: | |
148 | description = fmt.Sprintf("Claimed in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339)) | |
149 | case cargo.Customs: | |
150 | description = fmt.Sprintf("Cleared customs in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339)) | |
151 | default: | |
152 | description = "[Unknown status]" | |
153 | } | |
154 | ||
155 | events = append(events, Event{ | |
156 | Description: description, | |
157 | Expected: c.Itinerary.IsExpected(e), | |
158 | }) | |
159 | } | |
160 | ||
161 | return events | |
162 | } |
0 | package tracking | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "encoding/json" | |
5 | "errors" | |
6 | "net/http" | |
7 | ||
8 | "github.com/gorilla/mux" | |
9 | ||
10 | kitlog "github.com/go-kit/kit/log" | |
11 | kittransport "github.com/go-kit/kit/transport" | |
12 | kithttp "github.com/go-kit/kit/transport/http" | |
13 | ||
14 | "github.com/go-kit/kit/examples/shipping/cargo" | |
15 | ) | |
16 | ||
17 | // MakeHandler returns a handler for the tracking service. | |
18 | func MakeHandler(ts Service, logger kitlog.Logger) http.Handler { | |
19 | r := mux.NewRouter() | |
20 | ||
21 | opts := []kithttp.ServerOption{ | |
22 | kithttp.ServerErrorHandler(kittransport.NewLogErrorHandler(logger)), | |
23 | kithttp.ServerErrorEncoder(encodeError), | |
24 | } | |
25 | ||
26 | trackCargoHandler := kithttp.NewServer( | |
27 | makeTrackCargoEndpoint(ts), | |
28 | decodeTrackCargoRequest, | |
29 | encodeResponse, | |
30 | opts..., | |
31 | ) | |
32 | ||
33 | r.Handle("/tracking/v1/cargos/{id}", trackCargoHandler).Methods("GET") | |
34 | ||
35 | return r | |
36 | } | |
37 | ||
38 | func decodeTrackCargoRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
39 | vars := mux.Vars(r) | |
40 | id, ok := vars["id"] | |
41 | if !ok { | |
42 | return nil, errors.New("bad route") | |
43 | } | |
44 | return trackCargoRequest{ID: id}, nil | |
45 | } | |
46 | ||
47 | func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { | |
48 | if e, ok := response.(errorer); ok && e.error() != nil { | |
49 | encodeError(ctx, e.error(), w) | |
50 | return nil | |
51 | } | |
52 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
53 | return json.NewEncoder(w).Encode(response) | |
54 | } | |
55 | ||
56 | type errorer interface { | |
57 | error() error | |
58 | } | |
59 | ||
60 | // encode errors from business-logic | |
61 | func encodeError(_ context.Context, err error, w http.ResponseWriter) { | |
62 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
63 | switch err { | |
64 | case cargo.ErrUnknown: | |
65 | w.WriteHeader(http.StatusNotFound) | |
66 | case ErrInvalidArgument: | |
67 | w.WriteHeader(http.StatusBadRequest) | |
68 | default: | |
69 | w.WriteHeader(http.StatusInternalServerError) | |
70 | } | |
71 | json.NewEncoder(w).Encode(map[string]interface{}{ | |
72 | "error": err.Error(), | |
73 | }) | |
74 | } |
0 | package voyage | |
1 | ||
2 | import "github.com/go-kit/kit/examples/shipping/location" | |
3 | ||
4 | // A set of sample voyages. | |
5 | var ( | |
6 | V100 = New("V100", Schedule{ | |
7 | []CarrierMovement{ | |
8 | {DepartureLocation: location.CNHKG, ArrivalLocation: location.JNTKO}, | |
9 | {DepartureLocation: location.JNTKO, ArrivalLocation: location.USNYC}, | |
10 | }, | |
11 | }) | |
12 | ||
13 | V300 = New("V300", Schedule{ | |
14 | []CarrierMovement{ | |
15 | {DepartureLocation: location.JNTKO, ArrivalLocation: location.NLRTM}, | |
16 | {DepartureLocation: location.NLRTM, ArrivalLocation: location.DEHAM}, | |
17 | {DepartureLocation: location.DEHAM, ArrivalLocation: location.AUMEL}, | |
18 | {DepartureLocation: location.AUMEL, ArrivalLocation: location.JNTKO}, | |
19 | }, | |
20 | }) | |
21 | ||
22 | V400 = New("V400", Schedule{ | |
23 | []CarrierMovement{ | |
24 | {DepartureLocation: location.DEHAM, ArrivalLocation: location.SESTO}, | |
25 | {DepartureLocation: location.SESTO, ArrivalLocation: location.FIHEL}, | |
26 | {DepartureLocation: location.FIHEL, ArrivalLocation: location.DEHAM}, | |
27 | }, | |
28 | }) | |
29 | ) | |
30 | ||
31 | // These voyages are hard-coded into the current pathfinder. Make sure | |
32 | // they exist. | |
33 | var ( | |
34 | V0100S = New("0100S", Schedule{[]CarrierMovement{}}) | |
35 | V0200T = New("0200T", Schedule{[]CarrierMovement{}}) | |
36 | V0300A = New("0300A", Schedule{[]CarrierMovement{}}) | |
37 | V0301S = New("0301S", Schedule{[]CarrierMovement{}}) | |
38 | V0400S = New("0400S", Schedule{[]CarrierMovement{}}) | |
39 | ) |
0 | // Package voyage provides the Voyage aggregate. | |
1 | package voyage | |
2 | ||
3 | import ( | |
4 | "errors" | |
5 | "time" | |
6 | ||
7 | "github.com/go-kit/kit/examples/shipping/location" | |
8 | ) | |
9 | ||
10 | // Number uniquely identifies a particular Voyage. | |
11 | type Number string | |
12 | ||
13 | // Voyage is a uniquely identifiable series of carrier movements. | |
14 | type Voyage struct { | |
15 | Number Number | |
16 | Schedule Schedule | |
17 | } | |
18 | ||
19 | // New creates a voyage with a voyage number and a provided schedule. | |
20 | func New(n Number, s Schedule) *Voyage { | |
21 | return &Voyage{Number: n, Schedule: s} | |
22 | } | |
23 | ||
24 | // Schedule describes a voyage schedule. | |
25 | type Schedule struct { | |
26 | CarrierMovements []CarrierMovement | |
27 | } | |
28 | ||
29 | // CarrierMovement is a vessel voyage from one location to another. | |
30 | type CarrierMovement struct { | |
31 | DepartureLocation location.UNLocode | |
32 | ArrivalLocation location.UNLocode | |
33 | DepartureTime time.Time | |
34 | ArrivalTime time.Time | |
35 | } | |
36 | ||
37 | // ErrUnknown is used when a voyage could not be found. | |
38 | var ErrUnknown = errors.New("unknown voyage") | |
39 | ||
40 | // Repository provides access a voyage store. | |
41 | type Repository interface { | |
42 | Find(Number) (*Voyage, error) | |
43 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "encoding/json" | |
5 | "errors" | |
6 | "log" | |
7 | "net/http" | |
8 | "strings" | |
9 | ||
10 | "github.com/go-kit/kit/endpoint" | |
11 | httptransport "github.com/go-kit/kit/transport/http" | |
12 | ) | |
13 | ||
14 | // StringService provides operations on strings. | |
15 | type StringService interface { | |
16 | Uppercase(string) (string, error) | |
17 | Count(string) int | |
18 | } | |
19 | ||
20 | // stringService is a concrete implementation of StringService | |
21 | type stringService struct{} | |
22 | ||
23 | func (stringService) Uppercase(s string) (string, error) { | |
24 | if s == "" { | |
25 | return "", ErrEmpty | |
26 | } | |
27 | return strings.ToUpper(s), nil | |
28 | } | |
29 | ||
30 | func (stringService) Count(s string) int { | |
31 | return len(s) | |
32 | } | |
33 | ||
34 | // ErrEmpty is returned when an input string is empty. | |
35 | var ErrEmpty = errors.New("empty string") | |
36 | ||
37 | // For each method, we define request and response structs | |
38 | type uppercaseRequest struct { | |
39 | S string `json:"s"` | |
40 | } | |
41 | ||
42 | type uppercaseResponse struct { | |
43 | V string `json:"v"` | |
44 | Err string `json:"err,omitempty"` // errors don't define JSON marshaling | |
45 | } | |
46 | ||
47 | type countRequest struct { | |
48 | S string `json:"s"` | |
49 | } | |
50 | ||
51 | type countResponse struct { | |
52 | V int `json:"v"` | |
53 | } | |
54 | ||
55 | // Endpoints are a primary abstraction in go-kit. An endpoint represents a single RPC (method in our service interface) | |
56 | func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint { | |
57 | return func(_ context.Context, request interface{}) (interface{}, error) { | |
58 | req := request.(uppercaseRequest) | |
59 | v, err := svc.Uppercase(req.S) | |
60 | if err != nil { | |
61 | return uppercaseResponse{v, err.Error()}, nil | |
62 | } | |
63 | return uppercaseResponse{v, ""}, nil | |
64 | } | |
65 | } | |
66 | ||
67 | func makeCountEndpoint(svc StringService) endpoint.Endpoint { | |
68 | return func(_ context.Context, request interface{}) (interface{}, error) { | |
69 | req := request.(countRequest) | |
70 | v := svc.Count(req.S) | |
71 | return countResponse{v}, nil | |
72 | } | |
73 | } | |
74 | ||
75 | // Transports expose the service to the network. In this first example we utilize JSON over HTTP. | |
76 | func main() { | |
77 | svc := stringService{} | |
78 | ||
79 | uppercaseHandler := httptransport.NewServer( | |
80 | makeUppercaseEndpoint(svc), | |
81 | decodeUppercaseRequest, | |
82 | encodeResponse, | |
83 | ) | |
84 | ||
85 | countHandler := httptransport.NewServer( | |
86 | makeCountEndpoint(svc), | |
87 | decodeCountRequest, | |
88 | encodeResponse, | |
89 | ) | |
90 | ||
91 | http.Handle("/uppercase", uppercaseHandler) | |
92 | http.Handle("/count", countHandler) | |
93 | log.Fatal(http.ListenAndServe(":8080", nil)) | |
94 | } | |
95 | ||
96 | func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
97 | var request uppercaseRequest | |
98 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
99 | return nil, err | |
100 | } | |
101 | return request, nil | |
102 | } | |
103 | ||
104 | func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
105 | var request countRequest | |
106 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
107 | return nil, err | |
108 | } | |
109 | return request, nil | |
110 | } | |
111 | ||
112 | func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { | |
113 | return json.NewEncoder(w).Encode(response) | |
114 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "time" | |
5 | ||
6 | "github.com/go-kit/kit/metrics" | |
7 | ) | |
8 | ||
9 | type instrumentingMiddleware struct { | |
10 | requestCount metrics.Counter | |
11 | requestLatency metrics.Histogram | |
12 | countResult metrics.Histogram | |
13 | next StringService | |
14 | } | |
15 | ||
16 | func (mw instrumentingMiddleware) Uppercase(s string) (output string, err error) { | |
17 | defer func(begin time.Time) { | |
18 | lvs := []string{"method", "uppercase", "error", fmt.Sprint(err != nil)} | |
19 | mw.requestCount.With(lvs...).Add(1) | |
20 | mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) | |
21 | }(time.Now()) | |
22 | ||
23 | output, err = mw.next.Uppercase(s) | |
24 | return | |
25 | } | |
26 | ||
27 | func (mw instrumentingMiddleware) Count(s string) (n int) { | |
28 | defer func(begin time.Time) { | |
29 | lvs := []string{"method", "count", "error", "false"} | |
30 | mw.requestCount.With(lvs...).Add(1) | |
31 | mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) | |
32 | mw.countResult.Observe(float64(n)) | |
33 | }(time.Now()) | |
34 | ||
35 | n = mw.next.Count(s) | |
36 | return | |
37 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/log" | |
6 | ) | |
7 | ||
8 | type loggingMiddleware struct { | |
9 | logger log.Logger | |
10 | next StringService | |
11 | } | |
12 | ||
13 | func (mw loggingMiddleware) Uppercase(s string) (output string, err error) { | |
14 | defer func(begin time.Time) { | |
15 | _ = mw.logger.Log( | |
16 | "method", "uppercase", | |
17 | "input", s, | |
18 | "output", output, | |
19 | "err", err, | |
20 | "took", time.Since(begin), | |
21 | ) | |
22 | }(time.Now()) | |
23 | ||
24 | output, err = mw.next.Uppercase(s) | |
25 | return | |
26 | } | |
27 | ||
28 | func (mw loggingMiddleware) Count(s string) (n int) { | |
29 | defer func(begin time.Time) { | |
30 | _ = mw.logger.Log( | |
31 | "method", "count", | |
32 | "input", s, | |
33 | "n", n, | |
34 | "took", time.Since(begin), | |
35 | ) | |
36 | }(time.Now()) | |
37 | ||
38 | n = mw.next.Count(s) | |
39 | return | |
40 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "net/http" | |
4 | "os" | |
5 | ||
6 | stdprometheus "github.com/prometheus/client_golang/prometheus" | |
7 | "github.com/prometheus/client_golang/prometheus/promhttp" | |
8 | ||
9 | "github.com/go-kit/kit/log" | |
10 | kitprometheus "github.com/go-kit/kit/metrics/prometheus" | |
11 | httptransport "github.com/go-kit/kit/transport/http" | |
12 | ) | |
13 | ||
14 | func main() { | |
15 | logger := log.NewLogfmtLogger(os.Stderr) | |
16 | ||
17 | fieldKeys := []string{"method", "error"} | |
18 | requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ | |
19 | Namespace: "my_group", | |
20 | Subsystem: "string_service", | |
21 | Name: "request_count", | |
22 | Help: "Number of requests received.", | |
23 | }, fieldKeys) | |
24 | requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ | |
25 | Namespace: "my_group", | |
26 | Subsystem: "string_service", | |
27 | Name: "request_latency_microseconds", | |
28 | Help: "Total duration of requests in microseconds.", | |
29 | }, fieldKeys) | |
30 | countResult := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ | |
31 | Namespace: "my_group", | |
32 | Subsystem: "string_service", | |
33 | Name: "count_result", | |
34 | Help: "The result of each count method.", | |
35 | }, []string{}) // no fields here | |
36 | ||
37 | var svc StringService | |
38 | svc = stringService{} | |
39 | svc = loggingMiddleware{logger, svc} | |
40 | svc = instrumentingMiddleware{requestCount, requestLatency, countResult, svc} | |
41 | ||
42 | uppercaseHandler := httptransport.NewServer( | |
43 | makeUppercaseEndpoint(svc), | |
44 | decodeUppercaseRequest, | |
45 | encodeResponse, | |
46 | ) | |
47 | ||
48 | countHandler := httptransport.NewServer( | |
49 | makeCountEndpoint(svc), | |
50 | decodeCountRequest, | |
51 | encodeResponse, | |
52 | ) | |
53 | ||
54 | http.Handle("/uppercase", uppercaseHandler) | |
55 | http.Handle("/count", countHandler) | |
56 | http.Handle("/metrics", promhttp.Handler()) | |
57 | logger.Log("msg", "HTTP", "addr", ":8080") | |
58 | logger.Log("err", http.ListenAndServe(":8080", nil)) | |
59 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "strings" | |
5 | ) | |
6 | ||
7 | // StringService provides operations on strings. | |
8 | type StringService interface { | |
9 | Uppercase(string) (string, error) | |
10 | Count(string) int | |
11 | } | |
12 | ||
13 | type stringService struct{} | |
14 | ||
15 | func (stringService) Uppercase(s string) (string, error) { | |
16 | if s == "" { | |
17 | return "", ErrEmpty | |
18 | } | |
19 | return strings.ToUpper(s), nil | |
20 | } | |
21 | ||
22 | func (stringService) Count(s string) int { | |
23 | return len(s) | |
24 | } | |
25 | ||
26 | // ErrEmpty is returned when an input string is empty. | |
27 | var ErrEmpty = errors.New("empty string") |
0 | package main | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "encoding/json" | |
5 | "net/http" | |
6 | ||
7 | "github.com/go-kit/kit/endpoint" | |
8 | ) | |
9 | ||
10 | func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint { | |
11 | return func(_ context.Context, request interface{}) (interface{}, error) { | |
12 | req := request.(uppercaseRequest) | |
13 | v, err := svc.Uppercase(req.S) | |
14 | if err != nil { | |
15 | return uppercaseResponse{v, err.Error()}, nil | |
16 | } | |
17 | return uppercaseResponse{v, ""}, nil | |
18 | } | |
19 | } | |
20 | ||
21 | func makeCountEndpoint(svc StringService) endpoint.Endpoint { | |
22 | return func(_ context.Context, request interface{}) (interface{}, error) { | |
23 | req := request.(countRequest) | |
24 | v := svc.Count(req.S) | |
25 | return countResponse{v}, nil | |
26 | } | |
27 | } | |
28 | ||
29 | func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
30 | var request uppercaseRequest | |
31 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
32 | return nil, err | |
33 | } | |
34 | return request, nil | |
35 | } | |
36 | ||
37 | func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
38 | var request countRequest | |
39 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
40 | return nil, err | |
41 | } | |
42 | return request, nil | |
43 | } | |
44 | ||
45 | func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { | |
46 | return json.NewEncoder(w).Encode(response) | |
47 | } | |
48 | ||
49 | type uppercaseRequest struct { | |
50 | S string `json:"s"` | |
51 | } | |
52 | ||
53 | type uppercaseResponse struct { | |
54 | V string `json:"v"` | |
55 | Err string `json:"err,omitempty"` | |
56 | } | |
57 | ||
58 | type countRequest struct { | |
59 | S string `json:"s"` | |
60 | } | |
61 | ||
62 | type countResponse struct { | |
63 | V int `json:"v"` | |
64 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "time" | |
5 | ||
6 | "github.com/go-kit/kit/metrics" | |
7 | ) | |
8 | ||
9 | func instrumentingMiddleware( | |
10 | requestCount metrics.Counter, | |
11 | requestLatency metrics.Histogram, | |
12 | countResult metrics.Histogram, | |
13 | ) ServiceMiddleware { | |
14 | return func(next StringService) StringService { | |
15 | return instrmw{requestCount, requestLatency, countResult, next} | |
16 | } | |
17 | } | |
18 | ||
19 | type instrmw struct { | |
20 | requestCount metrics.Counter | |
21 | requestLatency metrics.Histogram | |
22 | countResult metrics.Histogram | |
23 | StringService | |
24 | } | |
25 | ||
26 | func (mw instrmw) Uppercase(s string) (output string, err error) { | |
27 | defer func(begin time.Time) { | |
28 | lvs := []string{"method", "uppercase", "error", fmt.Sprint(err != nil)} | |
29 | mw.requestCount.With(lvs...).Add(1) | |
30 | mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) | |
31 | }(time.Now()) | |
32 | ||
33 | output, err = mw.StringService.Uppercase(s) | |
34 | return | |
35 | } | |
36 | ||
37 | func (mw instrmw) Count(s string) (n int) { | |
38 | defer func(begin time.Time) { | |
39 | lvs := []string{"method", "count", "error", "false"} | |
40 | mw.requestCount.With(lvs...).Add(1) | |
41 | mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) | |
42 | mw.countResult.Observe(float64(n)) | |
43 | }(time.Now()) | |
44 | ||
45 | n = mw.StringService.Count(s) | |
46 | return | |
47 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "time" | |
4 | ||
5 | "github.com/go-kit/kit/log" | |
6 | ) | |
7 | ||
8 | func loggingMiddleware(logger log.Logger) ServiceMiddleware { | |
9 | return func(next StringService) StringService { | |
10 | return logmw{logger, next} | |
11 | } | |
12 | } | |
13 | ||
14 | type logmw struct { | |
15 | logger log.Logger | |
16 | StringService | |
17 | } | |
18 | ||
19 | func (mw logmw) Uppercase(s string) (output string, err error) { | |
20 | defer func(begin time.Time) { | |
21 | _ = mw.logger.Log( | |
22 | "method", "uppercase", | |
23 | "input", s, | |
24 | "output", output, | |
25 | "err", err, | |
26 | "took", time.Since(begin), | |
27 | ) | |
28 | }(time.Now()) | |
29 | ||
30 | output, err = mw.StringService.Uppercase(s) | |
31 | return | |
32 | } | |
33 | ||
34 | func (mw logmw) Count(s string) (n int) { | |
35 | defer func(begin time.Time) { | |
36 | _ = mw.logger.Log( | |
37 | "method", "count", | |
38 | "input", s, | |
39 | "n", n, | |
40 | "took", time.Since(begin), | |
41 | ) | |
42 | }(time.Now()) | |
43 | ||
44 | n = mw.StringService.Count(s) | |
45 | return | |
46 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "flag" | |
5 | "net/http" | |
6 | "os" | |
7 | ||
8 | stdprometheus "github.com/prometheus/client_golang/prometheus" | |
9 | "github.com/prometheus/client_golang/prometheus/promhttp" | |
10 | ||
11 | "github.com/go-kit/kit/log" | |
12 | kitprometheus "github.com/go-kit/kit/metrics/prometheus" | |
13 | httptransport "github.com/go-kit/kit/transport/http" | |
14 | ) | |
15 | ||
16 | func main() { | |
17 | var ( | |
18 | listen = flag.String("listen", ":8080", "HTTP listen address") | |
19 | proxy = flag.String("proxy", "", "Optional comma-separated list of URLs to proxy uppercase requests") | |
20 | ) | |
21 | flag.Parse() | |
22 | ||
23 | var logger log.Logger | |
24 | logger = log.NewLogfmtLogger(os.Stderr) | |
25 | logger = log.With(logger, "listen", *listen, "caller", log.DefaultCaller) | |
26 | ||
27 | fieldKeys := []string{"method", "error"} | |
28 | requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ | |
29 | Namespace: "my_group", | |
30 | Subsystem: "string_service", | |
31 | Name: "request_count", | |
32 | Help: "Number of requests received.", | |
33 | }, fieldKeys) | |
34 | requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ | |
35 | Namespace: "my_group", | |
36 | Subsystem: "string_service", | |
37 | Name: "request_latency_microseconds", | |
38 | Help: "Total duration of requests in microseconds.", | |
39 | }, fieldKeys) | |
40 | countResult := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ | |
41 | Namespace: "my_group", | |
42 | Subsystem: "string_service", | |
43 | Name: "count_result", | |
44 | Help: "The result of each count method.", | |
45 | }, []string{}) | |
46 | ||
47 | var svc StringService | |
48 | svc = stringService{} | |
49 | svc = proxyingMiddleware(context.Background(), *proxy, logger)(svc) | |
50 | svc = loggingMiddleware(logger)(svc) | |
51 | svc = instrumentingMiddleware(requestCount, requestLatency, countResult)(svc) | |
52 | ||
53 | uppercaseHandler := httptransport.NewServer( | |
54 | makeUppercaseEndpoint(svc), | |
55 | decodeUppercaseRequest, | |
56 | encodeResponse, | |
57 | ) | |
58 | countHandler := httptransport.NewServer( | |
59 | makeCountEndpoint(svc), | |
60 | decodeCountRequest, | |
61 | encodeResponse, | |
62 | ) | |
63 | ||
64 | http.Handle("/uppercase", uppercaseHandler) | |
65 | http.Handle("/count", countHandler) | |
66 | http.Handle("/metrics", promhttp.Handler()) | |
67 | logger.Log("msg", "HTTP", "addr", *listen) | |
68 | logger.Log("err", http.ListenAndServe(*listen, nil)) | |
69 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "errors" | |
5 | "fmt" | |
6 | "net/url" | |
7 | "strings" | |
8 | "time" | |
9 | ||
10 | "golang.org/x/time/rate" | |
11 | ||
12 | "github.com/sony/gobreaker" | |
13 | ||
14 | "github.com/go-kit/kit/circuitbreaker" | |
15 | "github.com/go-kit/kit/endpoint" | |
16 | "github.com/go-kit/kit/log" | |
17 | "github.com/go-kit/kit/ratelimit" | |
18 | "github.com/go-kit/kit/sd" | |
19 | "github.com/go-kit/kit/sd/lb" | |
20 | httptransport "github.com/go-kit/kit/transport/http" | |
21 | ) | |
22 | ||
23 | func proxyingMiddleware(ctx context.Context, instances string, logger log.Logger) ServiceMiddleware { | |
24 | // If instances is empty, don't proxy. | |
25 | if instances == "" { | |
26 | logger.Log("proxy_to", "none") | |
27 | return func(next StringService) StringService { return next } | |
28 | } | |
29 | ||
30 | // Set some parameters for our client. | |
31 | var ( | |
32 | qps = 100 // beyond which we will return an error | |
33 | maxAttempts = 3 // per request, before giving up | |
34 | maxTime = 250 * time.Millisecond // wallclock time, before giving up | |
35 | ) | |
36 | ||
37 | // Otherwise, construct an endpoint for each instance in the list, and add | |
38 | // it to a fixed set of endpoints. In a real service, rather than doing this | |
39 | // by hand, you'd probably use package sd's support for your service | |
40 | // discovery system. | |
41 | var ( | |
42 | instanceList = split(instances) | |
43 | endpointer sd.FixedEndpointer | |
44 | ) | |
45 | logger.Log("proxy_to", fmt.Sprint(instanceList)) | |
46 | for _, instance := range instanceList { | |
47 | var e endpoint.Endpoint | |
48 | e = makeUppercaseProxy(ctx, instance) | |
49 | e = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e) | |
50 | e = ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), qps))(e) | |
51 | endpointer = append(endpointer, e) | |
52 | } | |
53 | ||
54 | // Now, build a single, retrying, load-balancing endpoint out of all of | |
55 | // those individual endpoints. | |
56 | balancer := lb.NewRoundRobin(endpointer) | |
57 | retry := lb.Retry(maxAttempts, maxTime, balancer) | |
58 | ||
59 | // And finally, return the ServiceMiddleware, implemented by proxymw. | |
60 | return func(next StringService) StringService { | |
61 | return proxymw{ctx, next, retry} | |
62 | } | |
63 | } | |
64 | ||
65 | // proxymw implements StringService, forwarding Uppercase requests to the | |
66 | // provided endpoint, and serving all other (i.e. Count) requests via the | |
67 | // next StringService. | |
68 | type proxymw struct { | |
69 | ctx context.Context | |
70 | next StringService // Serve most requests via this service... | |
71 | uppercase endpoint.Endpoint // ...except Uppercase, which gets served by this endpoint | |
72 | } | |
73 | ||
74 | func (mw proxymw) Count(s string) int { | |
75 | return mw.next.Count(s) | |
76 | } | |
77 | ||
78 | func (mw proxymw) Uppercase(s string) (string, error) { | |
79 | response, err := mw.uppercase(mw.ctx, uppercaseRequest{S: s}) | |
80 | if err != nil { | |
81 | return "", err | |
82 | } | |
83 | ||
84 | resp := response.(uppercaseResponse) | |
85 | if resp.Err != "" { | |
86 | return resp.V, errors.New(resp.Err) | |
87 | } | |
88 | return resp.V, nil | |
89 | } | |
90 | ||
91 | func makeUppercaseProxy(ctx context.Context, instance string) endpoint.Endpoint { | |
92 | if !strings.HasPrefix(instance, "http") { | |
93 | instance = "http://" + instance | |
94 | } | |
95 | u, err := url.Parse(instance) | |
96 | if err != nil { | |
97 | panic(err) | |
98 | } | |
99 | if u.Path == "" { | |
100 | u.Path = "/uppercase" | |
101 | } | |
102 | return httptransport.NewClient( | |
103 | "GET", | |
104 | u, | |
105 | encodeRequest, | |
106 | decodeUppercaseResponse, | |
107 | ).Endpoint() | |
108 | } | |
109 | ||
110 | func split(s string) []string { | |
111 | a := strings.Split(s, ",") | |
112 | for i := range a { | |
113 | a[i] = strings.TrimSpace(a[i]) | |
114 | } | |
115 | return a | |
116 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "strings" | |
5 | ) | |
6 | ||
7 | // StringService provides operations on strings. | |
8 | type StringService interface { | |
9 | Uppercase(string) (string, error) | |
10 | Count(string) int | |
11 | } | |
12 | ||
13 | type stringService struct{} | |
14 | ||
15 | func (stringService) Uppercase(s string) (string, error) { | |
16 | if s == "" { | |
17 | return "", ErrEmpty | |
18 | } | |
19 | return strings.ToUpper(s), nil | |
20 | } | |
21 | ||
22 | func (stringService) Count(s string) int { | |
23 | return len(s) | |
24 | } | |
25 | ||
26 | // ErrEmpty is returned when an input string is empty. | |
27 | var ErrEmpty = errors.New("empty string") | |
28 | ||
29 | // ServiceMiddleware is a chainable behavior modifier for StringService. | |
30 | type ServiceMiddleware func(StringService) StringService |
0 | package main | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "context" | |
5 | "encoding/json" | |
6 | "io/ioutil" | |
7 | "net/http" | |
8 | ||
9 | "github.com/go-kit/kit/endpoint" | |
10 | ) | |
11 | ||
12 | func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint { | |
13 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
14 | req := request.(uppercaseRequest) | |
15 | v, err := svc.Uppercase(req.S) | |
16 | if err != nil { | |
17 | return uppercaseResponse{v, err.Error()}, nil | |
18 | } | |
19 | return uppercaseResponse{v, ""}, nil | |
20 | } | |
21 | } | |
22 | ||
23 | func makeCountEndpoint(svc StringService) endpoint.Endpoint { | |
24 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
25 | req := request.(countRequest) | |
26 | v := svc.Count(req.S) | |
27 | return countResponse{v}, nil | |
28 | } | |
29 | } | |
30 | ||
31 | func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
32 | var request uppercaseRequest | |
33 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
34 | return nil, err | |
35 | } | |
36 | return request, nil | |
37 | } | |
38 | ||
39 | func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
40 | var request countRequest | |
41 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
42 | return nil, err | |
43 | } | |
44 | return request, nil | |
45 | } | |
46 | ||
47 | func decodeUppercaseResponse(_ context.Context, r *http.Response) (interface{}, error) { | |
48 | var response uppercaseResponse | |
49 | if err := json.NewDecoder(r.Body).Decode(&response); err != nil { | |
50 | return nil, err | |
51 | } | |
52 | return response, nil | |
53 | } | |
54 | ||
55 | func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { | |
56 | return json.NewEncoder(w).Encode(response) | |
57 | } | |
58 | ||
59 | func encodeRequest(_ context.Context, r *http.Request, request interface{}) error { | |
60 | var buf bytes.Buffer | |
61 | if err := json.NewEncoder(&buf).Encode(request); err != nil { | |
62 | return err | |
63 | } | |
64 | r.Body = ioutil.NopCloser(&buf) | |
65 | return nil | |
66 | } | |
67 | ||
68 | type uppercaseRequest struct { | |
69 | S string `json:"s"` | |
70 | } | |
71 | ||
72 | type uppercaseResponse struct { | |
73 | V string `json:"v"` | |
74 | Err string `json:"err,omitempty"` | |
75 | } | |
76 | ||
77 | type countRequest struct { | |
78 | S string `json:"s"` | |
79 | } | |
80 | ||
81 | type countResponse struct { | |
82 | V int `json:"v"` | |
83 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "context" | |
4 | "encoding/json" | |
5 | "errors" | |
6 | "log" | |
7 | "strings" | |
8 | "flag" | |
9 | "net/http" | |
10 | ||
11 | "github.com/go-kit/kit/endpoint" | |
12 | natstransport "github.com/go-kit/kit/transport/nats" | |
13 | httptransport "github.com/go-kit/kit/transport/http" | |
14 | ||
15 | "github.com/nats-io/nats.go" | |
16 | ) | |
17 | ||
18 | // StringService provides operations on strings. | |
19 | type StringService interface { | |
20 | Uppercase(context.Context, string) (string, error) | |
21 | Count(context.Context, string) int | |
22 | } | |
23 | ||
24 | // stringService is a concrete implementation of StringService | |
25 | type stringService struct{} | |
26 | ||
27 | func (stringService) Uppercase(_ context.Context, s string) (string, error) { | |
28 | if s == "" { | |
29 | return "", ErrEmpty | |
30 | } | |
31 | return strings.ToUpper(s), nil | |
32 | } | |
33 | ||
34 | func (stringService) Count(_ context.Context, s string) int { | |
35 | return len(s) | |
36 | } | |
37 | ||
38 | // ErrEmpty is returned when an input string is empty. | |
39 | var ErrEmpty = errors.New("empty string") | |
40 | ||
41 | // For each method, we define request and response structs | |
42 | type uppercaseRequest struct { | |
43 | S string `json:"s"` | |
44 | } | |
45 | ||
46 | type uppercaseResponse struct { | |
47 | V string `json:"v"` | |
48 | Err string `json:"err,omitempty"` // errors don't define JSON marshaling | |
49 | } | |
50 | ||
51 | type countRequest struct { | |
52 | S string `json:"s"` | |
53 | } | |
54 | ||
55 | type countResponse struct { | |
56 | V int `json:"v"` | |
57 | } | |
58 | ||
59 | // Endpoints are a primary abstraction in go-kit. An endpoint represents a single RPC (method in our service interface) | |
60 | func makeUppercaseHTTPEndpoint(nc *nats.Conn) endpoint.Endpoint { | |
61 | return natstransport.NewPublisher( | |
62 | nc, | |
63 | "stringsvc.uppercase", | |
64 | natstransport.EncodeJSONRequest, | |
65 | decodeUppercaseResponse, | |
66 | ).Endpoint() | |
67 | } | |
68 | ||
69 | func makeCountHTTPEndpoint(nc *nats.Conn) endpoint.Endpoint { | |
70 | return natstransport.NewPublisher( | |
71 | nc, | |
72 | "stringsvc.count", | |
73 | natstransport.EncodeJSONRequest, | |
74 | decodeCountResponse, | |
75 | ).Endpoint() | |
76 | } | |
77 | ||
78 | func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint { | |
79 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
80 | req := request.(uppercaseRequest) | |
81 | v, err := svc.Uppercase(ctx, req.S) | |
82 | if err != nil { | |
83 | return uppercaseResponse{v, err.Error()}, nil | |
84 | } | |
85 | return uppercaseResponse{v, ""}, nil | |
86 | } | |
87 | } | |
88 | ||
89 | func makeCountEndpoint(svc StringService) endpoint.Endpoint { | |
90 | return func(ctx context.Context, request interface{}) (interface{}, error) { | |
91 | req := request.(countRequest) | |
92 | v := svc.Count(ctx, req.S) | |
93 | return countResponse{v}, nil | |
94 | } | |
95 | } | |
96 | ||
97 | // Transports expose the service to the network. In this fourth example we utilize JSON over NATS and HTTP. | |
98 | func main() { | |
99 | svc := stringService{} | |
100 | ||
101 | natsURL := flag.String("nats-url", nats.DefaultURL, "URL for connection to NATS") | |
102 | flag.Parse() | |
103 | ||
104 | nc, err := nats.Connect(*natsURL) | |
105 | if err != nil { | |
106 | log.Fatal(err) | |
107 | } | |
108 | defer nc.Close() | |
109 | ||
110 | uppercaseHTTPHandler := httptransport.NewServer( | |
111 | makeUppercaseHTTPEndpoint(nc), | |
112 | decodeUppercaseHTTPRequest, | |
113 | httptransport.EncodeJSONResponse, | |
114 | ) | |
115 | ||
116 | countHTTPHandler := httptransport.NewServer( | |
117 | makeCountHTTPEndpoint(nc), | |
118 | decodeCountHTTPRequest, | |
119 | httptransport.EncodeJSONResponse, | |
120 | ) | |
121 | ||
122 | uppercaseHandler := natstransport.NewSubscriber( | |
123 | makeUppercaseEndpoint(svc), | |
124 | decodeUppercaseRequest, | |
125 | natstransport.EncodeJSONResponse, | |
126 | ) | |
127 | ||
128 | countHandler := natstransport.NewSubscriber( | |
129 | makeCountEndpoint(svc), | |
130 | decodeCountRequest, | |
131 | natstransport.EncodeJSONResponse, | |
132 | ) | |
133 | ||
134 | uSub, err := nc.QueueSubscribe("stringsvc.uppercase", "stringsvc", uppercaseHandler.ServeMsg(nc)) | |
135 | if err != nil { | |
136 | log.Fatal(err) | |
137 | } | |
138 | defer uSub.Unsubscribe() | |
139 | ||
140 | cSub, err := nc.QueueSubscribe("stringsvc.count", "stringsvc", countHandler.ServeMsg(nc)) | |
141 | if err != nil { | |
142 | log.Fatal(err) | |
143 | } | |
144 | defer cSub.Unsubscribe() | |
145 | ||
146 | http.Handle("/uppercase", uppercaseHTTPHandler) | |
147 | http.Handle("/count", countHTTPHandler) | |
148 | log.Fatal(http.ListenAndServe(":8080", nil)) | |
149 | ||
150 | } | |
151 | ||
152 | func decodeUppercaseHTTPRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
153 | var request uppercaseRequest | |
154 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
155 | return nil, err | |
156 | } | |
157 | return request, nil | |
158 | } | |
159 | ||
160 | func decodeCountHTTPRequest(_ context.Context, r *http.Request) (interface{}, error) { | |
161 | var request countRequest | |
162 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { | |
163 | return nil, err | |
164 | } | |
165 | return request, nil | |
166 | } | |
167 | ||
168 | func decodeUppercaseResponse(_ context.Context, msg *nats.Msg) (interface{}, error) { | |
169 | var response uppercaseResponse | |
170 | ||
171 | if err := json.Unmarshal(msg.Data, &response); err != nil { | |
172 | return nil, err | |
173 | } | |
174 | ||
175 | return response, nil | |
176 | } | |
177 | ||
178 | func decodeCountResponse(_ context.Context, msg *nats.Msg) (interface{}, error) { | |
179 | var response countResponse | |
180 | ||
181 | if err := json.Unmarshal(msg.Data, &response); err != nil { | |
182 | return nil, err | |
183 | } | |
184 | ||
185 | return response, nil | |
186 | } | |
187 | ||
188 | func decodeUppercaseRequest(_ context.Context, msg *nats.Msg) (interface{}, error) { | |
189 | var request uppercaseRequest | |
190 | ||
191 | if err := json.Unmarshal(msg.Data, &request); err != nil { | |
192 | return nil, err | |
193 | } | |
194 | return request, nil | |
195 | } | |
196 | ||
197 | func decodeCountRequest(_ context.Context, msg *nats.Msg) (interface{}, error) { | |
198 | var request countRequest | |
199 | ||
200 | if err := json.Unmarshal(msg.Data, &request); err != nil { | |
201 | return nil, err | |
202 | } | |
203 | return request, nil | |
204 | } | |
205 |