The RESTful web service

The Spring framework offers an intuitive and easy-to-use model to expose your data, using the RESTful web service, which is the de facto standard for API communication between microservices.

The version that we will use in this section, the 2.0.5 release, automatically exposes CRUD operations as a RESTful API.

As we described in the Repository – JPA section, our FootballPlayerRepository interface extends the org.springframework.data.repository.CrudRepository, from which it inherits the following methods:

...

public <S extends T> S save(S s);

public <S extends T> Iterable<S> saveAll(Iterable<S> itrbl);

public Optional<T> findById(ID id);

public boolean existsById(ID id);

public Iterable<T> findAll();

public Iterable<T> findAllById(Iterable<ID> itrbl);

public long count();

public void deleteById(ID id);

public void delete(T t);

public void deleteAll(Iterable<? extends T> itrbl);

public void deleteAll();

Using the Spring Data Rest module, as I mentioned previously, you can automatically expose those methods. In order to do so, you should insert the spring-boot-starter-data-rest dependency into your Maven pom.xml file, as follows:

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

Then compile and run your project, as follows:

$ mvn clean package && mvn spring-boot:run

You will be able to obtain the links for the APIs available in your application. If you open a browser and call http://localhost:8080/, you will obtain the links to http://localhost:8080/footballPlayers, as shown in the following screenshot:

By invoking http://localhost:8080/footballPlayers, you will obtain a list of the football players stored in the application, as follows:

{
"_embedded" : {
"footballPlayers" : [ {
"name" : "Gianluigi",
"surname" : "Buffon",
"age" : 40,
"team" : "Paris Saint Germain",
"position" : "goalkeeper",
"price" : 2,
"_links" : {
"self" : {
"href" : "http://localhost:8080/footballPlayers/1"
},
"footballPlayer" : {
"href" : "http://localhost:8080/footballPlayers/1"
}
}
},

{
"name" : "Manuel",
"surname" : "Neuer",
"age" : 32,
"team" : "Bayern Munchen",
"position" : "goalkeeper",
"price" : 35,
"_links" : {
"self" : {
"href" : "http://localhost:8080/footballPlayers/2"
},
"footballPlayer" : {
"href" : "http://localhost:8080/footballPlayers/2"
}
}
},
...
}

Otherwise, in this section we will define the old-style approach: I will create a service and controller class, in order to invoke the JPA layer (the repository) and expose the API. Due to a bug present in the SpringFox framework, it is impossible to document the API using Swagger, one of the key features of a good microservice. I will therefore follow the scenario that was described earlier, while waiting for the bug to be resolved.

First of all, I will create a service class that is used to decouple the business logic between the REST API controller and the data access layer. I will only implement the basic CRUD method, and not all collections made available by the CRUD repository interface.

Its implementation, in our case, is very easy, and it seems like only a pass-through; in a real production use case, you can use the business logic operation, in order to adhere to the separation of duties pattern:

@Service
public class FootballPlayerService {

@Autowired
private FootballPlayerRepository repository;

public Iterable<FootballPlayer> findAll() {
return repository.findAll();
}

public FootballPlayer save(FootballPlayer entity) {
return repository.save(entity);
}

public void deleteById(Integer id) {
repository.deleteById(id);
}

public Optional<FootballPlayer> findById(Integer id) {

 return repository.findById(id);
}

}

Now we will expose our services method by using a RESTful web service, as follows:

@RestController
@RequestMapping("/footballplayer")
public class FootballPlayerRESTController {

@Autowired
private FootballPlayerService service;

@RequestMapping(method = RequestMethod.GET, produces = "application/json")
public Iterable<FootballPlayer> findAll() {
return service.findAll();
}

@RequestMapping(value = "/save", method = RequestMethod.POST, produces = "application/json")
public FootballPlayer save(@RequestBody FootballPlayer entity) {
return service.save(entity);
}

@RequestMapping(value = "/update/{id}", method = RequestMethod.PUT, produces = "application/json")
public FootballPlayer edit(@PathVariable Integer id, @RequestBody FootballPlayer entity) {
return service.save(entity);
}

@RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE, produces = "application/json")
public void delete(@PathVariable Integer id) {
service.deleteById(id);
}

@RequestMapping(value = "/show/{id}", method = RequestMethod.GET, produces = "application/json")
public Optional<FootballPlayer> findById(@PathVariable Integer id) {
return service.findById(id);
}
}

As you can see, we have defined the following:

  • The API paths
  • The API parameters 
  • The producers and consumers payload types

Now we can invoke the API that retrieves the list of football players, as follows:

$ curl http://localhost:8080/footballplayer | json_pp

The output should be similar to the following (for convenience, we have only shown a portion of the code):

[
{
"id":1,
"name":"Gianluigi",
"surname":"Buffon",
"age":40,
"team":"Paris Saint Germain",
"position":"goalkeeper",
"price":2
},
{
"id":2,
"name":"Manuel",
"surname":"Neuer",
"age":32,
"team":"Bayern Munchen",
"position":"goalkeeper",
"price":35
},
{
"id":3,
"name":"Keylor",
"surname":"Navas",
"age":31,
"team":"Real Madrid",
"position":"goalkeeper",
"price":18
},
...
]

Finally, we will create the JUnit test, in order to ensure that our APIs work properly.

Let's add the Maven dependencies, as follows:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>

As usual, I didn't specify the version, because it is automatically handled by the spring-boot-starter-parent BOM.

Next, I will build the test methods inside the class com.packtpub.springboot.footballplayermicroservice.FootballPlayerMicroserviceApplicationTests, which was created by the Spring Initializr utility.

The test class will look as follows:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = FootballPlayerMicroserviceApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class FootballPlayerMicroserviceApplicationTests {
private final HttpHeaders headers = new HttpHeaders();

private final TestRestTemplate restTemplate = new TestRestTemplate();

@LocalServerPort
private int port;

@Test
public void test_1_FindAll() throws IOException {
System.out.println("findAll");
HttpEntity<String> entity = new HttpEntity<>(null, headers);
ResponseEntity<String> response =
restTemplate.exchange(createURLWithPort("/footballplayer"),
HttpMethod.GET, entity, String.class);

assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);

JSONArray jsonArray = JsonPath.read(response.getBody(), "$.[*]");
assertThat(23).isEqualTo(jsonArray.size());
}

@Test
public void test_2_Create() {
System.out.println("create");
FootballPlayer player = new FootballPlayer("Mauro", "Vocale", 38, "Juventus", "central
midfielder", new BigInteger("100"));

HttpEntity<FootballPlayer> entity = new HttpEntity<>(player, headers);
ResponseEntity<String> response = restTemplate.exchange(
createURLWithPort("/footballplayer/save"),
HttpMethod.POST, entity, String.class);

assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo(
"{"id":24,"name":"Mauro","surname":"Vocale","age":38,"team":"Juventus","position":"central midfielder","price":100}");
}
....

Let's analyze the most important sections of the class.

I will run the test suite using the SpringRunner class, as defined inside the @RunWith annotation. SpringRunner is an alias for the SpringJUnit4ClassRunner class, and it has the utilities needed to create the Spring context and perform the test.

I decided to implement an integration test, so I didn't mock anything; instead I called the real methods.

In order to do this, I needed to create a real Spring execution environment. Using the @SpringBootTest annotation, I set the class to launch (in our case FootballPlayerMicroserviceApplication.class), and a random port where the embedded servlet containers were executed.

Finally, I had to execute the test in a well-defined order, to avoid failures related to the absence of my test records. In order to do this, I set the FixMethodOrder(MethodSorters.NAME_ASCENDING). This way, Spring executed the test based on the method's name. This is the reason for using an ascending number in the method's name.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset