thentos
thentos copied to clipboard
API questions
A lot of questions are popping up related to API design in #229, and that's not quite the right place for all of them. Most of these pertain to thentos-core, since in thentos-adhocracy the API we need is externally constrained much more.
When do we return complex objects?
We have an endpoint to retrieve user email. Another one to retrieve user name. Why do we split those up, rather than have a single endpoint that returns an object with both? More generally, when do we split up?
There are also endpoints that return whether a session exists (a Bool
). And then another one that returns metadata about the session. Presumably that second endpoint returns a 404 if the endpoint doesn't exist. So why have the first? It also doesn't conform very well to the idea of thinking in term of resources. The session is the resource. By default we should probably follow normal conventions:
/resource [POST]
/resource/:id [GET, PUT, DELETE]
How do we deal with duplication?
There's starting to be some duplication in functionality (with very different code paths) between the servant and snap servers. This seems bad. How do we deal with?
How should we represent sum types?
One suggestion was to add an extra type
field.
What endpoints do we actually need?
If we're redesigning, it'd be important to know what we need and what we don't. Does there need to be an endpoint for deleting session tokens? If a frontend wants to end a session, should it just not send the session token instead? It'd be nice to be concrete about this. E.g.:
CRUD user
CRUD service
POST/GET service/:servicename/session
What should POST return?
I think a common practice is to either redirect, or return the link to the created resource. Should we return this in the body? In the Location
header? Not at all?
Service and Thentos Session
Should these be unified somehow?
EDIT: Actually, I'm still not clear on how they interact and what their different purposes are.
Point and Extensibility
As thentos-adhocracy shows, when we need to have slightly different endpoints and JSON formats, we don't really gain much from the definition of the API in thentos-core. Is this the right way of designing things?
EDIT: As an example of an option, which I don't think is necessarily a good one but is a point in the design space to consider, we could end up with a an API type parametrised by a few paths:
type ThentosAPI (userPath :: Symbol) (servicePath :: Symbol) ....
And then making it fit a specific format would be a question of just instantiating that type appropriately, and providing (orphan) JSON instances for the datatypes involved in the interface.
(The reason is may not be a good idea is because it might not always work, and just end up complicating things. I think I'd prefer to have a couple of concrete examples - more than just A3 - of having to meet an API spec before trying to abstract or generalize over them.)
EDIT2: Even A3 itself doesn't seem like it'd fit neatly into this pattern.
My viewpoints on some of this stuff:
We have an endpoint to retrieve user email. Another one to retrieve user name. Why do we split those up, rather than have a single endpoint that returns an object with both?
Yes, single endpoint. Like you said later:
CRUD user
CRUD service
There are also endpoints that return whether a session exists (a Bool). And then another one that returns metadata about the session.
I'd suspect that we can (and should) simple delete this stuff.
What should POST return?
Random Google result from Best Practices for Designing a Pragmatic RESTful API:
A PUT, POST or PATCH call may make modifications to fields of the underlying resource that weren't part of the provided parameters (for example: created_at or updated_at timestamps). To prevent an API consumer from having to hit the API again for an updated representation, have the API return the updated (or created) representation as part of the response.
In case of a POST that resulted in a creation, use a HTTP 201 status code and include a Location header that points to the URL of the new resource.
That sounds pretty reasonable.
This table might also be useful: Using HTTP Methods for RESTful Services.
Random Google result ...
That sounds pretty good! Maybe:
type CRUD resource i cts a =
resource :> ReqBody cts a :> Post200 a
:<|> resource :> Capture "id" i :> Get cts a
:<|> resource :> Capture "id" i :> ReqBody cts a :> Put cts a
:<|> resource :> Capture "id" i :> Delete cts ()
And then
type API = CRUD "user" UserId '[JSON] UserData
:<|> CRUD "service" ServiceID '[JSON] ServiceData
Or some such.
(Or Post '[JSON] (Headers '["Location", Link] ())
)