8

When designing a RESTful Web Service should the API be designed to work ID for Strings for values passed back and forth between the server?

Here's an example: Let's say I have an Employee resource, which has a status and gender attributes. In the database Status and Gender and separate tables and thus separate Domain object, each with its own identifier.

Let's say the client request /employee/1. There server might return something like these....

Case 1:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "gender": "FEMALE"
    },
    "status": {
        "id": 3,
        "status": "FULL_TIME"
    }
}

Case 2:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

Case 3:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "genderId": 1,
    "statusId": 3
}

Case 3 seems to make the least sense as the client has no idea what genderId 1 is unless it turns around and makes another call to the server to get that data.

However now let's say the client is updating the user through:

PUT /employee/1

Should the request Payload use the ids or a string? Either way, the back-end has to look them up to make sure they are valid, but it is nicer to work with IDs over Strings.

3

Let's say I have an Employee resource, which has a status and gender attributes. In the database Status and Gender and separate tables and thus separate Domain object, each with its own identifier.

Your API representations should not be tightly coupled to your implementation details. I'd go so far as to say that deriving your API representations from your implementation details is exactly backwards.

Think Adapter Pattern from the Gang of Four book. The messages of the web are that of a document store. Your goal in creating an API is to produce the documents that your consumers want, while insulating them from the nitty gritty details of producing those documents.

The motivation for doing so, is that you can then change the implementation details any time you want, secure in the knowledge that -- so long as you don't change the representations you return, your clients won't break.

Also, keep in mind that a single logical resource might have many representations, only some of which support modification.

let's say the client is updating the user

As a consumer, which representation do you want to work with? My guess is that the closest is

{
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

If I PUT that representation to a location that you specify, you really ought to be able to work out the rest.

If you were creating representations for machines to use, then you'd probably want less ambiguity in your spelling

{
    "https://schema.org/givenName": "Jane",
    "https://schema.org/familyName": "Doe",
    "active": true,
    "https://schema.org/gender": "https://schema.org/Female",
    "https://schema.org/employmentType": "FULL_TIME"
}

Same logical resource, two different representations. Horses for courses.

1

Both Case 1 and Case 2 looks good. The choice can be predicted by the way you organize your Domain model.

You reflected the Employee, Gender and Status tables in the Domain (using ORM I suppose). Each of these classes in this particular model is an entity that has got own identifier. Further exposing the entire model via REST API looks logical and fits Case 1.

Alternatively, you may stick to the DDD principles which pays a lot of attention to the differences between entities and value objects. From this point of view, Employee is an entity (with id) and Gender and Status might be good candidates to become value objects (embedded into the Employee entity; without identifiers). This fits Case 2.

Fully agree with you that Case 3 is a no go.

  • 1
    Unless there's a compelling reason, I wouldn't tightly couple a web service API to a database design. APIs and databases have different clients with different needs. – Eric Stein Feb 24 '17 at 15:50
  • Fully agree. That is just my observation of the authors API design (Gender and State expose ids). Adhering to the DDD principles, I'd design them as value objects in my Domain Model, and as a result, I'd not expose their identifiers via REST API (that is case 2). – Serhii Shushliapin Feb 27 '17 at 9:01
1

Case 2 is the only real option. You've already pointed out the problems with Case 3. Case 1 provides information the client of the API doesn't care about (the internal IDs for statuses, for example), and requires the client to know about those to construct a PUT request. Yes, the PUT request is a little more terse if it can use the IDs instead of the full strings, but specifying "FULL_TIME" or "PART_TIME" is what the client knows about, not that they happen to have some arbitrary IDs in your database.

Of course, you can document the IDs in your API documentation, but it is just as easy to document the valid values the strings are allowed to be, and probably clearer.

  • 2
    You should note this means that a gender or status can't ever be renamed. If clients are only working with the name, than the name is effectively the unique identifier. If clients need to start using an id as the unique identifier, then that's a breaking change. – Eric Stein Feb 24 '17 at 15:55
0

Enumerated data like you've got here is highly cacheable. Use links instead of objects. Use caching headers to allow clients to cache genders and statuses locally, say for 24 hours. Then only the first call of the day leaves the client machine. You can probably also configure caching to allow intermediate servers to hold the information, so some client requests don't even make it to your server.

GET /employees/1
{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "/genders/1",
    "status": "/statuses/3"
}

// clients populate their dropdown with
GET /genders
[
    {"id":1, "gender":"FEMALE"},
    {"id":2, "gender":"MALE"},
    ...
]

// clients look up an employee's gender with
GET /genders/1
{
    "id": 1,
    "gender": FEMALE
}

One downside is that /genders/1 isn't human-readable. You can instead use /genders/female, but then you can't ever change the name of a gender without breaking clients. That's the synthetic key vs. natural key tradeoff - flexibility vs. human-readability.

You might also want to consider putting all your lists of values under one common endpoint, such as

/lists/genders/1
/lists/statuses/3

This will clarify to clients that they are all basically key-value pairs that belong to different groupings.

0

I'd go for something in between 1 and 2, for the reasons David mentioned:

You do not want to expose the ID of things unless necessary.

However, exposing the ID might become necessary at some point in time. If that happens, backwards compatibility is a concern. So, i would do this:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "name": "FEMALE"
    },
    "status": {
        "name": "FULL_TIME"
    }
}

That has the same properties as option 2 has; but it has the benefit that adding the ID later on does not introduce a BC break:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "name": "FEMALE"
    },
    "status": {
        "id": 3,
        "name": "FULL_TIME"
    }
}

As Eric points out in the comments, this still uses the entity name as a uniqe identifier. If the ID is introduced later, the name must still remain the same because older clients could (or rather will) have coded towards it.

  • This approach introduce a new option. Having 2 different resources: the first for querying and the second foro create or update. While it might seem too much code, it makes easier the maintenance. – Laiv Feb 24 '17 at 7:32
  • @Laiv: i didnt suggest that; for now i'd Use the name for querying and updating. – marstato Feb 24 '17 at 8:03
  • 1
    You should note this means that a gender or status can't ever be renamed. If clients are only working with the name, than the name is effectively the unique identifier. If clients need to start using an id as the unique identifier, then that's a breaking change. – Eric Stein Feb 24 '17 at 15:55

Your Answer

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

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