Skip to content

Instantly share code, notes, and snippets.

@posener
Last active February 23, 2024 21:35
Show Gist options
  • Star 84 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save posener/330c2b08aaefdea6f900ff0543773b2e to your computer and use it in GitHub Desktop.
Save posener/330c2b08aaefdea6f900ff0543773b2e to your computer and use it in GitHub Desktop.
Why I Recommend to Avoid Using the go-kit Library

Why I Recommend to Avoid Using the go-kit Library

There is a trending 'microservice' library called go-kit. I've been using the go-kit library for a while now. The library provide a lot of convenience integrations that you might need in your service: with service discovery with Consul, distributed tracing with Zipkin, for example, and nice logic utilities such as round robin client side load balancing, and circuit breaking. It is also providing a way to implement communication layer, with support of RPC and REST.

The toolchain that the library provide is very nice, and it does try to solve a fundamental problem we have in the Go community: missing ecosystem to write microservices. I do like the approach of the package: 'take what you want', you can write your service and use certain tools given in this libary, and not use all of it. But, I recommend not to use the library server implementation for your REST microservices. (I think this recommendation will also apply for the RPC part of the library, but I don't have any experience with that).

You could read why, and I would love to hear your opinion about it, if you agree, disagree, and why.

1. Too verbose

Usually microservices expose APIs: external, which are exposed, and internal for inter-service communication.

When using the go-kit, it was very noticeable that the overhead of adding API you your service is very high. You need to add a lot of code, which is mostly copy-paste of other APIs and there are too many places to make mistakes.

To add a single simple API, you should add:

a. Function in the interface (make sense) b. Implementation (make sense) c. Endpoint factory function d. Transport function e. Request encoder, request decoder, response encoder and response decoder. f. Add the endpoint to the server g. Add the endpoint to the client.

For a quick impression of what I mean by "a lot of code" take a look at the simple example in the go-kit website

2. Hard to understand (at least, for me)

I think the main reason for this verbosity is the layer separation for the business logic, endpoint and transport, which is nice, and benefits in nice abstractions for the client-side load balancing, circuit breaking, tracing, etc.

But, it is hard to understand.

If you are using the go-kit as REST service library, I recommend to know the following ServerHTTP function by heart: Only then you truly understand how your service is expected to behave.

3. The interface{} API

When using the go-kit, your endpoints get an interface{} object and return an interface{}, error tuple. You need to explicitly write the conversion to your implementation function. Actually, your endpoint factory will almost be a copy-paste of the following function:

func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
  return func(ctx context.Context, request interface{}) (interface{}, error) {
    req := request.(myRequest)
    v, err := svc.Function(req.A, req.B)
    if err != nil {
      return myResponse{v, err.Error()}, nil
    }
    return myResponse{v, ""}, nil
  }
}

To summarize

This library is really nice, and with really good intensions. It looks very popular, but can't really understand how much production ready it is, and how many actual use cases there are out there. I personally don't like the basic concepts of it, specially because of the interface{} and the verbosity issues discussed above, and I find them not easy to use, and not intuitive.

You couldn't use the nice features of the library of load-balancing, circuit breaking, and tracing, if you are not using the endpoint APIs, but if you look at the code, it is not that big. You could use the actual code or find other libraries that provide them as middleware for the standard http client/server.

Thanks for reading,

If you find this interesting, please, ✎ comment below or ★ star above ☺

For more stuff: gist.github.com/posener.

Cheers, Eyal

@eldad87
Copy link

eldad87 commented Apr 6, 2021

I wrote my own opinionated microservice example:
https://github.com/eldad87/go-boilerplate

My approach is simple, define the service HTTP & gRPC transport layers using protobuf and work your way top to bottom.
The foundations of the service are rock-solid & decoupled, which makes every component replaceable.
In addition, the HTTP layer is been document seemly using Swagger.

@ricardodovalle
Copy link

What would you recommend for microservices? @posener

@NishanthSpShetty
Copy link

anyone tried using https://github.com/go-coldbrew ?

@nyelnizy
Copy link

Why not use kubernettes with vanilla go??

@KacperPerschke
Copy link

KacperPerschke commented Jan 1, 2024

I came across prometheus/common/promlog based on go-kit/kit/log. I suggested replacing promlog with log/slog.

Usage comparison:

  1. simple case
    • promlog → level.Debug(c.logger).Log("msg", "Fetching unavailable mirrors")
    • slog → c.logger.Debug("Fetching unavailable mirrors")
  2. the case is a bit complicated
    • promlog → level.Warn(c.logger).Log("msg", "The endpoint does not exist", "endpoint", fullPath, "err", fmt.Sprintf("%v", apiErrors.Errors), "status", 404)
    • slog → I deliberately wrote it on multiple lines for readability:
c.logger.Warn(
        "The endpoint does not exist",
        "endpoint", fullPath, 
        "err", fmt.Sprintf("%v", apiErrors.Errors),
        "status", 404,
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment