1

What's the most intuitive way to organize a set of endpoints to fetch users and their roles in groups? i.e.

  • Users and their roles in a specified group.
  • Groups a user is in, and their role in each group.

Attempt 1

We began with the following:

message UserWithRole {
  User user = 1;
  Role role = 2;
}

message GetUsersInGroupRequest {
  string group_id = 1;
}

message GetUsersInGroupResponse {
  repeated UserWithRole users = 1;
}

rpc getUsersInGroup (GetUsersInGroupRequest) returns (GetUsersInGroupResponse)

And for the flip side (groups a user belongs to):

message GroupWithRole {
  Group group = 1;
  Role role = 2;
}

message GetGroupsForUserRequest {
  string user_id = 1;
}

message GetGroupsForUserResponse {
  repeated GroupWithRole groups = 1;
}

rpc getGroupsForUser (GetGroupsForUserRequest) returns (GetGroupsForUserResponse)

This felt awkward and unnecessarily verbose.

Attempt 2

So, what about a using a single model?

message UserGroupMapping {
  User user = 1;
  Group group = 2;
  Role role = 3;
}

message GetUsersInGroupRequest {
  string group_id = 1;
}

message GetUsersInGroupResponse {
  repeated UserGroupMapping user_group_mappings = 1;
}

rpc getUsersInGroup (GetUsersInGroupRequest) returns (GetUsersInGroupResponse)

message GetGroupsForUserRequest {
  string user_id = 1;
}

message GetGroupsForUserResponse {
  repeated UserGroupMapping user_group_mappings = 1;
}

rpc getGroupsForUser (GetGroupsForUserRequest) returns (GetGroupsForUserResponse)

This appeals because the same model can be used for other endpoints that return both a user and group:

rpc getUserAndPrimaryGroup (string user_id) returns (UserGroupMapping);

Let's even say we consolidate the above like this:

Attempt 2b

message UserGroupMapping {
  User user = 1;
  Group group = 2;
  Role role = 3;
}

message GetUserGroupMappingsRequest {
  enum IdType {
    USER_ID = 0;
    GROUP_ID = 1;
  }
  IdType id_type = 1;
  string id = 2;
}

message GetUserGroupMappingsResponse {
  repeated UserGroupMapping user_group_mappings = 1;
}

rpc getUserGroupMappings (GetUserGroupMappingsRequest) returns (GetUserGroupMappingsResponse)

But either way, the problem is that one of the dimensions, User or Group is always needlessly repeated despite being the uniform for any given request, _i.e. if you're getting a user's groups, there's no need to repeat User again and again, but here we are.

Summary

I know either way "works," but since this will be a very central microservice (used by other microservices managing resources, payments, auditing, etc.) I want to design the API right from the start.

Meanwhile I'll continue searching the web for protobuf user group management api design examples etc. but just getting a lot of irrelevant results.

2
  • What is your problem with the inclusion of User/Group in attempt 2? Is it aesthetics, message size, something else? Dec 3 '19 at 13:20
  • 1
    I guess it feels "wrong" that an endpoint should return User A / Group 1, User A / Group 5, User A / Group 6, User A / Group 7, etc. where each "User A" includes all the user's details: id, f_name, l_name, email, timezone, etc. Dec 3 '19 at 13:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.