Intro à Spring Remoting avec HTTP Invokers

Introduction à Spring Remoting avec HTTP Invokers

1. Vue d'ensemble

Dans certains cas, nous devons décomposer un système en plusieurs processus, chacun prenant la responsabilité d'un aspect différent de notre application. Dans ces scénarios, il n’est pas rare que l’un des processus doive obtenir simultanément des données d’un autre.

Spring Framework propose une gamme d'outils appelés globalementSpring Remoting qui nous permettent d'appeler des services distants comme s'ils étaient, au moins dans une certaine mesure, disponibles localement.

Dans cet article, nous allons configurer une application basée sur lesHTTP invoker de Spring, qui exploite la sérialisation Java native et HTTP pour fournir un appel de méthode à distance entre un client et une application serveur.

2. Définition du service

Supposons que nous devions implémenter un système permettant aux utilisateurs de réserver un trajet en taxi.

Supposons également que nous choisissions de construiretwo distinct applications pour atteindre cet objectif:

  • une application du moteur de réservation pour vérifier si une demande de taxi peut être servie, et

  • une application Web frontale qui permet aux clients de réserver leurs trajets, en s'assurant de la disponibilité d'un taxi

2.1. Interface de service

Lorsque nous utilisonsSpring Remoting avecHTTP invoker,, nous devons définir notre service appelable à distance via une interface pour permettre à Spring de créer des proxies côté client et côté serveur qui encapsulent les aspects techniques de l'appel à distance. Commençons donc par l'interface d'un service qui nous permet de réserver un taxi:

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

Lorsque le service est capable d'allouer un taxi, il retourne un objetBooking avec un code de réservation. Booking doit être sérialisable car l'appelant HTTP de Spring doit transférer ses instances du serveur vers le client:

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
}

Si le service n'est pas en mesure de réserver un taxi, unBookingException est lancé. Dans ce cas, il n'est pas nécessaire de marquer la classe commeSerializable carException l'implémente déjà:

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

2.2. Emballage du service

L'interface de service ainsi que toutes les classes personnalisées utilisées comme arguments, types de retour et exceptions doivent être disponibles dans le chemin de classe du client et du serveur. L’un des moyens les plus efficaces d’y parvenir est de les regrouper tous dans un fichier.jar qui peut être ultérieurement inclus en tant que dépendance dans lespom.xml du serveur et du client.

Mettons donc tout le code dans un module Maven dédié, appelé «api»; nous utiliserons les coordonnées Maven suivantes pour cet exemple:

com.example
api
1.0-SNAPSHOT

3. Application serveur

Créons l'application du moteur de réservation pour exposer le service à l'aide de Spring Boot.

3.1. Dépendances Maven

Tout d’abord, vous devez vous assurer que votre projet utilise Spring Boot:


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

Vous pouvez trouver la dernière version de Spring Boothere. Nous avons ensuite besoin du module de démarrage Web:


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

Et nous avons besoin du module de définition de service que nous avons assemblé à l'étape précédente:


    com.example
    api
    1.0-SNAPSHOT

3.2. Mise en œuvre des services

Nous définissons d'abord une classe qui implémente l'interface du service:

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());
    }
}

Faisons semblant qu’il s’agit d’une mise en œuvre probable. En utilisant un test avec une valeur aléatoire, nous serons en mesure de reproduire à la fois des scénarios réussis - lorsqu'un taxi disponible a été trouvé et un code de réservation renvoyé - et des scénarios défaillants - lorsqu'une exception BookingException est lancée pour indiquer qu'il n'y a pas de taxi disponible.

3.3. Exposer le service

Il faut ensuite définir une application avec un bean de typeHttpInvokerServiceExporter dans le contexte. Il se chargera d'exposer un point d'entrée HTTP dans l'application Web qui sera appelé ultérieurement par le client:

@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);
    }
}

Il convient de noter queHTTP invoker de Spring utilise le nom du beanHttpInvokerServiceExporter comme chemin relatif pour l'URL du point de terminaison HTTP.

Nous pouvons maintenant démarrer l’application serveur et la laisser fonctionner pendant la configuration de l’application client.

4. Application client

Écrivons maintenant l'application cliente.

4.1. Dépendances Maven

Nous utiliserons la même définition de service et la même version de Spring Boot que nous avons utilisées côté serveur. Nous avons toujours besoin de la dépendance de démarrage Web, mais comme nous n'avons pas besoin de démarrer automatiquement un conteneur intégré, nous pouvons exclure le démarreur Tomcat de la dépendance:


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

4.2. Implémentation client

Implémentons le client:

@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"));
    }
}

La méthode de l'appelant annoté@Bean() crée une instance deHttpInvokerProxyFactoryBean. Nous devons fournir l'URL à laquelle le serveur distant répond via la méthodesetServiceUrl().

De la même manière que nous avons fait pour le serveur, nous devons également fournir l'interface du service que nous voulons appeler à distance via la méthodesetServiceInterface().

HttpInvokerProxyFactoryBean implémente lesFactoryBean de Spring. UnFactoryBean est défini comme un bean, mais le conteneur Spring IoC injectera l'objet qu'il crée, pas la fabrique elle-même. Vous pouvez trouver plus de détails surFactoryBean dans nosfactory bean article.

La méthodemain() amorce l'application autonome et obtient une instance deCabBookingService à partir du contexte. Sous le capot, cet objet n'est qu'un proxy créé par leHttpInvokerProxyFactoryBean qui s'occupe de toutes les technicités impliquées dans l'exécution de l'invocation à distance. Grâce à cela, nous pouvons maintenant utiliser facilement le proxy, comme si l'implémentation du service était disponible localement.

Exécutons l'application plusieurs fois pour exécuter plusieurs appels à distance afin de vérifier le comportement du client lorsqu'une cabine est disponible et quand elle ne l'est pas.

5. Caveat Emptor

Lorsque nous travaillons avec des technologies qui permettent des invocations à distance, nous devons être conscients de certains pièges.

Nous devrions toujours nous attendre à l'inattendu lorsque nous travaillons avec une ressource non fiable comme le réseau.

Supposons que le client appelle le serveur alors qu'il ne peut pas être atteint - soit à cause d'un problème de réseau, soit parce que le serveur est en panne - alors Spring Remoting lèvera unRemoteAccessException qui est_ a _RuntimeException.

Le compilateur ne nous obligera pas ensuite à inclure l'invocation dans un bloc try-catch, mais nous devrions toujours envisager de le faire pour gérer correctement les problèmes de réseau.

5.2. Les objets sont transférés par valeur et non par référence

Spring Remoting HTTP marshale les arguments de la méthode et renvoie les valeurs pour les transmettre sur le réseau. Cela signifie que le serveur agit sur une copie de l'argument fourni et que le client agit sur une copie du résultat créé par le serveur.

Nous ne pouvons donc pas nous attendre, par exemple, à invoquer une méthode sur l'objet résultant pour modifier le statut du même objet côté serveur car il n'y a pas d'objet partagé entre le client et le serveur.

5.3. Méfiez-vous des interfaces à grain fin

L'appel d'une méthode au-delà des limites du réseau est nettement plus lent que son invocation sur un objet dans le même processus.

Pour cette raison, il est généralement recommandé de définir les services qui doivent être appelés à distance avec des interfaces à grain plus grossier, capables de réaliser des transactions commerciales nécessitant moins d’interactions, même au prix d’une interface plus lourde.

6. Conclusion

Avec cet exemple, nous avons vu à quel point il est facile avec Spring Remoting d'appeler un processus distant.

La solution est légèrement moins ouverte que d'autres mécanismes répandus tels que REST ou les services Web, mais dans les scénarios où tous les composants sont développés avec Spring, elle peut représenter une alternative viable et beaucoup plus rapide.

Comme d'habitude, vous trouverez les sourcesover on GitHub.