Moved to google.golang.org/genproto/googleapis/api/annotations

Fixes #52
This commit is contained in:
Valerio Gheri
2017-03-31 18:01:58 +02:00
parent 024c5a4e4e
commit c40779224f
2037 changed files with 831329 additions and 1854 deletions

View File

@@ -0,0 +1,4 @@
# profilesvc
This example demonstrates how to use Go kit to implement a REST-y HTTP service.
It leverages the excellent [gorilla mux package](https://github.com/gorilla/mux) for routing.

View File

@@ -0,0 +1,120 @@
// Package client provides a profilesvc client based on a predefined Consul
// service name and relevant tags. Users must only provide the address of a
// Consul server.
package client
import (
"io"
"time"
consulapi "github.com/hashicorp/consul/api"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/profilesvc"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/sd"
"github.com/go-kit/kit/sd/consul"
"github.com/go-kit/kit/sd/lb"
)
// New returns a service that's load-balanced over instances of profilesvc found
// in the provided Consul server. The mechanism of looking up profilesvc
// instances in Consul is hard-coded into the client.
func New(consulAddr string, logger log.Logger) (profilesvc.Service, error) {
apiclient, err := consulapi.NewClient(&consulapi.Config{
Address: consulAddr,
})
if err != nil {
return nil, err
}
// As the implementer of profilesvc, we declare and enforce these
// parameters for all of the profilesvc consumers.
var (
consulService = "profilesvc"
consulTags = []string{"prod"}
passingOnly = true
retryMax = 3
retryTimeout = 500 * time.Millisecond
)
var (
sdclient = consul.NewClient(apiclient)
endpoints profilesvc.Endpoints
)
{
factory := factoryFor(profilesvc.MakePostProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.PostProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeGetProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.GetProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakePutProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.PutProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakePatchProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.PatchProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeDeleteProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.DeleteProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeGetAddressesEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.GetAddressesEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeGetAddressEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.GetAddressEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakePostAddressEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.PostAddressEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeDeleteAddressEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.DeleteAddressEndpoint = retry
}
return endpoints, nil
}
func factoryFor(makeEndpoint func(profilesvc.Service) endpoint.Endpoint) sd.Factory {
return func(instance string) (endpoint.Endpoint, io.Closer, error) {
service, err := profilesvc.MakeClientEndpoints(instance)
if err != nil {
return nil, nil, err
}
return makeEndpoint(service), nil, nil
}
}

View File

@@ -0,0 +1,52 @@
package main
import (
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/go-kit/kit/examples/profilesvc"
"github.com/go-kit/kit/log"
)
func main() {
var (
httpAddr = flag.String("http.addr", ":8080", "HTTP listen address")
)
flag.Parse()
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
}
var s profilesvc.Service
{
s = profilesvc.NewInmemService()
s = profilesvc.LoggingMiddleware(logger)(s)
}
var h http.Handler
{
h = profilesvc.MakeHTTPHandler(s, log.With(logger, "component", "HTTP"))
}
errs := make(chan error)
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
errs <- fmt.Errorf("%s", <-c)
}()
go func() {
logger.Log("transport", "HTTP", "addr", *httpAddr)
errs <- http.ListenAndServe(*httpAddr, h)
}()
logger.Log("exit", <-errs)
}

View File

@@ -0,0 +1,387 @@
package profilesvc
import (
"context"
"net/url"
"strings"
"github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
)
// Endpoints collects all of the endpoints that compose a profile service. It's
// meant to be used as a helper struct, to collect all of the endpoints into a
// single parameter.
//
// In a server, it's useful for functions that need to operate on a per-endpoint
// basis. For example, you might pass an Endpoints to a function that produces
// an http.Handler, with each method (endpoint) wired up to a specific path. (It
// is probably a mistake in design to invoke the Service methods on the
// Endpoints struct in a server.)
//
// In a client, it's useful to collect individually constructed endpoints into a
// single type that implements the Service interface. For example, you might
// construct individual endpoints using transport/http.NewClient, combine them
// into an Endpoints, and return it to the caller as a Service.
type Endpoints struct {
PostProfileEndpoint endpoint.Endpoint
GetProfileEndpoint endpoint.Endpoint
PutProfileEndpoint endpoint.Endpoint
PatchProfileEndpoint endpoint.Endpoint
DeleteProfileEndpoint endpoint.Endpoint
GetAddressesEndpoint endpoint.Endpoint
GetAddressEndpoint endpoint.Endpoint
PostAddressEndpoint endpoint.Endpoint
DeleteAddressEndpoint endpoint.Endpoint
}
// MakeServerEndpoints returns an Endpoints struct where each endpoint invokes
// the corresponding method on the provided service. Useful in a profilesvc
// server.
func MakeServerEndpoints(s Service) Endpoints {
return Endpoints{
PostProfileEndpoint: MakePostProfileEndpoint(s),
GetProfileEndpoint: MakeGetProfileEndpoint(s),
PutProfileEndpoint: MakePutProfileEndpoint(s),
PatchProfileEndpoint: MakePatchProfileEndpoint(s),
DeleteProfileEndpoint: MakeDeleteProfileEndpoint(s),
GetAddressesEndpoint: MakeGetAddressesEndpoint(s),
GetAddressEndpoint: MakeGetAddressEndpoint(s),
PostAddressEndpoint: MakePostAddressEndpoint(s),
DeleteAddressEndpoint: MakeDeleteAddressEndpoint(s),
}
}
// MakeClientEndpoints returns an Endpoints struct where each endpoint invokes
// the corresponding method on the remote instance, via a transport/http.Client.
// Useful in a profilesvc client.
func MakeClientEndpoints(instance string) (Endpoints, error) {
if !strings.HasPrefix(instance, "http") {
instance = "http://" + instance
}
tgt, err := url.Parse(instance)
if err != nil {
return Endpoints{}, err
}
tgt.Path = ""
options := []httptransport.ClientOption{}
// Note that the request encoders need to modify the request URL, changing
// the path and method. That's fine: we simply need to provide specific
// encoders for each endpoint.
return Endpoints{
PostProfileEndpoint: httptransport.NewClient("POST", tgt, encodePostProfileRequest, decodePostProfileResponse, options...).Endpoint(),
GetProfileEndpoint: httptransport.NewClient("GET", tgt, encodeGetProfileRequest, decodeGetProfileResponse, options...).Endpoint(),
PutProfileEndpoint: httptransport.NewClient("PUT", tgt, encodePutProfileRequest, decodePutProfileResponse, options...).Endpoint(),
PatchProfileEndpoint: httptransport.NewClient("PATCH", tgt, encodePatchProfileRequest, decodePatchProfileResponse, options...).Endpoint(),
DeleteProfileEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteProfileRequest, decodeDeleteProfileResponse, options...).Endpoint(),
GetAddressesEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressesRequest, decodeGetAddressesResponse, options...).Endpoint(),
GetAddressEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressRequest, decodeGetAddressResponse, options...).Endpoint(),
PostAddressEndpoint: httptransport.NewClient("POST", tgt, encodePostAddressRequest, decodePostAddressResponse, options...).Endpoint(),
DeleteAddressEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteAddressRequest, decodeDeleteAddressResponse, options...).Endpoint(),
}, nil
}
// PostProfile implements Service. Primarily useful in a client.
func (e Endpoints) PostProfile(ctx context.Context, p Profile) error {
request := postProfileRequest{Profile: p}
response, err := e.PostProfileEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(postProfileResponse)
return resp.Err
}
// GetProfile implements Service. Primarily useful in a client.
func (e Endpoints) GetProfile(ctx context.Context, id string) (Profile, error) {
request := getProfileRequest{ID: id}
response, err := e.GetProfileEndpoint(ctx, request)
if err != nil {
return Profile{}, err
}
resp := response.(getProfileResponse)
return resp.Profile, resp.Err
}
// PutProfile implements Service. Primarily useful in a client.
func (e Endpoints) PutProfile(ctx context.Context, id string, p Profile) error {
request := putProfileRequest{ID: id, Profile: p}
response, err := e.PutProfileEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(putProfileResponse)
return resp.Err
}
// PatchProfile implements Service. Primarily useful in a client.
func (e Endpoints) PatchProfile(ctx context.Context, id string, p Profile) error {
request := patchProfileRequest{ID: id, Profile: p}
response, err := e.PatchProfileEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(patchProfileResponse)
return resp.Err
}
// DeleteProfile implements Service. Primarily useful in a client.
func (e Endpoints) DeleteProfile(ctx context.Context, id string) error {
request := deleteProfileRequest{ID: id}
response, err := e.DeleteProfileEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(deleteProfileResponse)
return resp.Err
}
// GetAddresses implements Service. Primarily useful in a client.
func (e Endpoints) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
request := getAddressesRequest{ProfileID: profileID}
response, err := e.GetAddressesEndpoint(ctx, request)
if err != nil {
return nil, err
}
resp := response.(getAddressesResponse)
return resp.Addresses, resp.Err
}
// GetAddress implements Service. Primarily useful in a client.
func (e Endpoints) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
request := getAddressRequest{ProfileID: profileID, AddressID: addressID}
response, err := e.GetAddressEndpoint(ctx, request)
if err != nil {
return Address{}, err
}
resp := response.(getAddressResponse)
return resp.Address, resp.Err
}
// PostAddress implements Service. Primarily useful in a client.
func (e Endpoints) PostAddress(ctx context.Context, profileID string, a Address) error {
request := postAddressRequest{ProfileID: profileID, Address: a}
response, err := e.PostAddressEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(postAddressResponse)
return resp.Err
}
// DeleteAddress implements Service. Primarily useful in a client.
func (e Endpoints) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
request := deleteAddressRequest{ProfileID: profileID, AddressID: addressID}
response, err := e.DeleteAddressEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(deleteAddressResponse)
return resp.Err
}
// MakePostProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakePostProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(postProfileRequest)
e := s.PostProfile(ctx, req.Profile)
return postProfileResponse{Err: e}, nil
}
}
// MakeGetProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeGetProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(getProfileRequest)
p, e := s.GetProfile(ctx, req.ID)
return getProfileResponse{Profile: p, Err: e}, nil
}
}
// MakePutProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakePutProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(putProfileRequest)
e := s.PutProfile(ctx, req.ID, req.Profile)
return putProfileResponse{Err: e}, nil
}
}
// MakePatchProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakePatchProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(patchProfileRequest)
e := s.PatchProfile(ctx, req.ID, req.Profile)
return patchProfileResponse{Err: e}, nil
}
}
// MakeDeleteProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeDeleteProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(deleteProfileRequest)
e := s.DeleteProfile(ctx, req.ID)
return deleteProfileResponse{Err: e}, nil
}
}
// MakeGetAddressesEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeGetAddressesEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(getAddressesRequest)
a, e := s.GetAddresses(ctx, req.ProfileID)
return getAddressesResponse{Addresses: a, Err: e}, nil
}
}
// MakeGetAddressEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeGetAddressEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(getAddressRequest)
a, e := s.GetAddress(ctx, req.ProfileID, req.AddressID)
return getAddressResponse{Address: a, Err: e}, nil
}
}
// MakePostAddressEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakePostAddressEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(postAddressRequest)
e := s.PostAddress(ctx, req.ProfileID, req.Address)
return postAddressResponse{Err: e}, nil
}
}
// MakeDeleteAddressEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeDeleteAddressEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(deleteAddressRequest)
e := s.DeleteAddress(ctx, req.ProfileID, req.AddressID)
return deleteAddressResponse{Err: e}, nil
}
}
// We have two options to return errors from the business logic.
//
// We could return the error via the endpoint itself. That makes certain things
// a little bit easier, like providing non-200 HTTP responses to the client. But
// Go kit assumes that endpoint errors are (or may be treated as)
// transport-domain errors. For example, an endpoint error will count against a
// circuit breaker error count.
//
// Therefore, it's often better to return service (business logic) errors in the
// response object. This means we have to do a bit more work in the HTTP
// response encoder to detect e.g. a not-found error and provide a proper HTTP
// status code. That work is done with the errorer interface, in transport.go.
// Response types that may contain business-logic errors implement that
// interface.
type postProfileRequest struct {
Profile Profile
}
type postProfileResponse struct {
Err error `json:"err,omitempty"`
}
func (r postProfileResponse) error() error { return r.Err }
type getProfileRequest struct {
ID string
}
type getProfileResponse struct {
Profile Profile `json:"profile,omitempty"`
Err error `json:"err,omitempty"`
}
func (r getProfileResponse) error() error { return r.Err }
type putProfileRequest struct {
ID string
Profile Profile
}
type putProfileResponse struct {
Err error `json:"err,omitempty"`
}
func (r putProfileResponse) error() error { return nil }
type patchProfileRequest struct {
ID string
Profile Profile
}
type patchProfileResponse struct {
Err error `json:"err,omitempty"`
}
func (r patchProfileResponse) error() error { return r.Err }
type deleteProfileRequest struct {
ID string
}
type deleteProfileResponse struct {
Err error `json:"err,omitempty"`
}
func (r deleteProfileResponse) error() error { return r.Err }
type getAddressesRequest struct {
ProfileID string
}
type getAddressesResponse struct {
Addresses []Address `json:"addresses,omitempty"`
Err error `json:"err,omitempty"`
}
func (r getAddressesResponse) error() error { return r.Err }
type getAddressRequest struct {
ProfileID string
AddressID string
}
type getAddressResponse struct {
Address Address `json:"address,omitempty"`
Err error `json:"err,omitempty"`
}
func (r getAddressResponse) error() error { return r.Err }
type postAddressRequest struct {
ProfileID string
Address Address
}
type postAddressResponse struct {
Err error `json:"err,omitempty"`
}
func (r postAddressResponse) error() error { return r.Err }
type deleteAddressRequest struct {
ProfileID string
AddressID string
}
type deleteAddressResponse struct {
Err error `json:"err,omitempty"`
}
func (r deleteAddressResponse) error() error { return r.Err }

View File

@@ -0,0 +1,88 @@
package profilesvc
import (
"context"
"time"
"github.com/go-kit/kit/log"
)
// Middleware describes a service (as opposed to endpoint) middleware.
type Middleware func(Service) Service
func LoggingMiddleware(logger log.Logger) Middleware {
return func(next Service) Service {
return &loggingMiddleware{
next: next,
logger: logger,
}
}
}
type loggingMiddleware struct {
next Service
logger log.Logger
}
func (mw loggingMiddleware) PostProfile(ctx context.Context, p Profile) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "PostProfile", "id", p.ID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.PostProfile(ctx, p)
}
func (mw loggingMiddleware) GetProfile(ctx context.Context, id string) (p Profile, err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "GetProfile", "id", id, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.GetProfile(ctx, id)
}
func (mw loggingMiddleware) PutProfile(ctx context.Context, id string, p Profile) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "PutProfile", "id", id, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.PutProfile(ctx, id, p)
}
func (mw loggingMiddleware) PatchProfile(ctx context.Context, id string, p Profile) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "PatchProfile", "id", id, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.PatchProfile(ctx, id, p)
}
func (mw loggingMiddleware) DeleteProfile(ctx context.Context, id string) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "DeleteProfile", "id", id, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.DeleteProfile(ctx, id)
}
func (mw loggingMiddleware) GetAddresses(ctx context.Context, profileID string) (addresses []Address, err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "GetAddresses", "profileID", profileID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.GetAddresses(ctx, profileID)
}
func (mw loggingMiddleware) GetAddress(ctx context.Context, profileID string, addressID string) (a Address, err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "GetAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.GetAddress(ctx, profileID, addressID)
}
func (mw loggingMiddleware) PostAddress(ctx context.Context, profileID string, a Address) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "PostAddress", "profileID", profileID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.PostAddress(ctx, profileID, a)
}
func (mw loggingMiddleware) DeleteAddress(ctx context.Context, profileID string, addressID string) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "DeleteAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.DeleteAddress(ctx, profileID, addressID)
}

View File

@@ -0,0 +1,185 @@
package profilesvc
import (
"context"
"errors"
"sync"
)
// Service is a simple CRUD interface for user profiles.
type Service interface {
PostProfile(ctx context.Context, p Profile) error
GetProfile(ctx context.Context, id string) (Profile, error)
PutProfile(ctx context.Context, id string, p Profile) error
PatchProfile(ctx context.Context, id string, p Profile) error
DeleteProfile(ctx context.Context, id string) error
GetAddresses(ctx context.Context, profileID string) ([]Address, error)
GetAddress(ctx context.Context, profileID string, addressID string) (Address, error)
PostAddress(ctx context.Context, profileID string, a Address) error
DeleteAddress(ctx context.Context, profileID string, addressID string) error
}
// Profile represents a single user profile.
// ID should be globally unique.
type Profile struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Addresses []Address `json:"addresses,omitempty"`
}
// Address is a field of a user profile.
// ID should be unique within the profile (at a minimum).
type Address struct {
ID string `json:"id"`
Location string `json:"location,omitempty"`
}
var (
ErrInconsistentIDs = errors.New("inconsistent IDs")
ErrAlreadyExists = errors.New("already exists")
ErrNotFound = errors.New("not found")
)
type inmemService struct {
mtx sync.RWMutex
m map[string]Profile
}
func NewInmemService() Service {
return &inmemService{
m: map[string]Profile{},
}
}
func (s *inmemService) PostProfile(ctx context.Context, p Profile) error {
s.mtx.Lock()
defer s.mtx.Unlock()
if _, ok := s.m[p.ID]; ok {
return ErrAlreadyExists // POST = create, don't overwrite
}
s.m[p.ID] = p
return nil
}
func (s *inmemService) GetProfile(ctx context.Context, id string) (Profile, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
p, ok := s.m[id]
if !ok {
return Profile{}, ErrNotFound
}
return p, nil
}
func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error {
if id != p.ID {
return ErrInconsistentIDs
}
s.mtx.Lock()
defer s.mtx.Unlock()
s.m[id] = p // PUT = create or update
return nil
}
func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error {
if p.ID != "" && id != p.ID {
return ErrInconsistentIDs
}
s.mtx.Lock()
defer s.mtx.Unlock()
existing, ok := s.m[id]
if !ok {
return ErrNotFound // PATCH = update existing, don't create
}
// We assume that it's not possible to PATCH the ID, and that it's not
// possible to PATCH any field to its zero value. That is, the zero value
// means not specified. The way around this is to use e.g. Name *string in
// the Profile definition. But since this is just a demonstrative example,
// I'm leaving that out.
if p.Name != "" {
existing.Name = p.Name
}
if len(p.Addresses) > 0 {
existing.Addresses = p.Addresses
}
s.m[id] = existing
return nil
}
func (s *inmemService) DeleteProfile(ctx context.Context, id string) error {
s.mtx.Lock()
defer s.mtx.Unlock()
if _, ok := s.m[id]; !ok {
return ErrNotFound
}
delete(s.m, id)
return nil
}
func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
p, ok := s.m[profileID]
if !ok {
return []Address{}, ErrNotFound
}
return p.Addresses, nil
}
func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
p, ok := s.m[profileID]
if !ok {
return Address{}, ErrNotFound
}
for _, address := range p.Addresses {
if address.ID == addressID {
return address, nil
}
}
return Address{}, ErrNotFound
}
func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error {
s.mtx.Lock()
defer s.mtx.Unlock()
p, ok := s.m[profileID]
if !ok {
return ErrNotFound
}
for _, address := range p.Addresses {
if address.ID == a.ID {
return ErrAlreadyExists
}
}
p.Addresses = append(p.Addresses, a)
s.m[profileID] = p
return nil
}
func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
s.mtx.Lock()
defer s.mtx.Unlock()
p, ok := s.m[profileID]
if !ok {
return ErrNotFound
}
newAddresses := make([]Address, 0, len(p.Addresses))
for _, address := range p.Addresses {
if address.ID == addressID {
continue // delete
}
newAddresses = append(newAddresses, address)
}
if len(newAddresses) == len(p.Addresses) {
return ErrNotFound
}
p.Addresses = newAddresses
s.m[profileID] = p
return nil
}

View File

@@ -0,0 +1,400 @@
package profilesvc
// The profilesvc is just over HTTP, so we just have a single transport.go.
import (
"bytes"
"context"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"github.com/gorilla/mux"
"github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http"
)
var (
// ErrBadRouting is returned when an expected path variable is missing.
// It always indicates programmer error.
ErrBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)")
)
// MakeHTTPHandler mounts all of the service endpoints into an http.Handler.
// Useful in a profilesvc server.
func MakeHTTPHandler(s Service, logger log.Logger) http.Handler {
r := mux.NewRouter()
e := MakeServerEndpoints(s)
options := []httptransport.ServerOption{
httptransport.ServerErrorLogger(logger),
httptransport.ServerErrorEncoder(encodeError),
}
// POST /profiles/ adds another profile
// GET /profiles/:id retrieves the given profile by id
// PUT /profiles/:id post updated profile information about the profile
// PATCH /profiles/:id partial updated profile information
// DELETE /profiles/:id remove the given profile
// GET /profiles/:id/addresses/ retrieve addresses associated with the profile
// GET /profiles/:id/addresses/:addressID retrieve a particular profile address
// POST /profiles/:id/addresses/ add a new address
// DELETE /profiles/:id/addresses/:addressID remove an address
r.Methods("POST").Path("/profiles/").Handler(httptransport.NewServer(
e.PostProfileEndpoint,
decodePostProfileRequest,
encodeResponse,
options...,
))
r.Methods("GET").Path("/profiles/{id}").Handler(httptransport.NewServer(
e.GetProfileEndpoint,
decodeGetProfileRequest,
encodeResponse,
options...,
))
r.Methods("PUT").Path("/profiles/{id}").Handler(httptransport.NewServer(
e.PutProfileEndpoint,
decodePutProfileRequest,
encodeResponse,
options...,
))
r.Methods("PATCH").Path("/profiles/{id}").Handler(httptransport.NewServer(
e.PatchProfileEndpoint,
decodePatchProfileRequest,
encodeResponse,
options...,
))
r.Methods("DELETE").Path("/profiles/{id}").Handler(httptransport.NewServer(
e.DeleteProfileEndpoint,
decodeDeleteProfileRequest,
encodeResponse,
options...,
))
r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
e.GetAddressesEndpoint,
decodeGetAddressesRequest,
encodeResponse,
options...,
))
r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
e.GetAddressEndpoint,
decodeGetAddressRequest,
encodeResponse,
options...,
))
r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
e.PostAddressEndpoint,
decodePostAddressRequest,
encodeResponse,
options...,
))
r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
e.DeleteAddressEndpoint,
decodeDeleteAddressRequest,
encodeResponse,
options...,
))
return r
}
func decodePostProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
var req postProfileRequest
if e := json.NewDecoder(r.Body).Decode(&req.Profile); e != nil {
return nil, e
}
return req, nil
}
func decodeGetProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
return getProfileRequest{ID: id}, nil
}
func decodePutProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
var profile Profile
if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
return nil, err
}
return putProfileRequest{
ID: id,
Profile: profile,
}, nil
}
func decodePatchProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
var profile Profile
if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
return nil, err
}
return patchProfileRequest{
ID: id,
Profile: profile,
}, nil
}
func decodeDeleteProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
return deleteProfileRequest{ID: id}, nil
}
func decodeGetAddressesRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
return getAddressesRequest{ProfileID: id}, nil
}
func decodeGetAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
addressID, ok := vars["addressID"]
if !ok {
return nil, ErrBadRouting
}
return getAddressRequest{
ProfileID: id,
AddressID: addressID,
}, nil
}
func decodePostAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
var address Address
if err := json.NewDecoder(r.Body).Decode(&address); err != nil {
return nil, err
}
return postAddressRequest{
ProfileID: id,
Address: address,
}, nil
}
func decodeDeleteAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
addressID, ok := vars["addressID"]
if !ok {
return nil, ErrBadRouting
}
return deleteAddressRequest{
ProfileID: id,
AddressID: addressID,
}, nil
}
func encodePostProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("POST").Path("/profiles/")
req.Method, req.URL.Path = "POST", "/profiles/"
return encodeRequest(ctx, req, request)
}
func encodeGetProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("GET").Path("/profiles/{id}")
r := request.(getProfileRequest)
profileID := url.QueryEscape(r.ID)
req.Method, req.URL.Path = "GET", "/profiles/"+profileID
return encodeRequest(ctx, req, request)
}
func encodePutProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("PUT").Path("/profiles/{id}")
r := request.(putProfileRequest)
profileID := url.QueryEscape(r.ID)
req.Method, req.URL.Path = "PUT", "/profiles/"+profileID
return encodeRequest(ctx, req, request)
}
func encodePatchProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("PATCH").Path("/profiles/{id}")
r := request.(patchProfileRequest)
profileID := url.QueryEscape(r.ID)
req.Method, req.URL.Path = "PATCH", "/profiles/"+profileID
return encodeRequest(ctx, req, request)
}
func encodeDeleteProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("DELETE").Path("/profiles/{id}")
r := request.(deleteProfileRequest)
profileID := url.QueryEscape(r.ID)
req.Method, req.URL.Path = "DELETE", "/profiles/"+profileID
return encodeRequest(ctx, req, request)
}
func encodeGetAddressesRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("GET").Path("/profiles/{id}/addresses/")
r := request.(getAddressesRequest)
profileID := url.QueryEscape(r.ProfileID)
req.Method, req.URL.Path = "GET", "/profiles/"+profileID+"/addresses/"
return encodeRequest(ctx, req, request)
}
func encodeGetAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}")
r := request.(getAddressRequest)
profileID := url.QueryEscape(r.ProfileID)
addressID := url.QueryEscape(r.AddressID)
req.Method, req.URL.Path = "GET", "/profiles/"+profileID+"/addresses/"+addressID
return encodeRequest(ctx, req, request)
}
func encodePostAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("POST").Path("/profiles/{id}/addresses/")
r := request.(postAddressRequest)
profileID := url.QueryEscape(r.ProfileID)
req.Method, req.URL.Path = "POST", "/profiles/"+profileID+"/addresses/"
return encodeRequest(ctx, req, request)
}
func encodeDeleteAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}")
r := request.(deleteAddressRequest)
profileID := url.QueryEscape(r.ProfileID)
addressID := url.QueryEscape(r.AddressID)
req.Method, req.URL.Path = "DELETE", "/profiles/"+profileID+"/addresses/"+addressID
return encodeRequest(ctx, req, request)
}
func decodePostProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response postProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeGetProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response getProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodePutProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response putProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodePatchProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response patchProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeDeleteProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response deleteProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeGetAddressesResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response getAddressesResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeGetAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response getAddressResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodePostAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response postAddressResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeDeleteAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response deleteAddressResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
// errorer is implemented by all concrete response types that may contain
// errors. It allows us to change the HTTP response code without needing to
// trigger an endpoint (transport-level) error. For more information, read the
// big comment in endpoints.go.
type errorer interface {
error() error
}
// encodeResponse is the common method to encode all response types to the
// client. I chose to do it this way because, since we're using JSON, there's no
// reason to provide anything more specific. It's certainly possible to
// specialize on a per-response (per-method) basis.
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if e, ok := response.(errorer); ok && e.error() != nil {
// Not a Go kit transport error, but a business-logic error.
// Provide those as HTTP errors.
encodeError(ctx, e.error(), w)
return nil
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
// encodeRequest likewise JSON-encodes the request to the HTTP request body.
// Don't use it directly as a transport/http.Client EncodeRequestFunc:
// profilesvc endpoints require mutating the HTTP method and request path.
func encodeRequest(_ context.Context, req *http.Request, request interface{}) error {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(request)
if err != nil {
return err
}
req.Body = ioutil.NopCloser(&buf)
return nil
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
if err == nil {
panic("encodeError with nil error")
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(codeFrom(err))
json.NewEncoder(w).Encode(map[string]interface{}{
"error": err.Error(),
})
}
func codeFrom(err error) int {
switch err {
case ErrNotFound:
return http.StatusNotFound
case ErrAlreadyExists, ErrInconsistentIDs:
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}
}