[JAVA] Introduction à Ratpack (6) - Promesse

Série d'introduction Ratpack

  1. Introduction à Ratpack (1) - Qu'est-ce que Ratpack
  2. Introduction à Ratpack (2) --Architecture
  3. Introduction à Ratpack (3) - Explication détaillée de Hello world
  4. Introduction à Ratpack (4) --Routage et contenu statique
  5. Introduction à Ratpack (5) --Json & Registry
  6. Introduction à Ratpack (6) - Promise
  7. Introduction à Ratpack (7) --Guice & Spring
  8. Introduction à Ratpack (8) - Session
  9. Introduction à Ratpack (9) --Thymeleaf

Promise

Ratpack est une bibliothèque événementielle non bloquante, il est donc supposé que chaque processus est également écrit de manière asynchrone. Si vous êtes un utilisateur Java expérimenté, vous savez qu'il peut être difficile d'écrire un traitement Java asynchrone. Ratpack fournit une classe «Promise» pour décrire brièvement le traitement asynchrone. L'image est similaire à la Promise JavaScript, et vous pouvez écrire un rappel lorsque le processus est terminé avec then ().

Faites une promesse

Je pense que le traitement d'E / S est l'opération de blocage la plus typique. Vous pouvez facilement créer une «Promise» en utilisant la classe utilitaire «Blocage».

chain.all( ctx -> {

    String query = ctx.getRequest().getQueryParams().get( "id" );

    Promise<String> result = Blocking.get( () -> {
        return Database.find( query );
    } );

    ctx.render( result );
} );

Considérez Database.find () comme le processus de recherche de données à partir d'une base de données fictive. Blocking.get () exécute la fermeture de l'argument de manière asynchrone et encapsule sa valeur de retour dans Promise. Vous pouvez également passer une Promise à Context.render ().

Utilisez ʻop () pour les opérations sans valeur de retour. La classe ʻOperation est une Promise sans valeur de retour dans Ratpack.

Blocking.op( () -> {
    String data = ctx.getRequest().getQueryParams().get( "data" );
    Database.persist( data );
} ).then( () -> {
    ctx.render( "OK" );
} );

Tout d'abord, enregistrez les informations dans une base de données fictive dans Blocking.op (). La méthode ʻop () renvoie ʻOperation pour cette opération. Ensuite, décrivez le traitement après avoir sauvegardé les données dans la base de données avec then (). J'appelle Context.render () pour créer une réponse appelée ʻOK`.

Promise.sync()

Créez une promesse depuis l'usine.

Promise<String> result = Promise.sync( () -> "data" );
result.then( data -> {
    ctx.render( "OK" );
} );

Promise.async()

Une fabrique statique Promise.async () est fournie lorsque vous travaillez avec d'autres bibliothèques asynchrones.

Promise<String> result = Promise.async( downstream -> {
    downstream.success( "data" );
} );
result.then( data -> {
    ctx.render( "OK" );
} );

Appelez la méthode success () pour vous dire que le processus est terminé. Notez que Promise.async () lui-même n'effectue pas de traitement des arguments de manière asynchrone. Il vous suffit d'écrire le processus asynchrone vous-même (ou dans la bibliothèque) (donc l'exemple officiel crée un Thread et appelle success ()).

Fonctionnement de "Promise"

then

Spécifie le rappel qui sera appelé lorsque le traitement de «Promise» est terminé. Je pense que le processus le plus courant consiste à appeler Context.render () dans le rappel et à créer une réponse.

Il convient de noter que «then ()» enregistre le rappel auprès de l'application et l'exécute séquentiellement. Considérez le code suivant.

@Data class Obj {
    public int a;
    public int b;
    public int c;
}
Obj o = new Obj();
Promise.value( 1 ).then( o::setA );
Promise.value( 2 ).then( o::setB );
Promise.value( 3 ).then( o::setC );
Operation.of( () -> ctx.render( o.toString() ) ).then();

Puisque Promise représente un traitement asynchrone, à première vue, il peut sembler que le champ ʻo lors de l'appel de ʻo.toString () dépend du timing. Cependant, l'appel à then () garantit que Ratpack s'exécutera séquentiellement dans l'ordre d'enregistrement, donc la valeur de ʻo.toString () `sera toujours ʻObj (a = 1, b = 2, c = 3). ) ». Cependant, ce comportement est non intuitif et déroutant, donc je pense que vous ne devriez pas trop l'utiliser.

map

Crée une «Promise» qui adapte la fonction spécifiée au résultat de la «Promise». C'est la même chose que "map" comme stream.

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .map( String::toUpperCase );
} ).getValue();

assertThat( result ).isEqualTo( "HOGE" );

blockingMap

C'est presque la même chose que map, mais il est exécuté par le thread pour bloquer le traitement. C'est une image qui encapsule le traitement dans map avec` Blocking.get () ʻetc. Il existe une méthode dérivée «blockingOp».

flatMap

Remplace le résultat de «Promise» par «Promise» renvoyé par la fonction spécifiée. Ratpack a beaucoup de traitement qui retourne Promise par défaut, il est donc utilisé plus fréquemment que prévu.

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .flatMap( v -> {
                      assertThat( v ).isEqualTo( "hoge" );
                      return Promise.value( "piyo" );
                  } );
} ).getValue();

assertThat( result ).isEqualTo( "piyo" );

mapIf

Applique la fonction de carte uniquement si le Predicate spécifié est positif.

mapError flatMapError

Si une exception se produit, le résultat de l'application de la fonction de mappage qui prend l'exception comme argument est renvoyé. Vous pouvez facilement écrire des branches lorsqu'elle se termine normalement et lorsqu'une erreur se produit.

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .mapIf( s -> true, s -> { throw new RuntimeException();} )
                  .mapError( t -> "piyo" );
} ).getValue();

assertThat( result ).isEqualTo( "piyo" );

apply

Prend une fonction qui prend l'appelant «Promise» lui-même et renvoie «Promise». Je ne sais pas comment m'en servir, mais il semble que le but soit de simplifier la description lorsque le processus est divisé en méthodes.

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" ).apply( p -> {
        assertThat( p == Promise.value( "hoge" ) ).isTrue();
        return p.map( String::toUpperCase );
    } );
} ).getValue();

assertThat( result ).isEqualTo( "HOGE" );

around

Insérer le traitement avant et après le calcul de «Promise». Cela semble utile en soi, mais c'est une méthode honteuse qui rend le code inutile car vous devez envelopper le résultat après dans ʻExecResult`.

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .around(
                          () -> "before",
                          ( before, r ) -> {
                              assertThat( before ).isEqualTo( "before" );
                              assertThat( r.getValue() ).isEqualTo( "hoge" );
                              return ExecResult.of( Result.success( "piyo" ) );
                          }
                  );
} ).getValue();

assertThat( result ).isEqualTo( "piyo" );

replace

Remplacez «Promise» par une autre «Promise». Bref, c'est une version qui ne prend pas l'argument de flatMap (). C'est aussi une méthode dont la nécessité n'est pas bien comprise.

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .replace( Promise.value( "piyo" ) );
} ).getValue();

assertThat( result ).isEqualTo( "piyo" );

route

Si «Predicate» est «true», exécutez le consommateur spécifié. En regardant JavaDoc, il semble qu'il soit destiné à être utilisé pour la validation de données etc ..., mais je pense que ce n'est pas très facile à utiliser.

ExecResult<String> result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .route( s -> false, System.out::println );
} );

assertThat( result.getValue() ).isEqualTo( "hoge" );
assertThat( result.isComplete() ).isFalse();

boolean completed = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .route( s -> true, System.out::println );
} ).isComplete();

assertThat( completed ).isTrue();

to

Convertit «Promise» en un autre type. Cela peut sembler difficile à utiliser, mais il est utilisé pour l'intégration de bibliothèques externes. Voici un exemple de RxRatpack.

List<String> resultHolder = new ArrayList<>();
ExecHarness.runSingle( e -> {
    Promise.value( "hoge" )
           .to( RxRatpack::observe )
           .subscribe( s -> resultHolder.add( s ) );
} );

assertThat( resultHolder ).containsExactly( "hoge" );

next

Il a un consommateur dont l'argument est le résultat de "Promise". La valeur de retour «Promise» renvoie le même résultat que la «Promise» d'origine. Il existe des méthodes dérivées telles que «nextOp».

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .next( System.out::println );
} ).getValue();

assertThat( result ).isEqualTo( "hoge" );

right left

Combine «Promise» avec une autre «Promise» et la renvoie comme «Promise» de «Pair».

Pair<String, String> result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .right( Promise.value( "piyo" ) );
} ).getValue();

assertThat( result.getLeft() ).isEqualTo( "hoge" );
assertThat( result.getRight() ).isEqualTo( "piyo" );

cache

Mettez en cache le résultat de «Promise». Si une exception se produit, cette exception est également mise en cache. Il existe des méthodes dérivées telles que cacheResultIf.

onError

Décrit le traitement lorsqu'une erreur se produit. Je pense que l'utilisation principale est d'écrire Context.render () au moment de l'erreur. Vous pouvez avoir plusieurs modèles d'arguments, comme celui qui prend une classe d'exception comme argument, celui qui reçoit un consommateur d'exception et celui qui sélectionne une exception avec Predicate.

close

Lorsque la Promise se termine ou qu'une exception se produit, le ʻAutoCloseable` spécifié dans l'argument est fermé. Je ne sais pas où l’utiliser.

retry

Si le processus échoue, il réessayera après un délai spécifié. Ceci est pratique lors de l'appel d'une API externe.

String result = ExecHarness.yieldSingle( e -> {
    return Promise.value( "hoge" )
                  .retry( 3, Duration.ofSeconds( 1 ), ( i, t ) -> System.out.printf( "retry: %d%n", i ) );
} ).getValue();

assertThat( result ).isEqualTo( "hoge" );

time

Il prend un consommateur comme argument qui renvoie le temps nécessaire pour exécuter la Promise. La mesure du rendement est-elle la principale utilisation?

À propos des fils et de la fourchette

Ratpack exécute «Promise» dans l'unité représentée par la classe «Execution» pour un traitement asynchrone. Normalement, vous n'êtes pas au courant de cette «exécution», mais pour exécuter plusieurs «promesses» en parallèle, vous devez «fork ()» «exécution». Promise.fork () est fourni comme méthode pratique, et vous pouvez facilement exécuter Promise dans un autre thread.

Le code ci-dessous est une légère modification de l'exemple dans le JavaDoc fork.

CyclicBarrier b = new CyclicBarrier( 2 );

Pair<String, String> result = ExecHarness.yieldSingle( e -> {
    Promise<String> p1 = Promise.sync( () -> {
        b.await();
        return "hoge";
    } ).fork();
    Promise<String> p2 = Promise.sync( () -> {
        b.await();
        return "piyo";
    } ).fork();
    return p1.right( p2 );
} ).getValue();

assertThat( result.getLeft() ).isEqualTo( "hoge" );
assertThat( result.getRight() ).isEqualTo( "piyo" );

Maintenant, si vous supprimez l'appel à fork (), p1 et p2 seront exécutés en séquence dans le même thread, ce qui entraînera un blocage. Si vous avez créé un autre thread avec fork (), cela fonctionnera normalement.

Résumé

La «Promise» de Ratpack prend en charge le traitement asynchrone, ce que Java n'est pas bon. Cependant, certaines pièces ont de fortes habitudes et il existe de nombreuses méthodes, il est donc difficile de les utiliser correctement. L'astuce peut être de ne pas essayer d'être très intelligent. Ratpack a un module RxRatpack pour prendre en charge RxJava. Si vous avez des bibliothèques asynchrones familières, vous pouvez également en tirer parti.

Recommandation personnelle

Recommended Posts

Introduction à Ratpack (6) - Promesse
Introduction à Ratpack (8) - Session
Introduction à Ratpack (9) --Thymeleaf
Introduction à Ratpack (2) -Architecture
Introduction à Ratpack (5) --Json & Registry
Introduction à Ratpack (7) --Guice & Spring
Introduction à Ratpack (1) - Qu'est-ce que Ratpack?
Introduction à Ruby 2
Introduction à web3j
Introduction à Micronaut 1 ~ Introduction ~
[Java] Introduction à Java
Introduction à la migration
Introduction à Java
Introduction à Doma
Introduction à Ratpack (Extra Edition) - Utilisation de Sentry
Introduction à Ratpack (3) - Explication détaillée de Hello World
Introduction aux fichiers JAR
Introduction à l'arithmétique des bits
Introduction à PlayFramework 2.7 ① Présentation
Introduction à la mise en page Android
Introduction aux modèles de conception (introduction)
Introduction à la programmation pratique
Introduction à la commande javadoc
Introduction à la commande jar
Introduction au style lambda
Introduction à la commande java
Introduction au développement de Keycloak
Introduction à la commande javac
Introduction aux modèles de conception (Builder)
Introduction au développement d'applications Android
Introduction à la métabase ~ Construction de l'environnement ~
(Installation par points) Introduction à Java8_Impression
Introduction aux modèles de conception (composite)
Introduction à Micronaut 2 ~ Test unitaire ~
Introduction à JUnit (note d'étude)
Introduction à Spring Boot ① ~ DI ~
Introduction aux modèles de conception (poids mouche)
[Java] Introduction à l'expression lambda
Introduction à Spring Boot ② ~ AOP ~
Introduction à Apache Beam (2) ~ ParDo ~
Introduction à l'API EHRbase 2-REST
Introduction au prototype de modèles de conception
[Java] Introduction à l'API Stream
Introduction aux modèles de conception (Iterator)
Introduction à Spring Boot, partie 1
Introduction aux modèles de conception (stratégie)
[Introduction aux jeux Janken (comme)] Java
[Introduction à Java] À propos des expressions lambda
Introduction aux algorithmes avec somme cumulée Java
Introduction à la programmation fonctionnelle (Java, Javascript)
Introduction aux algorithmes avec la méthode java-Shakutori