Fork me on GitHub

I’m curious how a resource in yada and/or strict RESTful approach would conceptually have more than one POST actions for a resource. For example take an imaginary API for cloud resources. All endpoints should take 1-n resource uuids as args.

Option 1: POST takes multiple :fn/:args messages.
POST /instance
- Start
- Stop
- Restart

Option 2: Multiple urls
POST /instance/start
POST /instance/stop
POST /instance/restart

Option 3: More CQRS actions endpoint
POST /action :fn/:args message 
Trouble with Options 1 and 3 is both have variable messages making the request/response dispatch on type and would be ambiguous potentially. You’d probably lose the benefit of swagger there too. Problem with Option 2 feels more verbose and seems to fly in the face of endpoint as resource location with multiple verbs. I’d love to hear the yada perspective on something like this! Cheers!


Option 3: query params ? POST /foo?method=stop


Option 4: PUT /instance/running true


@kingoftheknoll: In REST, verbs are discouraged. Resources are nouns. POST /instances returns 201 Location /instances/af4b3c. Shutdown that instance with DELETE /instances/af4b3c


Your suggestion seems a bit like RPC which is creating a bespoke API. One of the REST constraints is a common API


@lmergen Option 3 would be a message in the body. @malcolmsparks resources as nouns makes great sense however, using HTTP methods as verbs acting on a resource doesn’t work once you have 15+ actions to can take on it. Thus you’re only given two choices to increase specificity 1) put the action type in body or 2) specify in the url.


is this really the best approach to have 15 different actions on a single object ?


that sounds awefully un-REST to me


the HTTP spec is actually quite strict in how you’re supposed to model these things


Hey there! How do I start up a yada-based web service from command line? I can do it from my repl, but not from command line without the service exiting again. I’m using leiningen


From the repl, I do

(yada/listener [“/endpoint” (yada/resource {:produces “text/plain” :response (fn [ctx] “hello world”)})])


@grav: add a @(promise) after you start the server


@grav this is due to a recent(ish) update in aleph. You should use wait-for-close


@malcolmsparks I'm updating an old project and I think I need to replace the old yada.bidi/partial :authorization with a method of yada.authorization/validate. The problem is that I get an error when I try to return {:status 403}. Am I supposed to return something else?


Is there a way to make the swagger UI show the endpoints in order of path length, or is that something completely out of yada's reach?


i'd prefer them in the same order as my bidi routes @frozenlock


Yeah... in my case they are both the same 😛


@frozenlock are you returning the response, i.e. (:response ctx) - if you just return a map yada will treat it as data


@frozenlock thanks for the hint re: .wait-for-close. But it seems to be an 0.4.2-alpha6 feature, according to the github link. Is there a method that works with 0.4.1? Would that be @(promise), as suggested by @malcolmsparks?


@lmergen I can’t change the fact that I have many actions. So I made the suggestion that I make multiple ‘resources’ but then that breaks convention with endpoints being nouns. The only way to do this in a true REST manner it seems would be to invent my own methods in addition to GET PUT POST DELETE which of course is silly. I think it comes down to a strict RESTful interface doesn’t work if your interface is any more complicated than CRUD.


I am trying to create a WFS endpoint for a Geoserver. The Geoserver sends accept header "accept: *; q=.2” which seems to crash yada. Any hints?


@kingoftheknoll: in that case I would probably settle with a POST and put a {"action": "create"} in the body or something like that


@lmergen I think that’s what I like but with Yada can I make is so swagger knows about multiple message types. Those actions might have different message bodies


it gets tricky, as soon as you start doing complicated stuff such as optionals and enums/sets the documentation starts to get weird.


schema validation works fine though, and the code is perfectly sound and reasonable


that’s why I keep going back to multiple endpoints. I mean when I comes down to it, the goal is to auto generate these endpoints for customers and documentation. However internally I’m going to have a single endpoint that takes any action message.


which comes to another question, can I skip swagger validation on some endpoints


yes but while that is an idea, you should try sticking to rest as close as possible. so DELETE /instance/:id and not POST /instance/:id/delete. POST /instance/:id/restart is fine though.


Another thing that’s tough is the requirements that all actions take a list of :ids to work on. I’m never guaranteed to be working with one resource. So the conceptual metaphor weakens.


So the lesser of all evils would be to have POST endpoints for all actions that take a list of ids.


That way I can spec all messages with swagger


you must start considering your set as a representation of resources. it is not the set you are modifying, it is each one of these resources. as such, your set should filter the resources as a parameter.


and your api design should reflect that


I think you should work with a base resource like /instances


which you could GET


or you could filter the set using query parameters DELETE /instances?ids=5,8,9


and then assuming you’d specify PUT /instances/restart?ids=2,3,4 for other actions


I could see you killing the max length of a url though pretty quick


no never a put. puts are supposed to be idempotent. it should be a post.


btw, I just thought about http's Range field, which would be a great fit for resource collections.


it's also recommended in several places I see


anyway I think you just have a problem that your actions don't map to REST in a sensible way. I would suggest mapping these actions as fields of a resource, so POST /instance/:id/restart, or POST /instances/restart, and I think I would use the Range header.


@lmergen Thanks for taking the time to chat with me! I'll check that out and I really appreciate the insight!


Seems “*” in accept header crashes yada 1.1.31. I’ve created an issue here:


I guess it’s a special case, so maybe just convert it to “*/*” in string->media-type?


Of course it’s not that easy, since it could be “*; q=0.2" or similar. That’s what Geoserver sends me...


@kingoftheknoll: no problem! it's an annoying problem with many possible solutions, so I would suggest you just choose one that seems right to you. be careful about some of the implied semantics of the http methods such as PUT, though.


Totally, thanks so much