Show HN: Deck of Cards – A playing card API
deckofcardsapi.com
deckofcardsapi.com
Next time maybe all your GETs will be RESTful, maybe not. Either way, you've done a thing, put it out there, and are presumably learning a lot by doing so.
Great work.
[0] http://carl.flax.ie/dothingstellpeople.html [1] http://www.garann.com/dev/2013/how-to-blog-about-code-and-gi...
So good job for that too... I think this Deck of Cards API would make a great case study for analyzing and discussing the varying takes on how a RESTful interface should handle certain situations which aren't as straightforward as the typical blog or todo list examples.
People release things they have worked long and hard on, only to get a bunch of negative comments picking on holes and mistakes.
The weird thing is they are the lucky ones; it's pretty damn hard to make it onto the front-page of HN now, regardless of the quality of the content. (Don't believe me? Take a look at how many times some articles have been reposted and the disparity in number of votes.)
Getting feedback should be great, but not many of us are good at it. Generally, we only comment on things we care about or find interesting (ignoring the stuff that is so bad it deserves harsh criticism like storing passwords in plain-text), but we forget to say that in the comment and only mention the minor flaw we saw.
I don't have a solution to this, other than to tell posters to be prepared for criticism and to take each up-vote as a major token of respect in what you've built.
A few comments/suggestions:
* You have DEBUG = True in your production Django config. (eg. http://deckofcardsapi.com/api/)
* You are mutating state with HTTP GET, this is an anti-pattern for a number of reasons. The common one I use with people I coach is that browsers/proxies will happily cache GET requests unless told not to, but there are a number of other reasons if you read up on REST [1].
* Being a public API in a well known domain this is a good opportunity to make the API self documenting & navigable with a hypermedia format. (eg HAL, Siren, JSON-LD) [2]
[1] http://martinfowler.com/articles/richardsonMaturityModel.htm...
[2] http://sookocheff.com/posts/2014-03-11-on-choosing-a-hyperme...
I agree. However, this is the first API I've seen where a mutable GET actually makes sense. Drawing a card is definitely a GET. How would you do it instead?
I see where you're coming from ("I'm drawing / taking / GETting some cards"), but drawing cards is not an idempotent action. I would implement it as a POST.
But this kind of bike shedding drives me mad because oh my lord who gives a crap just build software and document it properly.
Looking at the cards in your hands, if the server maintains your hand state, would be a GET though.
Is an api that is consistent, fast and documented well, but uses only GET requests for everything including state changes really a horrible api?
Yes, it is. Because HTTP clients (including browser) rely on the common method properties as defined by the HTTP standard (GET having no side effects, PUT being idempotent, etc.). The simplest example is caching / proxying, but there is other behaviour.
I strong recommend acutally taking a look at the HTTP standard. The RFCs such as RFC 7231 are well organized and human readable. For the definition of the HTTP verbs, see:
RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
4.2 Common Method Properties
https://tools.ietf.org/html/rfc7231#section-4.2DELETE /api/<deck-id>/top?count=2
------
200 Ok
8♣
10♥
> Idempotence refers to the state of the system after the request has completed
> ...
> The key bit there is the side-effects of N > 0 identical requests is the same as for a single request.
http://stackoverflow.com/questions/4088350/is-rest-delete-re...
Sometimes RPC-style interfaces like that are ok, but don't pretend that they are RESTful.
Also PUTs are supposed to be idempotent, so don't use a PUT for this.
In other words, an action parameter which is actually used and which doesn't violate the HTTP standard, well, that almost certainly means that all requests are POST requests.
However, if all requests are POST, no matter if they are side-effect-free or not, no matter if they are idempotent or not, then this is not very REST-like.
Note that it is not important whether you think this argument does or doesn't holds for your particular API. My point is that this whole judgement is solely about HTTP methods, and has nothing to do with URLs being opaque.
But the examples given by anilgulechas were 'action=draw' and 'action=shuffle'. Neither are idempotent, let alone safe, so presumably the only method to use in either case would be POST (notwithstanding anilgulecha's suggestion of a PUT).
So we need to distinguish between POSTs requesting a draw, and POSTs requesting a shuffle. Two options:
a) indicate it in the POST's body
b) indicate it in the URL.
If we go for (b), the query string seems as good a place as any.
What have I got wrong? Where would this violate REST (or the HTTP spec)?
Other than learning, what's the benefit here?
Martin Fowler does a good job explaining it (http://martinfowler.com/articles/richardsonMaturityModel.htm...).
As does Mike Amundsen (http://www.infoq.com/presentations/Building-Hypermedia-API).
<openSlotList>
<slot id = "1234" doctor = "mjones" start = "1400" end = "1450">
<link rel = "/linkrels/slot/book" uri = "/slots/1234"/>
</slot>
<slot id = "5678" doctor = "mjones" start = "1600" end = "1650">
<link rel = "/linkrels/slot/book" uri = "/slots/5678"/>
</slot>
</openSlotList>
Each slot now has a link element which contains a URI to tell us how to book an appointment.
Well, not really - we can only guess that by the fact that the link ends in "book".We also don't know if we are supposed to GET, PUT, DELETE, POST.. or what data we are supposed to send to the url to actually make the booking.
In the human readable description of the "/linksrels/slot/book" link relation, information should be given about what generally can be done with such links (like if GET or POST is supported, etc). This allows you to build your client applications. The value of the actual link (the 'uri' attribute) should be completely opaque to the client.
2. GET makes sense here. If anything, it would be a PATCH. But it's silly to get bogged down in details.
Those "details" are quite important, as already explained in several other comments:
https://news.ycombinator.com/item?id=9523225
https://news.ycombinator.com/item?id=9523106
https://news.ycombinator.com/item?id=9523075
* The API doesn't really follow HTTP. It allows GET for state-modifying actions, which breaks assumptions that could be made by browsers, client libraries, caches, and proxies. Also, the "success" field seems fishy; a 200 response code is the usual HTTP way to indicate success.
* The API is not resource-oriented; the URLs "shuffle" and "draw" are verbs that describe the action to take. In a "real" REST API, URLs define conceptual resources (nouns), and you interact with the API by interacting with those resources.
* The API does not use hypermedia.
Hypermedia and HTTP compliance are discussed elsewhere in this thread, but I wonder if others have an opinion on the fact that the API isn't resource-oriented.
This "resource-oriented" rule has always bugged me about REST APIs. My impression has been that REST advocates claim that pretty much every API can and should be implemented with a CRUD-like interface (where the entire API consists of performing GET, POST, PUT, and DELETE on a conceptual set of resources), but it seems like that would be really awkward for this "deck of cards" use case. Is there value in trying to use resources here? What might it look like?
SHUFFLE /api/deck/<deck-id>
DRAW /api/deck/<deck-id>?count=2
Things become a lot simpler to use, document and implement.
I'd actually take advantage of the custom media-type option and return something much more succinct:
DRAW /api/deck/<deck-id>?count=2
200 OK
8♣
Have the response body be UTF-8 and use the card face runes in the response. Nice and simple.
You dont necessarily have to map your REST API to existing HTTP verbs and media types - in fact the real benefit in a use case like this is that you can implement something appropriate
Think WebDAV; specify the API via an RFC like document and implement it that way the end result is not only easier to use, document and implement, other providers could implement compatible API implementations.
Seriously, has anyone here actually READ Fieldings paper?
But it wasn't hard to find something about standard methods and media types.
> REST enables intermediate processing by constraining messages to be self-descriptive: interaction is stateless between requests, standard methods and media types are used to indicate semantics and exchange information, and responses explicitly indicate cacheability.
https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arc...
I'm not sure if your response is an attempt at a counter argument, but the line "... standard methods and media types are used to indicate semantics and exchange information, and responses explicitly indicate cacheability" is exactly the point I'm making.
If the original author was keen to make their API RESTful (which I'm not particularly sure they are. Aside from the couple of minor issues people have already raised, there is nothing wrong with a JSON API served over HTTP. It's just not RESTful), they would standardize their media-types (by registering them with IANA) and document their custom HTTP verbs via a RFC. That is what is required to be truely RESTful.
In the example I gave, showed that by adhering to the principles of REST, they could actually simplify their API dramatically whilst allowing others to lean on their work (by implementing their own services that use their newly registered media-types).
Again, all this is documented in Fieldings dissertation. It is dense reading and very academic, but it's worth reading. I personally found it very enlightening.
POST /decks : returns a url to a new deck
GET /decks/<id> : returns a deck
POST /decks/<id>/draw : returns a url to a card
POST /decks/<id>/shuffle : returns a url to the deck
GET /cards/<id> : returns a card drawn from a deck
The draw/shuffle verbs arguably could be implemented like this: POST /cards : returns a url to a card (post body having deck id)
POST /shuffles : returns a url to the deck (post body having deck id)
The cards POST makes a lot of sense, now that I look at it I think I would use that interface. And you could argue that the shuffles resource makes sense as at some point you may want to record and share when someone shuffles the deck. POST /decks/<id>/draw : returns a url to a card
POST /decks/<id>/shuffle : returns a url to the deck
Are you adding a "draw" to deck <id>? POST /cards : returns a url to a card (post body having deck id)
POST /shuffles : returns a url to the deck (post body having deck id)
Are you creating a card or a shuffle?Here's how things should be, in my opinion:
DELETE /deckCard/<deckId>
This removes a card from an abstract entity representing the relation between decks and cards. Thus, it draws a card and returns it, sort of like popping something off a stack. PUT /decks/<id> with body {shuffle: true}
This edits the abstract "shuffle" attribute of deck <id>, shuffling the deck. This is indeed odd, but I'm afraid it's the best you can do for a mutating request. I would recommend a non-mutating request that makes a new deck out of an old deck, perhaps using "source" as an abstract attribute. POST /decks with body {source: <oldId>}
It just goes to show that while REST is a good standard for CRUD operations, and is surprisingly extensible for non-CRUD operations, it can get confusing and become a real pain. Should one just deal with it or move to e.g. RPC? I haven't figured this out.Commenting directly on you suggestions:
DELETE /deckCard/<deckId>
This means each DELETE on this url would result in a different deck state which isn't idempotent as DELETE is mean to be. PUT /decks/<id> with body {shuffle: true}
Same with this, the resource would end up in a new state each time making it unsafe to do repeatedly as the HTTP spec says.I think the issue is REST is taught with a CRUD view point and people have difficulty thinking about it in other ways. Also the English meanings of the HTTP verbs get confused with their HTTP meanings which doesn't help.
I liked this blog post which talks about the concept of "REST without PUT".
http://www.thoughtworks.com/insights/blog/rest-api-design-re...
edit: you've edited you comment since I started my reply:
I like your idea of replacing shuffle with a new deck:
POST /decks with body {source: <oldId>}
You could then do things like lock the source deck which would make it easier to implement a multi-client system where you cannot draw from a deck until you have been informed it has been shuffled.You can create new resources, just as I have, it just doesn't make sense to "POST /cards" when you're not making a new card at all.
A deck only makes sense in the context of a game, a draw only makes sense in the context of moving the card from the deck to some other container (hand, discard pile, arbitrary pile on the table).
I also like to model games as sequential actions (because that's how they work) so my API would be more like:
GET /game/<id>/turn/<index>/decks/<id>/card/<index> <- 1 or 0 for top card, other numbers to view more than one
Then your draw action is a move the card from the deck to one of the other places, either with a POST to the new place, that redirects you to the next turn of that place, or with a PUT to the card itself if you have some "location" field on the card that can be updated.
The question that strikes me though, is whether it makes sense at all for a client to be controlling the draw in a game of CaH, as it's not an optional step. Why not just have the model automatically put cards back in the players hands when they make their moves?
I suppose it is a bit like de-normalizing your database for performance.
For a situation like this, I would keep it as restful as practically possible. Since the shuffle mutates the state, it should be a post/put. Since it doesn't create a new resource,you could argue it should be put, but you don't have new data anyway, so the point is kind of moot.
I'd probably collect my custom verbs behind a common path to make it more clear, like
/api/decks/mydeckid/verbs/shuffle
Or to make it stand out more: /api/decks/mydeckid?verb=shuffle
The response would use http error/success codes like any rest interface.As far as constructing the XML, you should be able to use something like xmlbuilderjs[0]. That said, I completely agree that dealing with REST APIs in the browser is far more practical.
You can do that via https://help.github.com/articles/remove-sensitive-data/, but once it's been cloned, the file is of course out of the toothpaste tube so to speak :-)
Edit: Could be an excellent idea - 8 of Clubs is:
* 28kb as a PNG.
* 14kb as an SVG
* 4kb as a gzipped SVG
It was a bit fiddly but definitely wins on file size. I would have used SVG for the face cards but they really killed performance with animation unfortunately, so I went with a compressed PNG (20kb for all 3).
But they seem familiar. I can't help but wonder, are they infringing on any copyrights?
These poker sized cards have been created and hand optimized in various vector formats (.eps and .svg) using the open source .svg graphics editor Inkscape http://www.inkscape.org.
....
Generally, this means the cards are free to use (commercially and non commercially) but must be properly attributed (see "README.txt" file) and (The library itself, not your project) re-distributed only under the same license.
One maybe useful thing about this api is the card images, but there are also probably plenty of decks in the public domain.
Depends. If you're writing a game you wanted the player to trust you might want to use an external shuffling service so you can easily show proof-of-deck after the game is over.
Of course, you could do this simply by giving the player a cryptographic hash of the shuffled deck beforehand, e.g. SHA-256(5H,4D,AS...)
https://news.ycombinator.com/item?id=9462184
Would be interesting to hear if some of the issues raised in the previous submission were fixed or updated.
https://github.com/crobertsbmw/deckofcards/blob/master/stati...
> After two weeks, if actions have been made on the deck then we throw it away.
Might be "if no actions"?
Nit: drawing from an empty deck does not throw an error:
> curl http://deckofcardsapi.com/api/draw/i763hn8lcg0e/
{"remaining": 0, "cards": [], "deck_id": "i763hn8lcg0e", "success": true}
As does drawing a comically large number of cards:
> http://deckofcardsapi.com/api/draw/vzlem7q4jhna/?count=10000...;
(...) "success": true}\n
1. Client can produce a signature that it drew the k'th card from the deck for any k.
2. Client can produce a signature that the k'th card is X if it drew the k'th card.
3. Client can produce a signature that card X came from any arbitrary set of card indices containing k if it drew the k'th card. (This is the hardest one.) If it's easier, you could get most of this even restricting it to sets of cards that the client has drawn.
With just that, I bet you could make a protocol for most card games where no one can cheat, where the central server's role is only to give out information about k'th card one and only one time. Certainly it's enough for blackjack, most forms of poker, hearts, and go fish. Anyone have a game that would be difficult? Maybe one where hidden cards get passed from player to player.
Different types of decks, handling of a discard, shuffling a card into the deck, shuffling the discard pile into the deck, handling hands, milling a card from the deck, multiple decks acting as one...
Though the simplicity of this api currently just having a deck that you take cards from is neat.
Great job on putting the project out there!
An idea: Multiple options for deck images - obviously would take time on the artwork, but you could set this in your initial call for a deck (Or maybe even make it so you can change it whenever - if someone would have a need for that for some reason). May be fun.
Great work!
Shielding yourself from criticism using an arbitrary criteria only perpetuates ignorance.
Would like an option to set parameters of a deck. Some card games use only 8 cards from a set and not the whole 13.
1. An example of how this could be used.
2. How to do something other than just shuffling the cards.
But how are you shuffling the cards, and how are you identifying unique shuffled decks?
It looks like the deck_id is 12 characters, numbers or lower-case letters. That gives a range of 36^12 or around 4.7 * 10^18. The total range of a physical deck of shuffled cards is 52! or around 8 * 10^67.
It's possible I have misunderstood what deck_id is for.
The model stores the state of the deck, including things like order and remaining cards and how many "decks" of 52 cards are in play.
I would use this API in the future.
The reason for GET and POST is that it was easier for my year ago self to copy and paste a GET url into chrome and hit Enter and see what happens.
numbers = ['2','3','4','5','6','7','8','9','10','J','K','Q','A']
suits = ['CLUBS', 'SPADES', 'HEARTS', 'DIAMONDS']
deck = [(num,suit) for num in numbers for suit in suits]
random.shuffle(deck)
draw_card = lambda deck: deck.pop() -people to spam
-
-weber@kcnext.com
-info@cremalab.com
-andy@lovekc.org
-info@truckily.com
-info@secondlifestudios.com
-help@getflywheel.com
-hello@elevate.co
-hello@goodtwin.co
-Info@ObjectLateral.com
-editor@siliconprairienews.com
-info@squareoffs.com
-josh@socialchangenation.com
-websupport@psicurity.com
-sales@rfp365.com
-travis@rivetcreative.com
-info@phlyppgear.com
-kyle@feralfew.com
-matt@feralfew.com
-zach@feralfew.com
-courtney@feralfew.com
-ben@homesforhackers.com
-info@cyberjammer.net
-info@SponsorShout.com
https://github.com/crobertsbmw/deckofcards/commit/2b5675df52...