I'm fairly new to go-kit and micro-service architecture as well.
I have a JWT that contains the authenticated user's id as a claim, and each service's method, I want to check if the user has permission to update its own client record, but it seems that wherever I put it, it feels wrong. What are the best practices to doing something like this?
// client.go
type Client struct {
ID string `json:"id"`
UserID string `json:"user_id"`
// ...
}
type ClientDataLayer interface {
Load(db *sql.DB, id string) *Client
Update(db *sql.DB, cl *Client) *Client
// ... more CRUD methods
}
Doing it within a service middleware I would have to call another service's method which seems to break the purpose of the architecture:
// service.go
type Service interface {
UpdateClient(ctx context.Context, cl *Client) (*Client, error)
// ... more methods
}
func NewClientService(db *sql.DB, cdl ClientDataLayer) {
// ....
}
type service struct {
db *sql.DB
cdl ClientDataLayer
}
func (svc *service) UpdateClient(ctx context.Context, id string) (*Client, error) {
return nil, nil
}
// service middleware
type accessControl struct {
svc Service
}
func (ac *accessControl) UpdateClient(ctx context.Context, cl *Client) (*Client, error) {
// ... get token and claims from context
// scopes := jwt.Claims().Get("scopes")
// Here I don't have the client's user_id,
// and it does not feel right to call another service's method
if !strings.Contains(scopes, "client:write") || cl.UserID != userID {
return nil, errors.New("forbidden")
}
// ...
return nil, nil
}
Doing it on the endpoint its pretty much the same
// transport.go
func makeUpdateClientEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
// type cast request
// ... get token and claims from context
// scopes := jwt.Claims().Get("scopes")
// Same problem as before, I don't have the client's user_id
if !strings.Contains(scopes, "client:write") || cl.UserID != userID {
return nil, errors.New("forbidden")
}
cl, _ := svc.UpdateClient(ctx, request.ClientID)
// ...
return nil, nil
}
}
I probably could get away by using the ClientDataLayer to retrieve the client and checking the user's id. As a way to avoid messing with another service method to load the client because it may have middlewares for logging or metrics too.
How could I achieve this without so much hassle where I feel like breaking the patterns?