Вступ до весняного видалення за допомогою HTTP Invokers

1. Огляд

У деяких випадках нам потрібно розкласти систему на кілька процесів, кожен з яких несе відповідальність за різні аспекти нашого додатку. У цих сценаріях нерідкі випадки, коли одному із процесів потрібно синхронно отримувати дані з іншого.

Spring Framework пропонує цілий ряд інструментів, які всебічно називаються Spring Remoting, що дозволяє нам викликати віддалені служби так, ніби вони принаймні певною мірою доступні локально.

У цій статті ми створимо додаток на основі HTTP-засобу виклику Spring , який використовує власну серіалізацію Java та HTTP для забезпечення віддаленого виклику методу між клієнтом та серверною програмою.

2. Визначення послуги

Припустимо, ми повинні впровадити систему, яка дозволяє користувачам замовляти поїздку в таксі.

Давайте також припустимо, що для досягнення цієї мети ми вирішили створити два окремі додатки :

  • заявку на механізм бронювання, щоб перевірити, чи можна задовольнити запит таксі, та
  • інтерфейсний веб-додаток, що дозволяє клієнтам замовляти свої поїздки, гарантуючи наявність кабіни

2.1. Сервісний інтерфейс

Коли ми використовуємо Spring Remoting із засобом виклику HTTP, нам потрібно визначити нашу віддалено викликану службу через інтерфейс, щоб Spring створював проксі-сервери як на стороні клієнта, так і на сервері, які містять технічні характеристики віддаленого виклику. Отже, почнемо з інтерфейсу сервісу, який дозволяє замовити таксі:

public interface CabBookingService { Booking bookRide(String pickUpLocation) throws BookingException; }

Коли служба може виділити кабіну, вона повертає об'єкт Бронювання з кодом бронювання. Бронювання повинно бути серіалізуваним, оскільки HTTP-інвертор Spring повинен передати свої екземпляри з сервера на клієнта:

public class Booking implements Serializable { private String bookingCode; @Override public String toString() { return format("Ride confirmed: code '%s'.", bookingCode); } // standard getters/setters and a constructor }

Якщо служба не може забронювати таксі, викидається BookingException . У цьому випадку немає необхідності позначати клас як Серіалізований, оскільки Exception це вже реалізує:

public class BookingException extends Exception { public BookingException(String message) { super(message); } }

2.2. Упаковка послуги

Інтерфейс служби разом із усіма користувацькими класами, що використовуються як аргументи, типи повернення та винятки, повинен бути доступний як у шляху клієнта, так і на сервері. Один з найефективніших способів зробити це - упакувати їх у файл .jar, який згодом можна включити як залежність до сервера pom.xml і сервера та клієнта .

Покладемо таким чином весь код у виділений модуль Maven, який називається “api”; для цього прикладу ми використаємо такі координати Maven:

com.baeldung api 1.0-SNAPSHOT

3. Серверна програма

Давайте створимо додаток механізму бронювання, щоб виставити послугу за допомогою Spring Boot.

3.1. Залежності Maven

Спочатку вам потрібно переконатися, що ваш проект використовує Spring Boot:

 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE 

Останню версію Spring Boot ви можете знайти тут. Потім нам потрібен модуль веб-початківця:

 org.springframework.boot spring-boot-starter-web 

І нам потрібен модуль визначення служби, який ми зібрали на попередньому кроці:

 com.baeldung api 1.0-SNAPSHOT 

3.2. Впровадження послуги

Спочатку ми визначаємо клас, який реалізує інтерфейс служби:

public class CabBookingServiceImpl implements CabBookingService { @Override public Booking bookPickUp(String pickUpLocation) throws BookingException { if (random() < 0.3) throw new BookingException("Cab unavailable"); return new Booking(randomUUID().toString()); } }

Давайте зробимо вигляд, що це ймовірна реалізація. Використовуючи тест із випадковим значенням, ми зможемо відтворити як успішні сценарії - коли буде знайдено доступну кабіну та повернуто код бронювання, так і невдалі сценарії - коли брошено BookingException, щоб вказати, що немає доступної кабіни.

3.3. Виставлення послуги

Потім нам потрібно визначити програму з компонентом типу HttpInvokerServiceExporter у контексті. Він подбає про виявлення точки входу HTTP у веб-програмі, яку згодом буде викликати клієнт:

@Configuration @ComponentScan @EnableAutoConfiguration public class Server { @Bean(name = "/booking") HttpInvokerServiceExporter accountService() { HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter(); exporter.setService( new CabBookingServiceImpl() ); exporter.setServiceInterface( CabBookingService.class ); return exporter; } public static void main(String[] args) { SpringApplication.run(Server.class, args); } }

Варто зазначити, що засіб виклику HTTP Spring використовує ім’я компонента HttpInvokerServiceExporter як відносний шлях до URL-адреси кінцевої точки HTTP.

Тепер ми можемо запустити серверну програму та продовжувати її працювати під час налаштування клієнтської програми.

4. Клієнтська заявка

Давайте тепер напишемо клієнтську програму.

4.1. Залежності Maven

Ми будемо використовувати те саме визначення служби та ту саму версію Spring Boot, яку ми використовували на стороні сервера. Нам все ще потрібна залежність веб-стартера, але оскільки нам не потрібно автоматично запускати вбудований контейнер, ми можемо виключити стартер Tomcat із залежності:

 org.springframework.boot spring-boot-starter-web   org.springframework.boot spring-boot-starter-tomcat   

4.2. Впровадження клієнта

Давайте реалізуємо клієнта:

@Configuration public class Client { @Bean public HttpInvokerProxyFactoryBean invoker() { HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean(); invoker.setServiceUrl("//localhost:8080/booking"); invoker.setServiceInterface(CabBookingService.class); return invoker; } public static void main(String[] args) throws BookingException { CabBookingService service = SpringApplication .run(Client.class, args) .getBean(CabBookingService.class); out.println(service.bookRide("13 Seagate Blvd, Key Largo, FL 33037")); } }

The @Bean annotated invoker() method creates an instance of HttpInvokerProxyFactoryBean. We need to provide the URL that the remote server responds at through the setServiceUrl() method.

Similarly to what we did for the server, we should also provide the interface of the service we want to invoke remotely through the setServiceInterface() method.

HttpInvokerProxyFactoryBean implements Spring's FactoryBean. A FactoryBean is defined as a bean, but the Spring IoC container will inject the object it creates, not the factory itself. You can find more details about FactoryBean in our factory bean article.

The main() method bootstraps the stand alone application and obtains an instance of CabBookingService from the context. Under the hood, this object is just a proxy created by the HttpInvokerProxyFactoryBean that takes care of all technicalities involved in the execution of the remote invocation. Thanks to it we can now easily use the proxy as we would do if the service implementation had been available locally.

Let's run the application multiple times to execute several remote calls to verify how the client behaves when a cab is available and when it is not.

5. Caveat Emptor

When we work with technologies that allow remote invocations, there are some pitfalls we should be well aware of.

5.1. Beware of Network Related Exceptions

We should always expect the unexpected when we work with an unreliable resource as the network.

Let's suppose the client is invoking the server while it cannot be reached – either because of a network problem or because the server is down – then Spring Remoting will raise a RemoteAccessException that is a RuntimeException.

The compiler will not then force us to include the invocation in a try-catch block, but we should always consider to do it, to properly manage network problems.

5.2. Objects Are Transferred by Value, Not by Reference

Spring Remoting HTTP marshals method arguments and returned values to transmit them on the network. This means that the server acts upon a copy of the provided argument and the client acts upon a copy of the result created by the server.

So we cannot expect, for instance, that invoking a method on the resulting object will change the status of the same object on the server side because there is not any shared object between client and server.

5.3. Beware of Fine-Grained Interfaces

Invoking a method across network boundaries is significantly slower than invoking it on an object in the same process.

For this reason, it is usually a good practice to define services that should be remotely invoked with coarser grained interfaces that are able to complete business transactions requiring fewer interactions, even at the expense of a more cumbersome interface.

6. Conclusion

With this example, we saw how it is easy with Spring Remoting to invoke a remote process.

Рішення дещо менш відкрите, ніж інші широко розповсюджені механізми, такі як REST або веб-сервіси, але в сценаріях, коли всі компоненти розробляються за допомогою Spring, воно може стати життєздатною та набагато швидшою альтернативою.

Як завжди, ви знайдете джерела на GitHub.