Codebase list golang-github-go-kit-kit / 38e63e2
Merge pull request #216 from go-kit/fix-small-logic-error-in-profilesvc Fix small logic error in profilesvc transport Peter Bourgon 8 years ago
6 changed file(s) with 277 addition(s) and 275 deletion(s). Raw diff Collapse all Expand all
4040
4141 func (r postProfileResponse) error() error { return r.Err }
4242
43 // Regarding errors returned from service methods, we have two options. We can
44 // return the error via the endpoint itself. That makes certain things a
45 // little bit easier, like providing non-200 HTTP responses to the client. But
46 // Go kit assumes that endpoint errors are (or may be treated as) transport-
47 // level errors. For example, an endpoint error will count against a circuit
48 // breaker error count. Therefore, it's almost certainly better to return
49 // service method (business logic) errors in the response object. This means
50 // we have to do a bit more work in the HTTP response encoder to detect e.g. a
51 // not-found error and provide a proper HTTP status code.
43 // Regarding errors returned from service (business logic) methods, we have two
44 // options. We could return the error via the endpoint itself. That makes
45 // certain things a little bit easier, like providing non-200 HTTP responses to
46 // the client. But Go kit assumes that endpoint errors are (or may be treated
47 // as) transport-domain errors. For example, an endpoint error will count
48 // against a circuit breaker error count. Therefore, it's almost certainly
49 // better to return service (business logic) errors in the response object. This
50 // means we have to do a bit more work in the HTTP response encoder to detect
51 // e.g. a not-found error and provide a proper HTTP status code. That work is
52 // done with the errorer interface, in transport.go.
5253
5354 func makePostProfileEndpoint(s ProfileService) endpoint.Endpoint {
5455 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
+0
-77
examples/profilesvc/logging_middleware.go less more
0 package main
1
2 import (
3 "time"
4
5 "golang.org/x/net/context"
6
7 "github.com/go-kit/kit/log"
8 )
9
10 type loggingMiddleware struct {
11 next ProfileService
12 logger log.Logger
13 }
14
15 func (mw loggingMiddleware) PostProfile(ctx context.Context, p Profile) (err error) {
16 defer func(begin time.Time) {
17 mw.logger.Log("method", "PostProfile", "id", p.ID, "took", time.Since(begin), "err", err)
18 }(time.Now())
19 return mw.next.PostProfile(ctx, p)
20 }
21
22 func (mw loggingMiddleware) GetProfile(ctx context.Context, id string) (p Profile, err error) {
23 defer func(begin time.Time) {
24 mw.logger.Log("method", "GetProfile", "id", id, "took", time.Since(begin), "err", err)
25 }(time.Now())
26 return mw.next.GetProfile(ctx, id)
27 }
28
29 func (mw loggingMiddleware) PutProfile(ctx context.Context, id string, p Profile) (err error) {
30 defer func(begin time.Time) {
31 mw.logger.Log("method", "PutProfile", "id", id, "took", time.Since(begin), "err", err)
32 }(time.Now())
33 return mw.next.PutProfile(ctx, id, p)
34 }
35
36 func (mw loggingMiddleware) PatchProfile(ctx context.Context, id string, p Profile) (err error) {
37 defer func(begin time.Time) {
38 mw.logger.Log("method", "PatchProfile", "id", id, "took", time.Since(begin), "err", err)
39 }(time.Now())
40 return mw.next.PatchProfile(ctx, id, p)
41 }
42
43 func (mw loggingMiddleware) DeleteProfile(ctx context.Context, id string) (err error) {
44 defer func(begin time.Time) {
45 mw.logger.Log("method", "DeleteProfile", "id", id, "took", time.Since(begin), "err", err)
46 }(time.Now())
47 return mw.next.DeleteProfile(ctx, id)
48 }
49
50 func (mw loggingMiddleware) GetAddresses(ctx context.Context, profileID string) (addresses []Address, err error) {
51 defer func(begin time.Time) {
52 mw.logger.Log("method", "GetAddresses", "profileID", profileID, "took", time.Since(begin), "err", err)
53 }(time.Now())
54 return mw.next.GetAddresses(ctx, profileID)
55 }
56
57 func (mw loggingMiddleware) GetAddress(ctx context.Context, profileID string, addressID string) (a Address, err error) {
58 defer func(begin time.Time) {
59 mw.logger.Log("method", "GetAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
60 }(time.Now())
61 return mw.next.GetAddress(ctx, profileID, addressID)
62 }
63
64 func (mw loggingMiddleware) PostAddress(ctx context.Context, profileID string, a Address) (err error) {
65 defer func(begin time.Time) {
66 mw.logger.Log("method", "PostAddress", "profileID", profileID, "took", time.Since(begin), "err", err)
67 }(time.Now())
68 return mw.next.PostAddress(ctx, profileID, a)
69 }
70
71 func (mw loggingMiddleware) DeleteAddress(ctx context.Context, profileID string, addressID string) (err error) {
72 defer func(begin time.Time) {
73 mw.logger.Log("method", "DeleteAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
74 }(time.Now())
75 return mw.next.DeleteAddress(ctx, profileID, addressID)
76 }
0 package main
1
2 import (
3 "time"
4
5 "golang.org/x/net/context"
6
7 "github.com/go-kit/kit/log"
8 )
9
10 type loggingMiddleware struct {
11 next ProfileService
12 logger log.Logger
13 }
14
15 func (mw loggingMiddleware) PostProfile(ctx context.Context, p Profile) (err error) {
16 defer func(begin time.Time) {
17 mw.logger.Log("method", "PostProfile", "id", p.ID, "took", time.Since(begin), "err", err)
18 }(time.Now())
19 return mw.next.PostProfile(ctx, p)
20 }
21
22 func (mw loggingMiddleware) GetProfile(ctx context.Context, id string) (p Profile, err error) {
23 defer func(begin time.Time) {
24 mw.logger.Log("method", "GetProfile", "id", id, "took", time.Since(begin), "err", err)
25 }(time.Now())
26 return mw.next.GetProfile(ctx, id)
27 }
28
29 func (mw loggingMiddleware) PutProfile(ctx context.Context, id string, p Profile) (err error) {
30 defer func(begin time.Time) {
31 mw.logger.Log("method", "PutProfile", "id", id, "took", time.Since(begin), "err", err)
32 }(time.Now())
33 return mw.next.PutProfile(ctx, id, p)
34 }
35
36 func (mw loggingMiddleware) PatchProfile(ctx context.Context, id string, p Profile) (err error) {
37 defer func(begin time.Time) {
38 mw.logger.Log("method", "PatchProfile", "id", id, "took", time.Since(begin), "err", err)
39 }(time.Now())
40 return mw.next.PatchProfile(ctx, id, p)
41 }
42
43 func (mw loggingMiddleware) DeleteProfile(ctx context.Context, id string) (err error) {
44 defer func(begin time.Time) {
45 mw.logger.Log("method", "DeleteProfile", "id", id, "took", time.Since(begin), "err", err)
46 }(time.Now())
47 return mw.next.DeleteProfile(ctx, id)
48 }
49
50 func (mw loggingMiddleware) GetAddresses(ctx context.Context, profileID string) (addresses []Address, err error) {
51 defer func(begin time.Time) {
52 mw.logger.Log("method", "GetAddresses", "profileID", profileID, "took", time.Since(begin), "err", err)
53 }(time.Now())
54 return mw.next.GetAddresses(ctx, profileID)
55 }
56
57 func (mw loggingMiddleware) GetAddress(ctx context.Context, profileID string, addressID string) (a Address, err error) {
58 defer func(begin time.Time) {
59 mw.logger.Log("method", "GetAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
60 }(time.Now())
61 return mw.next.GetAddress(ctx, profileID, addressID)
62 }
63
64 func (mw loggingMiddleware) PostAddress(ctx context.Context, profileID string, a Address) (err error) {
65 defer func(begin time.Time) {
66 mw.logger.Log("method", "PostAddress", "profileID", profileID, "took", time.Since(begin), "err", err)
67 }(time.Now())
68 return mw.next.PostAddress(ctx, profileID, a)
69 }
70
71 func (mw loggingMiddleware) DeleteAddress(ctx context.Context, profileID string, addressID string) (err error) {
72 defer func(begin time.Time) {
73 mw.logger.Log("method", "DeleteAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
74 }(time.Now())
75 return mw.next.DeleteAddress(ctx, profileID, addressID)
76 }
+0
-186
examples/profilesvc/profile_service.go less more
0 package main
1
2 import (
3 "errors"
4 "sync"
5
6 "golang.org/x/net/context"
7 )
8
9 // ProfileService is a simple CRUD interface for user profiles.
10 type ProfileService interface {
11 PostProfile(ctx context.Context, p Profile) error
12 GetProfile(ctx context.Context, id string) (Profile, error)
13 PutProfile(ctx context.Context, id string, p Profile) error
14 PatchProfile(ctx context.Context, id string, p Profile) error
15 DeleteProfile(ctx context.Context, id string) error
16 GetAddresses(ctx context.Context, profileID string) ([]Address, error)
17 GetAddress(ctx context.Context, profileID string, addressID string) (Address, error)
18 PostAddress(ctx context.Context, profileID string, a Address) error
19 DeleteAddress(ctx context.Context, profileID string, addressID string) error
20 }
21
22 // Profile represents a single user profile.
23 // ID should be globally unique.
24 type Profile struct {
25 ID string `json:"id"`
26 Name string `json:"name,omitempty"`
27 Addresses []Address `json:"addresses,omitempty"`
28 }
29
30 // Address is a field of a user profile.
31 // ID should be unique within the profile (at a minimum).
32 type Address struct {
33 ID string `json:"id"`
34 Location string `json:"location,omitempty"`
35 }
36
37 var (
38 errInconsistentIDs = errors.New("inconsistent IDs")
39 errAlreadyExists = errors.New("already exists")
40 errNotFound = errors.New("not found")
41 )
42
43 type inmemService struct {
44 mtx sync.RWMutex
45 m map[string]Profile
46 }
47
48 func newInmemService() ProfileService {
49 return &inmemService{
50 m: map[string]Profile{},
51 }
52 }
53
54 func (s *inmemService) PostProfile(ctx context.Context, p Profile) error {
55 s.mtx.Lock()
56 defer s.mtx.Unlock()
57 if _, ok := s.m[p.ID]; ok {
58 return errAlreadyExists // POST = create, don't overwrite
59 }
60 s.m[p.ID] = p
61 return nil
62 }
63
64 func (s *inmemService) GetProfile(ctx context.Context, id string) (Profile, error) {
65 s.mtx.RLock()
66 defer s.mtx.RUnlock()
67 p, ok := s.m[id]
68 if !ok {
69 return Profile{}, errNotFound
70 }
71 return p, nil
72 }
73
74 func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error {
75 if id != p.ID {
76 return errInconsistentIDs
77 }
78 s.mtx.Lock()
79 defer s.mtx.Unlock()
80 s.m[id] = p // PUT = create or update
81 return nil
82 }
83
84 func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error {
85 if p.ID != "" && id != p.ID {
86 return errInconsistentIDs
87 }
88
89 s.mtx.Lock()
90 defer s.mtx.Unlock()
91
92 existing, ok := s.m[id]
93 if !ok {
94 return errNotFound // PATCH = update existing, don't create
95 }
96
97 // We assume that it's not possible to PATCH the ID, and that it's not
98 // possible to PATCH any field to its zero value. That is, the zero value
99 // means not specified. The way around this is to use e.g. Name *string in
100 // the Profile definition. But since this is just a demonstrative example,
101 // I'm leaving that out.
102
103 if p.Name != "" {
104 existing.Name = p.Name
105 }
106 if len(p.Addresses) > 0 {
107 existing.Addresses = p.Addresses
108 }
109 s.m[id] = existing
110 return nil
111 }
112
113 func (s *inmemService) DeleteProfile(ctx context.Context, id string) error {
114 s.mtx.Lock()
115 defer s.mtx.Unlock()
116 if _, ok := s.m[id]; !ok {
117 return errNotFound
118 }
119 delete(s.m, id)
120 return nil
121 }
122
123 func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
124 s.mtx.RLock()
125 defer s.mtx.RUnlock()
126 p, ok := s.m[profileID]
127 if !ok {
128 return []Address{}, errNotFound
129 }
130 return p.Addresses, nil
131 }
132
133 func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
134 s.mtx.RLock()
135 defer s.mtx.RUnlock()
136 p, ok := s.m[profileID]
137 if !ok {
138 return Address{}, errNotFound
139 }
140 for _, address := range p.Addresses {
141 if address.ID == addressID {
142 return address, nil
143 }
144 }
145 return Address{}, errNotFound
146 }
147
148 func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error {
149 s.mtx.Lock()
150 defer s.mtx.Unlock()
151 p, ok := s.m[profileID]
152 if !ok {
153 return errNotFound
154 }
155 for _, address := range p.Addresses {
156 if address.ID == a.ID {
157 return errAlreadyExists
158 }
159 }
160 p.Addresses = append(p.Addresses, a)
161 s.m[profileID] = p
162 return nil
163 }
164
165 func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
166 s.mtx.Lock()
167 defer s.mtx.Unlock()
168 p, ok := s.m[profileID]
169 if !ok {
170 return errNotFound
171 }
172 newAddresses := make([]Address, 0, len(p.Addresses))
173 for _, address := range p.Addresses {
174 if address.ID == addressID {
175 continue // delete
176 }
177 newAddresses = append(newAddresses, address)
178 }
179 if len(newAddresses) == len(p.Addresses) {
180 return errNotFound
181 }
182 p.Addresses = newAddresses
183 s.m[profileID] = p
184 return nil
185 }
0 package main
1
2 import (
3 "errors"
4 "sync"
5
6 "golang.org/x/net/context"
7 )
8
9 // ProfileService is a simple CRUD interface for user profiles.
10 type ProfileService interface {
11 PostProfile(ctx context.Context, p Profile) error
12 GetProfile(ctx context.Context, id string) (Profile, error)
13 PutProfile(ctx context.Context, id string, p Profile) error
14 PatchProfile(ctx context.Context, id string, p Profile) error
15 DeleteProfile(ctx context.Context, id string) error
16 GetAddresses(ctx context.Context, profileID string) ([]Address, error)
17 GetAddress(ctx context.Context, profileID string, addressID string) (Address, error)
18 PostAddress(ctx context.Context, profileID string, a Address) error
19 DeleteAddress(ctx context.Context, profileID string, addressID string) error
20 }
21
22 // Profile represents a single user profile.
23 // ID should be globally unique.
24 type Profile struct {
25 ID string `json:"id"`
26 Name string `json:"name,omitempty"`
27 Addresses []Address `json:"addresses,omitempty"`
28 }
29
30 // Address is a field of a user profile.
31 // ID should be unique within the profile (at a minimum).
32 type Address struct {
33 ID string `json:"id"`
34 Location string `json:"location,omitempty"`
35 }
36
37 var (
38 errInconsistentIDs = errors.New("inconsistent IDs")
39 errAlreadyExists = errors.New("already exists")
40 errNotFound = errors.New("not found")
41 )
42
43 type inmemService struct {
44 mtx sync.RWMutex
45 m map[string]Profile
46 }
47
48 func newInmemService() ProfileService {
49 return &inmemService{
50 m: map[string]Profile{},
51 }
52 }
53
54 func (s *inmemService) PostProfile(ctx context.Context, p Profile) error {
55 s.mtx.Lock()
56 defer s.mtx.Unlock()
57 if _, ok := s.m[p.ID]; ok {
58 return errAlreadyExists // POST = create, don't overwrite
59 }
60 s.m[p.ID] = p
61 return nil
62 }
63
64 func (s *inmemService) GetProfile(ctx context.Context, id string) (Profile, error) {
65 s.mtx.RLock()
66 defer s.mtx.RUnlock()
67 p, ok := s.m[id]
68 if !ok {
69 return Profile{}, errNotFound
70 }
71 return p, nil
72 }
73
74 func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error {
75 if id != p.ID {
76 return errInconsistentIDs
77 }
78 s.mtx.Lock()
79 defer s.mtx.Unlock()
80 s.m[id] = p // PUT = create or update
81 return nil
82 }
83
84 func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error {
85 if p.ID != "" && id != p.ID {
86 return errInconsistentIDs
87 }
88
89 s.mtx.Lock()
90 defer s.mtx.Unlock()
91
92 existing, ok := s.m[id]
93 if !ok {
94 return errNotFound // PATCH = update existing, don't create
95 }
96
97 // We assume that it's not possible to PATCH the ID, and that it's not
98 // possible to PATCH any field to its zero value. That is, the zero value
99 // means not specified. The way around this is to use e.g. Name *string in
100 // the Profile definition. But since this is just a demonstrative example,
101 // I'm leaving that out.
102
103 if p.Name != "" {
104 existing.Name = p.Name
105 }
106 if len(p.Addresses) > 0 {
107 existing.Addresses = p.Addresses
108 }
109 s.m[id] = existing
110 return nil
111 }
112
113 func (s *inmemService) DeleteProfile(ctx context.Context, id string) error {
114 s.mtx.Lock()
115 defer s.mtx.Unlock()
116 if _, ok := s.m[id]; !ok {
117 return errNotFound
118 }
119 delete(s.m, id)
120 return nil
121 }
122
123 func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
124 s.mtx.RLock()
125 defer s.mtx.RUnlock()
126 p, ok := s.m[profileID]
127 if !ok {
128 return []Address{}, errNotFound
129 }
130 return p.Addresses, nil
131 }
132
133 func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
134 s.mtx.RLock()
135 defer s.mtx.RUnlock()
136 p, ok := s.m[profileID]
137 if !ok {
138 return Address{}, errNotFound
139 }
140 for _, address := range p.Addresses {
141 if address.ID == addressID {
142 return address, nil
143 }
144 }
145 return Address{}, errNotFound
146 }
147
148 func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error {
149 s.mtx.Lock()
150 defer s.mtx.Unlock()
151 p, ok := s.m[profileID]
152 if !ok {
153 return errNotFound
154 }
155 for _, address := range p.Addresses {
156 if address.ID == a.ID {
157 return errAlreadyExists
158 }
159 }
160 p.Addresses = append(p.Addresses, a)
161 s.m[profileID] = p
162 return nil
163 }
164
165 func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
166 s.mtx.Lock()
167 defer s.mtx.Unlock()
168 p, ok := s.m[profileID]
169 if !ok {
170 return errNotFound
171 }
172 newAddresses := make([]Address, 0, len(p.Addresses))
173 for _, address := range p.Addresses {
174 if address.ID == addressID {
175 continue // delete
176 }
177 newAddresses = append(newAddresses, address)
178 }
179 if len(newAddresses) == len(p.Addresses) {
180 return errNotFound
181 }
182 p.Addresses = newAddresses
183 s.m[profileID] = p
184 return nil
185 }
218218 // errorer is implemented by all concrete response types. It allows us to
219219 // change the HTTP response code without needing to trigger an endpoint
220220 // (transport-level) error. For more information, read the big comment in
221 // endpoint.go.
221 // endpoints.go.
222222 type errorer interface {
223223 error() error
224224 }
238238 }
239239
240240 func encodeError(w stdhttp.ResponseWriter, err error) {
241 if err == nil {
242 panic("encodeError with nil error")
243 }
241244 w.WriteHeader(codeFrom(err))
242245 json.NewEncoder(w).Encode(map[string]interface{}{
243246 "error": err.Error(),
246249
247250 func codeFrom(err error) int {
248251 switch err {
249 case nil:
250 return stdhttp.StatusOK
251252 case errNotFound:
252253 return stdhttp.StatusNotFound
253254 case errAlreadyExists, errInconsistentIDs: