From f32ad6b4f6eed91d1b2bdcac7698c21039da7721 Mon Sep 17 00:00:00 2001 From: "Deployment Bot (from Travis CI)" Date: Sun, 9 Feb 2020 14:49:15 +0000 Subject: [PATCH 1/8] Deploy gokit.io to github.com/go-kit/go-kit.github.io.git:master --- 404.html | 70 ++++ CNAME | 1 + blog/index.html | 161 ++++++++ css/global.css | 64 ++++ examples/index.html | 96 +++++ examples/stringsvc.html | 794 ++++++++++++++++++++++++++++++++++++++++ faq/index.html | 458 +++++++++++++++++++++++ faq/onion.png | Bin 0 -> 25832 bytes gokit-logo-header.png | Bin 0 -> 10406 bytes index.html | 151 ++++++++ 10 files changed, 1795 insertions(+) create mode 100755 404.html create mode 100644 CNAME create mode 100755 blog/index.html create mode 100755 css/global.css create mode 100755 examples/index.html create mode 100755 examples/stringsvc.html create mode 100755 faq/index.html create mode 100755 faq/onion.png create mode 100755 gokit-logo-header.png create mode 100755 index.html diff --git a/404.html b/404.html new file mode 100755 index 0000000..ce4ff9d --- /dev/null +++ b/404.html @@ -0,0 +1,70 @@ + + + +Go kit - Page not found + + + + + + + +
+
+ + +
+

Go kit

+

Page not found

+
+
+ +
+
+

+ Home    + Examples    + FAQ    + Blog   ·   + GitHub    + GoDoc    + Slack    + Mailing list +

+
+ +
+ +
+

Page not found

+ +

If you got here without being nefarious, would you be so kind as to + file an issue?

+
+ +
+
+ + +
+ + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..9eec7a4 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +gokit.io diff --git a/blog/index.html b/blog/index.html new file mode 100755 index 0000000..ad738d4 --- /dev/null +++ b/blog/index.html @@ -0,0 +1,161 @@ + + + +Go kit - Blog + + + + + + + +
+
+ + +
+

Go kit

+

Blog

+
+
+ +
+
+

+ Home    + Examples    + FAQ    + Blog   ·   + GitHub    + GoDoc    + Slack    + Mailing list +

+
+ +
+ +
+

Instrumentation v. log aggregation?

+ +

12 October 2016

+ +

On the Go kit mailing list, Chris Ford recently asked

+ +
+

I came across the instrumentation part of Go kit and I am wondering if there +is any advantage over aggregating log events? e.g. the instrumentation +examples are about response time, request count and these kind of metrics. You +could simply log every request and count the request events in the log +afterwards. Am I correct about this assumption or am I missing something? +Because I do not see a use case that can’t be realized with log aggregation. +Is guess the performance and realtimeness is better with instrumentation, but +besides that?

+
+ +

Great question, and one I see a lot. In theory Chris is correct: if you logged +the complete details of every request to a system like ELK, in a way that was +queryable, you could construct the same information (99th quantile latencies, +error rates, etc.) post-hoc using a sufficiently sophisticated query language. +The reason to use (and, spoiler alert, almost always prefer) instrumentation +is ultimately about complexity, maintainability, and fitness to purpose.

+ +

First of all, you want to consider plain data volume: a human- and +machine-readable log line for each request with enough information is going to +generate a lot of traffic. I can speak from firsthand experience to say that +this can quickly outpace actual production traffic and completely saturate all +kinds of resources: your network, your log shippers, the CPUs on the VMs +themselves, the machines/cluster responsible for storing your logs and making +them queryable, etc. I’m happy to acknowledge that this seems counterintuitive, +and like pointless worrying when you’re starting out; trust me that it becomes +problematic a lot sooner than you think.

+ +

Then there’s complexity in terms of what you actually want to accomplish by +tracking this stuff. The whole point is observability: getting some view into +the inner workings of the distributed system you’ve built. That means processing +latencies, quantiles, error rates, top-K resource consumers, etc. as first-order +concepts in some sense, rather than the text output of a map/reduce process over +some log data. And, ideally, you want those things on a close-to-realtime view +of your infra, with granularity on the order of seconds. Again, all of this is +in theory computable from text or structured application logs, but not in a way +that is nearly as useful, and not in a way that allows that information to serve +well as the foundation for more sophisticated feedback systems, reports, and as +a tool for system operators.

+ +

Finally, there’s costs within the service itself. This loops back to my first +point: spitting out a line of text to stdout or rsyslog or whatever for every +trackable thing is a lot more expensive (CPU) than how the good instrumentation +systems do things: Prometheus instrumentation, for example, involves just +updating a register in memory, and at thousands of QPS that absolutely adds up. +There’s also a point about software engineering lurking in here: recording and +making available good instrumentation about how your service is doing is part of +the contract of that service to its runtime environment. Keeping your 99th +quantile latencies below 10ms (for example) is part of your service’s SLA, and +recording/emitting data to validate that SLA is as much a necessary thing to +test for as anything else in your business logic. All this is to say you want +reliable, purpose-built primitives for this kind of thing: it’s very difficult +to define a contract for your log lines, or unit test them properly.

+ +

For more on the latest point, including many many good reasons for +investing in proper instrumentation, see + the Site Reliability Engineering book +from O’Reilly. And for more on logging v. instrumentation generally, shout out to me & my +eponymous blog post Logging v instrumentation.

+ +


+ +

Welcome to the new Go kit website!

+ +

01 July 2016

+ +

Go kit started its life as just a talk at a small conference, + and spent its first 18 months as a GitHub repo with lots of breaking changes. +(Sorry about that.) +Many design discussions took place, initially in RFCs, and then in GitHub issues, + on the mailing list, and especially in the Slack channel. +While the code can capture the rationale and outcome of those discussions, + it can’t so easily capture the context: the conversations themselves, the alternatives, + the pros and cons of all the decisions, the intended usage, the caveats. +And it’s often these things that are the most valuable, especially to newcomers.

+ +

So, welcome to the new Go kit website! +The primary purpose will be to capture and document the context + behind the decisions that shaped Go kit. +I’ll record and keep supplementary information to help you + understand and effectively apply Go kit in your organization.

+ +

Initially, I’ll focus on building up an extensive FAQ + based on common questions and conversation in Slack, the mailing list, etc. +But no resource has proved nearly as useful as the examples, + so I’ve also ported the examples and tutorials from the repo + and will be adding to them as time goes on.

+ +

Happy programming 🏌

+
+ +
+
+ + +
+ + + + \ No newline at end of file diff --git a/css/global.css b/css/global.css new file mode 100755 index 0000000..4ec75d6 --- /dev/null +++ b/css/global.css @@ -0,0 +1,64 @@ +body { + background-color: #fafafa; + color: #1a1a1a; + font-family: sans-serif; + line-height: 1.4em; +} + +div#c { + width: 85%; + min-width: 750px; + margin: 15px auto; +} + +div.hero { display: flex; } +div.hero .logo { margin-right: 20px; } + +div.hero h2 { + margin-top: -5px; + margin-left: 5px; + font-weight: 200; +} + +div.blockcontainer { + display: flex; + justify-content: space-between; +} + +div.pitch .block { width: 31%; } +div.compare .block { width: 48%; } +div.why .block { width: 31%; } + +div.content { + min-width: 600px; + width: 75%; +} + +div.content h1, div.content h2 { + margin-top: 20px; + padding-top: 20px; +} + +div.content li { + margin-left: -15px; +} + +div.content pre { + /* background-color: #e6e6e6; */ + margin: 20px 40px 20px 20px; + line-height: 1.3em; + padding: 10px; + border-top: solid 1px #999; + border-bottom: solid 1px #999; +} + +div.footer p { + color: #bababa; + font-size: 9pt; +} +div.footer p a { + color: #bababa; +} +div.footer p a:visited { + color: #bababa; +} \ No newline at end of file diff --git a/examples/index.html b/examples/index.html new file mode 100755 index 0000000..fca3b32 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,96 @@ + + + +Go kit - Examples + + + + + + + +
+
+ + +
+

Go kit

+

Examples

+
+
+ +
+
+

+ Home    + Examples    + FAQ    + Blog   ·   + GitHub    + GoDoc    + Slack    + Mailing list +

+
+ +
+ +
+

Users have consistently found that the most effective way to learn Go kit + is to study and learn from example services. +Here you can find some examples that will orient you to Go kit idioms, + patterns, and best practices.

+ +
    +
  • stringsvc is a tutorial that takes you through +writing a service from first principles. It can help you understand +the decisions that went into Go kit’s design.

  • + +
  • addsvc +is the original example service. +It exposes a set of operations over all supported transports. +It’s fully logged, instrumented, and uses distributed tracing. +It also demonstrates how to create and use client packages. +It demonstrates almost all of Go kit’s features.

  • + +
  • profilesvc +demonstrates how to use Go kit +to write a microservice with a REST-ish API. +It uses net/http and the excellent Gorilla web toolkit.

  • + +
  • shipping +is a complete, “real-world” application composed of multiple microservices, +based on Domain Driven Design principles.

  • + +
  • apigateway +demonstrates how to implement the API gateway pattern +backed by a Consul service discovery system.

  • +
+
+ +
+
+ + +
+ + + + \ No newline at end of file diff --git a/examples/stringsvc.html b/examples/stringsvc.html new file mode 100755 index 0000000..5f5fd0f --- /dev/null +++ b/examples/stringsvc.html @@ -0,0 +1,794 @@ + + + +Go kit - The stringsvc tutorial + + + + + + + +
+
+ + +
+

Go kit

+

The stringsvc tutorial

+
+
+ +
+
+

+ Home    + Examples    + FAQ    + Blog   ·   + GitHub    + GoDoc    + Slack    + Mailing list +

+
+ +
+ +
+ + +

First principles

+ +

Let’s create a minimal Go kit service. For now, we will use a single main.go file for that.

+ +

Your business logic

+ +

Your service starts with your business logic. +In Go kit, we model a service as an interface.

+ +
// StringService provides operations on strings.
+import "context"
+
+type StringService interface {
+	Uppercase(string) (string, error)
+	Count(string) int
+}
+
+ +

That interface will have an implementation.

+ +
import (
+	"context"
+	"errors"
+	"strings"
+)
+
+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.

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

+ +

An endpoint is defined as follows (you don’t have to put it anywhere in the code, it is provided by go-kit):

+ +
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
+
+ +

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

+ +
import (
+	"context"
+	"github.com/go-kit/kit/endpoint"
+)
+
+func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
+	return func(_ 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(_ 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.

+ +

For this minimal example service, let’s use JSON over HTTP. +Go kit provides a helper struct, in package transport/http.

+ +
import (
+	"context"
+	"encoding/json"
+	"log"
+	"net/http"
+
+	httptransport "github.com/go-kit/kit/transport/http"
+)
+
+func main() {
+	svc := stringService{}
+
+	uppercaseHandler := httptransport.NewServer(
+		makeUppercaseEndpoint(svc),
+		decodeUppercaseRequest,
+		encodeResponse,
+	)
+
+	countHandler := httptransport.NewServer(
+		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.

+ +
$ go get github.com/go-kit/kit/examples/stringsvc1
+$ stringsvc1
+
+ +
$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/uppercase
+{"v":"HELLO, WORLD"}
+$ curl -XPOST -d'{"s":"hello, world"}' localhost:8080/count
+{"v":12}
+
+ +

Middlewares

+ +

No service can be considered production-ready without thorough logging and instrumentation.

+ +

Separation of concerns

+ +

Separating each layer of the call graph into individual files makes a go-kit project easier to read as you increase the number of service endpoints. +Our first example stringsvc1 had all of these layers in a single main file. +Before we add more complexity, let’s separate our code into the following files and leave all remaining code in main.go

+ +

Place your services into a service.go file with the following functions and types.

+ +
type StringService
+type stringService
+func Uppercase
+func Count
+var ErrEmpty
+
+ +

Place your transports into a transport.go file with the following functions and types.

+ +
func makeUppercaseEndpoint
+func makeCountEndpoint
+func decodeUppercaseRequest
+func decodeCountRequest
+func encodeResponse
+type uppercaseRequest
+type uppercaseResponse
+type countRequest
+type countResponse
+
+ +

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.

+ +
type Middleware func(Endpoint) Endpoint
+
+ +
+

Note, that the Middleware type is provided for you by go-kit.

+
+ +

In between, it can do anything. +Below you can see how a basic logging middleware could be implemented (you don’t need to copy/paste this code anywhere):

+ +
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)
+		}
+	}
+}
+
+ +

Use the go-kit log package and remove the standard libraries log. +You will need to remove log.Fatal from the bottom of the main.go file.

+ +
import (
+ "github.com/go-kit/kit/log"
+)
+
+ +

And wire it into each of our handlers. +Note that the next code section will not compile until you follow the Application Logging section, which defines loggingMiddleware.

+ +
logger := log.NewLogfmtLogger(os.Stderr)
+
+svc := stringService{}
+
+var uppercase endpoint.Endpoint
+uppercase = makeUppercaseEndpoint(svc)
+uppercase = loggingMiddleware(log.With(logger, "method", "uppercase"))(uppercase)
+
+var count endpoint.Endpoint
+count = makeCountEndpoint(svc)
+count = loggingMiddleware(log.With(logger, "method", "count"))(count)
+
+uppercaseHandler := httptransport.NewServer(
+	uppercase,
+	// ...
+)
+
+countHandler := httptransport.NewServer(
+	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.

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

+ +
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 = stringService{}
+	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…

+ +

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

+ +
type instrumentingMiddleware struct {
+	requestCount   metrics.Counter
+	requestLatency metrics.Histogram
+	countResult    metrics.Histogram
+	next           StringService
+}
+
+func (mw instrumentingMiddleware) Uppercase(s string) (output string, err error) {
+	defer func(begin time.Time) {
+		lvs := []string{"method", "uppercase", "error", fmt.Sprint(err != nil)}
+		mw.requestCount.With(lvs...).Add(1)
+		mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
+	}(time.Now())
+
+	output, err = mw.next.Uppercase(s)
+	return
+}
+
+func (mw instrumentingMiddleware) Count(s string) (n int) {
+	defer func(begin time.Time) {
+		lvs := []string{"method", "count", "error", "false"}
+		mw.requestCount.With(lvs...).Add(1)
+		mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
+		mw.countResult.Observe(float64(n))
+	}(time.Now())
+
+	n = mw.next.Count(s)
+	return
+}
+
+ +

And wire it into our service.

+ +
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.NewCounterFrom(stdprometheus.CounterOpts{
+		Namespace: "my_group",
+		Subsystem: "string_service",
+		Name:      "request_count",
+		Help:      "Number of requests received.",
+	}, fieldKeys)
+	requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
+		Namespace: "my_group",
+		Subsystem: "string_service",
+		Name:      "request_latency_microseconds",
+		Help:      "Total duration of requests in microseconds.",
+	}, fieldKeys)
+	countResult := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
+		Namespace: "my_group",
+		Subsystem: "string_service",
+		Name:      "count_result",
+		Help:      "The result of each count method.",
+	}, []string{}) // no fields here
+
+	var svc StringService
+	svc = stringService{}
+	svc = loggingMiddleware{logger, svc}
+	svc = instrumentingMiddleware{requestCount, requestLatency, countResult, svc}
+
+	uppercaseHandler := httptransport.NewServer(
+		makeUppercaseEndpoint(svc),
+		decodeUppercaseRequest,
+		encodeResponse,
+	)
+
+	countHandler := httptransport.NewServer(
+		makeCountEndpoint(svc),
+		decodeCountRequest,
+		encodeResponse,
+	)
+
+	http.Handle("/uppercase", uppercaseHandler)
+	http.Handle("/count", countHandler)
+	http.Handle("/metrics", promhttp.Handler())
+	logger.Log("msg", "HTTP", "addr", ":8080")
+	logger.Log("err", http.ListenAndServe(":8080", nil))
+}
+
+ +

stringsvc2

+ +

The complete service so far is 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"}
+$ 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.

+ +
// 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 {
+	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.

+ +
func (mw proxymw) Uppercase(s string) (string, error) {
+	response, err := mw.uppercase(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.

+ +
import (
+	httptransport "github.com/go-kit/kit/transport/http"
+)
+
+func proxyingMiddleware(proxyURL string) ServiceMiddleware {
+	return func(next StringService) StringService {
+		return proxymw{next, makeUppercaseProxy(proxyURL)}
+	}
+}
+
+func makeUppercaseProxy(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.

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

+ +
type Factory func(instance string) (endpoint.Endpoint, error)
+
+ +

So far, our factory function, makeUppercaseProxy, just calls the URL directly. +But it’s important to put some safety middleware, like circuit breakers and rate limiters, into your factory, too.

+ +
var e endpoint.Endpoint
+e = makeUppercaseProxy(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.

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

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

+ +
func proxyingMiddleware(instances string, 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(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{next, retry}
+	}
+}
+
+ +

stringsvc3

+ +

The complete service so far is 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"}
+{"v":"BAR"}
+{"v":"BAZ"}
+
+ +
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

+ +
type MyService interface {
+	Foo(context.Context, string, int) (string, error)
+	Bar(context.Context, string) error
+	Baz(context.Context) (int, error)
+}
+
+ +

Distributed 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 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 addsvc/cmd/addcli + or package profilesvc/client + for examples.

+
+ +
+
+ + +
+ + + + \ No newline at end of file diff --git a/faq/index.html b/faq/index.html new file mode 100755 index 0000000..8cfa689 --- /dev/null +++ b/faq/index.html @@ -0,0 +1,458 @@ + + + +Go kit - Frequently asked questions + + + + + + + +
+
+ + +
+

Go kit

+

Frequently asked questions

+
+
+ +
+
+

+ Home    + Examples    + FAQ    + Blog   ·   + GitHub    + GoDoc    + Slack    + Mailing list +

+
+ +
+ +
+ + +

General

+ +

What is Go kit?

+ +

Go kit is a collection of Go (golang) packages (libraries) that help you build robust, reliable, maintainable microservices. +It was originally conceived as a toolkit to help larger (so-called modern enterprise) organizations adopt Go as an implementation language. +But it very quickly “grew downward”, and now serves smaller startups and organizations just as well. +For more about the origins of Go kit, see Go kit: Go in the modern enterprise.

+ +

Why should I use Go kit?

+ +

You should use Go kit if you know you want to adopt the microservices pattern in your organization. +Go kit will help you structure and build out your services, + avoid common pitfalls, and write code that grows with grace.

+ +

Go kit can also help to justify Go as an implementation language + to stakeholders like engineering managers or tech leads. +Go kit de-risks both Go and microservices + by providing mature patterns and idioms, + written and maintained by a large group of experienced contributors, + and validated in production environments.

+ +

Who is behind Go kit?

+ +

Go kit was originally conceived by Peter Bourgon, + but is now built and maintained by a large group of contributors + from a diverse set of backgrounds and organizations. +Go kit is presently an all-volunteer effort, and has no commercial backing.

+ +

Is Go kit production-ready?

+ +

Yes. Go kit is being used in production in several organizations, large and small.

+ +

Which organizations are using Go kit?

+ +

Watch this space :)

+ +

How does Go kit compare to Micro?

+ +

Like Go kit, Micro describes itself as a microservice toolkit. +But unlike Go kit, Micro also describes itself as a microservice ecosystem. +It takes a broader view, encoding expectations and opinions about the infrastructure and architecture. +In short, I think Micro wants to be a platform; + Go kit, in contrast, wants to integrate into your platform.

+ +

Architecture and design

+ +

Introduction — Understanding Go kit key concepts

+ +

If you’re coming from Symfony (PHP), Rails (Ruby), Django (Python), + or one of the many popular MVC-style frameworks out there, + the first thing you should know is that Go kit is not an MVC framework. +Instead, Go kit services are laid out in three layers:

+ +
    +
  1. Transport layer
  2. +
  3. Endpoint layer
  4. +
  5. Service layer
  6. +
+ +

Requests enter the service at layer 1, flow down to layer 3, and responses take the reverse course.

+ +

This may be a bit of an adjustment, but once you grok the concepts, + you should see that the Go kit design is well-suited for modern software design: + both microservices and so-called + elegant + monoliths.

+ +

Transports — What are Go kit transports?

+ +

The transport domain is bound to concrete transports like HTTP or gRPC. +In a world where microservices may support one or more transports, + this is very powerful; you can support a legacy HTTP API and a newer RPC service, +all in a single microservice.

+ +

When implementing a REST-ish HTTP API, your routing is defined within a HTTP transport. +It’s most common to see routes defined in a HTTP Router function like this:

+ +
r.Methods("POST").Path("/profiles/").Handler(httptransport.NewServer(
+		e.PostProfileEndpoint,
+		decodePostProfileRequest,
+		encodeResponse,
+		options...,
+))
+
+ +

Endpoints — What are Go kit endpoints?

+ +

An endpoint is like an action/handler on a controller; it’s where safety and antifragile logic lives. +If you implement two transports (HTTP and gRPC), you might have two methods of sending requests to the same endpoint.

+ +

Services — What is a Go kit service?

+ +

Services are where all of the business logic is implemented. +A service usually glues together multiple endpoints. +In Go kit, services are typically modeled as interfaces, + and implementations of those interfaces contain the business logic. +Go kit services should strive to abide + the Clean Architecture or + the Hexagonal Architecture. +That is, the business logic should have no knowledge of endpoint- or especially transport-domain concepts: + your service shouldn’t know anything about HTTP headers, or gRPC error codes.

+ +

Middlewares — What are middlewares, in Go kit?

+ +

Go kit tries to enforce a strict separation of concerns through use of the middleware (or decorator) pattern. +Middlewares can wrap endpoints or services to add functionality, such as + logging, rate limiting, load balancing, or distributed tracing. +It’s common to chain multiple middlewares around an endpoint or service.

+ +

Design — How is a Go kit microservice modeled?

+ +

Go kit service diagram

+ +

Putting all these concepts together, we see that Go kit microservices are modeled like an onion, with many layers. +The layers can be grouped into our three domains. +The innermost service domain is where everything is based on your specific service definition, + and where all of the business logic is implemented. +The middle endpoint domain is where each method of your service is abstracted to the generic + endpoint.Endpoint, + and where safety and antifragile logic is implemented. +Finally, the outermost transport domain is where endpoints are bound + to concrete transports like HTTP or gRPC.

+ +

You implement the core business logic by defining an interface for your service and providing a concrete implementation. +Then, you write service middlewares to provide additional functionality, + like logging, analytics, instrumentation — anything that needs knowledge of your business domain.

+ +

Go kit provides endpoint and transport domain middlewares, + for functionality like rate limiting, circuit breaking, load balancing, + and distributed tracing — all of which are generally agnostic to your business domain.

+ +

In short, Go kit tries to enforce strict separation of concerns + through studious use of the middleware (or decorator) pattern.

+ +

Dependency Injection — Why is func main always so big?

+ +

Go kit encourages you to design your services as multiple interacting components, + including several single-purpose middlewares. +Experience has taught us that the most comprehensible, maintainable, and expressive method + of defining and wiring up the component graph in a microservice + is through an explicit and declarative composition in a large func main.

+ +

Inversion of control is a common feature of other frameworks, + implemented via Dependency Injection or Service Locator patterns. +But in Go kit, you should wire up your entire component graph in your func main. +This style reinforces two important virtues. +By strictly keeping component lifecycles in main, + you avoid leaning on global state as a shortcut, + which is critical for testability. +And if components are scoped to main, + the only way to provide them as dependencies to other components + is to pass them explicitly as parameters to constructors. +This keeps dependencies explicit, which stops a lot of technical debt before it starts.

+ +

As an example, let’s say we have the following components:

+ +
    +
  • Logger
  • +
  • TodoService, implementing the Service interface
  • +
  • LoggingMiddleware, implementing the Service interface, requiring Logger and concrete TodoService
  • +
  • Endpoints, requiring a Service interface
  • +
  • HTTP (transport), requiring Endpoints
  • +
+ +

Your func main should be wired up as follows:

+ +
logger := log.NewLogger(...)
+
+var service todo.Service    // interface
+service = todo.NewService() // concrete struct
+service = todo.NewLoggingMiddleware(logger)(service)
+
+endpoints := todo.NewEndpoints(service)
+transport := todo.NewHTTPTransport(endpoints)
+
+ +

At the cost of having a potentially large func main, composition is explicit and declarative. +For more general Go design tips, see + Go best practices, six years in.

+ +

Deployment — How should I deploy Go kit services?

+ +

It’s totally up to you. +You can build a static binary, scp it to your server, + and use a supervisor like runit. +Or you can use a tool like Packer to create an AMI, + and deploy it into an EC2 autoscaling group. +Or you can package your service up into a container, ship it to a registry, + and deploy it onto a cloud-native platform like Kubernetes.

+ +

Go kit is mostly concerned with good software engineering within your service; + it tries to integrate well with any kind of platform or infrastructure.

+ +

Errors — How should I encode errors?

+ +

Your service methods will probably return errors. +You have two options for encoding them in your endpoints. +You can include an error field in your response struct, and return your business domain errors there. +Or, you can choose to return your business domain errors in the endpoint error return value.

+ +

Both methods can be made to work. +But errors returned directly by endpoints are recognized by middlewares that check for failures, + like circuit breakers. +It’s unlikely that a business-domain error from your service + should cause a circuit breaker to trip in a client. +So, it’s likely that you want to encode errors in your response struct.

+ +

addsvc + contains examples of both methods.

+ +

More specific topics

+ +

Transports — Which transports are supported?

+ +

Go kit ships with support for HTTP, + gRPC, + Thrift, and + net/rpc. +It’s straightforward to add support for new transports; + just file an issue + if you need something that isn’t already offered.

+ +

Service Discovery — Which service discovery systems are supported?

+ +

Go kit ships with support for + Consul, + etcd, + ZooKeeper, + and DNS SRV records.

+ +

Service Discovery — Do I even need to use package sd?

+ +

It depends on your infrastructure.

+ +

Some platforms, like Kubernetes, take care of registering services instances + and making them discoverable automatically, via + platform-specific concepts. +So, if you run on Kubernetes, you probably don’t need to use package sd.

+ +

But if you’re putting together your own infrastructure or platform with open-source components, + then your services will likely need to register themselves with the service registry. +Or if you have reached a scale where internal load balancers become a bottleneck, + you may need to have your services subscribe to the system of record directly, + and maintain their own connection pools. +(This is the client-side discovery pattern.) +In these situations, package sd will be useful.

+ +

Observability — Which monitoring systems are supported?

+ +

Go kit ships with support for modern monitoring systems like + Prometheus and InfluxDB, + as well as more traditional systems like + statsd, + Graphite, and + expvar, + and hosted systems like + Datadog via DogStatsD + and Circonus.

+ +

Observability — Which monitoring system should I use?

+ +

Prometheus.

+ +

Logging — Why is package log so different?

+ +

Experience has taught us that a good logging package should be based on a minimal interface + and should enforce so-called structured logging. +Based on these invariants, Go kit’s package log has evolved through + many design iterations, extensive benchmarking, and plenty of real-world use + to arrive at its current state.

+ +

With a well-defined core contract, + ancillary concerns like levels, colorized output, and synchronization + can be easily bolted on using the familiar decorator pattern. +It may feel a little unfamiliar at first, + but we believe package log strikes the ideal balance between + usability, maintainability, and performance.

+ +

For more details on the evolution of package log, see issues and PRs + 63, + 76, + 131, + 157, and + 252. +For more on logging philosophy, see + The Hunt for a Logger Interface, + Let’s talk about logging, and + Logging v. instrumentation.

+ +

Logging — How should I aggregate my logs?

+ +

Collecting, shipping, and aggregating logs is the responsibility of the platform, not individual services. +So, just make sure you’re writing logs to stdout/stderr, and let another component handle the rest.

+ +

Panics — How should my service handle panics?

+ +

Panics indicate programmer error and signal corrputed program state. +They shouldn’t be treated as errors, or ersatz exceptions. +In general, you shouldn’t explicitly recover from panics: + you should allow them to crash your program or handler goroutine, + and allow your service to return a broken response to the calling client. +Your observability stack should alert you to these problems as they occur, + and you should fix them as soon as possible.

+ +

With that said, if you have the need to handle exceptions, the best strategy + is probably to wrap the concrete transport with a transport-specific middleware + that performs a recover. +For example, with HTTP:

+ +
var h http.Handler
+h = httptransport.NewServer(...)
+h = newRecoveringMiddleware(h, ...)
+// use h normally
+
+ +

Persistence — How should I work with databases and data stores?

+ +

Accessing databases is typically part of the core business logic. +Therefore, it probably makes sense to include an e.g. *sql.DB pointer in the concrete implementation of your service.

+ +
type MyService struct {
+	db     *sql.DB
+	value  string
+	logger log.Logger
+}
+
+func NewService(db *sql.DB, value string, logger log.Logger) *MyService {
+	return &MyService{
+		db:     db,
+		value:  value,
+		logger: logger,
+	}
+}
+
+ +

Even better: consider defining an interface to model your persistence operations. +The interface will deal in business domain objects, and have an implementation that wraps the database handle. +For example, consider a simple persistence layer for user profiles.

+ +
type Store interface {
+	Insert(Profile) error
+	Select(id string) (Profile, error)
+	Delete(id string) error
+}
+
+type databaseStore struct{ db *sql.DB }
+
+func (s *databaseStore) Insert(p Profile) error            { /* ... */ }
+func (s *databaseStore) Select(id string) (Profile, error) { /* ... */ }
+func (s *databaseStore) Delete(id string) error            { /* ... */ }
+
+ +

In this case, you’d include a Store, rather than a *sql.DB, in your concrete implementation.

+
+ +
+
+ + +
+ + + + \ No newline at end of file diff --git a/faq/onion.png b/faq/onion.png new file mode 100755 index 0000000000000000000000000000000000000000..a59d8eaef29f88dcc704d0e3865da7a839bfef5d GIT binary patch literal 25832 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 + + +Go kit - A toolkit for microservices + + + + + + + +
+
+ + +
+

Go kit

+

A toolkit for microservices

+
+
+ +
+
+

+ Home    + Examples    + FAQ    + Blog   ·   + GitHub    + GoDoc    + Slack    + Mailing list +

+
+ +
+ +
+
+

🚀  Adopt Go in your organization.

+

+ Go is a lovely little language that's perfectly suited to writing microservices. + Go kit fills in the gaps left by the otherwise excellent standard library, + giving your team the confidence to adopt Go throughout your stack. +

+
+
+

🔍  Focus on your business logic.

+

+ Adopting microservices means building a distributed system, and that comes with a lot of challenges. + Go kit provides guidance and solutions for most of the common operational and infrastructural concerns. + Allowing you to focus your mental energy on your business. +

+
+
+

😌  Few opinions, lightly held.

+

+ You know your domain and context better than anyone. + Go kit is lightly opinionated, and was designed for interoperability from day one. + Use the databases, components, platform, and architecture that works best for you. +

+
+
+ +
+ +
+
+

👥  Similar to...

+

+ Finagle • + Dropwizard • + Spring Boot • + Netflix + Eureka · + Ribbon · + Hystrix • + Nameko +

+
+
+

⚽️  Plays nice with...

+

+ Kubernetes • + Mesos • + Marathon • + DC/OS • + Docker • + Docker Swarm • + Heroku +

+
+
+ +
+ +
+
+

Why Go?

+

+ Go is designed from first principles to advance the practice of software engineering. + It's easy to learn, easy to master, and — most importantly — easy to maintain, by large and dynamic teams of engineers. + And with highly-efficient concurrency, an expansive standard library, and a steadily-improving runtime, + it's practically the perfect language for writing microservices. +

+
+
+

Why microservices?

+

+ Almost all of contemporary software engineering is focused on the singular goal of improving time-to-market. + Microservices are an evolution of the service-oriented architecture pattern that elegantly eliminate organizational friction, + giving your engineers and teams the autonomy they need to continuously ship, iterate, and improve. +

+
+
+

Why Go kit?

+

+ Go is a great general-purpose language, but microservices require a certain amount of specialized support. + RPC safety, system observability, infrastructure integration, even program design — + Go kit fills in the gaps left by the standard library, + and makes Go a first-class language for writing microservices in any organization. +

+
+
+ +
+
+ + +
+ + + + From ff321e0c0d3620a280e732653b7bd3ab0b7a0867 Mon Sep 17 00:00:00 2001 From: Peter Bourgon Date: Wed, 24 Feb 2021 17:35:41 +0100 Subject: [PATCH 2/8] rm CNAME to fix website --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index 9eec7a4..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -gokit.io From 374b7afd49c20ac379b368c0ac70290515fee03c Mon Sep 17 00:00:00 2001 From: Peter Bourgon Date: Wed, 24 Feb 2021 17:35:59 +0100 Subject: [PATCH 3/8] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..e69a060 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +gokit.io \ No newline at end of file From 0bb05dea37fb0dabce033a09ed5cbb11c438012f Mon Sep 17 00:00:00 2001 From: Peter Bourgon Date: Fri, 9 Jul 2021 03:45:36 +0200 Subject: [PATCH 4/8] .nojekyll --- .nojekyll | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .nojekyll diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 From 5c4e3e98ac6218ef66412f0f503db9c9d1f71284 Mon Sep 17 00:00:00 2001 From: peterbourgon Date: Sat, 17 Jul 2021 16:15:21 +0000 Subject: [PATCH 5/8] Deploying site --- examples/index.html | 8 ++++---- examples/stringsvc.html | 12 ++++++------ faq/index.html | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/index.html b/examples/index.html index fca3b32..b7c12c1 100755 --- a/examples/index.html +++ b/examples/index.html @@ -48,23 +48,23 @@

Examples

writing a service from first principles. It can help you understand the decisions that went into Go kit’s design.

-
  • addsvc +

  • addsvc is the original example service. It exposes a set of operations over all supported transports. It’s fully logged, instrumented, and uses distributed tracing. It also demonstrates how to create and use client packages. It demonstrates almost all of Go kit’s features.

  • -
  • profilesvc +

  • profilesvc demonstrates how to use Go kit to write a microservice with a REST-ish API. It uses net/http and the excellent Gorilla web toolkit.

  • -
  • shipping +

  • shipping is a complete, “real-world” application composed of multiple microservices, based on Domain Driven Design principles.

  • -
  • apigateway +

  • apigateway demonstrates how to implement the API gateway pattern backed by a Consul service discovery system.

  • diff --git a/examples/stringsvc.html b/examples/stringsvc.html index 5f5fd0f..144aa51 100755 --- a/examples/stringsvc.html +++ b/examples/stringsvc.html @@ -240,7 +240,7 @@

    Transports

    stringsvc1

    -

    The complete service so far is stringsvc1.

    +

    The complete service so far is stringsvc1.

    $ go get github.com/go-kit/kit/examples/stringsvc1
     $ stringsvc1
    @@ -259,7 +259,7 @@ 

    Middlewares

    Separation of concerns

    Separating each layer of the call graph into individual files makes a go-kit project easier to read as you increase the number of service endpoints. -Our first example stringsvc1 had all of these layers in a single main file. +Our first example stringsvc1 had all of these layers in a single main file. Before we add more complexity, let’s separate our code into the following files and leave all remaining code in main.go

    Place your services into a service.go file with the following functions and types.

    @@ -526,7 +526,7 @@

    Application instrumentation

    stringsvc2

    -

    The complete service so far is stringsvc2.

    +

    The complete service so far is stringsvc2.

    $ go get github.com/go-kit/kit/examples/stringsvc2
     $ stringsvc2
    @@ -700,7 +700,7 @@ 

    Service discovery and load balanci

    stringsvc3

    -

    The complete service so far is stringsvc3.

    +

    The complete service so far is stringsvc3.

    $ go get github.com/go-kit/kit/examples/stringsvc3
     $ stringsvc3 -listen=:8001 &
    @@ -763,8 +763,8 @@ 

    Creating a client package

    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 addsvc/cmd/addcli - or package profilesvc/client +See addsvc/cmd/addcli + or package profilesvc/client for examples.

    diff --git a/faq/index.html b/faq/index.html index 8cfa689..e700dee 100755 --- a/faq/index.html +++ b/faq/index.html @@ -284,7 +284,7 @@

    Errors — How should I enc should cause a circuit breaker to trip in a client. So, it’s likely that you want to encode errors in your response struct.

    -

    addsvc +

    addsvc contains examples of both methods.

    More specific topics

    @@ -373,7 +373,7 @@

    Logging — How should

    Panics — How should my service handle panics?

    -

    Panics indicate programmer error and signal corrputed program state. +

    Panics indicate programmer error and signal corrupted program state. They shouldn’t be treated as errors, or ersatz exceptions. In general, you shouldn’t explicitly recover from panics: you should allow them to crash your program or handler goroutine, From 0f8e1bb25b216a1b62a14eeb7cc794b4eba4b2c9 Mon Sep 17 00:00:00 2001 From: peterbourgon Date: Sat, 17 Jul 2021 16:17:26 +0000 Subject: [PATCH 6/8] Deploying site --- 404.html | 2 +- blog/index.html | 2 +- examples/index.html | 2 +- examples/stringsvc.html | 2 +- faq/index.html | 2 +- index.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/404.html b/404.html index ce4ff9d..1395bbd 100755 --- a/404.html +++ b/404.html @@ -48,7 +48,7 @@

    Page not found