Introdução ao Spring Remoting com HTTP Invokers
1. Visão geral
Em alguns casos, precisamos decompor um sistema em vários processos, cada um assumindo a responsabilidade por um aspecto diferente de nosso aplicativo. Nesses cenários, não é incomum que um dos processos precise obter dados de outro de forma síncrona.
O Spring Framework oferece uma gama de ferramentas chamadaSpring Remoting, que nos permite invocar serviços remotos como se estivessem, pelo menos até certo ponto, disponíveis localmente.
Neste artigo, vamos configurar um aplicativo baseado emHTTP invoker do Spring, que aproveita a serialização Java nativa e HTTP para fornecer invocação de método remoto entre um cliente e um aplicativo de servidor.
2. Definição de Serviço
Suponhamos que tenhamos que implementar um sistema que permita aos usuários reservar uma viagem de táxi.
Suponhamos também que escolhemos construirtwo distinct applications para obter este objetivo:
-
um aplicativo de mecanismo de reserva para verificar se uma solicitação de táxi pode ser atendida e
-
um aplicativo Web front-end que permite que os clientes reservem suas viagens, garantindo a disponibilidade de um táxi
2.1. Interface de Serviço
Quando usamosSpring Remoting comHTTP invoker,, temos que definir nosso serviço que pode ser chamado remotamente por meio de uma interface para permitir que o Spring crie proxies tanto no cliente quanto no servidor que encapsulem os aspectos técnicos da chamada remota. Então, vamos começar com a interface de um serviço que nos permite reservar um táxi:
public interface CabBookingService {
Booking bookRide(String pickUpLocation) throws BookingException;
}
Quando o serviço consegue alocar um táxi, ele retorna um objetoBooking com um código de reserva. Booking tem que ser serializável porque o invocador HTTP do Spring tem que transferir suas instâncias do servidor para o cliente:
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
}
Se o serviço não conseguir reservar um táxi, umBookingException é lançado. Neste caso, não há necessidade de marcar a classe comoSerializable porqueException já a implementa:
public class BookingException extends Exception {
public BookingException(String message) {
super(message);
}
}
2.2. Empacotando o serviço
A interface de serviço, juntamente com todas as classes personalizadas usadas como argumentos, tipos de retorno e exceções devem estar disponíveis no classpath do cliente e do servidor. Uma das maneiras mais eficazes de fazer isso é compactar todos eles em um arquivo.jar que pode ser incluído posteriormente como uma dependência nopom.xml do servidor e do cliente.
Vamos então colocar todo o código em um módulo Maven dedicado, chamado “api”; usaremos as seguintes coordenadas Maven para este exemplo:
com.example
api
1.0-SNAPSHOT
3. Aplicativo de servidor
Vamos construir o aplicativo de mecanismo de reservas para expor o serviço usando Spring Boot.
3.1. Dependências do Maven
Primeiro, você precisa garantir que seu projeto esteja usando o Spring Boot:
org.springframework.boot
spring-boot-starter-parent
1.4.3.RELEASE
Você pode encontrar a última versão do Spring Boothere. Em seguida, precisamos do módulo inicial da Web:
org.springframework.boot
spring-boot-starter-web
E precisamos do módulo de definição de serviço que montamos na etapa anterior:
com.example
api
1.0-SNAPSHOT
3.2. Implementação de Serviço
Em primeiro lugar, definimos uma classe que implementa a interface do serviço:
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());
}
}
Vamos fingir que esta é uma implementação provável. Usando um teste com um valor aleatório, seremos capazes de reproduzir ambos os cenários de sucesso - quando um táxi disponível foi encontrado e um código de reserva retornado - e cenários de falha - quando um BookingException é lançado para indicar que não há nenhum táxi disponível.
3.3. Expondo o serviço
Em seguida, precisamos definir um aplicativo com um bean do tipoHttpInvokerServiceExporter no contexto. Ele cuidará da exposição de um ponto de entrada HTTP no aplicativo Web que será posteriormente chamado pelo cliente:
@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);
}
}
É importante notar queHTTP invoker do Spring usa o nome do beanHttpInvokerServiceExporter como um caminho relativo para a URL do endpoint HTTP.
Agora podemos iniciar o aplicativo do servidor e mantê-lo em execução enquanto configuramos o aplicativo cliente.
4. Aplicativo Cliente
Vamos agora escrever o aplicativo cliente.
4.1. Dependências do Maven
Usaremos a mesma definição de serviço e a mesma versão do Spring Boot que usamos no lado do servidor. Ainda precisamos da dependência do iniciador da web, mas como não precisamos iniciar automaticamente um contêiner incorporado, podemos excluir o iniciador do Tomcat da dependência:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
4.2. Implementação do Cliente
Vamos implementar o cliente:
@Configuration
public class Client {
@Bean
public HttpInvokerProxyFactoryBean invoker() {
HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
invoker.setServiceUrl("http://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"));
}
}
O método() do invocador anotado@Bean cria uma instância deHttpInvokerProxyFactoryBean. Precisamos fornecer a URL para a qual o servidor remoto responde por meio do métodosetServiceUrl().
Da mesma forma que fizemos para o servidor, também devemos fornecer a interface do serviço que queremos invocar remotamente por meio do métodosetServiceInterface().
HttpInvokerProxyFactoryBean implementaFactoryBean do Spring. UmFactoryBean é definido como um bean, mas o contêiner Spring IoC injetará o objeto que ele cria, não a própria fábrica. Você pode encontrar mais detalhes sobreFactoryBean em nossofactory bean article.
O métodomain() inicializa o aplicativo independente e obtém uma instância deCabBookingService do contexto. Nos bastidores, esse objeto é apenas um proxy criado porHttpInvokerProxyFactoryBean que cuida de todos os detalhes técnicos envolvidos na execução da invocação remota. Graças a isso, agora podemos usar facilmente o proxy como faríamos se a implementação do serviço estivesse disponível localmente.
Vamos executar o aplicativo várias vezes para executar várias chamadas remotas para verificar como o cliente se comporta quando um táxi está disponível e quando não está.
5. Caveat Emptor
Quando trabalhamos com tecnologias que permitem invocações remotas, há algumas armadilhas que devemos estar cientes.
5.1. Cuidado com as exceções relacionadas à rede
Sempre devemos esperar o inesperado quando trabalhamos com um recurso não confiável como a rede.
Suponhamos que o cliente esteja chamando o servidor enquanto ele não pode ser alcançado - seja por um problema de rede ou porque o servidor está fora do ar - então Spring Remoting levantará umRemoteAccessException que é_ a _RuntimeException.
O compilador não nos forçará a incluir a chamada em um bloco try-catch, mas devemos sempre considerar fazê-lo, para gerenciar adequadamente os problemas de rede.
5.2. Objetos são transferidos por valor, não por referência
Spring Remoting HTTP empacota os argumentos do método e retorna valores para transmiti-los na rede. Isso significa que o servidor atua sobre uma cópia do argumento fornecido e o cliente atua sobre uma cópia do resultado criado pelo servidor.
Portanto, não podemos esperar, por exemplo, que invocar um método no objeto resultante altere o status do mesmo objeto no lado do servidor, porque não há nenhum objeto compartilhado entre cliente e servidor.
5.3. Cuidado com interfaces refinadas
Invocar um método através dos limites da rede é significativamente mais lento do que invocá-lo em um objeto no mesmo processo.
Por esse motivo, geralmente é uma boa prática definir serviços que devem ser chamados remotamente com interfaces de granulação mais grossa, capazes de concluir transações comerciais que exigem menos interações, mesmo à custa de uma interface mais complicada.
6. Conclusão
Com este exemplo, vimos como é fácil com o Spring Remoting invocar um processo remoto.
A solução é um pouco menos aberta do que outros mecanismos comuns, como REST ou serviços da web, mas em cenários em que todos os componentes são desenvolvidos com o Spring, ela pode representar uma alternativa viável e muito mais rápida.
Como de costume, você encontrará as fontesover on GitHub.