Spring Boot Exercise
Create the Basic Application
- Navigate to https://start.spring.io
- Create a Gradle project with Kotlin and the latest version of Spring Boot (2.2.0 at the time of writing)
- Specify group:
microservice.workshop
- Specify artifact:
springboot-demo
- For dependencies, add the following:
- Spring Web
- Spring Boot Actuator
- Spring Data JPA
- H2 Database
- Generate the project (causes a download)
- Unzip the downloaded file somewhere convenient
- Add the new project to your IDE workspace (we only recommend IntelliJ for Kotlin Projects)
- IntelliJ: Import Project, then point to the unzipped directory
- Rename
application.properties
insrc/main/resources
toapplication.yml
Configure The Info Actuator
- Open
application.yml
insrc/main/resources
-
Add this value
info: app: name: Person Service management: endpoint: health: show-details: always
Configure Swagger
-
Open
build.gradle.kts
, add the following dependencies:implementation("io.springfox:springfox-swagger2:2.9.2") implementation("io.springfox:springfox-swagger-ui:2.9.2")
-
Create a class
SwaggerConfiguration
in themicoservice.workshop.springbootdemo
package. Add the following:package microservice.workshop.springbootdemo import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.stereotype.Controller import springfox.documentation.swagger2.annotations.EnableSwagger2 import org.springframework.web.bind.annotation.RestController import springfox.documentation.builders.RequestHandlerSelectors import springfox.documentation.spi.DocumentationType import springfox.documentation.spring.web.plugins.Docket import org.springframework.web.servlet.view.RedirectView import org.springframework.web.bind.annotation.RequestMapping @Configuration @EnableSwagger2 @Controller class SwaggerConfiguration { @RequestMapping("/") fun redirectToSwagger()= RedirectView("swagger-ui.html") @Bean fun api(): Docket { return Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.withClassAnnotation(RestController::class.java)) .build() } }
This configuration does three important things:
- It enables Swagger
- It redirects the root URL to the Swagger UI. I find this convenient, but YMMV
- It tells Springfox that we only want to use Swagger for REST controllers. Without this there will be Swagger documentation for the redirect controller, as well as the basic Spring error controller and we usually don’t want this.
Create a Person Repository
- Create a package
microservice.workshop.springbootdemo.model
- Create a class in the new package called
Person
-
Set the content of
Person
to the following:package microservice.workshop.springbootdemo.model import javax.persistence.Entity import javax.persistence.GenerationType import javax.persistence.GeneratedValue import javax.persistence.Id @Entity data class Person( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Int?, var firstName: String?, var lastName: String? )
- Create a package
microservice.workshop.springbootdemo.data
- Create an interface in the new package called
PersonRepository
-
Set the content of
PersonRepository
to the following:package microservice.workshop.springbootdemo.data import microservice.workshop.springbootdemo.model.Person import org.springframework.data.jpa.repository.JpaRepository interface PersonRepository: JpaRepository<Person, Int> { fun findByLastName(lastName: String): List<Person> }
-
Create a file called
import.sql
insrc/main/resources
. Set the contents to the following:insert into person(first_name, last_name) values('Fred', 'Flintstone'); insert into person(first_name, last_name) values('Wilma', 'Flintstone'); insert into person(first_name, last_name) values('Barney', 'Rubble'); insert into person(first_name, last_name) values('Betty', 'Rubble');
Create a REST Controller
- Create a package
microservice.workshop.springbootdemo.http
- Create a class in the new package called
PersonController
-
Set the content of
PersonController
to the following:package microservice.workshop.springbootdemo.http import microservice.workshop.springbootdemo.data.PersonRepository import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/person") class PersonController(private val repository: PersonRepository) { @GetMapping fun findAll() = ResponseEntity.ok(repository.findAll()) @GetMapping("/{id}") fun findById(@PathVariable("id") id: Int) = ResponseEntity.of(repository.findById(id)) @GetMapping("/search") fun search(@RequestParam("lastName") lastName: String) = ResponseEntity.ok(repository.findByLastName(lastName)) }
Unit Tests
- Make a new package
microservice.workshop.springbootdemo.http
in thesrc/test/kotlin
tree - Create a class in the new package called
PersonControllerTest
-
Set the content of
PersonControllerTest
to the following:package microservice.workshop.springbootdemo.http import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.hasSize import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.test.context.junit.jupiter.SpringExtension import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.springframework.web.context.WebApplicationContext @ExtendWith(SpringExtension::class) @SpringBootTest class PersonControllerTest(private val webApplicationContext: WebApplicationContext) { private lateinit var mockMvc: MockMvc @BeforeEach fun setup() { mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build() } @Test @Throws(Exception::class) fun testFindAll() { mockMvc.perform(get("/person")) .andExpect(status().`is`(HttpStatus.OK.value())) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$", hasSize<Any>(4))) } @Test @Throws(Exception::class) fun testFindOne() { mockMvc.perform(get("/person/1")) .andExpect(status().`is`(HttpStatus.OK.value())) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.firstName", `is`("Fred"))) .andExpect(jsonPath("$.lastName", `is`("Flintstone"))) } @Test @Throws(Exception::class) fun testFindNone() { mockMvc.perform(get("/person/22")) .andExpect(status().`is`(HttpStatus.NOT_FOUND.value())) } @Test @Throws(Exception::class) fun testSearch() { mockMvc.perform(get("/person/search?lastName=Rubble")) .andExpect(status().`is`(HttpStatus.OK.value())) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$", hasSize<Any>(2))) } }
Testing
- Run the unit tests:
- (Windows Command Prompt)
gradlew clean test
- (Windows Powershell)
.\gradlew clean test
- (Mac/Linux)
./gradlew clean test
- Or your IDE’s method of running tests
- (Windows Command Prompt)
- Start the application:
- (Windows Command Prompt)
gradlew bootRun
- (Windows Powershell)
.\gradlew bootRun
- (Mac/Linux)
./gradlew bootRun
- Or your IDE’s method of running the main application class
- (Windows Command Prompt)
- Test Swagger http://localhost:8080
- Test the acuator health endpoint http://localhost:8080/actuator/health
- Test the acuator info endpoint http://localhost:8080/actuator/info