Search

Dark theme | Light theme
Showing posts with label Ratpacked:Promise. Show all posts
Showing posts with label Ratpacked:Promise. Show all posts

April 6, 2017

Ratpacked: Conditionally Map Or Flatmap A Promise

When we want to transform a Promise value we can use the map and flatMap methods. There are also variants to this methods that will only transform a value when a given predicate is true: mapIf and flatMapIf. We provide a predicate and function to the methods. If the predicate is true the function is invoked, otherwise the function is not invoked and the promised value is returned as is.

In the following example we have two methods that use the mapIf and flatMapIf methods of the Promise class:

// File: src/main/java/mrhaki/ratpack/NumberService.java
package mrhaki.ratpack;

import ratpack.exec.Promise;

public class NumberService {

    public Promise<Integer> multiplyEven(final Integer value) {
        return Promise.value(value)
                      .mapIf(number -> number % 2 == 0, number -> number * number);
    }
    
    public Promise<Integer> multiplyTens(final Integer value) {
        return Promise.value(value)
                      .flatMapIf(number -> number % 10 == 0, number -> multiplyEven(number));
    }
    
}

Now we take a look at the following specification to see the result of the methods with different input arguments:

// File: src/test/groovy/mrhaki/ratpack/NumberServiceSpec.groovy
package mrhaki.ratpack

import ratpack.test.exec.ExecHarness
import spock.lang.Specification
import spock.lang.Subject

class NumberServiceSpec extends Specification {

    @Subject
    private final numberService = new NumberService()

    void 'even numbers must be transformed with mapIf'() {
        when:
        final result = ExecHarness.yieldSingle {
            numberService.multiplyEven(startValue)
        }

        then:
        result.value == expected

        where:
        startValue || expected
        1          || 1
        2          || 4
        3          || 3
        4          || 16
    }

    void 'ten-th numbers must be transformed with flatMapIf'() {
        when:
        final result = ExecHarness.yieldSingle {
            numberService.multiplyTens(startValue)
        }

        then:
        result.value == expected

        where:
        startValue || expected
        1          || 1
        10         || 100
        2          || 2
        20         || 400
    }
}

Written with Ratpack 1.4.5.

April 3, 2017

Ratpacked: Get Time Taken To Fulfil Promise

The Promise class has a lot of methods. One of the methods is the time method. We can invoke this method an a Promise instance. The method creates a Duration object that we can use inside the method. The duration is the time taken from when the promise is subscribed to to when the result is available. The promise value is not changed, so we can add the time method at any position of a method chain for the Promise object.

In the following specification we check the duration for a Promise that is returned by the method generate of the class Numbers. For our example we wait for a number of seconds dependent on the argument of the generate method. In the specification we use the time method and check the time spent to fulfil the promise.

package mrhaki.ratpack

import ratpack.exec.Promise

import ratpack.test.exec.ExecHarness
import spock.lang.Specification
import spock.lang.Unroll

import java.time.Duration

class NumbersSpec extends Specification {

    @Unroll('with argument #num response time should be at least #responseTime')
    void 'time used by Numbers.generate method increased and dependent on argument'() {
        given:
        final numbers = new Numbers()

        and:
        long timer

        when:
        final result = ExecHarness.yieldSingle {
            numbers.generate(num)
                   .map { value -> value - 1 }
                   .time { duration -> timer = duration.toMillis() }
        }

        then:
        timer >= responseTime
        result.value == generateResult

        where:
        num | responseTime | generateResult
        1   | 1_000        | 0
        2   | 2_000        | 3
        10  | 10_000       | 99
    }

}

class Numbers  {
    Promise<Long> generate(final Long multiplier) {
        Promise
            .sync { -> 
                 // Wait for n-seconds...
                 sleep(multiplier * 1000)
                 multiplier
            }
            .map { value ->
                value * value 
            }
    }
}

Written with Ratpack 1.4.5.

October 31, 2016

Ratpacked: Creating Pairs From Promises

The Pair class in Ratpack is an easy way to create a growing data structure, passed on via Promise methods. A Pair object has a left and right part containing data. These parts can even be other Pair objects. Since Ratpack 1.4.0 the Promise class has methods to set the right or left part of a Pair: left, flatLeft, right and flatRight. The result of these methods is a Promise<Pair> object. The input can be Promise type or a Function that can use a previous Promise.

In the following example specification we use the different new methods to create a Pair. We also create a simple Ratpack server with a asynchronous HTTP client implementation to simulate remote calls returning a Promise:

package mrhaki

import ratpack.exec.Promise
import ratpack.func.Pair
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.http.HttpUrlBuilder
import ratpack.http.client.HttpClient
import ratpack.test.embed.EmbeddedApp
import ratpack.test.exec.ExecHarness
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class PromisePairSpec extends Specification {

    /**
     * Simple server to serve /{value} and /{value}/size
     * GET requests.
     */
    @Shared
    @AutoCleanup
    private EmbeddedApp serverApi = GroovyEmbeddedApp.of({ 
        handlers {
            get(':value') { render pathTokens.value }
            get(':value/size') { render String.valueOf(pathTokens.value.size()) }
        }
    })

    /**
     * Asynchronous HTTP client. 
     */
    @AutoCleanup
    private HttpClient api = HttpClient.of { client ->
        client.poolSize 1
    }

    def "set right side of Pair with result of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in right method as argument.
                   .right { s -> s.size() }
        }.value == Pair.of('Ratpack', 7)
    }

    def "set right side of Pair with result of other Promise"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack is')
                   // Use Promise value 
                   .right(getApiText('cool'))
        }.value == Pair.of('Ratpack is', 'cool')
    }
    
    def "set right side of Pair with result Promise of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                   // Use Promise 'Ratpack' in flatRight method as argument.
                   .flatRight { s -> getApiText("${s}/size") }
        }.value == Pair.of('Ratpack', '7')
    }

    def "set right side of Pair with result Promise of function using initial Promise value using flatMap"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in flatMap method as argument.
                    // This is the way to set the Pair values 
                    // before the flatRight method was added
                   .flatMap { s -> getApiText("${s}/size").map { content -> Pair.of(s, content) } }
        }.value == Pair.of('Ratpack', '7')
    }

    def "set left side of Pair with result of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in left method as argument.
                   .left { s -> s.size() }
        }.value == Pair.of(7, 'Ratpack')
    }

    def "set left side of Pair with result of other Promise"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('cool')
                   // Get Promise value without using Promise 'cool' value.
                   .left(getApiText('Ratpack is'))
        }.value == Pair.of('Ratpack is', 'cool')
    }

    def "set left side of Pair with result Promise of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in flatLeft method as argument.
                   .flatLeft { s -> getApiText("${s}/size") }
        }.value == Pair.of('7', 'Ratpack')
    }
    
    private Promise<String> getApiText(final String path) {
        api.get(createRequest(path))
           .map { response -> response.body.text }
    }
    
    private URI createRequest(final String path) {
        HttpUrlBuilder.base(serverApi.address).path(path).build()
    }
    
}

Written with Ratpack 1.4.3.

January 8, 2016

Ratpacked: Tapping In On A Promise

We can use the wiretap method of the Promise interface to listen in on results. We write an Action implementation which has the result of a Promise encapsulated in a Result object. The wiretap method can be used to do something with a Promise value without interrupting a method chain.

In the following example we tap in on Promise results:

import com.mrhaki.ratpack.Book
import com.mrhaki.ratpack.BookService
import com.mrhaki.ratpack.DefaultBookService
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import ratpack.exec.Result
import ratpack.jackson.Jackson

import static ratpack.groovy.Groovy.ratpack

final Logger logger = LoggerFactory.getLogger(ratpack.class)

ratpack {

    bindings {
        bind(BookService, DefaultBookService)
    }

    handlers { BookService bookService ->
         get('book/:isbn') { 
            final String isbn = pathTokens.isbn
            bookService
                    .findBook(isbn)
                    .wiretap { Result<Book> result ->
                        logger.debug 'Book {}', result.value
                    }
                    .map({ Book book ->
                        [author: book.author, title: book.title]
                    })
                    .wiretap({ Result<Map<String, String>> result ->
                        logger.debug 'Book as map {}', result.value
                    })
                    .map(Jackson.&json)
                    .then { bookAsJson -> 
                        render(bookAsJson)
                    }
        }     
    }
}

Written with Ratpack 1.1.1.

December 17, 2015

Ratpacked: Special Routing Of Promise Values Using Predicates

One of the strengths of Ratpack is the asynchronous execution model. An important class is the Promise class. An instance of the class will represent a value that is available later. We can invoke several operations that need be applied to a value when a Promise is activated. Usually the activation happens when we subscribe to a Promise using the then method. We can use the route method for a Promise to have a different action when a certain predicate is true. The action will stop the flow of operations, so methods that are executed after the route method are not executed anymore if the predicate is true. If the predicate is false then those methods are invoked.

The Promise class has a method onNull as a shorthand for the route method where the predicate checks if the value is null. For example we could have a service in our application that returns a Promise<User>. If the value is null we want some special behaviour like sending a 404 status code to the client. With the following code we could achieve this:

import ratpack.jackson.Jackson

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        bind(UserService, DefaultUserService)
    }
    handlers { UserService userService ->
        get('user/:username') { 
            final String username = pathTokens.username
            userService
                    .findUser(username)
                    // If user is not found, then value
                    // of Promise object is null. 
                    .onNull { clientError(404) }
                    .map(Jackson.&json)
                    .then { JsonRender userAsJson ->
                        render(userAsJson)
                    }
        }   
    }
}

In the following Spock specification we can see both the onNull and route methods and how they work:

package com.mrhaki.ratpack.promise

import ratpack.exec.Promise
import ratpack.func.Predicate
import ratpack.test.exec.ExecHarness
import spock.lang.AutoCleanup
import spock.lang.Specification

class PromiseValueRoutingSpec extends Specification {

    @AutoCleanup
    ExecHarness execHarness = ExecHarness.harness()
    
    def "when Promise value is null then special action is used"() {
        given:
        String execResult

        when:
        execHarness.run({
            Promise.value(promiseValue)
                // If Promise has null value then 
                // execute the action, otherwise
                // proceed.
                .onNull {
                    execResult = 'null value' 
                }
                // Promise value was not null, so
                // we get to this action block
                .then { String value ->
                    execResult = value 
                }
        })

        then:
        execResult == result

        where:
        promiseValue    || result
        'Ratpack rules' || 'Ratpack rules'
        ''              || ''
        null            || 'null value'
    }

    def "when a Promise value meets a predicate a custom action is used"() {
        given:
        String execResult
        
        and:
        // Create Ratpack predicate for checking if Optional object
        // is empty.
        final Predicate<Optional<String>> emptyOptional = { Optional<String> optional ->
            // Check if value is present
            !optional.present
        } 

        when:
        execHarness.run({
            Promise.value(promiseValue)
                // If Promise has null value then 
                // execute the action, otherwise
                // proceed.
                .onNull {
                    execResult = 'null value' 
                }
                // Custom check for the Promise value
                // which executes the given action when
                // the predicate is true.
                .route(emptyOptional) { 
                    execResult = 'empty optional value' 
                }
                // Promise value was not null and
                // optional value was present, so
                // we get to this action block
                .then { Optional<String> optional ->
                    execResult = optional.get() 
                }
        })

        then:
        execResult == result

        where:
        promiseValue                 || result
        Optional.empty()             || 'empty optional value'
        Optional.of('Ratpack rules') || 'Ratpack rules'
        null                         || 'null value'
    }
}

Written with Ratpack 1.1.1