Approach for exposing a sub-collection in a Hypermedia API

65 views
Skip to first unread message

Chris DaMour

unread,
May 9, 2014, 12:11:12 PM5/9/14
to api-...@googlegroups.com
I'm building a hypermedia api for my companies e-commerce application.  Currently we are exposing our promotions as a paged resource collection (using Spring-HATEOAS library primarily using the HAL media type FWIW).

The current requirements only necessitate the need to expose active promotions, however it's on the roadmap to expose all promotions in the future.  The active promotions will be very highly traffic'd by our own internal applications, much more than the promotions collection will ever need to be.

What i'm struggling with is if i should treat the collection of active promotions as a first class citizen of the API or just as a filter case of the [all] promotions collection.  Thus my API root document would include a link directly to the "active promotions" collection and then at some future date also a link to the "promotions" collection.  

I'm thinking because the active collection is so much more important to my use cases that this is the right choice.  It's akin to a view, but looking for opinions and experiences about doing this.

Also, i know URLs shouldn't matter too much in a Hypermedia API because i can change the underlying URL and a "good" client should be able to deal, but given I want to keep my URLs "cool" i'm also debating between exposing the collection at /activePromotions vs /promotions/active. Opinions on this are appreciated as well!

thanks for taking the time!

Pete Johanson

unread,
May 9, 2014, 2:13:50 PM5/9/14
to api-craft@googlegroups com

I'm all for exposing the active promotions as their own resource, that embed the promotions that apply to that view.

Doing so minimizes the 'surface area' of your API and frees you to make more changes behind the scenes than a generic filterable resource for all promotions.

-pete

--
You received this message because you are subscribed to the Google Groups "API Craft" group.
To unsubscribe from this group and stop receiving emails from it, send an email to api-craft+...@googlegroups.com.
Visit this group at http://groups.google.com/group/api-craft.
For more options, visit https://groups.google.com/d/optout.

Jack Repenning

unread,
May 9, 2014, 2:39:56 PM5/9/14
to api-...@googlegroups.com
On May 9, 2014, at 9:11 AM, Chris DaMour <drda...@gmail.com> wrote:

Also, i know URLs shouldn't matter too much in a Hypermedia API because i can change the underlying URL and a "good" client should be able to deal, but given I want to keep my URLs "cool" i'm also debating between exposing the collection at /activePromotions vs /promotions/active. Opinions on this are appreciated as well!

URLs should be opaque to the client code -- meaning such things as that the client code should not be constructing URLs, but rather  picking them from the predecessor response. 

But "client code" is not the only, nor even the most important customer of an API. The customer that really matters is the client *developer*, and while developers should no more be composing URLs than should code be, developers should--must--learn more about what your API does from the  patterns of the resource URLs, and should reason about the relationships among the resources and their behaviors. Resource URL design does matter, and it's not about "cool," it's about "comprehensible."

In that light, then, you haven't really told us much about this "active" distinction, so it's hard to say how the semantics of "active" should be expressed in URL regularities. Some possibilities:

If "active" just means "used more often than the others" -- "popular" or "featured" or similar -- then there probably aren't any differences in the available operations: there's no reason to make them separate resources. Furthermore, under this presumption, it seems likely that a given promotion might move freely between "active" and not, without changing its fundamental identity: there's good reason to make them the same resource. This would argue for some sort of subset or query model.

But maybe "active" means "performs activities on its own," like changing the UI, activating other promotions, something like that. In this case, we would expect that "active" promotions have callable functions not available on "inactive" promotions. This argues that they're a different class of object, and should have their own name, such as your /activePromotions idea.

-- 
Jack Repenning
Repenni...@gmail.com

signature.asc

Chris DaMour

unread,
May 9, 2014, 3:08:30 PM5/9/14
to api-...@googlegroups.com

Quite right on the ambiguity of active.  Its an either or concept. A promotion is active or inactive. 

It may be inactive because it expired or isn't available to a country or specific customer be a use of their cart items

Chris DaMour

unread,
May 9, 2014, 3:16:01 PM5/9/14
to api-...@googlegroups.com
to further clarify...there is no difference between an active promotion and an inactive promotion other that the state of being active.  Further all active promotions are still promotions.

Jack Repenning

unread,
May 9, 2014, 3:37:17 PM5/9/14
to api-...@googlegroups.com

On May 9, 2014, at 12:08 PM, Chris DaMour <drda...@gmail.com> wrote:

It may be inactive because it expired or isn't available to a country or specific customer be a use of their cart items

If the same promotion can be "active" for one customer yet "inactive" for another, then it's definitely a query, not a distinct resource.

-- 
Jack Repenning
Repenni...@gmail.com

signature.asc

Chris DaMour

unread,
May 9, 2014, 3:56:24 PM5/9/14
to api-...@googlegroups.com
not trying to be obtuse, but why is it definitely a query?

Jack Repenning

unread,
May 9, 2014, 4:22:42 PM5/9/14
to api-...@googlegroups.com
On May 9, 2014, at 12:56 PM, Chris DaMour <drda...@gmail.com> wrote:

not trying to be obtuse, but why is it definitely a query?

To forestall confusion, I didn't necessarily mean "it should be expressed after the '?' in the URL." Some kinds of paths are best understood as queries, too.

A resource is an object. An object has state, behavior, and identity. Saying "the resource is 'active' for one customer but 'inactive' for another" demonstrates that the two customers are accessing the same resource / object / identity. The resource type has some rather complex behaviors (such as the customer-sensitivity of "active"), but the phrasing itself of the "active/inactive" claim presupposes we're talking about the same resource / object / identity. 

Finely dividing our reflexive language about things, pickily pettifogging the phrasing, is a good way to expose how we really think about things: we said it was one object, we must naturally think of it as one object. And we should encourage and expect the customer-programming to think of things in the same way we do; otherwise, confusion will arise somewhere, sooner or later.

In resource URL terms, we did not say "/promotions/10 is not available to customers in Taiwan but /promotions/15 is available to customers in Yugoslavia." We make two statements about one object, not one statement about each of two objects. 

It's common to have several URLs that name the same object. For example, in the system we're discussing, we might define /carts/15/promotions as the list of promotions that presently apply to that cart, and /carts/15/promotions/39 might be one of them. But there might also be a /carts/57/promotions/39, and it should mean the same promotion ("30% off for fans of Edgar Allan Poe"). And there should also be a canonical address for the  promotion, probably /promotions/39. But all URLs ending in "s" (all collections) are a form of query, and commonly (including this case) they can have complex selection criteria. "GET /cards/15/promotions" should return a list that includes #39, but it probably doesn't include others (let's say #42), because they're not "active."  But "GET /promotions" might include #42 (if it's 'available' to the caller). It's quite common to implicitly filter a collection for various business-logic reasons, and that's what seems to be going on here.

-- 
Jack Repenning
Repenni...@gmail.com

signature.asc

Chris DaMour

unread,
May 9, 2014, 4:45:35 PM5/9/14
to api-...@googlegroups.com
thanks for that deeper explination, that makes some good sense.

I am under the impression that filtering with business logic is exactly what the server is supposed to be doing in a hypermedia api.  I do NOT plan to expose a property on my Promotion resource named active because this status is based more on context than an innate property of the promotion, contrary to something like promotion name which is not subject to any context.

You bring up interesting points with the multiple URLs.  Right after promotions is going to be products and it's going to make sense to have /products/{productId}/promotions as a collection of promotion resources that are for that specific product.

So i think i'm hearing that addressing the active promotions collection as it's own resource seems reasonable.  

Any opinions on the URL structure?
/promotions/active
/cart/15/promotions/active
/products/55/promotions active

vs

/activePromotions
/cart/15/activePromotions
/products/55/activePromotions


this is probably more a ? of what conventions are generally in use and what's most comfortable for devs?

Jack Repenning

unread,
May 9, 2014, 4:56:53 PM5/9/14
to api-...@googlegroups.com
On May 9, 2014, at 1:45 PM, Chris DaMour <drda...@gmail.com> wrote:

So i think i'm hearing that addressing the active promotions collection as it's own resource seems reasonable.  

I think I agree with what you meant, there, but I think you used some of the words unconventionally. Ordinarily that's OK, but this particular conversation is precisely about these very conventions, so being sure we mean the same things by the same words takes on a peculiar importance. So:

A collection is not "a resource," it is "a collection *of* *resources*." Addressing a promotions collection is reasonable. Expecting that collection's contents to depend on contexts like "cart" or "product" is reasonable.

Any opinions on the URL structure?
/promotions/active
/cart/15/promotions/active
/products/55/promotions active

vs

/activePromotions
/cart/15/activePromotions
/products/55/activePromotions

As  I understand your domain, the two first items are not meaningful: we can't know which promotions are "active" without some context like "cart" or "product." If there's some unexpressed context (such as authentication context or "RBAC"), then you can just say "promotions".

So I like:

/promotions # all this user is allowed to know about
/carts/15/promotions # all "active" for this cart
/products/55/promotions # all "active" for this product

-- 
Jack Repenning
Repenni...@gmail.com

signature.asc

Chris DaMour

unread,
May 9, 2014, 5:14:55 PM5/9/14
to api-...@googlegroups.com
thank you for the corrections on terminology, what you propose is what i meant.

After re-reading the whole thread, i now believe active is the wrong word as it's clearly conveying the wrong meaning.  I think available would be the more accurate term to use.

Some promotions are available and others are not.  Availability of a promotion is tied to a lot of external context and properties of the promotion itself.  Additionally there is a need for our clients to consume all promotions whether they are available or not, but this is of secondary concern.

Some quick use cases (i probably should have started with these, i think i'm learning that good api design is worthless without them):

Anonymous use navigates to product X1 on May 5th
Application presents the 3 promotions for X1 that are available on may 5th
Same user executes the login process successfully and navigates to product X1
Application presents the 3 promotions for X1 that are available on may 5th
Application presents 1 promotion for X1 available because the authenticated user has previously purchased the product and there's a repeat purchaser promotion in the system (business logic here)


User has added product X1 and X2 to their cart
User navigates to cart page
Application presents promotion that is available because X1 & X2 are in the cart (more business logic)

Does this clear some things up?

Jack Repenning

unread,
May 9, 2014, 6:29:55 PM5/9/14
to api-...@googlegroups.com
On May 9, 2014, at 2:14 PM, Chris DaMour <drda...@gmail.com> wrote:

> After re-reading the whole thread, i now believe active is the wrong word as it's clearly conveying the wrong meaning. I think available would be the more accurate term to use.

Yeah, I was beginning to grasp that.

Whatever you call it, this "available" thing isn't a property of the object/resource, it's a relation among several of your entities. If I dig my hand deep into your barrel o' promotions, drag one out at random, and ask you "is this one available," then you can only respond with a bunch of questions: "available to which cart? for which product? with which purchase history?" and so on. The object itself does not define the relation; other info about context, plus the business logic, are just as determining as the promotion itself.

That, said at great length with two-dollar words, is what in simpler language we call "a query" ;-)

It may even be less than honest (and if so, it would be less than advisable) to talk about /carts/37/promotions/, since I think I hear that even the collection "the promotions available to this cart" might be colored by things having nothing to do with the cart: maybe it's almost Mothers Day and you have a promotion on flowers "available"! So I'm starting to suspect that even the "/carts/37/promotions" path is dubious, and incline towards "GET /promotions?cart=37&contents=SKU,SKU,SKU&..." Two kinds of selectors should not appear in the URL (either the path or the query parameters): security stuff like passwords and session IDs (URLs get logged), and global context things like the current date (we all have watches). You can use them as required by your business rules, of course, but don't put 'em in the URL.

I also note that you're talking about a "human to machine" (h2m) API (of course, there's a browser and probably some JavaScript at the human end--there usually is--but there's a human). In contrast to a "machine to machine" (m2m) API, writing h2m usually means you yourself are both the API-provider-developer and the API-consumer-developer. I've justified some of what I've suggested under m2m assumptions: the provider-developer is communicating with the consumer-developer. If they're both "you," you can take some liberties, because you'll already know about them. But don't get too carried away with that: it's amazing how much you can forget in the week between writing the service and writing the consumer!


--
Jack Repenning
Repenni...@gmail.com

signature.asc

Chris DaMour

unread,
May 9, 2014, 6:34:11 PM5/9/14
to api-...@googlegroups.com
a wise lesson, i'm lucky enough that we are big enough that the consuming team and the producing team are not the same, so i do not get to cheat that much.

you've brought up a bunch of interesting points, many thanks!
Reply all
Reply to author
Forward
0 new messages