REST API adapter (BuildIt side)

From this week and onwards, you will be implementing BuildIt’s information system. This new application relies on RentIt’s information system, such that the lack of some features in RentIt’s side can block the progress in the implementation of BuildIt’s project. To break with this dependency, we will use test mocks. A test mock is a fake implementation for a system component that is used in a situation like ours. In our context, we will use a well known library called Mockito as described in the following subsection.

A RentIT mock service

As way of example, let us consider the implementation of the service that connects BuildIt application with RentIt’s as shown in the following snippet.

@Service
public class RentalService {
    @Autowired
    RestTemplate restTemplate;

    public List<PlantInventoryEntryDTO> findAvailablePlants(String plantName, LocalDate startDate, LocalDate endDate) {
        PlantInventoryEntryDTO[] plants = restTemplate.getForObject(
                "http://localhost:8090/api/inventory/plants?name={name}&startDate={start}&endDate={end}",
                PlantInventoryEntryDTO[].class, plantName, startDate, endDate);
        return Arrays.asList(plants);
    }
}

The connection to RentIt’s REST endpoint is made by means of spring’s RestTemplate class. This class provides methods for starting RESTful interactions in a convenient way. In the example above, we are executing an HTTP GET interaction, as hinted by the method’s name getForObject(). The latter method is called with the corresponding URI, and will return an array with PlantInventoryEntryDTO (it seems that RestTemplate cannot return lists). Note that we are using a URI template, and that we can just pass the values to be replaced in the URI template at the end of the call.

As for RentIt, we have to add a class for properly configuring the object mapper used by Spring hateoas. Also, we need to configure the restTemplate bean to use Spring hateoas object mapper. This is important because otherwise it will not properly handle the serialization/desearialization of DTOs. To this end, use the following code:
@SpringBootApplication
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class ProcurementApplication {

  @Configuration
  static class ObjectMapperCustomizer {
    @Autowired @Qualifier("_halObjectMapper")
    private ObjectMapper springHateoasObjectMapper;

    @Bean(name = "objectMapper")
    ObjectMapper objectMapper() {
      return springHateoasObjectMapper
          .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
          .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
          .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
          .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
          .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
          .registerModules(new JavaTimeModule());
		}
    @Bean
		public RestTemplate restTemplate() {
			RestTemplate _restTemplate = new RestTemplate();
			List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
			messageConverters.add(new MappingJackson2HttpMessageConverter(springHateoasObjectMapper));
			_restTemplate.setMessageConverters(messageConverters);
			return _restTemplate;
		}
	}

	public static void main(String[] args) {
		SpringApplication.run(ProcurementApplication.class, args);
	}
}

The method above should already work with our prototype RentIt implementation. However, we would need to have an instance of RentIt running all the time, if we want to test something in BuildIt. This approach has several drawbacks (e.g. high memory/cpu requirements) and can block the progress in the development of the BuildIt implementation (e.g. if we needed to test a feature that is not yet implemented in RentIt). The alternative is to use a test mock. To that end, we will use Mockito, a popular test mock framework for java. We will provide the basic setup of Mockito for our Spring-based application, and will refer you to Mockito’s documentation to get further information.

Let us now have a look at the test code.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {ProcurementApplication.class,
  ProcurementRestControllerTests.RentalServiceMock.class}) (1)
@WebAppConfiguration
public class PlantHireRequestControllerTests {
	@Autowired
	private WebApplicationContext wac;
	private MockMvc mockMvc;

	@Autowired @Qualifier("_halObjectMapper")
	ObjectMapper mapper;

	@Autowired
	RentalService rentalService;

  @Configuration
  static class RentalServiceMock { (2)
      @Bean
      public RentalService rentalService() {
          return Mockito.mock(RentalService.class); (3)
      }
  }

	@Before
	public void setup() {
		this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
	}

	@Test
	public void testGetAllPlants() throws Exception {
		Resource responseBody = new ClassPathResource("trucks.json", this.getClass()); (4)
		List<PlantInventoryEntryDTO> list =
        mapper.readValue(responseBody.getFile(), new TypeReference<List<PlantInventoryEntryDTO>>() { });
		LocalDate startDate = LocalDate.now();
		LocalDate endDate = startDate.plusDays(2);
		when(rentalService.findAvailablePlants("Truck", startDate, endDate)).thenReturn(list); (5)
		MvcResult result = mockMvc.perform(
        get("/api/procurements/plants?name=Truck&startDate={start}&endDate={end}", startDate, endDate))
				.andExpect(status().isOk())
				.andReturn();

    // Add test expectations
	}
}
trucks.json
[{"_id":13,"name":"D-Truck","description":"15 Tonne Articulating Dump Truck","price":250.00,
       "_links":{"self":{"href":"http://localhost:8090/api/inventory/plants/13"}}},
{"_id":14,"name":"D-Truck","description":"30 Tonne Articulating Dump Truck","price":300.00,
       "_links":{"self":{"href":"http://localhost:8090/api/inventory/plants/14"}}}]

Note that the configuration of this test class is slightly different w.r.t. to the previous ones. In this case, as specified in the line with bullet 1, the class requires to load the configuration specified by our project’s entry class (i.e. in this case ProcurementApplication), in addition to class ProcurementApplication.RentalServiceMock which is defined as an inner class, in the line marked with bullet 2. Note that the latter inner class serves only to replace the instance of class RentalService with a mock created by Mockito in the line with bullet 3. Recall that a mock is a Java object that implements the same interface as the original Java object, which we will use to spy on the calls made towards the original Java object. The mock object can also be use to setup fake responses to simulate the behavior that is expected from the original Java object.

The line marked with bullet 5 shows how the mock object is configured with a fake response. In that line, we are specifying that the mock object would return a list of plant inventory entries when the application "interacts" with the rental service, calling the method findAvailablePlants. Instead of hardwiring the list of plant inventory entries to be returned, we are storing them in a separate json file called trucks.json (as usual, the file should be located in the source folder src/test/resources, within a relative path that mirrors the package of the test class). The line marked with bullet 4 and the subsequent line show how to load the content of trucks.json and parse its content to get the corresponding list of plant inventory entries.

Tasks

clipboard checklist pencil icon display Your to do list

Starting from this practical, you will have two spring boot projects: one for RentIt and one for BuildIt. To help you understand the organization of both projects, their architecture and give you time to complete this part of the project, we have decided to split the assignment into two subtasks that will be submitted one each week.

To understand this assignment, you would need to read the Plant Hire Scenario (PHR).

Milestone 1

As a first milestone, we ask you to focus on the interaction of BuildIt and RentIt’s information systems. In some way, what we are going to do is to write a component that resides in BuildIt’s side that forwards requests to the Rest controller implemented in the previous week. The approach is already illustrated in this handout, as we already forward the query made by the site engineer to Rentit’s plant catalog.

Another interaction occurs, when a works engineer at BuildIt accepts a Plant Hire Request. According to the scenario, such operation should automatically fire the creation of a purchase order (i.e. the partial representation that contains a reference to the plant inventory entry, the rental period) that is forward to RentIt. Therefore, we need to set up at least part of BuildIt’s side Domain model to store the PHRs. This domain model will be complemented in the second milestone. To this end, we propose you to consider the following model (of course, you can refine it):

buildit domain model lab7

Note that classes PlantInventoryEntry and PurchaseOrder basically hold only the reference to resources in RentIt’s information system, i.e. attribute href. In some way, such classes follow the pattern that we called "identifier wrappers", introduced in lecture 3. The idea is that we don’t need to store the full representation of those resources because we can always retrieve it from RentIt’s information system. However, it is a good idea to store, in addition to href, some additional information to avoid an interaction with RentIt’s information system when we need to render something in BuildIt’s side.

In sum, in this milestone you have to complete the following subtasks:

  • Set up the minimum domain model presented above (e.g. creating Entity/Value object classes, repositories). Let’s use procurement as the name for this bounded context.

  • Add an application service class to interact with the procurement bounded context.

  • Add methods to ProcurementRestController to allow the creation of PHRs (assume that you create the PHR already in accepted state, to fire the creation of a PO in RentIt’s side) and other methods to request to RentIt’s information system (e.g. catalog query, querying POs, closing/resubmitting POs).

  • Write test cases for testing:

    • Creation of a PO from BuildIt’s Information system (this means that you have to complete the test that we started writing in this handout)

    • Creation of an already accepted PHR, with the subsequent interaction with RentIt’s Information system (use Mockito to replace the actual RentIt information system)

Milestone 2

Complete the following elements:

  • Domain model for the procurement process (e.g. information about site/works engineer, comments written by the works engineer when he/she decides to reject a PHR). USE VALUE OBJECTS TO REPRESENT REMOTE CONCEPTS (E.G. PlantInventoryEntry, PurchaseOrder, etc.) AND STORE ONLY THE REFERENCES TO THEM (I.E. their hrefs). TO REDUCE THE ROUND TRIPS YOU CAN STORE ADDITIONAL INFORMATION. HOWEVER, YOU MUST TRY TO AVOID STORING THE FULL RESOURCE REPRESENTATION

  • Design and implement the state model for Plant Hire Request using Hypermedia. Do not forget to include the diagram in your submission.

  • Write at least the two following tests:

    • One simulating the case, starting from the creation of a PHR up to the moment where the corresponding PO is created in RentIt’s side. Assume in this test that the works engineer accepts the PHR without any further iteration with the site engineer. The sequence of steps should include:

      (1) querying RentIt's plant catalog, (2) selecting one plant for creating a Plant hire request, (3) accepting the plant hire request (this should entail the creation of the Purchase order in RentIt's side), and (4) checking the state of the Plant hire request after receiving the response from RentIt's (it could be an acceptance or a rejection).
    • One simulating the case where the PHR is rejected. The sequence of steps should include:

      (1) querying RentIt's plant catalog, (2) selecting one plant for creating a Plant hire request, (3) rejecting the plant hire request

You must write BuildIt’s tests using a mock service instead of RentIt.