2017-03-31 18:01:58 +02:00
|
|
|
package addsvc
|
|
|
|
|
|
|
|
// This file contains the Service definition, and a basic service
|
|
|
|
// implementation. It also includes service middlewares.
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"time"
|
|
|
|
|
2017-05-18 18:54:23 +02:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
|
2017-03-31 18:01:58 +02:00
|
|
|
"github.com/go-kit/kit/log"
|
|
|
|
"github.com/go-kit/kit/metrics"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Service describes a service that adds things together.
|
|
|
|
type Service interface {
|
|
|
|
Sum(ctx context.Context, a, b int) (int, error)
|
|
|
|
Concat(ctx context.Context, a, b string) (string, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Business-domain errors like these may be served in two ways: returned
|
|
|
|
// directly by endpoints, or bundled into the response struct. Both methods can
|
|
|
|
// be made to work, but errors returned directly by endpoints are counted by
|
|
|
|
// middlewares that check errors, like circuit breakers.
|
|
|
|
//
|
|
|
|
// If you don't want that behavior -- and you probably don't -- then it's better
|
|
|
|
// to bundle errors into the response struct.
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrTwoZeroes is an arbitrary business rule for the Add method.
|
|
|
|
ErrTwoZeroes = errors.New("can't sum two zeroes")
|
|
|
|
|
|
|
|
// ErrIntOverflow protects the Add method. We've decided that this error
|
|
|
|
// indicates a misbehaving service and should count against e.g. circuit
|
|
|
|
// breakers. So, we return it directly in endpoints, to illustrate the
|
|
|
|
// difference. In a real service, this probably wouldn't be the case.
|
|
|
|
ErrIntOverflow = errors.New("integer overflow")
|
|
|
|
|
|
|
|
// ErrMaxSizeExceeded protects the Concat method.
|
|
|
|
ErrMaxSizeExceeded = errors.New("result exceeds maximum size")
|
|
|
|
)
|
|
|
|
|
|
|
|
// These annoying helper functions are required to translate Go error types to
|
|
|
|
// and from strings, which is the type we use in our IDLs to represent errors.
|
|
|
|
// There is special casing to treat empty strings as nil errors.
|
|
|
|
|
|
|
|
func str2err(s string) error {
|
|
|
|
if s == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return errors.New(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func err2str(err error) string {
|
|
|
|
if err == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return err.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewBasicService returns a naïve, stateless implementation of Service.
|
|
|
|
func NewBasicService() Service {
|
|
|
|
return basicService{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type basicService struct{}
|
|
|
|
|
|
|
|
const (
|
|
|
|
intMax = 1<<31 - 1
|
|
|
|
intMin = -(intMax + 1)
|
|
|
|
maxLen = 102400
|
|
|
|
)
|
|
|
|
|
|
|
|
// Sum implements Service.
|
|
|
|
func (s basicService) Sum(_ context.Context, a, b int) (int, error) {
|
|
|
|
if a == 0 && b == 0 {
|
|
|
|
return 0, ErrTwoZeroes
|
|
|
|
}
|
|
|
|
if (b > 0 && a > (intMax-b)) || (b < 0 && a < (intMin-b)) {
|
|
|
|
return 0, ErrIntOverflow
|
|
|
|
}
|
|
|
|
return a + b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Concat implements Service.
|
|
|
|
func (s basicService) Concat(_ context.Context, a, b string) (string, error) {
|
|
|
|
if len(a)+len(b) > maxLen {
|
|
|
|
return "", ErrMaxSizeExceeded
|
|
|
|
}
|
|
|
|
return a + b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Middleware describes a service (as opposed to endpoint) middleware.
|
|
|
|
type Middleware func(Service) Service
|
|
|
|
|
|
|
|
// ServiceLoggingMiddleware returns a service middleware that logs the
|
|
|
|
// parameters and result of each method invocation.
|
|
|
|
func ServiceLoggingMiddleware(logger log.Logger) Middleware {
|
|
|
|
return func(next Service) Service {
|
|
|
|
return serviceLoggingMiddleware{
|
|
|
|
logger: logger,
|
|
|
|
next: next,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type serviceLoggingMiddleware struct {
|
|
|
|
logger log.Logger
|
|
|
|
next Service
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mw serviceLoggingMiddleware) Sum(ctx context.Context, a, b int) (v int, err error) {
|
|
|
|
defer func(begin time.Time) {
|
|
|
|
mw.logger.Log(
|
|
|
|
"method", "Sum",
|
|
|
|
"a", a, "b", b, "result", v, "error", err,
|
|
|
|
"took", time.Since(begin),
|
|
|
|
)
|
|
|
|
}(time.Now())
|
|
|
|
return mw.next.Sum(ctx, a, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mw serviceLoggingMiddleware) Concat(ctx context.Context, a, b string) (v string, err error) {
|
|
|
|
defer func(begin time.Time) {
|
|
|
|
mw.logger.Log(
|
|
|
|
"method", "Concat",
|
|
|
|
"a", a, "b", b, "result", v, "error", err,
|
|
|
|
"took", time.Since(begin),
|
|
|
|
)
|
|
|
|
}(time.Now())
|
|
|
|
return mw.next.Concat(ctx, a, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServiceInstrumentingMiddleware returns a service middleware that instruments
|
|
|
|
// the number of integers summed and characters concatenated over the lifetime of
|
|
|
|
// the service.
|
|
|
|
func ServiceInstrumentingMiddleware(ints, chars metrics.Counter) Middleware {
|
|
|
|
return func(next Service) Service {
|
|
|
|
return serviceInstrumentingMiddleware{
|
|
|
|
ints: ints,
|
|
|
|
chars: chars,
|
|
|
|
next: next,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type serviceInstrumentingMiddleware struct {
|
|
|
|
ints metrics.Counter
|
|
|
|
chars metrics.Counter
|
|
|
|
next Service
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mw serviceInstrumentingMiddleware) Sum(ctx context.Context, a, b int) (int, error) {
|
|
|
|
v, err := mw.next.Sum(ctx, a, b)
|
|
|
|
mw.ints.Add(float64(v))
|
|
|
|
return v, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mw serviceInstrumentingMiddleware) Concat(ctx context.Context, a, b string) (string, error) {
|
|
|
|
v, err := mw.next.Concat(ctx, a, b)
|
|
|
|
mw.chars.Add(float64(len(v)))
|
|
|
|
return v, err
|
|
|
|
}
|