Spring Boot REST APIs Distilled

25-06-2019 14:06

DRAFT WORK IN PROGRESS


Below is a reference I’ve created from my Spring Boot learning journey from a variety of online courses and resources.  The material assumes you have knowledge of Java.  I wrote this in part because I find it useful to have a one stop place to lay out my understanding of how to write Spring Boot REST APIs.  However, this can also be used as a supplementary learning resource for others.  

Creating a new Spring Boot project

Maven project from archetype “maven-archetype-quickstart”

Maven parent section to include spring boot starter, eg:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.1.RELEASE</version> </parent>

Add web dependency:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.1.4.RELEASE</version>
</dependency>

 

Enable spring boot  with annotation at class level for the class with the main method:

@SpringBootApplication

(in IntelliJ this will add the required import)

Main method:
public static void main( String[] args )
{
SpringApplication.run(App.class, args)
}

Spring Boot Dependency Management

BOM – Bill of Materials

Shows you what versions of different dependencies work well with each other.  With Spring Boot – the Spring Boot Starter Parent sets up defaults.

Spring Boot Initializer

Form based utility to generate a new project: http://start.spring.io

What does Spring Boot do to Start:

  1. Main method entry point
  2. Initialize Spring Application context
  3. Embedded servlet container is autoconfigured and started

Implicit Annotations

@Configuration – performs spring configuration on startup
@EnableAutoConfiguration – auto configures and wires spring frameworks found in the classpath and wired up and integrated auatomically with Spring
@ComponentScan – scans project for Spring components – place the main class at the top level, as the scan begins from the package in which the main class is in

Creating a Controller

A controller handles incoming HTTP requests.

Class level annotations:

@RestController - declares class is a rest endpoint handler
@RequestMapping("api/v1/")  -- sets up the base URL

Method level annotations

Appends to the class level annotation

@RequestMapping(value = "resource-url", method = RequestMethod.GET)
public List getList()

@RequestMapping(value = "resource/{id}", method = RequestMethod.GET)
public ObjectType get(@PathVariable Long id)

@RequestMapping(value = "resource/{id}", method = RequestMethod.DELETE)
public ObjectType get(@PathVariable Long id)

@RequestMapping(value = "resource", method = RequestMethod.POST)
public ObjectType create(@RequestBody ObjectType objectName)

@RequestMapping(value = "resource", method = RequestMethod.PUT)
public ObjectType create(@PathVariable Long id, @RequestBody ObjectType objectName)

Note there are also other annotations that are method type specific, which offer syntactic sugar by avoiding the need to include the RequestMethod:

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping

Properties and Environment Configuration

application.properties – needs to be in the classpath root – in src/main/resources folder.  Can also be YAML format, but requires snake yaml dependency.

Environment specific configuration

With spring you can define environment specific properties with environment specific profile files named: application-{profile}.properties – where profile represents the environment

To specify the environment, when the application is run, you need to supply a VM argument:

-Dspring.profiles.active={profile}

Common Properties

Adjust logging level

logging.level.{package}={level}

for example to set for spring framework itself:

logging.level.org.springframework.web={level}

Set Server Port

server.port={port-number}    default is 8080

Data Source Framework Configuration and Access

Use H2 for development activities (note – test scope limits it to unit test usage)

Maven dependency for H2:

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
<!--            <scope>test</scope> Uncomment when using for test only -->
        </dependency>

H2 console properties in application.properties:

spring.h2.console.enabled=true
spring.h2.console.path=/h2

JPA starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Data Source Configuration

You can configure the data source in application.properties

spring.datasource.url={jdbc-value}

For h2 you can store in memory (mem) or in file:

spring.datasource.url=jdbc:h2:mem:{db-name}
spring.datasource.url=jdbc:h2:file{db-file}    eg: ~/mydatabase

Username for the database:

spring.datasource.username={username}
spring.datasource.password={password}
spring.datsource.driver-class.name={jdbc-driver-classname} 

for H2:

spring.datasource.driver-class.name=org.h2.Driver

Application.properties settings

To show SQL queries:

spring.jpa.show-sql=true

Turn on hibernate statistics:

spring.jpa.properties.hibernate.generate_statistics=true

Logging for statistics:

logging.level.org.hibernate.stat=debug

Logging for query parameters

logging.level.org.hibernate.type=debug

Format SQL that is outputted:

spring

 

Data Source Pooling

Spring boot detects standard pooling libraries in your classpath

tomcat-jdbc is the default pooling strategy used by spring boot, others are also possible.  The spring-boot-starter-data-jpa dependency in the pom.xml includes this pooling strategy

Common pooling properties

spring.datasource.max-active=10
spring-datasource.max-idle=8
spring.datasource.max-wait=10000
spring.datasource.min-evictable-idle-time-millis=1000
spring.datasource.min-idle=8
spring.datasource.time-between-eviction-runs-millis=1
@Configuration
public class DatasourcePersistenceConfiguration
{
     // Bean annotation says DS will be stored as spring bean in spring context
     // CF annotation specifies prefix in application.properties
     // Primary annotation says it is the default
     @Bean
     @ConfigurationProperties(prefix="spring.datasource")
     @Primary
     public DataSource dataSource()
     {
         return DataSourceBuilder.create().build();
     }

     // Another datasource - could be used for schema changes
     // FDS annotation - for flyway to recognize use with flyway
     @Bean
     @ConfigurationProperties(prefix="datasource.flyway")
     @FlywayDataSource
     public DataSource secondDataSource()
     {
         return DataSourceBuilder.create().build();
     }

Testing

Spring Boot Starter Test

  • JUnit – unit test framework
  • Hamcrest – Matching and assertions
  • Mockito – Mock objects and verification
  • Spring Test  – Testing tools and integration testing support
    org.springframework.boot
    spring-boot-starter-test
    test

Test Classes

Test classes are regular Java classes but test methods have the annotation:

@Test

Use static imports to import assert methods into the class:

import static org.junit.Assert.assertEquals;

Mock tests using Mockito

Use Mockito to manage objects by declaring them in the Test class and including the following attribute annotation, for example for a controller called MyController:

@InjectMocks
private MyController controller;   // eg: MyController controller

Also use Mockito to create a mock object, eg: for a repository of type MyRepository:

@Mock
private MyRepository repo;

Mockito also needs to be instructed to run the annotations using an init method run before the tests are executed:

@Before
public void init()
{
    MockitoAnnotations.initMocks(this);
}

To use the stubbed objects, first include static import:

import static org.mockito.Mockito.*;

Now, consider the repository is an interface as follows:

public interface MyRepository extends JpaRepository<EntityType, Long>
{ 
    public String findNameById(Long id)
}

You can now instruct Mockito to return a mock object for a call to MyRepository within your controller as follows:

EntityType myName = "Steve";
myName.setId(1L);
when(repo.findOne(1L)).thenReturn(myName);

// Assume controller here has a get method that returns object by Id
result = controller.get(1L);

// verify the mock object worked correctly
verify(repo).findOne(1L);
assertEquals(1L, result.getId().longValue());

Hamcrest

Provides syntactic sugar benefits over the JUnit assert api.  For example, the previous example would be coded as follows:

assertThat(result.getId(), is(1L));

Integration Testing

Create integration test class for testing the actual repository integration with the database (as opposed to stubbed out version in previous example):

@RunWith(SpringRunner.class)  // Part of Spring test tools
@SpringApplicationConfiguration(App.class) // Class with main method
public class RepoIntegrationTest {

@Autowired
private MyRepository repo;

   @Test
   public void testFindAll() { 

    List entities = repo.findAll();
    assertThat(entities.size(), is(greaterThanOrEqualTo(0)));
}

Web Integration Test

These are expensive to run, so they may be run on the build server

@RunWith(SpringRunner.class)  // Part of Spring test tools
@SpringApplicationConfiguration(App.class) // Class with main method
public class MyControllerWebIntegrationTest {

@Autowired
private MyRepository repo;

@Test
public void testGetAll() { 
    RestTemplate restTemplate = new TestRestTemplate();
    ResponseEntity response = restTemplate.getForEntity("http://localhost:8080/api/v1/mytest", String.class);
     assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));
     
    ObjectMapper mapper = new ObjectMapper();
    JsonNode responseJson = mapper.readTree(response.getBody());
 
    // Ensure that state is correct
    assertThat(responseJson.isMissingNode(), is(false));
    assertThat(responseJson.toString(), equalTo("[]");
}

Swagger

Reference: https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api

Open source tools built around Open API specification to help design, build, document, consume REST API’s.

  • Swagger Editor – browser based editor
  • Swagger UI – Interactive API documentation
  • Swagger Codegen – generates server stubs and client libraries

Swagger UI

  • To generate documentation from an API that is Swagger-compliant
  • To view the API documentation via browser
  • Allows consumers of the API to interact with API resources

Maven dependencies

  io.springfox
  springfox-swagger2
  2.9.2
  compile


  io.springfox
  springfox-swagger-ui
  2.9.2
  compile

Swagger requires configuration of a Docket bean, which is performed within a class you define called SwaggerConfig.java.  Each microservice requires its own independent configuration.  The following class level annotations are required:

@Configuration
@EnableSwagger2

Then an api method is required to configure Swagger for the microservice:
@Bean
public Docket api() {

String title="My Test API" ;
String description="Test API.";
String version="1.0";


return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.apiInfo(new ApiInfoBuilder()
.title(title)
.description(description)
.version(version)
.build());

}

Explanations

  • select() method on Docket bean allows for a way to control the endpoints exposed by Swagger via an ApiSelectorBuilder
  • apis() and paths() methods on the ApiSelectorBuilder can specify which documentation Swagger will expose – any() allows for the entire API’s documentation to be exposed.
  • build() method tells the ApiSelectorBuilder() to build the configuration
  • apiInfo() method allows for additional information about the API to be added, with values set for specific fields shown (title, version, description, etc)

To access the Swagger UI, go to:

http://localhost:8080/swagger-ui.html

Spring Concepts

Injection Using Annotations

One key benefit of Spring is that Beans can have their attributes automatically set using two key annotations:
@Component required to declare a Bean for Spring to pick up the Bean object using its Component Scanner to determine available beans
@Autowired allows attributes of the current bean to be automatically injected, ie: without having to write Java code that does this
When the Bean object is created, dependencies are automatically injected by Spring using setter methods or an appropriate constructor.

Setter Injection

For example, assume Class1 has an attribute of type Class2, which we want to use setter injection:
@Component
public class Class1 {

    @Autowired
    private Class2 foo;

    // Required for setter injection - as Spring will need to first instantiate Class 1
    public Class1() {}

    public void setFoo(Class2 foo)
    {
        this.foo = foo;
    }
}

@Component
public class Class2 {
   public Class2() {}
}

Constructor Injection

Very similar to Setter Injection, except now we use Constructor parameters to inject the dependency.
@Component
public class Class1 {@Autowired
    private Class2 foo;// Constructor now sets the value

    public Class1(Class2 foo) {
       this.foo = foo
    }

    public void setFoo(Class2 foo) {
       this.foo = foo;
    }
}

Services

The Service tier of a Spring Boot application describe the actions that can be performed within the system.  The implementation of the services is where the underlying business logic resides to ensure business objects are in the correct state and manage transactions.

Spring Boot and Java Persistence API (JPA)

Reference: https://www.baeldung.com/the-persistence-layer-with-spring-and-jpa
JPA provides the ability to map Java objects to a relational database. It is a specification (JSR 338) that was standardized through the Java Community Process.  The key benefit is that the underlying implementation for the specification is plug-and-play.  Hibernate is one of the implementations.
Note, JPA is not the appropriate tool to map objects to a NoSQL database or to JSon or XML.

JPA Provider for Spring Boot

Hibernate is the default JPA provider for Spring Boot so EntityManagerFactory does need to be defined within the application unless customizations are required.

Entities

Objects that need to be persisted in the database are annotated in the class definition:
@Entity
public class MyClass
The primary key attribute within the object is annotated:
@Id
private Long id;

Other key annotations are described as follows:

Class Level

@Table(name = "table_name"    -- to specify the table name

Attribute Level

Specify how the Id attribute is generated:

@GeneratedValue(strategy = GenerationType.AUTO)

Specify column name, length, nullability:

@Column (name = "column_name", nullable=[true|false], length = value)

Repository

Declared as an interface, which extends JpaRepository.  There is typically a 1:1 mapping between Repositories and Objects that are persisted in the database.

 

Lombok

Annotation utility for generating boilerplate methods like getters and setters in your classes.

Project Lombok

Maven Dependency

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.6</version>
</dependency>

Class Level or Method Level Annotations

@Getter will add a getter for every private attribute.
@Setter will add a setter for every private attribute
@AllArgsConstructor will generate a constructor containing arguments for all attributes
@NoArgsConstructor will generate a 
@ToString generates a toString method that outputs the attributes
@EqualsAndHashCode generates equals and hash code methods based on the attributes in the class
@Data bundles @Getter, @Setter, @ToString, @EqualsAndHashCode and @RequiredArgsConstructor
@Builder generates a builder() method that returns a class that allows you to set attributes or call other methods in a class

@Getter, @Setter, @Builder can also be method level

 

 

 

 

Read more...