3

Suppose the following: Building has 0...n Rooms, Room has 0...n Seats.

I am implementing a REST API which accepts data from different companies, with the caveat that the companies must be able themselves define the id (reference) of the entity. I'm generally unable to make any assumptions about those ids, other than being compatible with a HTTP URL. This means there's no guarantee ids are unique across companies, or even within a company (e.g. two rooms could have the same id but be linked to different buildings).

Since its possible that two companies would have the same id for a Building, I've decided to treat companyId and buildingReference as a composite id. The API endpoints I came up with are as follows:

buildings/
buildings/{companyId}/{buildingReference}/rooms
buildings/{companyId}/{buildingReference}/rooms/{roomReference}/seats
etc.

I have some doubts whether this is a good idea. Among other things:

  • Most APIs I've come across only use a singular id
  • If I ever decide to expose GET /rooms or /seats, I'd have to return more and more fields to make the resource uniquely identifiable, companyId, buildingReference, roomReference for room and companyId, buildingReference, roomReference, seatReference for seat

I could possibly get rid of companyId by requiring that the buildingReference be some kind of String concatenation of the two, e.g. CompanyOne-1234, but that doesn't strike me as a great idea either.

Are composite keys like this a good idea? Are there better alternatives?

4
  • @Downvoters: May I ask for suggestions on how to improve the question?
    – Robus
    Aug 24, 2022 at 13:04
  • 2
    Why do you need to compose the id? companyId, buildingId and roomId are at different levels of the hierarchy. How can they collide? If companies are allowed to generate their own ids then they are responsible for avoiding collisions within their hierarchy. As you said, you cannot make assumptions. ID composition as the one suggested is made upon a lot of assumptions you don't even know yet. Basically those made (wrongly) by the clients of the API (who might generate their IDs ignoring they are not the only company in the system)
    – Laiv
    Aug 25, 2022 at 10:15
  • Can two companies define two different ids for the same building? Aug 25, 2022 at 13:32
  • 1
    It's really important to think about security implications as well. For example, if it's a party foul for Company-1234 to update data for Company-7777 you have to consider how you get that information. I.e. if that is encoded into the user's token or account, then you don't need to call it out in the URL. Also, what is the expected behavior if buildingReference is mis-typed? Do you implicitly create a new resource, or do you return an error because that resource hasn't been made yet? That can help design the API. I would avoid composite IDs if possible. Aug 25, 2022 at 17:39

4 Answers 4

1

One alternative is to generate ids that you use yourself. Have the companies POST something, they get the URI for that resource. They don't really have to know how it is managed on your side, do they?

Also, if you are doing a straight CRUD thing, you might get away with a database with a HTTP interface. You don't have to code anything.

3
  • Have the companies POST something, they get the URI for that resource it is sadly a requirement that the clients be able to access the resources using their ids, without saving anything specific to my application (URI, generated id, etc). I intend to use internal generated ids in my data model, not exposed via the API.
    – Robus
    Aug 24, 2022 at 13:03
  • I guess the companies log in with some account? If so, having the company id in the URI is redundant. If you already have your internal ids, then the URIs are essentially search functions. You are not assembling an id, you are searching. Aug 25, 2022 at 5:46
  • "It is sadly a requirement that the clients be able to access the resources using their ids, without saving anything specific to my application (URI, generated id, etc)." I see. That means all IDs will be highly predictable. In a security focused system, that's actually the opposite of what you want because then people who may not be supposed to can guess the ID for a resource. Aug 25, 2022 at 17:32
1

Composite keys are a pain in the arse and should be avoided.

But what to do here, where you have a clear "human use" Id's Room "1A" in building "North Terminal"?

  1. Force a system assigned GUID and use the eternal ID as a separate 'Name' field which is only used for display.

    Great for APIs, but you lose the nice human readable url

  2. Reject item creation if the ID conflicts with another company/building

    Likely simply forces the user to start making composite IDs

  3. Search on the human ID but use other meta data to work out which one you want. ie you may know the company the user is assigned to from their login.

    Fails for users that can see more than one company and doesn't help with duplicates within a company such as buildings.

In almost all cases solution 1 is the best. The only down side is that the API isn't human readable. but APIs are not designed to be used by humans anyway.

Solution 2 is arguably better, but relies on the user understanding the potential problems with different ID creation schemes. You don't want annoyed and confused users who have created rooms 1 through 4 but now find they cant use "5" because its been used by someone else.

1

The scenario you outline is quite messy so you'll need to put some order in there yourself.

First things first: resources.

I believe your resources are the fallowing:

  1. companies
  2. buildings
  3. rooms
  4. seats

So you should have the fallowing CRUD endpoints:

  1. /companies and /companies/:companyIternalID
  2. /buildings and /buildings/:buildingInternalID
  3. /rooms and /rooms/:roomInternalID
  4. /seats and /seats/:seatInternalID

Now you can have nesting if you wish too:

  1. /companies/:companyIternalID/buildings
  2. /buildings/:buildingInternalID/rooms
  3. /rooms/:roomInternalID/seats

I think it's better avoid any further nesting like /companies/:companyIternalID/buildings/:buildingInternalID/rooms because it's kinda ugly and you can as well start with the buildings resource since the companyIternalID in this case won't add any useful information to the selection.

Ok so now you have your cool CRUD endpoints based on your own unique identifiers you can start from!

The idea that clients should make up their own unique identifiers is quite weird itself right? I mean UUIDs are created when you insert data in the DB so it's perfectly normal for companies to make up their not-so-unique identifiers themselves (because it's not their job to make up unique ones!).

So now the problem is how to select all the rooms in a given building of a given company like you wrote in your endpoint?

buildings/{companyId}/{buildingReference}/rooms

my suggestion is to use query-string parameters to define filters like this:

/rooms?company=companyId&building=buildingReference

This would return any room that has a given company and building reference.

0

You can do something like this:
/business/{businessId}/building/{buildingId}/room/{roomId}

Alternatively, if you do not want to expose hierarchy via URL then you can always do something like:

  • /business/{businessId}
  • /building/{businessId}-{buildingId}
  • /room/{businessId}-{buildingId}-{roomId}

This of course assumes that dash - can not be a part of an Id so it will not work with Guid identifiers.

Of course if you can use your own internal IDs then its simple because you are in control.

1
  • This. Simplicity is usually best. Once you have designed the hierarchy you can start thinking in aliases to make URL more user-friendly if needed. You can allow companies to define their own aliases. Aliases that can be mapped / forwarded to concrete URIs. Of their own buildings and rooms.
    – Laiv
    Aug 26, 2022 at 16:13

Not the answer you're looking for? Browse other questions tagged or ask your own question.