Spring Application Deployed with Kubernetes
Step by step building an application using Spring Boot and deployed via Docker on Kubernetes with Helm
full course- Setup: IDE and New Project
- Create the Data Repository
- Building a Service Layer
- Create a REST Controller
- Logging, Tracing and Error Handling
- Documentation and Code Coverage
- Database as a Service
- Containerize the Service With Docker
- Docker Registry
- Automated Build Pipeline
- Helm for Deployment
- Setting up a Kubernetes Cluster
- Automating Deployment (for CICD)
- System Design
- Messaging and Event Driven Design
- Web UI with React
- Containerizing our UI
- UI Build Pipeline
- Put the UI in to Helm
- Creating an Ingress in Kubernetes
- Simplify Deployment
- Conclusion and Review
Before we move on to getting our service ready for deployment we need to do a little more housecleaning. Sometimes these tasks get de-prioritized so we might as well take care of it now, before we move on.
Endpoint Documentation
Using OpenAPI is a powerful tool for documenting our REST endpoints. Documenting our endpoints reduces the amount of unplanned time we need to take from our priorities to support our users or clients of our service. Additionally, it can also be used to generate the clients, so that we don’t have to build and maintain client libraries.
First we need to add the dependencies to our maven pom file.
<properties>
...
<springdoc-openapi-core.version>1.1.49</springdoc-openapi-core.version>
</properties>
<dependencies>
...
<!-- documentation -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc-openapi-core.version}</version>
</dependency>
...
</dependencies>
Restart our application and go to http://localhost:10000/swagger-ui/
. You should see the swagger ui and you can see our endpoint. You can also confirm on the CustomerDTO
schema that the validation requirements are documented.
Code Coverage
We really need to be aware of how our code is evolving. One particularly useful metric is code coverage. We’ll be following this example to configure our project to generate code coverage reports via jacoco.
Add this configuration to the pom.xml
file:
<properties>
...
<jacoco-maven-plugin.version>0.8.5</jacoco-maven-plugin.version>
</properties>
<build>
<plugins>
...
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
And build the report with mvn clean test jacoco:report
. View the report at /target/site/jacoco/index.html
. Its should look something like this:
Which looks kind of bad, but its actually failing because of all of the generated Lombok code (as well as some classes we shouldn’t be testing). Lets exclude that from the report. Finally, we see that the controller advice as well as the config
package are being analyzed. Lets move controller advice to config
and exclude the config
directory. Additionally, lets exclude the spring boot Application class and the exceptions from the jacoco-maven-plugin
maven plugin definition.
</execution>
</executions>
<configuration>
<excludes>
<exclude>com/brianrook/medium/customer/config/**/*</exclude>
<exclude>com/brianrook/medium/customer/*Application*</exclude>
<exclude>com/brianrook/medium/customer/exception/**/*</exclude>
</excludes>
</configuration>
</plugin>
Rerunning our report looks much better, but we can see two issues. One is that the code generated by mapstruct has some untested paths. We can and should write tests for these because mapping classes are high risk. If you look at the report closely, you can see that we don’t have test cases around null input. These tests are easily written.
package com.brianrook.medium.customer.controller.mapper;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class CustomerDTOMapperTest {
@Test
public void testNullCustomer(){
assertThat(CustomerDTOMapper.INSTANCE.customerToCustomerDTO(null)).isNull();
}
@Test
public void testNullCustomerDTO(){
assertThat(CustomerDTOMapper.INSTANCE.customerDTOToCustomer(null)).isNull();
}
}
package com.brianrook.medium.customer.dao.mapper;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class CustomerEntityMapperTest {
@Test
public void testNullCustomer(){
assertThat(CustomerEntityMapper.INSTANCE.customerToCustomerEntity(null)).isNull();
}
@Test
public void testNullCustomerDTO(){
assertThat(CustomerEntityMapper.INSTANCE.customerEntityToCustomer(null)).isNull();
}
}
More importantly, we can see that the service class has an untested exception path. We could easily write a unit test here that mocks the DAO and throws an exception. I would say: please don’t do that! This is a path that we added for a specific behavior, so lets create a behavior test. Add this additional test to CustomerControllerTest
:
@Test
public void testAddCustomerConflict() throws URISyntaxException
{
CustomerEntity savedRecord = CustomerEntity.builder()
.firstName("test")
.lastName("user")
.email("[email protected]")
.phoneNumber("(123)654-7890")
.build();
customerDAO.save(savedRecord);
RestTemplate restTemplate = new RestTemplate();
String baseUrl = "http://localhost:"+randomServerPort+customerPath;
URI uri = new URI(baseUrl);
CustomerDTO customerDTO = CustomerDTO.builder()
.firstName(savedRecord.getFirstName())
.lastName(savedRecord.getLastName())
.email(savedRecord.getEmail())
.phoneNumber(savedRecord.getPhoneNumber())
.build();
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
HttpEntity<CustomerDTO> request = new HttpEntity<>(customerDTO, headers);
try {
ResponseEntity<CustomerDTO> result = restTemplate.postForEntity(uri, request, CustomerDTO.class);
}catch (HttpServerErrorException e) {
//Verify request failed
Assertions.assertEquals(500, e.getRawStatusCode());
}
}
Rerun the report. We should have 100% coverage now. The best part is it only took 3 test classes and 6 tests to get there.
Build and Commit
git checkout -b coverage
mvn clean install
git add .
git commit -m "OpenAPI docs and code coverage"
git push --set-upstream origin coverage
git checkout master
git merge coverage
git push
0 comments on “Documentation and Code Coverage”Add yours →