My partner and I are developing a webapp and we expose our Spring backend logic as a Restful API. Our classes have various Many-to-one/one-to-many relationships but not all Frontend use cases require all pieces of information within a class.
Case in point: say [GET] /api/v1/person returns a list of people. The call also supports sorting and pagination through the pageSize, page, sortColumn and sortDirection params. When retrieving person objects, some frontend features require only id and name, others require more details and the final set of features require the full details (including one-to-many relationship objects).
There seem to be 2 ways to implement this - either make 3 specific calls that return the desired #JsonView() data
[GET] /api/v1/person/feature1
[GET] /api/v1/person/feature2
[GET] /api/v1/person/feature3
or allow for one additional view param that lets the user pick the particular view they need for a given request
[GET] /api/v1/person?view=basic
[GET] /api/v1/person?view=detailed
[GET] /api/v1/person?view=full
At the moment we've picked the second approach as it seems to provide more flexibility. Also it reduces the number of REST controller methods we need to maintain.
So my question is whether there is a best practice on this matter. Passing the view param seems very flexible but at the same time it doesn't seem anyone else is using this approach so we figured maybe we are doing something wrong.
Just wanted to check if there is a well established "correct" way of filtering the columns of your result objects - separate specific calls or a general API call that takes in a view parameter.
Thanks in advance!
Related
My line o thinking is this:
get evaluations //would retrieve all evaluations of the system. (in practice never used).
post evaluations create a new evaluation
Following the Rest pattern
get evaluations/1 //details of the evaluation with id "1"
But I was using
get evaluation/{product_id}
To retrieve all evaluations of a product.
For what I know of Rest design this is not good. It would be something like this:
evaluations?product_id=5
Now, what it would be an endpoint to retrieve all reviews of a specific client? I am using Auth 2.0.
I am really confused.
Is it possible to shove it all in one controller?
In summary, I would like to know if the endpoint evaluations?product_id=9 is suitable to retrieve all evaluations of a product. And what would be and endpoint to retrieve all evaluations of a client that has a token.
You have many approachs here:
Separate Endpoints:
Suche as:
get /users/{user_id}/evaluations
get /products/{product_ID}/evaluations
get /evaluations/{evaluation_id}
single endpoint
get /evaluations/{type}/{id}
And in your controller implement a logic to retrieve evaluations according to the type (user, product,...) and the id of the selected type.
Personally, I prefer the first one.
I am having someone create a bunch of templates (themes) for a website, and want to keep data passed to the views flexible.
For example, with the users in the system I want to be able to supply the top x users and the most recent x users. In my controller I don't want to pass this data to the view, because he might just need the top 5 users and I am querying the top 10 - or worse, I might only get the top 5 and he wants the top 10.
I am thinking there would be two ways to do this.
1 - A view "helpers" file, which could contain functions like. getTopUsers($count) and getNewestUsers($count) which would do the model / repo call.
2 - Create a view presenter to keep these extra functions. I've had a look and there seems to be two main presenter packages - https://github.com/ShawnMcCool/laravel-auto-presenter and https://github.com/laracasts/Presenter
Maybe there is a better way?
There could be half a dozen of these, for various models...
I would pop some client side code into your views and access a route to a controller action (which returns JSON by default) and conditionally add that particular snippet into your view (via a variable passed to the view that determines if the person is logged in). Then you can apply an auth filter to your route to protect it.
Note: with this approach you can pass url parameters to your action. This means you can tell your controller to limit your results more easily.
This is a very interesting question, my friend. What I can think of is the following
1) cheap way, just query 10 or whichever the biggest number, and then pass a variable $count to the view or let view pass a variable to the sub view
2) api call, if you'd like to do AJAX call, then as others suggested, you could just design a new route, getData?count=5.
Normally it's not easy to meet all requirements, and practically speaking in the prototype stage, it'll be more cost-effective to write fixed function like getData5, and getData10, or just make two pages :) it'll be a lot faster than coming up another new architecture design and then realize in the end nobody really uses them.
I am planning to use "Dapper" for retrieval and "NHibernate" for CRUD operations. So, would it be a good design to follow this approach. One of the problems I recently faced is with CRUD Screens.
Suppose I have Edit Order form. I am retrieving the entity (order) from Dapper and while updating it I need to attach these objects to NHibernate session to perform CRUD operations. It is not directly what is required, I mean object.delete().
Could anyone provide suggestions on this design and is there a possibility to make it better. It is web application developed using asp.net mvc 3.
Questions on the reply:
Does session filter on the action mean what we are using for current operation. If so, for the GET operation, should it be [DapperSession] instead of [NHSession]?
[NHSession] <<--------[DapperSession]
GET ACTION RESULT
DapperSession.Get.Entity(1000)
Return view
I am still trying to understand PRG pattern you've posted, I'll post if I have any doubts.
Since all this happening for the "EDIT" operation, would be wise to just to get the object using NHibernate too. This get rids of all this process with cost of little overhead.
At first glance this seems like a logical choice use Dapper (or another other micro ORM's) for GET and NHibernate for any POST action methods on controllers. See pseudo-code):-
[DapperSession]
GET ACTION RESULT
- DapperSession.Get.Entity(1000)
- Return view
and for the post method
[NHSession, DapperSession]
POST ACTION RESULT
If Model.State is valid
- entity = NHSession.Get.Entity(1000)
- update entity
- redirect to ...
end if
- DapperSession.Get.Entity(1000)
- return view
As you can see this is not that elegant, as you may have to different action filters to your POST action result. To tidy this up you could utilise the Post Redirect Get (PRG) pattern so GET controllers use Dapper and POST controllers only have access to NHSessions.
See this accepted answer on SO for details on how to set up this pattern..
Now your GET and POST action results would look like this:-
[DapperSession, ImportModelStateFromTempData]
GET ACTION RESULT
- DapperSession.Get.Entity(1000)
- Return view
and for the post method
[NHSession, ExportModelStateToTempData]
POST ACTION RESULT
If Model.State is valid
- entity = NHSession.Get.Entity(1000)
- update entity
- redirect to ...
end if
- return redirect to GET
However as you can see all I am really doing is substituting one action filter attribute for another one. BUT (and this is a big but in my opinion) you do get the benefits of utilising the PRG pattern! To be honest this probably doesn't answer our question directly but it is a good method to follow.
I am trying to make a RESTful api and have some function which needs credentials. For example say I'm writing a function which finds all nearby places within a certain radius, but only authorised users can use it.
One way to do it is to send it all using GET like so:
http://myapi.heroku.com/getNearbyPlaces?lon=12.343523&lat=56.123533&radius=30&username=john&password=blabla123
but obviously that's the worst possible way to do it.
Is it possible to instead move the username and password fields and embed them as POST variables over SSL, so the URL will only look like so:
https://myapi.heroku.com/getNearbyPlaces?lon=12.343523&lat=56.123533&radius=30
and the credentials will be sent encrypted.
How would I then in Sinatra and Ruby properly get at the GET and POST variables? Is this The Right Way To Do It? If not why not?
If you are really trying to create a restful API instead if some URL endpoints which happen to speak some HTTP dialect, you should stick to GET. It's even again in your path, so you seem to be pretty sure it's a get.
Instead of trying to hide the username and password in GET or POST parameters, you should instead use Basic authentication, which was invented especially for that purpose and is universally available in clients (and is available using convenience methods in Sinatra).
Also, if you are trying to use REST, you should embrace the concept of resources and resoiurce collections (which is implied by the R and E of REST). So you have a single URL like http://myapi.heroku.com/NearbyPlaces. If you GET there, you gather information about that resource, if you POST, you create a new resource, if you PUT yopu update n existing resource and if you DELETE, well, you delete it. What you should do before is th structure your object space into these resources and design your API around it.
Possibly, you could have a resource collection at http://myapi.heroku.com/places. Each place as a resource has a unique URL like http://myapi.heroku.com/places/123. New polaces can be created by POSTing to http://myapi.heroku.com/places. And nearby places could be gathered by GETing http://myapi.heroku.com/places/nearby?lon=12.343523&lat=56.123533&radius=30. hat call could return an Array or URLs to nearby places, e.g.
[
"http://myapi.heroku.com/places/123",
"http://myapi.heroku.com/places/17",
"http://myapi.heroku.com/places/42"
]
If you want to be truly discoverable, you might also embrace HATEOAS which constraints REST smentics in a way to allows API clients to "browse" through the API as a user with a browser would do. To allow this, you use Hyperlink inside your API which point to other resources, kind of like in the example above.
The params that are part of the url (namely lon, lat and radius) are known as query parameters, the user and password information that you want to send in your form are known as form parameters. In Sinatra both of these type of parameters are made available in the params hash of a controller.
So in Sinatra you would be able to access your lon parameter as params[:lon] and the user parameter as params[:user].
I suggest using basic or digest authentication and a plain GET request. In other words, your request should be "GET /places?lat=x&lon=x&radius=x" and you should let HTTP handle the authentication. If I understand your situation correctly, this is the ideal approach and will certainly be the most RESTful solution.
As an aside, your URI could be improved. Having verbs ("get") and query-like adjectives ("nearby") in your resource names is not really appropriate. In general, resources should be nouns (ie. "places", "person", "books"). See the example request I wrote above; "get" is redundant because you are using a GET request and "nearby" is redundant because you are already querying by location.
I have a RESTful URL that requires either the offset or the prefix request parameter (but not both).
GET /users?offset=0&count=20
GET /users?prefix=J&count=20
What's the best way to enforce this rule? Spring has the #RequestParam annotation with the 'required' property for optional parameters, but I want to enforce an "either-or" rule on these two parameters. I know I could do it in the code, but is there another way to do it?
Also, what's the proper way to handle "impractical" requests? Say I have 100 million users; the following request, although properly RESTful, is not something I want to support:
GET /users <-- Gets all 100 million users, crashes server and browser!
What should I send back?
You can create two methods and choose one of them with #RequestMapping's params attribute:
#RequestMapping(..., params = {"prefix", "!offset"})
public String usersWithPrefix(#RequestParam("prefix") ...) { ... }
#RequestMapping(..., params = {"offset", "!prefix"})
public String usersWithOffset(#RequestParam("offset") ...) { ... }
what's the proper way to handle "impractical" requests?
The lesser-practiced principles of REST include the requirement that resources be "discoverable". If you are asked for a complete list of 800 million users and you don't want to provide it, you might instead consider serving a page that describes in some way how to filter the collection: for example, an XForms document or HTML containing a FORM element with fields for offset/prefix/count, or a URI template with the appropriate parameters
Or you could just send a "413 Entity too large" error - edit: no you can't. Sorry, I misread the description of whath this code is for
If you decide to go down the route of just sending the first page, I think I would send it as an HTTP redirect to /users?offset=0&count=20 so that the client has a better idea they've not got the full collection (and if your response contains a link to access subsequent pages, even better)