4

this is one of the few moments I couldn't find the same question that I have at this place so I'm trying to describe my problem and hope to get some help an ideas!

Let's say...

I want to design a RESTful API for a domain model, that might have entities/resources like the following:

class Product
{
    String id;
    String name;
    Price price;
    Set<Tag> tags;
}


class Price
{
    String id;
    String currency;
    float amount;
}


class Tag
{
    String id;
    String name;
}

The API might look like:

GET /products
GET /products/<product-id>
PUT /prices/<price-id>?currency=EUR&amount=12.34
PATCH /products/<product-id>?name=updateOnlyName

When it comes to updating references:

PATCH /products/<product-id>?price=<price-id>
PATCH /products/<product-id>?price=

may set the Products' Price-reference to another existing Price, or delete this reference.

But how can I add a new reference of an existing Tag to a Product?

If I wanted to store that reference in a relational database, I needed a relationship table 'products_tags' for that many-to-many-relationship, which brings us to a clear solution:

POST /product_tags [product: <product-id>, tag: <tag-id>]

But a document-based NoSQL database (like MongoDB) could store this as a one-to-many-relationship for each Product, so I don't need to model a 'new resource' that has to be created to save a relationship.

But

POST /products/<product-id>/tags/ [name: ...]
    creates a new Tag (in a Product),

PUT /products/<product-id>/tags/<tag-id>?name=
    creates a new Tag with <tag-id> or replaces an existing 
    Tag with the same id (in a Product),

PATCH /products/<product-id>?tags=<tag-id>
    sets the Tag-list and doesn't add a new Tag, and

PATCH /products/<product-id>/tags/<tag-id>?name=... 
    sets a certain attribute of a Tag.

So I might want to say something link this:

ATTACH /products/<product-id>?tags=<tag-id>
ATTACH /products/<product-id>/tags?tag=<tag-id>

So the point is:

I don't want to create a new resource,

I don't want to set the attribute of a resource, but

I want to ADD a resource to another resources attribute, which is a set. ^^

Since everything is about resources, one could say:

I want to ATTACH a resource to another.

My question: Which Method is the right one and how should the URL look like?

Emanuel
  • 41
  • 3
  • Btw, note that your API isn't RESTful unless you application is driving the state and then you don't give a crap about what the URI looks like! You care about Verbs and semantics though. – Henrik Apr 05 '12 at 10:37

1 Answers1

3

Your REST is an application state driver, not aimed to be reflection of your entity relationships.

As such, there's no 'if this was the case in the db' in REST. That said, you have pretty good URIs.

You talk about IDs. What is a tag? Isn't a tag a simple string? Why does it have an id? Why isn't its id its namestring?

Why not have PUT /products/<product-id>/tags/tag_name=?

PUT is idempotent, so you are basically asserting the existance of a tag for the product referred to by product-id. If you send this request multiple times, you'd get 201 Created the first time and 200 OK the next time.

If you are building a simple system with a single concurrent user running on a single web server with no concurrency in requests, you may stop reading now

If someone in between goes and deletes that tag, your next put request would re-create the tag. Is this what you want?

With optimistic concurrency control, you would pass along the ETag a of the document everytime, and return 409 Conflict if you have a newer version b on the server and the diff, a..b cannot be reconciled. In the case of tags, you are just using PUT and DELETE verbs; so you wouldn't have to diff/look at reconciliation.

If you are building a moderately advanced concurrent system, with first-writer-wins semantics, running on a single sever, you can stop reading now

That said, I don't think you have considered your transactional boundaries. What are you modifying? A resource? No, you are modifying value objects of the product resource; its tags. So then, according to your model of resources, you should be using PATCH. Do you care about concurrency? Well, then you have much more to think about with regards to PATCH:

The RFC for HTTP PATCH says this:

With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version. The PATCH method affects the resource identified by the Request-URI, and it also MAY have side effects on other resources; i.e., new resources may be created, or existing ones modified, by the application of a PATCH.

PATCH is neither safe nor idempotent as defined by [RFC2616], Section 9.1.

I'm probably going to stop putting strange ideas in your head now. Comment if you want me to continue down this path a bit longer ;). Suffice to say that there are many more considerations that can be done.

Community
  • 1
  • 1
Henrik
  • 9,714
  • 5
  • 53
  • 87