Numerical IDs vs names
As an example, which of these would you choose for identifying a single transaction, from a single bank account, for a single company:
/companies/freds-painting-ltd/accounts/savings/transactions/4831
/companies/freds-painting-ltd/accounts/1/transactions/4831
/companies/62362/accounts/1/transactions/4831
You idiot, something totally different! Crikey, did you even READ Fielding's dissertation?
Now, I think the 1st one is the most readable. If I have more than one company, or if I'm someone like an accountant managing multiple companies, it's immediately clear which company, and which account, I'm looking at. It's also more bookmarkable/emailable and would prevent 'fishing' for other companies by changing the company ID. I would want transaction IDs to be unique to an account (I.e. Both 'savings' and 'current' accounts could have transaction '1'
A 'company' will be my 'top-level', or 'first class' resource. Nothing at all would ever be shared between companies. As such, it would be the ideal candidate for a shard (or 'ancestor'/'namespace' in Google App Engine parlance). So I'd only have to worry about the account names being unique within one company. Every company could have an account called 'savings'.
Not sure what the situation in the rest of the world is, though LTDs or PLCs in UK would have a unique name, there could be many 'Dave's Window Cleaning' businesses (what's know as a trading name).
The business owner(s) could potentially opt for the top level /company/company-name URI to be public, and contain some basic details like their website, contact details etc, but everything below that would NEVER be accessible by search engines.
So my thoughts/concerns are:
1) Is it reasonable, when someone signs in to add their business, to say "Sorry, 'Dave's Window Cleaning' business is taken. How about 'Dave's Window Cleaning Portsmouth' (Having taken their location in another field)? My worry with this is that, for a more well known company, you're giving away the fact that they have an account with you. Or that someone could use that form to search for names. Perhaps not a biggie.
2) The size of the company name. Would it be reasonable for a name like 'Dave's Window cleaning, gardening, and loads of other stuff'? Thus creating a URL like 'daves-window-cleaning-gardening-and-loads-of-other-stuff/'
3) How to deal with someone changing their business name - I would approach it by creating a new company with that string ID, copying over everything, then deleting the old resource. The original URI would return 404 rather than redirecting - as you can't guarantee someone else won't want to take the now unused name, or even if more than one person has used the same name in the past.
4) Should the 'real' unique ID be an number in the back end, and for every request to be handled by first doing a query for what company ID this name actually related to.
5) The impact of searching for a transaction in the persistence layer.
6) The possibility of URL rewriting, but then that wouldn't work cleanly in GAE, nor would it solve the issue of ensuring company names are unique.
RESTful webservice vs RESTful website
So, we potentially have this lovely RESTful webservice that the latest snazzy iphone/android app can use (delusions of grandeur). But what about the main website itself? I note, right now, that the URL I see at the top of my page is not 'RESTful': /questions/ask is an action. There is no 'ask' resource on the server. It's more the state of the page, the preparation for POSTing to /questions/ - or if I'm editing, PUTing to /questions/{id}
I also note that Stackoverflow has URIs like /questions/362352/name-of-the-question, and that the latter part can be omitted, and one will be redirected to it.
Should I host a completely separate webapp that consumes my lovely webservice (from the same domain)? Do I even need a separate REST server, or can I rely on content negotiation (JSON/XML) and HTTP verb to select the right method (I'm using Jersey), and return the right representation?
So I could have /companies/aboxo/ return the whole HTML page (using stringtemplate.org) if it's a GET /,text/plain or test/html, and JSON/XML for others?
But what happens for 'add/edit/delete' transaction? Would GET / /companies/freds-painting-ltd/savings/transactions/?template=add be ok (or GET ../transactions/352?template=edit), and that would return the right HTML?
Thinking about this last detail is driving me mad for some reason.
Comments, suggestions, outright ridicule - all welcome!
Marcos
Rails solves the "id vs name" problem by displaying both in the URL but using only the id to actually identify eg:
/companies/62362-freds-painting-ltd/accounts/1-savings/transactions/4831
ie - for the ones that have a "pretty url" the function that generates your path write both the id and the name... but for your router, where relevant: you strip off everything thats not the id.
incidentally, it means your customer could actually write whatever they like into the URL and it'd make no difference:
/companies/62362-i_luv_blue_turtles/accounts/1-your_mum/transactions/4831
and your router still just sees:
/companies/62362/accounts/1/transactions/4831
:)
For a cannonical URI I suggest just /transactions/{id} as I presume the transaction knows what the company and account is. Therefore, #4 :-)
Is SEO a concern? I presume you don't want random folks off the internet googling for X company's transactions?! Therefore, I would just keep names (which may change) out of the URI.
Related
I am looking for a good URL, following REST principes, to "Move the competitor from team1 to to team2
My first guess is :
/teams/{oldTeamId}/{newTeamId}/competitors/{competitorId}/move
But it doesn't look much like REST.
Should I break it into 2 basics calls ?
Remove competitor from team1,
Add competitor to team2,
Should I remove some data from URL and pass it into the body ?
I don't really know what to do for this one.
Think about how you would implement this API as a web site.
You would probably have a link to a form -- it might be a form where the competitor, old team, and new team are all blank, or it might be a form where the competitor and old team are pre-populated. Your consumer updates the default information in the form as required, and submits it.
Notice the first point (raised by Roman Vottner as well) -- your consumer doesn't need to look at the URL at all. The client knows the HTML form processing rules, so it can create the correct HTTP request without knowing anything about the domain.
The second point is that, since the client is just submitting the form to wherever the HTML tells it to, you can make that anything you want.
One of the interesting properties of HTTP is cache invalidation. See RFC 7234, any non error response to an unsafe request will invalidate all cached representations of one resource.
So you can choose which resource gets invalidated by specifying its URI as the target of the form. In effect, it gives you a mechanism for ensuring that a consumer can read its own writes.
So some reasonable choices for the target might be
/teams/{oldTeamId}
if the team roster is the most important thing. Or
/competitors/{competitorId}
if the resource that describes the player is what is most important.
I don't really know what to do for this one.
Concentrate on make it easy to use. Your resource model is not your domain model is not your data model.
It will likely be useful to watch Jim Webber's talk REST: DDD In the Large to get clearer insights into what your "REST" API should really look like.
To answer your questions, I would not break it into two calls, I would however take some data from that (GET) url and put it in the body of your request. The request would probably be a POST or PUT (or maybe even patch), but definitely not a GET since something is actually changing.
As for a solution, how about a POST request to a /transfer. After all you are (could be) creating a new transfer which takes for example the player, their new team and maybe their old team.
I would use URL to identify the resource which in this case seems to be a competitor's team.
So would
Make the url as /competitors/{competitorId}/teams
Make the call PUT
Have a body with newTeamId and if required the oldTeamId.
How many sub-collections can have a resource?
Just imagine that we have this model:
accounts -> posts -> comments
Everything is clear for accounts-posts pair.
/accounts/{account_id}/posts/{post_id}
But what about comments? What is a proper way to point to a single comment?
/posts/{post_id}/comments/{comment_id}
or
/accounts/{account_id}/posts/{post_id}/comments/{comment_id}
or (direct pointing)
comments/{comment_id}
The general rule of thumb is to only go one layer of subresources deep. An account may have many posts like you show, and a post may have many comments, but since the post id is unique, including the account id is a bit redundant when trying to get the comments for a post.
/accounts/:account_id/posts
/posts/:post_id/comments
Ruby On Rails also lists this in their docs (just for a bit of a citation). https://guides.rubyonrails.org/routing.html#limits-to-nesting
Additionally, if you already have the id of a resource you don’t need to nest it. As short of a route that gets the job done is preferred.
/comments/:id
So the application is for a warehouse and you need to retrieve all of the products in the warehouse created by a particular client. What would a URI that is RESTful look like to accomodate this?
Here are some ideas that I had:
/Product/Client/[the client's ID]
/Product?clientID=[the client's ID]
What would a RESTful URI for this scenario look like?
From my point of understanding, first one is better options. In first scenario, you will only check the route from the URI and client id will be in the body param. In the second scenario, you are adding clientsID in the header. Although I am not a master but what saw the way people write, they follow the first option. You might get an idea from here: http://www.restapitutorial.com/lessons/restfulresourcenaming.html
Wait for the response from master in that area. thanks
You're identifying a specific client, so start with
/Client/[Client ID]
and then specify a resource "belonging to" that client
/Client/[Client ID]/Products
It all depends on the use case for this requirement.
If the client would usually navigate to a client, and then needs to view its products, then as #esorf said:
/client/{clientId}/products
However, if the client is displaying products for various clients (albeit only one at a time), something like this might make more sense:
/products?clientId={clientId}
This latter one could also be extended to use URI Templates in order display products of more than one client like so:
/products{?clientId*}
which expands into
/products?clientId={clientId1}&clientId={clientId2}
I am working with GWT / RequestFactory and a set of customer requirements regarding permissions. Let me explain a basic example:
Every user is assigned to a company. Every user should be able to edit company's core data - but only e.g contact information, website etc. Security-relevant ones like BIC/SWIFT, IBAN, Company name and so on can only be changed if the user has a certain permission XY.
So far so good, on the client side I can check the permissions and disable those fields the user is not allowed to edit. But what would be the most elegant way to ensure on the server side that those fields have not been set without permission?
My problem is that I cannot track changes on the server side. Having #PreAuthorize on every setter is not an option too, because it would end in an authorization-massacre in each and every entity.
At the moment I am following a workaround: every field that is secured / depends on a given permission is passed as an argument to the entity-method and is excluded from the proxy. That way, values cannot be set using the proxy and I can check in my server code if the user has permissions. If not, nothing happens. If user has permissions, I set the values manually. But that produces a lot of boilerplate-code and ugly method signatures because the number of values passed to the method could get large.
I hope you understand my issue. I'm looking forward for your opinions and tips. Thank you in advance.
Well, you can receive many answers (different each other), and all of them could be right, so, at the end is your call. Wait for others answers. I am going to give you the approach that I followed (and it worked pretty well). :D.
Under my opinion, the server should do less as possible, so keep the logic for allowing modify each param on the server I think it is not a scalable solution (if your system has 1M users modifying everything at the same time, will your server work fluent?). I prefer let the client do the job (like Roomba :D).
For solving that problem, in our system we implemented an Access Control List solution. You can store in your db, on each user entity, a list with granted permissions. So, when that information arrives to the client (after user's log in, for example), you can get them, and show the fields that he/she is allow to modify.
Something like:
if (canModifyPersonalDetails(user.getAcls(), ...) ) {
//show labels ...
}
if (canModifyBankDetails(user.getAcls(), ...) ) {
//show labels
}
You can not avoid server call for log in, so it is not a big deal send the extra information (think about the ACLs could be simple list of integers 0 means personal details, 1 bank details....).
If you are dealing with very compromised information and you prefer do some stuff on the server, in that case probably I'd set up a security level, when you are persisting/updating your proxy, I'd do something like:
if (isAllowForPersonalDetails(user.getSecurityCode()) {
//update the modified personal details
}
if (isAllowForBankDetails(user.getSecurityCode()) {
//update the modified bank details
}
user.update();
I am a big fan of clear User GUI's, and a very big fan of let the server free as much as possible, so I prefer the first option. But if you have constraints for modifying user entity in db, or you prefer do not modify your views, or any constraint with security, maybe the second option is the best one for you.
Hope that helps!
I have a web application which uses URLs that look like this:
http://library.example.com/Register.aspx?query=academic&key=586c70bb-5683-419c-aae9-e596af9ab66a
(The GUID is used instead of a plain int to discourage guessing, which is all we need for now.)
The problem: that long URL frequently breaks when sent via email. It's humans sending the emails, so I can't control the formatting. Sometimes it's the sending email program at fault, sometimes the receiving, but regardless I'm spending too much time on talking people through fixing problems.
Everything has to be from this domain, so I can't use a third-party shortener. I could host my own, but that seems like a kludge.
Any suggestions?
Edits
#Sunny: Thanks for elaborating, but my situation differs from what you assume. A corporate customer (of mine) passes this URL to its employees, and they use it to get to a branded Registration page. They need to give a working email as part of registration, and that gets forwarded to the corporate supervisor.
Registration gets them access to a database, but what they see is not specific to the corporate customer. So the occasional interloper is not a big deal; when they get weeded out by the corporate supervisor, we invite them to subscribe.
#Everybody: the email breakage is not on the punctuation (?&=), but at some predetermined line-length. Surprised me, too. Note that the domain name is long, as is the path to the virtual directory, which is a part of the problem.
After reading the responses, I'm going to use base64 as a pseudo-shortener, something like:
http://a.MyLongDomainName.com/?q=a&key=base64_encoded_GUID
...and see if that survives. Thanks to all.
You can at least shorten it a bit. Right now, you're send a GUID, which is a 128-bit number, in a format that is essentially hexadecimal with extra dashes. If you view the GUID as a byte array and convert it to Base64, you can cut things down a bit. Likewise, "query=academic" could be "q=a".
The GUID is currently taking up 36 characters. Converting to Base-64 cuts this down to 22, saving 14 chars. Replacing "query=academic&key=" with "q=a&k=" shaves off another 13. Cutting a total of 27 characters may well keep your URL short enough not to wrap, despite the presence of ampersands and equal signs.
One more detail: the Base-64 text is going to end with an "=", which will then be hex-encoded into "%3D". The solution is to cut that character off, because it's just padding.
With credit to the original posters, it looks like the best bet is a combination of things:
Compact GUID with base-64.
Shorten key names and, if possible, values.
Wrap URL in angle-braces to encourage client to parse it properly.
If possible, replace key names with URL-rewriting, so that it looks like a path.
If you can't use a third-party URL shortener, then your only option (besides changing the URL structure, as Sunny suggested) is to surround your URL with angle brackets, like this:
<http://library.YourDomainNameHere.com/Register.aspx?query=academic&key=586c70bb-5683-419c-aae9-e596af9ab66a>
Any email client that follows the guidelines found in the Uniform Resource Identifiers (URI): Generic Syntax document should display a clickable link. This is not a fool-proof solution, however, and you'll likely end up resorting to a URL shortening service or restructuring your URLs.
The only alternative to installing your own shortener service (which would be the ideal solution IMO), may be base64 encoding of the whole URL (and using a shorter key). But that would increase string length by 33% (very likely to break in E-Mail clients as well), and look ugly.
I would go with building a URL shortener service that shortens URLs on demand to something like this:
http://library.example.com/go/586c70bb-5683-419c-aae9-e596af9ab66a
There are some prepackaged URL Shorteners that you could host on your own. Here's a codeplex search
http://www.codeplex.com/site/search?query=url%20shortener
This will give you the ability to keep your short url's in house
Alternatively you could some how implement a RESTFul URL that would be a lot harder to screw up
http://library.example.com/Register/Academic/586c70bb-5683-419c-aae9-e596af9ab66a
This solution should work better than the querystring simply because what usually breaks in the email clients is the ?, the =, and the &
I personally think a RESTFul solution is best as it creates the cleanest urls that still make "some" sense.
How about replacing the GUIDS with YouTube style keys
e.g. http://library.example.com/Register.aspx?q=academic&k=jkGlkNu8
By using base-64 strings (instead of Guids which are base-16) and dropping those pesky dashes, you can pack a decent range of unique keys into a small amount of characters.
What about a combination of the methods described here?
Combining shorter URLs with Base64 encoding of the key would turn
http://library.example.com/Register.aspx?query=academic&key=586c70bb-5683-419c-aae9-e596af9ab66a
into
http://l.example.com/register/ac/WGxwu1aDQZyq6eWWr5q2ag
Much more readable, IMO. And lack of chars like ? and & reduces the risk of cut'n'paste errors.
REST-ful url like:
http://www.yourdomainhere.com/register/academic/{userName_here}
might help IMO.
If the user is not registered, this will do it & return a message confirming the fact
If the user has already been registered, there will be no action & perhaps a notification that the user has been registered can be shown.
The routing of the URL and/or validating the request etc. can be implementation detail best left to a module looking at the request pipeline...
HTH.
EDIT:
As pointed out by #Steven below, there is an addition step involved in this solution:
When the user clicks on the REST URL, launch the confirmation/login screen with the user name pre-filled. The user can login to the account & this is confirmation that the user is valid. Till he does the first login, the status of the account can be "not confirmed" & at his first login, it can be "confirmed" without bothering if the click/request has come from the email sent and/or via a request in a web browser.
This will also ensure that it will work for authentic email account since till the user actually does a valid login, the account will not be in "confirmed" status...