6: Query Authors
What goes up must come down!
or in our case
What goes into the DB must come out!
OPTIONAL: Run the app on localhost
By the way, if you run your docker compose file and start the Spring app you can also test your API at runtime manually.
Got to http/AuthorCommands.http
and run the request against your localhost:8080.
(in case you don't have it, as it is part of my part of prepared *-done branches)
### Register an author
POST /authors/commands/register HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{
"firstName": "PLACE_YOUR_FIRST_NAME",
"lastName": "PLACE_YOUR_LAST_NAME"
}
Implement the Query endpoint
We want to make our authors readable via our REST API. For that purpose we introduce
### Get authors
GET /authors HTTP/1.1
Host: localhost:8080
Accept: application/json
This chapter will be a bit less guided in terms of code snippets. Let's see how far you get!
- You need to create a GET mapping in a new file
/query/AuthorQueries.java
in our query section of the clean hexagonal onion. Remember, that we also need to decouple the query layer from our domain core. Hence, we introduce the following view model that our query will return:
public record AuthorView (Long id, String name) {
public AuthorView(Author author) {
this(author.getId(), author.getFullName());
}
}
- Update the
AuthorService.java
(and in turn also theAuthorServiceImpl.java
) - Update the
AuthorMapper.java
because we now need to map from data model to domain model. (add Builders and Getters where necessary)
Hint 1: The annotation
@Builder(builderMethodName = "restore")
might come in quite useful in the Author.class
Hint 2: You may want to try TDD to complete this one :-)
Validate
Let's test your implementation:
@SpringBootTest
@AutoConfigureMockMvc
class AuthorQueriesTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private EntityManager entityManager;
@BeforeEach
void beforeAll() {
entityManager.createNativeQuery("DELETE FROM author WHERE true;").executeUpdate();
}
@Test
@Transactional
void getAll() throws Exception {
// given
var authorJPA = AuthorJPA.builder().firstName("firstName").lastName("lastName").build();
entityManager.persist(authorJPA);
entityManager.flush();
AuthorView expected = new AuthorView(Author.createAuthor("firstName", "lastName"));
// when then
MvcResult result = mockMvc.perform(get("/authors")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
var resultingAuthorViews = objectMapper.readValue(
result.getResponse().getContentAsString(), new TypeReference<List<AuthorView>>() { });
assertThat(resultingAuthorViews)
.usingRecursiveFieldByFieldElementComparatorIgnoringFields("id")
.containsExactly(expected);
}
}
if (allTestsGreen == true) {
log.info("DONE! Let's move on to the next topic: Separate the Domain Interaction Layer")}
else{
log.error("Shout for help!") || (git stash && git checkout 6-query-author-done)
}