Remote versus local clients

As we have previously seen in this chapter, our clients were defined by using interfaces. The main motivation for this added level of abstraction is so that we can substitute a remote implementation for a local one. Let's say that component A depends on component B. If these components were deployed on separate servers, we would want to use an implementation of B's client that makes remote calls to the component. If, however, both components co-existed within the same JVM, using a remote client would incur unnecessary network latency. Substituting the client for one that directly invokes component B's Java implementation ensures that no networking takes place, thus reducing latency.

Note

This pattern is commonly used in Microservice architectures (https://en.wikipedia.org/wiki/Microservices), which are becoming very popular. This style of architecture advocates the breaking up of complex applications into small, independent components.

Availability and booking services

Let's look at the availability and booking components of our sample property management system. The availability service is dependent on the booking service. To deal with this, we define a simple client interface for the booking service, as shown here:

public interface BookingServiceClient {

    /**
     * Looks up the booking with the given identifier.
     *
     * @param bookingId the booking identifier to look up
     *
     * @return the booking with the given ID
     */
    public Booking getBooking(long bookingId);
}

This client interface defines a single method to look up a booking by its identifier.

Our first deployment approach is to have both components running in separate JVMs. Therefore, we implement a remote client that the availability service can leverage:

public class RemoteBookingServiceClient implements BookingServiceClient {

  private final String serviceUrl;
  private final RestTemplate template;

  public RemoteBookingServiceClient(String serviceUrl) {
    if (serviceUrl == null) {
      throw new IllegalArgumentException("serviceUrl cannot be null");
    }
    this.serviceUrl = serviceUrl;
    template = new RestTemplate();
  }

  @Override
  public Booking getBooking(long bookingId) {
    //omitted to clarity
    return (Booking) ResponseHandler.handle(
      () -> template.exchange(serviceUrl + "/bookings/" + bookingId, HttpMethod.GET, null, typeReference).getBody());
  }
}

In previous sections of this chapter, we've covered how to use org.springframework.web.client.RestTemplate to remotely invoke the service.

Now, to reduce latency, we decide to run both components in the same JVM. Using this client implementation will negate any benefit of hosting both services in the same process. Therefore, we need a new implementation:

public class LocalBookingServiceClient implements BookingServiceClient {

  @Autowired
  private BookingService bookingService;

  @Override
  public Booking getBooking(long bookingId) {
    com.packtpub.springrest.model.Booking booking = bookingService.getBooking(bookingId);
    Booking clientBooking = new Booking();
    clientBooking.setId(booking.getId());
    // omitted setting other fields for clarity
    return clientBooking;
  }
}

With this new implementation, we simply delegate the booking retrieval to the actual service, bypassing any networking. We then need to transform the data into what the client invoker is expecting.

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

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