go get github.com/objenious/kitty
Kitty is a slightly opinionated framework based on go-kit. It's goal is to ease development of microservices deployed on Kubernetes (or any similar orchestration platform).
Kitty has an opinion on:
- transports: HTTP only (additional transports can be added as long as they implement kitty.Transport, a Google Pub/Sub transport is available as a separate package),
- errors: an error may be Retryable (e.g. 5XX status codes) or not (e.g. 4XX status codes),
- status codes: unless specified, request decoding errors will generate 400 HTTP status codes.
Kitty has no opinion on:
- logging: no logs are generated by default, you can plug your logger and it will get additional context,
- packages: kitty only imports go-kit and the standard library,
- routers: you can use any router (a Gorilla Mux implementation is available in a sub-package, other routers can easily be plugged),
- encoding: use whatever encoding you want (JSON, messagepack, protobuf, ...),
- monitoring, metrics and tracing: use Istio, a sidecar process or a middleware.
Kitty includes 2 sub-packages:
- backoff: Retryable-aware exponential backoff (only Retryable errors trigger retries),
- circuitbreaker: Retryable-aware circuit breaker (only Retryable errors trigger the circuit breaker).
Server-side
t := kitty.NewHTTPTransport(kitty.Config{HTTPPort: 8081}).
Router(gorilla.Router()).
Endpoint("POST", "/foo", Foo, kitty.Decoder(decodeFooRequest)).
Endpoint("GET", "/bar", Bar)
kitty.NewServer(t).Run(ctx)
// Foo is a go-kit Endpoint
func Foo(ctx context.Context, request interface{}) (interface{}, error) {
fr := request.(fooRequest)
return fooResponse{Message: fmt.Sprintf("Good morning %s !", fr.Name)}, nil
}
// decodeFooRequest
func decodeFooRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var request fooRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
Client-side (with circuit breaker & exponential backoff)
u, err := url.Parse("http://example.com/foo")
e := kitty.NewClient(
"POST",
u,
kithttp.EncodeJSONRequest,
decodeFooResponse
).Endpoint()
cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{Name: "foo"})
e = kittycircuitbreaker.NewCircuitBreaker(cb)(e)
bo := backoff.NewExponentialBackOff()
e = kittybackoff.NewBackoff(bo)(e)
kitty.NewServer(t).
// Log as JSON
Logger(log.NewJSONLogger(log.NewSyncWriter(os.Stdout))).
// Add path and method to all log lines
LogContext("http-path", "http-method").
// Log request only if an error occurred
Middlewares(kitty.LogEndpoint(kitty.LogErrors))
TBD
Using github.com/heptiolabs/healthcheck:
health := healthcheck.NewHandler()
health.AddLivenessCheck("goroutine-threshold", healthcheck.GoroutineCountCheck(100))
health.AddReadinessCheck("database", healthcheck.DatabasePingCheck(db, 1*time.Second))
t := kitty.NewTransport(kitty.Config{}).Liveness(health.LiveEndpoint).Readiness(health.ReadyEndpoint)
https://github.com/objenious/kitty-gcp adds a Google Pub/Sub transport to kitty:
import "github.com/objenious/kitty-gcp/pubsub"
tr := pubsub.NewTransport(ctx, "project-id").
Endpoint(subscriptionName, endpoint, Decoder(decodeFunc))
err := kitty.NewServer(tr).Run(ctx)
Go > 1.11
Contributions are welcome, as long as :
- unit tests & comments are included,
- no external package is added to the top-level package (implementations can be added as sub-packages).
kitty is heavily inspired by gizmo/kit (https://godoc.org/github.com/NYTimes/gizmo/server/kit), with a different approach to server setup and without the gRPC clutter.
MIT - See LICENSE file