3

I'm confused as what is the best preference for singleton resource. I want to have endpoint which has user id as:

/users/{id}

But I also want username as parameter:

/users/{username}

I'm using Spring MVC (via Spring Boot) and it says the 2 endpoints are in conflict. So I decided to the first for retrieving singleton resource for users. But in order for the client to still use username as parameter, I added username as query parameter for the collection resource of users:

/users?username=<username>

In my repository and service layers, the return is Optional<User>, i.e. it's either empty or one user result. But in the controller, I wrap this in a list, either empty or one, to make it consistent with the return of /users as list.

Is this reasonable design? or is there any better design? Thanks.

Julez
  • 1,010
  • 22
  • 44

4 Answers4

5

Basically, you have two options:

  • You either have two separate endpoints, for example, /users/byId/{id} and /users/byName/{username} each mapped to a different controller method
class UserController {
    @GetMapping("/byId/{id}")
    Optional<User> findById(@PathVariable("id") String id){
       ...
    }

    @GetMapping("/byName/{username}")
    Optional<User> findByUsername(@PathVariable("username") String username){
       ...
    }
}
  • Or one endpoint /users/{id-or-name} and you make the union query, so: findAllByIdOrName()

I tend to say that it's cleaner and more efficient to do it in separate endpoints.


Edit

The RESTful convention would mean that you have 2 endpoints:

    @GetMapping("/{id}")
    Optional<User> findById(@PathVariable("id") String id){
       ...
    }

    @GetMapping("/")
    List<User> findUsers(@RequestParam("username") String username){
       // if username is not empty, filter users
       // we could also filter with other user properties according to specs
       ...
    }

Anything other than such would be already deviating from the conventions.

Ahmed Sayed
  • 1,429
  • 12
  • 12
  • The first option does not follow RESTful URI convention so I won't consider it. For the second one, what would be my @PathVariable then? – Julez Jan 26 '20 at 14:08
  • Thanks. I just read it now. Actually, your second option was the one I considered. That is the exact option I posted above. Gladly, I pushed for it. – Julez Feb 01 '20 at 13:19
1

You should try to prevent clients from assembling the URIs with string operations, because for that the clients would have to have intimate knowledge about server internals.

Nevertheless, elegant URI schemes are often a sign of a reasonable resource structure. When URIs are well designed, it makes life easier for a developer who wants to use a REST API; clients will appreciate URIs that are easy to remember. The ability to edit URIs manually (e.g., by truncating some of them after the "/") is a definite advantage.

As you've already seen: URIs identify resources. Resources are "things", nouns, not actions or verbs! If you have done a corrosive resource design, matching names usually come up by themselves. However, I would like to give an example here that might be an indication of a problem because of its structure or name.

http://example.com/customers/create?name=XYZ

There's been a bit of a mix-up here: The verb "create" has no place in the URI, and the structure suggests that the action is identified by the URI, not by the HTTP verb.

Your URIs should contain nouns. For URIs that identify resources as they are intended, you can also easily imagine how the different HTTP methods work: A PUT updates a DELETE deletes, and a GET fetches information. Here is an example of how the above URI would look better:

POST http://example.com/customers/

An obvious rule for a transparent and comprehensible URI design is the mapping of hierarchical relationships to the path element structure of the URI.

Example: http://example.com/organization/it/support/networks

So if you are the developer of the server and therefore the designer of the URI, you should make sure that a GET on http://example.com/organization/it/support also returns a useful result. In your client you should not make such an assumption in any case! This contributes fundamentally to the stability of a distributed system and is also known as the robustness principle or Postel's law.

For the design of URIs for Filter they have different possibilities. The most obvious is to use query parameters. For a list of customers under /customers that you want to filter for all customers from Germany, an example URI might look like this:

http://example.com/customers?country=Germany Several query components can be linked with an "&":

http://example.com/customers?country=Germany&year=2020

Here, too, they meet the expectations of the users with such a URI design. If the query string is omitted (?country=Germany&year=2020), the users are taken to the unfiltered list of all customers.

It is important to have stable URIs, and the cool URIs does not change (Tim Berners-Lee).

To answer your question:

The design should not be overrated; it should be useful, but not perfect (which is impossible to achieve anyway).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
H4x9r
  • 199
  • 10
1

I met the same problem. The two endpoints were in conflict, so event /user/{id} did not want to work, so that is why I did give findbyusername a different endpoint, like:

@GetMapping("/user/username/{username}") 
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

You cannot use the same endpoint name for one particular mapping. In here you use the same endpoint name for both @GetMapping's. If you want to do this thing, you should use the parameters for that.

class UserController {
   @GetMapping("/users")
   Optional<User> findById(@RequestParam(required = false) String id, @RequestParam(required = false) String username){
      if (!username.equals("")) {
         // Response using a username
      }
      if (!id.equals("")) {
         // Response using an id
      }
   }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hasee Amarathunga
  • 1,901
  • 12
  • 17
  • 1
    I want cleaner URI following RESTful URI convention. So `id` as query string is not acceptable. – Julez Jan 26 '20 at 14:09