My company has a REST API, and one design issue that has come up a few times is that we use null to indicate "get everything".
For example /api/books?genre=romance
will get all romance books, but /api/books
will return all books from all genres.
The current technique used in the code is something like this
public ResponseEntity getBooks(@RequestParam(required = false) String genre) {
List<BookDto> bookDtos = new ArrayList<>();
List<Book> allBooks = getAllBooks();
for (Book book : allBooks) {
// This if condition is what I take issue with
if (genre == null || book.genre.equals(genre)) {
bookDtos.add(book.convertToDto());
}
}
return bookDtos;
}
I think it is bad practice to use null here to convey the meaning of get everything. Is this correct?
My current solution is to distinguish between these two cases at the very top level like so:
public ResponseEntity getBooks(@RequestParam(required = false) String genre) {
if (genre == null) {
return getAllBooks();
} else {
return getBooks(genre);
}
}
And then a few supporting functions that funnel all of the logic into the same place by massaging the inputs a little bit.
// This is the core code, that takes a list of genres and returns all books from all those genres.
public Collection<BookDto> getBooks(List<String> genre) {
// And here is the code that will actually get the books from the database.
// ...
// ...
}
// This is a supporting function, that finds all genres and funnels that into the above function.
public Collection<BookDto> getAllBooks() {
List<String> allGenres = getAllGenres()
return getBooks(allGenres);
}
// This is a supporting function that builds a list from a single genre, to again funnel into the 1st function.
public Collection<BookDto> getBooks(String genre) {
List<String> genres = new ArrayList<>();
genres.add(yearCode);
return getBooks(genres);
}
The idea here is to have a single place where the work actually takes place and the database is accessed, but to have multiple ways to access it. And more importantly to remove the idea of null == all
in the codebase, aside from the very top level where the execution starts after the user requests /api/books
.
It's always felt natural to me but I've not seen it used by anyone else (I've only worked at a few places, so this could just be inexperience on my part). Is the proposed design good? Is there a standard way of solving this problem?
/api/books
will return all books from all genres." I am pretty sure it's a common pattern. I see it in every book on rest api. C# exemple of this patern used in a tutorial \$\endgroup\$