diff --git a/README.md b/README.md index daf208d..e65c7ee 100644 --- a/README.md +++ b/README.md @@ -4,115 +4,10 @@ in large organizations. We solve common problems in distributed systems, so you can focus on your business logic. +- Website: [gokit.io](https://gokit.io) - Mailing list: [go-kit](https://groups.google.com/forum/#!forum/go-kit) - Slack: [gophers.slack.com](https://gophers.slack.com) **#go-kit** ([invite](https://gophersinvite.herokuapp.com/)) -## Documentation - -### Examples - -Perhaps the best way to understand Go kit is to follow along as we build an -[example service][examples] from first principles. This can serve as a -blueprint for your own new service, or demonstrate how to adapt your existing -service to use Go kit components. - -[examples]: https://github.com/go-kit/kit/tree/master/examples - -### Endpoint - -Go kit primarily deals in the RPC messaging pattern. We use an abstraction -called an **[endpoint][]** to model individual RPCs. An endpoint can be -implemented by a server, and called by a client. It's the fundamental building -block of many Go kit components. - -[endpoint]: https://github.com/go-kit/kit/tree/master/endpoint/endpoint.go - -#### Circuit breaker - -The [circuitbreaker package][circuitbreaker] provides endpoint adapters to -several popular circuit breaker libraries. Circuit breakers prevent thundering -herds, and improve resiliency against intermittent errors. Every client-side -endpoint should be wrapped in a circuit breaker. - -[circuitbreaker]: https://github.com/go-kit/kit/tree/master/circuitbreaker - -#### Rate limiter - -The [ratelimit package][ratelimit] provides endpoint adapters to rate limiting -packages. Rate limiters are equally applicable to both server- and client-side -endpoints. Use rate limiters to enforce upper thresholds on incoming or -outgoing request throughput. - -[ratelimit]: https://github.com/go-kit/kit/tree/master/ratelimit - -### Transport - -The [transport package][transport] provides helpers to bind endpoints to -specific serialization mechanisms. At the moment, Go kit just provides helpers -for simple JSON over HTTP. If your organization uses a fully-featured -transport, bindings are typically provided by the Go library for the -transport, and there's not much for Go kit to do. In those cases, see the -examples to understand how to write adapters for your endpoints. For now, see -the [addsvc][addsvc] to understand how transport bindings work. We have -specific examples for Thrift, gRPC, net/rpc, and JSON over HTTP. JSON/RPC and -Swagger support is planned. - -[transport]: https://github.com/go-kit/kit/tree/master/transport -[addsvc]: https://github.com/go-kit/kit/tree/master/examples/addsvc - -### Logging - -Services produce logs to be consumed later, either by humans or machines. -Humans might be interested in debugging errors, or tracing specific requests. -Machines might be interested in counting interesting events, or aggregating -information for offline processing. In both cases, it's important that the log -messages be structured and actionable. Go kit's [log package][log] is designed -to encourage both of these best practices. - -[log]: https://github.com/go-kit/kit/tree/master/log - -### Metrics (Instrumentation) - -Services can't be considered production-ready until they're thoroughly -instrumented with metrics that track counts, latency, health, and other -periodic or per-request information. Go kit's [metrics package][metrics] -provides a robust common set of interfaces for instrumenting your service. -Bindings exist for common backends, from [expvar][] to [statsd][] to -[Prometheus][]. - -[metrics]: https://github.com/go-kit/kit/tree/master/metrics -[expvar]: https://golang.org/pkg/expvar/ -[statsd]: https://github.com/etsy/statsd -[Prometheus]: http://prometheus.io - -### Request tracing - -As your infrastructure grows, it becomes important to be able to trace a -request, as it travels through multiple services and back to the user. Go -kit's [tracing package][tracing] provides enhancements for your endpoints and -transport bindings to capture information about requests and emit them to -request tracing systems. (Currently, [Zipkin][] is supported; [Appdash][] -support is planned.) - -[tracing]: https://github.com/go-kit/kit/tree/master/tracing -[Zipkin]: https://github.com/openzipkin/zipkin -[Appdash]: https://github.com/sourcegraph/appdash - -### Service discovery and load balancing - -If your service calls another service, it needs to know how to find it, and -should intelligently spread its load among those discovered instances. Go -kit's [loadbalancer package][loadbalancer] provides client-side endpoint -middleware to solve that problem, whether your organization uses static hosts -or IPs, [DNS SRV records][dnssrv], Consul, etcd, or Zookeeper. And if you use -a custom system, it's very easy to write your own [Publisher][] and use Go -kit's load balancing strategies. (Currently, static hosts, DNS SRV, etcd, Consul -and ZooKeeper are supported) - -[loadbalancer]: https://github.com/go-kit/kit/tree/master/loadbalancer -[dnssrv]: https://github.com/go-kit/kit/tree/master/loadbalancer/dnssrv -[Publisher]: https://github.com/go-kit/kit/tree/master/loadbalancer/publisher.go - ## Motivation Go has emerged as the language of the server, but it remains underrepresented @@ -123,13 +18,12 @@ directly support their microservice architectures. To reach its next level of success, Go needs more than simple primitives and idioms. It needs a comprehensive toolkit, for coherent distributed programming -in the large. Go kit is a set of packages and best practices, leveraging years -of production experience, and providing a comprehensive, robust, and trustable -platform for organizations of any size. - -In short, Go kit makes Go a viable choice for business-domain microservices. +in the large. Go kit is a set of packages and best practices, which provide a +comprehensive, robust, and trustable way of building microservices for +organizations of any size. For more details, see + [the website](https://gokit.io), [the motivating blog post](http://peter.bourgon.org/go-kit/) and [the video of the talk](https://www.youtube.com/watch?v=iFR_7AKkJFU). See also the @@ -150,26 +44,28 @@ See also the ## Contributing -Please see [CONTRIBUTING.md][]. Thank you, [contributors][]! - -[CONTRIBUTING.md]: /CONTRIBUTING.md -[contributors]: https://github.com/go-kit/kit/graphs/contributors +Please see [CONTRIBUTING.md](/CONTRIBUTING.md). +Thank you, [contributors](https://github.com/go-kit/kit/graphs/contributors)! ## Dependency management Go kit is a library, designed to be imported into a binary package. -Vendoring is currently the best way for binary package authors to ensure reliable, reproducible builds. -Therefore, we strongly recommend our users use vendoring for all of their dependencies, including Go kit. -To avoid compatibility and availability issues, Go kit doesn't vendor its own dependencies, and doesn't recommend use of third-party import proxies. +Vendoring is currently the best way for binary package authors + to ensure reliable, reproducible builds. +Therefore, we strongly recommend our users use vendoring for all of their dependencies, + including Go kit. +To avoid compatibility and availability issues, + Go kit doesn't vendor its own dependencies, + and doesn't recommend use of third-party import proxies. -There are several tools which make vendoring easier, including [gb][], [glide][], [gvt][], [govendor][], and [vendetta][]. -In addition, Go kit uses a variety of continuous integration providers to find and fix compatibility problems as soon as they occur. - -[gb]: http://getgb.io -[glide]: https://github.com/Masterminds/glide -[gvt]: https://github.com/FiloSottile/gvt -[govendor]: https://github.com/kardianos/govendor -[vendetta]: https://github.com/dpw/vendetta +There are several tools which make vendoring easier, including + [gb](http://getgb.io), + [glide](https://github.com/Masterminds/glide), + [gvt](https://github.com/FiloSottile/gvt), + [govendor](https://github.com/kardianos/govendor), and + [vendetta](https://github.com/dpw/vendetta). +In addition, Go kit uses a variety of continuous integration providers + to find and fix compatibility problems as soon as they occur. ## Related projects @@ -179,10 +75,10 @@ Projects with a ★ have had particular influence on Go kit's design (or vice-ve - [gizmo](https://github.com/nytimes/gizmo), a microservice toolkit from The New York Times ★ - [go-micro](https://github.com/myodc/go-micro), a microservices client/server library ★ -- [gocircuit](https://github.com/gocircuit/circuit), dynamic cloud orchestration -- [gotalk](https://github.com/rsms/gotalk), async peer communication protocol & library - [h2](https://github.com/hailocab/h2), a microservices framework ★ +- [gotalk](https://github.com/rsms/gotalk), async peer communication protocol & library - [Kite](https://github.com/koding/kite), a micro-service framework +- [gocircuit](https://github.com/gocircuit/circuit), dynamic cloud orchestration ### Individual components @@ -206,12 +102,12 @@ Projects with a ★ have had particular influence on Go kit's design (or vice-ve ### Web frameworks -- [Beego](http://beego.me/) -- [Gin](https://gin-gonic.github.io/gin/) -- [Goji](https://github.com/zenazn/goji) - [Gorilla](http://www.gorillatoolkit.org) -- [Martini](https://github.com/go-martini/martini) +- [Gin](https://gin-gonic.github.io/gin/) - [Negroni](https://github.com/codegangsta/negroni) +- [Goji](https://github.com/zenazn/goji) +- [Martini](https://github.com/go-martini/martini) +- [Beego](http://beego.me/) - [Revel](https://revel.github.io/) (considered harmful) ## Additional reading @@ -219,4 +115,3 @@ Projects with a ★ have had particular influence on Go kit's design (or vice-ve - [Architecting for the Cloud](http://fr.slideshare.net/stonse/architecting-for-the-cloud-using-netflixoss-codemash-workshop-29852233) — Netflix - [Dapper, a Large-Scale Distributed Systems Tracing Infrastructure](http://research.google.com/pubs/pub36356.html) — Google - [Your Server as a Function](http://monkey.org/~marius/funsrv.pdf) (PDF) — Twitter - diff --git a/circuitbreaker/doc.go b/circuitbreaker/doc.go new file mode 100644 index 0000000..fdfc1ef --- /dev/null +++ b/circuitbreaker/doc.go @@ -0,0 +1,6 @@ +// Package circuitbreaker implements the circuit breaker pattern. +// +// Circuit breakers prevent thundering herds, and improve resiliency against +// intermittent errors. Every client-side endpoint should be wrapped in a +// circuit breaker. +package circuitbreaker diff --git a/endpoint/doc.go b/endpoint/doc.go new file mode 100644 index 0000000..84e27b9 --- /dev/null +++ b/endpoint/doc.go @@ -0,0 +1,5 @@ +// Package endpoint defines an abstraction for RPCs. +// +// Endpoints are a fundamental building block for many Go kit components. +// Endpoints are implemented by servers, and called by clients. +package endpoint diff --git a/examples/README.md b/examples/README.md index 0b11c21..2891d78 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,721 +1,5 @@ # Examples -1. [A minimal example](#a-minimal-example) - 1. [Your business logic](#your-business-logic) - 1. [Requests and responses](#requests-and-responses) - 1. [Endpoints](#endpoints) - 1. [Transports](#transports) - 1. [stringsvc1](#stringsvc1) -1. [Logging and instrumentation](#logging-and-instrumentation) - 1. [Transport logging](#transport-logging) - 1. [Application logging](#application-logging) - 1. [Instrumentation](#instrumentation) - 1. [stringsvc2](#stringsvc2) -1. [Calling other services](#calling-other-services) - 1. [Client-side endpoints](#client-side-endpoints) - 1. [Service discovery and load balancing](#service-discovery-and-load-balancing) - 1. [stringsvc3](#stringsvc3) -1. [Advanced topics](#advanced-topics) - 1. [Creating a client package](#creating-a-client-package) - 1. [Request tracing](#request-tracing) - 1. [Threading a context](#threading-a-context) -1. [Other examples](#other-examples) - 1. [addsvc](#addsvc) - 1. [profilesvc](#profilesvc) - 1. [apigateway](#apigateway) - 1. [shipping](#shipping) - -## A minimal example - -Let's create a minimal Go kit service. - -### Your business logic - -Your service starts with your business logic. -In Go kit, we model a service as an **interface**. - -```go -// StringService provides operations on strings. -type StringService interface { - Uppercase(string) (string, error) - Count(string) int -} -``` - -That interface will have an implementation. - -```go -type stringService struct{} - -func (stringService) Uppercase(s string) (string, error) { - if s == "" { - return "", ErrEmpty - } - return strings.ToUpper(s), nil -} - -func (stringService) Count(s string) int { - return len(s) -} - -// ErrEmpty is returned when input string is empty -var ErrEmpty = errors.New("Empty string") -``` - -### Requests and responses - -In Go kit, the primary messaging pattern is RPC. -So, every method in our interface will be modeled as a remote procedure call. -For each method, we define **request and response** structs, - capturing all of the input and output parameters respectively. - -```go -type uppercaseRequest struct { - S string `json:"s"` -} - -type uppercaseResponse struct { - V string `json:"v"` - Err string `json:"err,omitempty"` // errors don't JSON-marshal, so we use a string -} - -type countRequest struct { - S string `json:"s"` -} - -type countResponse struct { - V int `json:"v"` -} -``` - -### Endpoints - -Go kit provides much of its functionality through an abstraction called an **endpoint**. - -```go -type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error) -``` - -An endpoint represents a single RPC. -That is, a single method in our service interface. -We'll write simple adapters to convert each of our service's methods into an endpoint. -Each adapter takes a StringService, and returns an endpoint that corresponds to one of the methods. - -```go -import ( - "golang.org/x/net/context" - "github.com/go-kit/kit/endpoint" -) - -func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(uppercaseRequest) - v, err := svc.Uppercase(req.S) - if err != nil { - return uppercaseResponse{v, err.Error()}, nil - } - return uppercaseResponse{v, ""}, nil - } -} - -func makeCountEndpoint(svc StringService) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(countRequest) - v := svc.Count(req.S) - return countResponse{v}, nil - } -} -``` - -### Transports - -Now we need to expose your service to the outside world, so it can be called. -Your organization probably already has opinions about how services should talk to each other. -Maybe you use Thrift, or custom JSON over HTTP. -Go kit supports many **transports** out of the box. -(Adding support for new ones is easy—just [file an issue](https://github.com/go-kit/kit/issues).) - -For this minimal example service, let's use JSON over HTTP. -Go kit provides a helper struct, in package transport/http. - -```go -import ( - "encoding/json" - "log" - "net/http" - - "golang.org/x/net/context" - - httptransport "github.com/go-kit/kit/transport/http" -) - -func main() { - ctx := context.Background() - svc := stringService{} - - uppercaseHandler := httptransport.NewServer( - ctx, - makeUppercaseEndpoint(svc), - decodeUppercaseRequest, - encodeResponse, - ) - - countHandler := httptransport.NewServer( - ctx, - makeCountEndpoint(svc), - decodeCountRequest, - encodeResponse, - ) - - http.Handle("/uppercase", uppercaseHandler) - http.Handle("/count", countHandler) - log.Fatal(http.ListenAndServe(":8080", nil)) -} - -func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) { - var request uppercaseRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - return nil, err - } - return request, nil -} - -func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) { - var request countRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - return nil, err - } - return request, nil -} - -func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { - return json.NewEncoder(w).Encode(response) -} -``` - -### stringsvc1 - -The complete service so far is [stringsvc1][]. - -[stringsvc1]: https://github.com/go-kit/kit/blob/master/examples/stringsvc1 - -``` -$ go get github.com/go-kit/kit/examples/stringsvc1 -$ stringsvc1 -``` - -``` -$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/uppercase -{"v":"HELLO, WORLD","err":null} -$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/count -{"v":12} -``` - -## Logging and instrumentation - -No service can be considered production-ready without thorough logging and instrumentation. - -### Transport logging - -Any component that needs to log should treat the logger like a dependency, same as a database connection. -So, we construct our logger in our `func main`, and pass it to components that need it. -We never use a globally-scoped logger. - -We could pass a logger directly into our stringService implementation, but there's a better way. -Let's use a **middleware**, also known as a decorator. -A middleware is a function that takes an endpoint and returns an endpoint. - -```go -type Middleware func(Endpoint) Endpoint -``` - -In between, it can do anything. -Let's create a basic logging middleware. - -```go -func loggingMiddleware(logger log.Logger) Middleware { - return func(next endpoint.Endpoint) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - logger.Log("msg", "calling endpoint") - defer logger.Log("msg", "called endpoint") - return next(ctx, request) - } - } -} -``` - -And wire it into each of our handlers. - -```go -logger := log.NewLogfmtLogger(os.Stderr) - -svc := stringService{} - -var uppercase endpoint.Endpoint -uppercase = makeUppercaseEndpoint(svc) -uppercase = loggingMiddleware(log.NewContext(logger).With("method", "uppercase"))(uppercase) - -var count endpoint.Endpoint -count = makeCountEndpoint(svc) -count = loggingMiddleware(log.NewContext(logger).With("method", "count"))(count) - -uppercaseHandler := httptransport.Server( - // ... - uppercase, - // ... -) - -countHandler := httptransport.Server( - // ... - count, - // ... -) -``` - -It turns out that this technique is useful for a lot more than just logging. -Many Go kit components are implemented as endpoint middlewares. - -### Application logging - -But what if we want to log in our application domain, like the parameters that are passed in? -It turns out that we can define a middleware for our service, and get the same nice and composable effects. -Since our StringService is defined as an interface, we just need to make a new type - which wraps an existing StringService, and performs the extra logging duties. - -```go -type loggingMiddleware struct { - logger log.Logger - next StringService -} - -func (mw loggingMiddleware) Uppercase(s string) (output string, err error) { - defer func(begin time.Time) { - mw.logger.Log( - "method", "uppercase", - "input", s, - "output", output, - "err", err, - "took", time.Since(begin), - ) - }(time.Now()) - - output, err = mw.next.Uppercase(s) - return -} - -func (mw loggingMiddleware) Count(s string) (n int) { - defer func(begin time.Time) { - mw.logger.Log( - "method", "count", - "input", s, - "n", n, - "took", time.Since(begin), - ) - }(time.Now()) - - n = mw.next.Count(s) - return -} -``` - -And wire it in. - -```go -import ( - "os" - - "github.com/go-kit/kit/log" - httptransport "github.com/go-kit/kit/transport/http" -) - -func main() { - logger := log.NewLogfmtLogger(os.Stderr) - - var svc StringService - svc = stringsvc{} - svc = loggingMiddleware{logger, svc} - - // ... - - uppercaseHandler := httptransport.NewServer( - // ... - makeUppercaseEndpoint(svc), - // ... - ) - - countHandler := httptransport.NewServer( - // ... - makeCountEndpoint(svc), - // ... - ) -} -``` - -Use endpoint middlewares for transport-domain concerns, like circuit breaking and rate limiting. -Use service middlewares for business-domain concerns, like logging and instrumentation. -Speaking of instrumentation... - -### Instrumentation - -In Go kit, instrumentation means using **package metrics** to record statistics about your service's runtime behavior. -Counting the number of jobs processed, - recording the duration of requests after they've finished, - and tracking the number of in-flight operations would all be considered instrumentation. - -We can use the same middleware pattern that we used for logging. - -```go -type instrumentingMiddleware struct { - requestCount metrics.Counter - requestLatency metrics.TimeHistogram - countResult metrics.Histogram - next StringService -} - -func (mw instrumentingMiddleware) Uppercase(s string) (output string, err error) { - defer func(begin time.Time) { - methodField := metrics.Field{Key: "method", Value: "uppercase"} - errorField := metrics.Field{Key: "error", Value: fmt.Sprintf("%v", err)} - mw.requestCount.With(methodField).With(errorField).Add(1) - mw.requestLatency.With(methodField).With(errorField).Observe(time.Since(begin)) - }(time.Now()) - - output, err = mw.next.Uppercase(s) - return -} - -func (mw instrumentingMiddleware) Count(s string) (n int) { - defer func(begin time.Time) { - methodField := metrics.Field{Key: "method", Value: "count"} - errorField := metrics.Field{Key: "error", Value: fmt.Sprintf("%v", error(nil))} - mw.requestCount.With(methodField).With(errorField).Add(1) - mw.requestLatency.With(methodField).With(errorField).Observe(time.Since(begin)) - mw.countResult.Observe(int64(n)) - }(time.Now()) - - n = mw.next.Count(s) - return -} -``` - -And wire it into our service. - -```go -import ( - stdprometheus "github.com/prometheus/client_golang/prometheus" - kitprometheus "github.com/go-kit/kit/metrics/prometheus" - "github.com/go-kit/kit/metrics" -) - -func main() { - logger := log.NewLogfmtLogger(os.Stderr) - - fieldKeys := []string{"method", "error"} - requestCount := kitprometheus.NewCounter(stdprometheus.CounterOpts{ - // ... - }, fieldKeys) - requestLatency := metrics.NewTimeHistogram(time.Microsecond, kitprometheus.NewSummary(stdprometheus.SummaryOpts{ - // ... - }, fieldKeys)) - countResult := kitprometheus.NewSummary(stdprometheus.SummaryOpts{ - // ... - }, []string{})) - - var svc StringService - svc = stringService{} - svc = loggingMiddleware{logger, svc} - svc = instrumentingMiddleware{requestCount, requestLatency, countResult, svc} - - // ... - - http.Handle("/metrics", stdprometheus.Handler()) -} -``` - -### stringsvc2 - -The complete service so far is [stringsvc2][]. - -[stringsvc2]: https://github.com/go-kit/kit/blob/master/examples/stringsvc2 - -``` -$ go get github.com/go-kit/kit/examples/stringsvc2 -$ stringsvc2 -msg=HTTP addr=:8080 -``` - -``` -$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/uppercase -{"v":"HELLO, WORLD","err":null} -$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/count -{"v":12} -``` - -``` -method=uppercase input="hello, world" output="HELLO, WORLD" err=null took=2.455µs -method=count input="hello, world" n=12 took=743ns -``` - -## Calling other services - -It's rare that a service exists in a vacuum. -Often, you need to call other services. -**This is where Go kit shines**. -We provide transport middlewares to solve many of the problems that come up. - -Let's say that we want to have our string service call out to a _different_ string service - to satisfy the Uppercase method. -In effect, proxying the request to another service. -Let's implement the proxying middleware as a ServiceMiddleware, same as a logging or instrumenting middleware. - -```go -// proxymw implements StringService, forwarding Uppercase requests to the -// provided endpoint, and serving all other (i.e. Count) requests via the -// next StringService. -type proxymw struct { - ctx context.Context - next StringService // Serve most requests via this service... - uppercase endpoint.Endpoint // ...except Uppercase, which gets served by this endpoint -} -``` - -### Client-side endpoints - -We've got exactly the same endpoint we already know about, but we'll use it to invoke, rather than serve, a request. -When used this way, we call it a _client_ endpoint. -And to invoke the client endpoint, we just do some simple conversions. - -```go -func (mw proxymw) Uppercase(s string) (string, error) { - response, err := mw.uppercase(mw.Context, uppercaseRequest{S: s}) - if err != nil { - return "", err - } - resp := response.(uppercaseResponse) - if resp.Err != "" { - return resp.V, errors.New(resp.Err) - } - return resp.V, nil -} -``` - -Now, to construct one of these proxying middlewares, we convert a proxy URL string to an endpoint. -If we assume JSON over HTTP, we can use a helper in the transport/http package. - -```go -import ( - httptransport "github.com/go-kit/kit/transport/http" -) - -func proxyingMiddleware(proxyURL string, ctx context.Context) ServiceMiddleware { - return func(next StringService) StringService { - return proxymw{ctx, next, makeUppercaseEndpoint(ctx, proxyURL)} - } -} - -func makeUppercaseEndpoint(ctx context.Context, proxyURL string) endpoint.Endpoint { - return httptransport.NewClient( - "GET", - mustParseURL(proxyURL), - encodeUppercaseRequest, - decodeUppercaseResponse, - ).Endpoint() -} -``` - -### Service discovery and load balancing - -That's fine if we only have a single remote service. -But in reality, we'll probably have many service instances available to us. -We want to discover them through some service discovery mechanism, and spread our load across all of them. -And if any of those instances start to behave badly, we want to deal with that, without affecting our own service's reliability. - -Go kit offers adapters to different service discovery systems, to get up-to-date sets of instances, exposed as individual endpoints. -Those adapters are called subscribers. - -```go -type Subscriber interface { - Endpoints() ([]endpoint.Endpoint, error) -} -``` - -Internally, subscribers use a provided factory function to convert each discovered instance string (typically host:port) to a usable endpoint. - -```go -type Factory func(instance string) (endpoint.Endpoint, error) -``` - -So far, our factory function, makeUppercaseEndpoint, just calls the URL directly. -But it's important to put some safety middleware, like circuit breakers and rate limiters, into your factory, too. - -```go -var e endpoint.Endpoint -e = makeUppercaseProxy(ctx, instance) -e = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e) -e = kitratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(float64(maxQPS), int64(maxQPS)))(e) -} -``` - -Now that we've got a set of endpoints, we need to choose one. -Load balancers wrap subscribers, and select one endpoint from many. -Go kit provides a couple of basic load balancers, and it's easy to write your own if you want more advanced heuristics. - -```go -type Balancer interface { - Endpoint() (endpoint.Endpoint, error) -} -``` - -Now we have the ability to choose endpoints according to some heuristic. -We can use that to provide a single, logical, robust endpoint to consumers. -A retry strategy wraps a load balancer, and returns a usable endpoint. -The retry strategy will retry failed requests until either the max attempts or timeout has been reached. - -```go -func Retry(max int, timeout time.Duration, lb Balancer) endpoint.Endpoint -``` - -Let's wire up our final proxying middleware. -For simplicity, we'll assume the user will specify multiple comma-separate instance endpoints with a flag. - -```go -func proxyingMiddleware(instances string, ctx context.Context, logger log.Logger) ServiceMiddleware { - // If instances is empty, don't proxy. - if instances == "" { - logger.Log("proxy_to", "none") - return func(next StringService) StringService { return next } - } - - // Set some parameters for our client. - var ( - qps = 100 // beyond which we will return an error - maxAttempts = 3 // per request, before giving up - maxTime = 250 * time.Millisecond // wallclock time, before giving up - ) - - // Otherwise, construct an endpoint for each instance in the list, and add - // it to a fixed set of endpoints. In a real service, rather than doing this - // by hand, you'd probably use package sd's support for your service - // discovery system. - var ( - instanceList = split(instances) - subscriber sd.FixedSubscriber - ) - logger.Log("proxy_to", fmt.Sprint(instanceList)) - for _, instance := range instanceList { - var e endpoint.Endpoint - e = makeUppercaseProxy(ctx, instance) - e = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e) - e = kitratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(float64(qps), int64(qps)))(e) - subscriber = append(subscriber, e) - } - - // Now, build a single, retrying, load-balancing endpoint out of all of - // those individual endpoints. - balancer := lb.NewRoundRobin(subscriber) - retry := lb.Retry(maxAttempts, maxTime, balancer) - - // And finally, return the ServiceMiddleware, implemented by proxymw. - return func(next StringService) StringService { - return proxymw{ctx, next, retry} - } -} -``` - -### stringsvc3 - -The complete service so far is [stringsvc3][]. - -[stringsvc3]: https://github.com/go-kit/kit/blob/master/examples/stringsvc3 - -``` -$ go get github.com/go-kit/kit/examples/stringsvc3 -$ stringsvc3 -listen=:8001 & -listen=:8001 caller=proxying.go:25 proxy_to=none -listen=:8001 caller=main.go:72 msg=HTTP addr=:8001 -$ stringsvc3 -listen=:8002 & -listen=:8002 caller=proxying.go:25 proxy_to=none -listen=:8002 caller=main.go:72 msg=HTTP addr=:8002 -$ stringsvc3 -listen=:8003 & -listen=:8003 caller=proxying.go:25 proxy_to=none -listen=:8003 caller=main.go:72 msg=HTTP addr=:8003 -$ stringsvc3 -listen=:8080 -proxy=localhost:8001,localhost:8002,localhost:8003 -listen=:8080 caller=proxying.go:29 proxy_to="[localhost:8001 localhost:8002 localhost:8003]" -listen=:8080 caller=main.go:72 msg=HTTP addr=:8080 -``` - -``` -$ for s in foo bar baz ; do curl -d"{\"s\":\"$s\"}" localhost:8080/uppercase ; done -{"v":"FOO","err":null} -{"v":"BAR","err":null} -{"v":"BAZ","err":null} -``` - -``` -listen=:8001 caller=logging.go:28 method=uppercase input=foo output=FOO err=null took=5.168µs -listen=:8080 caller=logging.go:28 method=uppercase input=foo output=FOO err=null took=4.39012ms -listen=:8002 caller=logging.go:28 method=uppercase input=bar output=BAR err=null took=5.445µs -listen=:8080 caller=logging.go:28 method=uppercase input=bar output=BAR err=null took=2.04831ms -listen=:8003 caller=logging.go:28 method=uppercase input=baz output=BAZ err=null took=3.285µs -listen=:8080 caller=logging.go:28 method=uppercase input=baz output=BAZ err=null took=1.388155ms -``` - -## Advanced topics - -### Threading a context - -The context object is used to carry information across conceptual boundaries in the scope of a single request. -In our example, we haven't yet threaded the context through our business logic. -But that's almost always a good idea. -It allows you to pass request-scoped information between business logic and middlewares, - and is necessary for more sophisticated tasks like granular distributed tracing annotations. - -Concretely, this means your business logic interfaces will look like - -```go -type MyService interface { - Foo(context.Context, string, int) (string, error) - Bar(context.Context, string) error - Baz(context.Context) (int, error) -} -``` - -### Request tracing - -Once your infrastructure grows beyond a certain size, it becomes important to trace requests through multiple services, so you can identify and troubleshoot hotspots. -See [package tracing](https://github.com/go-kit/kit/blob/master/tracing) for more information. - -### Creating a client package - -It's possible to use Go kit to create a client package to your service, to make consuming your service easier from other Go programs. -Effectively, your client package will provide an implementation of your service interface, which invokes a remote service instance using a specific transport. -See [package addsvc/client](https://github.com/go-kit/kit/tree/master/examples/addsvc/client) - or [package profilesvc/client](https://github.com/go-kit/kit/tree/master/examples/profilesvc/client) - for examples. - -## Other examples - -### addsvc - -[addsvc](https://github.com/go-kit/kit/blob/master/examples/addsvc) is the original example service. -It exposes a set of operations over **all supported transports**. -It's fully logged, instrumented, and uses Zipkin request tracing. -It also demonstrates how to create and use client packages. -It's a good example of a fully-featured Go kit service. - -### profilesvc - -[profilesvc](https://github.com/go-kit/kit/blob/master/examples/profilesvc) - demonstrates how to use Go kit to build a REST-ish microservice. - -### apigateway - -[apigateway](https://github.com/go-kit/kit/blob/master/examples/apigateway/main.go) - demonstrates how to implement the API gateway pattern, - backed by a Consul service discovery system. - -### shipping - -[shipping](https://github.com/go-kit/kit/tree/master/examples/shipping) - is a complete, "real-world" application composed of multiple microservices, - based on Domain Driven Design principles. +For more information about these examples, + including a walkthrough of the stringsvc example, + see [gokit.io/examples](https://gokit.io/examples). diff --git a/examples/addsvc/doc.go b/examples/addsvc/doc.go index 8865046..4dd8414 100644 --- a/examples/addsvc/doc.go +++ b/examples/addsvc/doc.go @@ -1,6 +1,5 @@ -// Package addsvc implements the business and transport logic for an example -// service that can sum integers and concatenate strings. -// -// A client library is available in the client subdirectory. A server binary is -// available in cmd/addsrv. An example client binary is available in cmd/addcli. +// Package addsvc is an example microservice, useful for education. It can sum +// integers and concatenate strings. A client library is available in the client +// subdirectory. A server binary is available in cmd/addsrv. An example client +// binary is available in cmd/addcli. package addsvc diff --git a/log/doc.go b/log/doc.go new file mode 100644 index 0000000..629f0aa --- /dev/null +++ b/log/doc.go @@ -0,0 +1,9 @@ +// Package log provides a structured logger. +// +// Services produce logs to be consumed later, either by humans or machines. +// Humans might be interested in debugging errors, or tracing specific requests. +// Machines might be interested in counting interesting events, or aggregating +// information for offline processing. In both cases, it's important that the +// log messages be structured and actionable. Package log is designed to +// encourage both of these best practices. +package log diff --git a/metrics/doc.go b/metrics/doc.go new file mode 100644 index 0000000..760dfba --- /dev/null +++ b/metrics/doc.go @@ -0,0 +1,4 @@ +// Package metrics provides a framework for application instrumentation. All +// metrics are safe for concurrent use. Considerable design influence has been +// taken from https://github.com/codahale/metrics and https://prometheus.io. +package metrics diff --git a/metrics/metrics.go b/metrics/metrics.go index 3871dd6..f12e56f 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -1,7 +1,3 @@ -// Package metrics provides an extensible framework to instrument your -// application. All metrics are safe for concurrent use. Considerable design -// influence has been taken from https://github.com/codahale/metrics and -// https://prometheus.io. package metrics // Counter is a monotonically-increasing, unsigned, 64-bit integer used to diff --git a/sd/doc.go b/sd/doc.go index b10d96f..9bcf3bd 100644 --- a/sd/doc.go +++ b/sd/doc.go @@ -1,5 +1,6 @@ -// Package sd provides utilities related to service discovery. That includes -// subscribing to service discovery systems in order to reach remote instances, -// and publishing to service discovery systems to make an instance available. -// Implementations are provided for most common systems. +// Package sd provides utilities related to service discovery. That includes the +// client-side loadbalancer pattern, where a microservice subscribes to a +// service discovery system in order to reach remote instances; as well as the +// registrator pattern, where a microservice registers itself in a service +// discovery system. Implementations are provided for most common systems. package sd diff --git a/tracing/doc.go b/tracing/doc.go new file mode 100644 index 0000000..8e5ec46 --- /dev/null +++ b/tracing/doc.go @@ -0,0 +1,8 @@ +// Package tracing provides helpers and bindings for distributed tracing. +// +// As your infrastructure grows, it becomes important to be able to trace a +// request, as it travels through multiple services and back to the user. +// Package tracing provides endpoints and transport helpers and middlewares to +// capture and emit request-scoped information. We use the excellent OpenTracing +// project to bind to concrete tracing systems. +package tracing diff --git a/transport/doc.go b/transport/doc.go new file mode 100644 index 0000000..6ac33ed --- /dev/null +++ b/transport/doc.go @@ -0,0 +1,2 @@ +// Package transport contains bindings to concrete transports. +package transport