1

I have an action method returning all books of a library by specifying libraryId. Should the action method be placed into LibraryController or into BookController?

There are many action methods in the controllers and the controllers are named according to entities: BookController, LibraryController, OrderController, LoginController.

There can be two rules for that.

1. Endpoints organized by url (the url is the same, the entity type differs)

The endpoint with url /library/{libraryId}/books returning list of books should be placed in LibraryController, but the endpoint with url /book/{bookId} returning one book should be placed in the BookController.

/library/{libraryId}/books => LibraryController
/book/{bookId} => BookController

Example:

[ApiController]
[Route("/library")]
public class LibraryController
{
   [HttpGet]
   [Route("{libraryId}/books")]
   public async Task<ActionResult<IList<Book>> GetBooks([FromRoute] Guid libraryId)
   {
      return await bookService.GetBooks(libraryId);
   }
}

[ApiController]
[Route("/book")]
public class BookController
{
   [HttpGet]
   [Route("{bookId}")]
   public async Task<ActionResult<Book>> GetBook([FromRoute] Guid bookId)
   {
      return await bookService.GetBook(bookId);
   }
}

2. Endpoints organized by returned entity (the url differs, the entity type is the same)

The endpoint with url /library/{libraryId}/books returning list of books and the endpoint with url /book/{bookId} returning one book should both be placed into BookController since they return Book entity and list of Book entities.

/library/{libraryId}/books => BookController
/book/{bookId} => BookController

Example:

[ApiController]
public class BookController
{
   [HttpGet]
   [Route("/book/{bookId}")]
   public async Task<ActionResult<Book>> GetBook([FromRoute] Guid bookId)
   {
      return await bookService.GetBook(bookId);
   }

   [HttpGet]
   [Route("/library/{libraryId}/books")]
   public async Task<ActionResult<IList<Book>> GetBooks([FromRoute] Guid libraryId)
   {
      return await bookService.GetBooks(libraryId);
   }
}

Which rule do you use and why?

Note: I added examples

quantumbit
  • 305
  • 1
  • 9
  • Possible duplicate of [What are best practices for REST nested resources?](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources) – Andrei Dragotoniu Apr 08 '19 at 12:02
  • ^ it's not, I'm questioning code organization not url path organization. – quantumbit Apr 08 '19 at 12:53
  • as your method return list of books ? right then as your controller is for book and as single responsibility of class book controller contains related to book only so on my point of view i used 2nd point. – Lalji Dhameliya Apr 08 '19 at 13:47
  • you can also regere https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2 and see example as mentioned books controller contains only there method – Lalji Dhameliya Apr 08 '19 at 13:49
  • Thanks Lalji! The MS examples indeed use the second variant. The question is if the single responsibility principle says the entity controller should take care of one type of entity or one type of url path (the path starting with an entity name). Which one is actually correct: a controller with no route attribute and with different route attributes on its action methods returning just one type of entity - or a controller with a base route attribute and specific route attributes on its action methods returning different type of entities? – quantumbit Apr 08 '19 at 14:26
  • Another findings: MS uses `/{controller}/{action}/{id}` template which would prefer the first variant, on the other hand as you've written, the single responsibility principle perhaps prefers the second variant. – quantumbit Apr 08 '19 at 15:01
  • I'd go for the second variant because the single responsibility of that controller is to retrieve books. I'd argue that the URL's don't make sense, in the second case you retrieve a list of books from a specific library, and in the first instance you retrieve a book from where? Is there an indication as to what libraries the book resides in? I'd say the following URL's would make it an easier problem to tackle, `/libraries/{libraryId}/books` - `/libraries/{libraryId}/books/{bookId}`. I've changed the URL's to plural because you're referencing a collection of libraries & books. – ColinM Apr 08 '19 at 15:24
  • Both variants return the same data, so that's ok - no need to argue about urls. With `bookId` you retrieve one book by the id. With `libraryId` you retrieve the books of the specific library according to `libraryId`. So it reads all books that have `book.libraryId == request.libraryId` from db. A library has more books: 1:N. You basically want to get one book or books of a library. The question is only which of the variant is the right one. – quantumbit Apr 08 '19 at 16:04
  • If there would be method `AddBook(Guid libraryId, string name)`, would it be in `LibraryController` or in `BooksController`? Since it seems such method belongs to `Library`. The `GetBooks(Guid libraryId)` might be the same case, but single responsibility says it should be in `BooksController`, or not? I am not sure... – quantumbit Apr 08 '19 at 16:04
  • @ColinM With such url `/libraries/{libraryId}/books/{bookId}` you would have redundant `libraryId` parameter. The `bookId` is unique guid for every book in the db. If you know it, you can retrieve the book by id. You don't have to know `libraryId`. You can reference a collection with singular as well. Neither way is right or wrong. – quantumbit Apr 08 '19 at 16:21
  • Here is the similar question with 2 variants and the first one was selected: https://stackoverflow.com/questions/25870412/webapi-2-building-nested-route-using-attribute-routing-results-in-mapping-to-tw – quantumbit Apr 08 '19 at 22:03
  • And here is almost the same one, but second variant won: https://stackoverflow.com/questions/30785544/webapi-how-to-deal-with-nested-resources – quantumbit Apr 08 '19 at 22:20

0 Answers0