Futuros en Java, Parte 1: Introducción

Comienzo hoy una seríe de post sobre los futuros en Java y sus posibilidades. En este primer post me centraré en explicar el concepto y los problemas de las aplicaciones «clásicas».

Esta serie constará inicialmente de las siguientes partes:
Parte 1: Introducción
Parte 2: interfaz Future
Parte 3: CompletableFuture, introducción
Parte 4: CompletableFuture, uso avanzado

Threads, bloqueos y demás

El funcionamiento de una clásica aplicación web montada con Java, con Spring por ejemplo, se basa principalmente en aceptar peticiones http, usando para cada una de estas peticiones (o request) un thread propio. De tal manera que cuando a nuestra aplicación acceden varios usuarios concurrentemente, tendremos un thread para cada uno de esos usuarios.

Si nuestra aplicación no es muy eficiente, e incluso tiene cuellos de botella grandes, por ejemplo en el acceso a base de datos, en peticiones a otras apis, etc, cada una de esos threads estará «activo» mucho tiempo… Lo que implicará que si tenemos una aplicación con un gran volumen de usuarios, en un momento dado puede reventar el servidor por el número de threads.

La única manera inicial para resolver esto es a base de fuerza bruta: añadiendo más memoria a la maquina, para que aguante más threads… lo cual no es práctico, al final volverá a pasar. Un sistema así no escala. Lo ideal es que los threads que usan las requests se liberen lo antes posible (para poder servir otras requests esos mismos, en lugar de crecer el número de threads hasta el infinito…).

Todo esto ocurre principalmente por que nuestros threads están mucho tiempo activos, y la gran mayoria de ese tiempo están bloqueados. En esos accesos a base de datos, otros recursos web, apis externas, etc es donde se va el mayor tiempo con diferencia. Y, como digo, el thread se queda ahi bloqueado. No se libera, aunque realmente no está haciendo nada, pero esta ocupado, esperando…

Manifiesto reactivo, asincronía y futuros

Para solucionar este problema surge el Manifiesto Reactivo. Viene a ser unas buenas prácticas y una manera concreta de desarrollo de aplicaciones en las que, entre otras cosas, debemos evitar esos bloqueos de threads a toda costa, para desarrollar aplicaciones que escalen correctamente.

¿Y cómo evitamos esos bloqueos? Con los futuros. El concepto de Futuro es el de un objeto que, en algún momento, contendrá el resultado de un método. Cuando llamemos a ese método, devolverá inmediatamente el resultado (un objeto Futuro), y se irá ejecutando de manera asincrona, mientras nuestro método «llamante» continua. En algún momento, el método llamado terminará, y dejara el resultado en el futuro.

Un pequeño ejemplo:

        // LLamada al método, que será asincrona
        Future<String> resultFuture = dao.methodAccessingDB();

        // Hacemos otras cosas

        // Más cosas

        // Obtenemos el resultado del futuro
        String result = resultFuture.get();

Aún así, en algún momento habrá que bloquear al método llamado para obtener ese resultado (como vemos en la linea 9 del ejemplo anterior). Para evitar esto tenemos varías «estrategias» a la hora de programar, que veremos más adelante.

En los proximos posts de esta seríe me centraré en explicar ejemplos con las clases que Java nos provee para los futuros: Future y CompletableFuture (esta última añadido en Java 8).

Spring Boot series: autoconfiguración con jOOQ

Hace ya como un año publiqué un post en el que explicaba como configurar un proyecto con jOOQ, que es una librería de acceso a base de datos. Vuelvo con ello, esta vez con Spring Boot y su sencillez 🙂

Concepto:
Con la nueva versión de Spring Boot, la 1.3, aparte de cosas como las developer tools, se han añadido nuevas ‘autoconfiguraciones’, entre ellas, para jOOQ. En este post veremos como configurar fácilmente jOOQ en un proyecto Spring Boot. ¡Al lío!

Entorno usado:
Java JDK 1.8
Maven 3.3.9
Git 2.6.3
IDE Intellij 15.0.2 Ultimate version

Pasos:

1. Crearemos un nuevo proyecto Maven básico con Spring Boot igual que siempre. Nuestro pom inicial quedaría así:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edwise.springbootseries.jooq</groupId>
    <artifactId>springboot-series-jooq</artifactId>
    <version>0.1.0</version>
    <packaging>jar</packaging>

    <name>springboot-series-jooq</name>
    <description>Test project for Spring Boot new featture: jooq autoconfig</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.3.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <testSource>${java.version}</testSource>
                    <testTarget>${java.version}</testTarget>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Para este caso no he añadido el starter web, simplemente el de logging para tener logback y slf4j. Así como el starter de tests, y assertj.
Añadimos también nuestra clase main para Spring Boot.

2. Añadimos ahora a nuestro pom la dependencia del nuevo ‘starter’ para jooq. Añadimos también la de H2 para usarlo como base de datos:

...
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jooq</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
...

Y con esto ya casi está. Spring Boot configurará jOOQ con valores por defecto y tendremos disponible un bean de la clase DSLContext.
Solo nos falta el código generado por jOOQ para las tablas de nuestra BD. Vamos a generarlos.

3. Para hacer más sencilla la ejecución de nuestra aplicación, primero vamos a hacer unos scripts sql para la creación y borrado de nuestra base de datos, y los ejecutaremos con el plugin ‘sql-maven-plugin‘ de maven.

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>sql-maven-plugin</artifactId>
                <version>1.5</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.h2database</groupId>
                        <artifactId>h2</artifactId>
                        <version>${h2.version}</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <driver>org.h2.Driver</driver>
                    <url>jdbc:h2:${basedir}/target/springbootseriesjooqDB</url>
                    <srcFiles>
                        <srcFile>${basedir}/src/main/resources/delete.sql</srcFile>
                        <srcFile>${basedir}/src/main/resources/schema.sql</srcFile>
                        <srcFile>${basedir}/src/main/resources/data.sql</srcFile>
                    </srcFiles>
                </configuration>
            </plugin>

Podéis consultar los tres ficheros .sql aquí. No tienen mucho misterio.

4. Ahora añadimos a nuestro pom.xml el plugin de jOOQ para la generación de su código:

            <plugin>
                <groupId>org.jooq</groupId>
                <artifactId>jooq-codegen-maven</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.h2database</groupId>
                        <artifactId>h2</artifactId>
                        <version>${h2.version}</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <jdbc>
                        <driver>org.h2.Driver</driver>
                        <url>jdbc:h2:${basedir}/target/springbootseriesjooqDB</url>
                    </jdbc>
                    <generator>
                        <name>org.jooq.util.DefaultGenerator</name>
                        <database>
                            <name>org.jooq.util.h2.H2Database</name>
                            <includes>.*</includes>
                            <excludes/>
                            <inputSchema>PUBLIC</inputSchema>
                        </database>
                        <target>
                            <packageName>sample.jooq.domain</packageName>
                            <directory>${basedir}/generated-jooq-code/main/java</directory>
                        </target>
                    </generator>
                </configuration>
            </plugin>

El plugin es similar a como lo hicimos en el post anterior de jOOQ:
– Asignamos la ejecución del plugin al ‘goal’ generate de maven.
– Añadimos la dependencia de H2.
– En la configuración le pasamos la url de base de datos h2 (línea 21), la misma que en el sql-maven-plugin, y en la información para la generación de código, la ruta donde generarlo (línea 33).

Con ejecutar ahora un ‘mvn package’ tendremos generado el código de jOOQ.

5. Vamos ahora a probarlo. Para ello creamos un DAO simplón, anotado como @Repository, tal que así:

package com.edwise.springbootseries.jooq.dao;

// imports...

@Repository
public class BooksDao {
    private static final Logger LOG = LoggerFactory.getLogger(BooksDao.class);

    private final DSLContext create;

    @Autowired
    public BooksDao(DSLContext dslContext) {
        this.create = dslContext;
    }

    public Result<Record> getAllBookCharacters() {
        Result<Record> result = create
                .select()
                .from(Tables.BOOK_CHARACTER)
                .fetch();

        LOG.info("Resultado query getAllBookCharacters: \n{}", result);
        return result;
    }

    public Result<Record> getAllBookCharactersOrderByName() {
        Result<Record> result = create
                .select()
                .from(Tables.BOOK_CHARACTER)
                .orderBy(Tables.BOOK_CHARACTER.NAME)
                .fetch();

        LOG.info("Resultado query getAllBookCharactersOrderByName: \n{}", result);
        return result;
    }

    public Optional<Record> getBookCharacterById(int id) {
        Optional<Record> result = create
                .select()
                .from(Tables.BOOK_CHARACTER)
                .where(Tables.BOOK_CHARACTER.ID.equal(id))
                .orderBy(Tables.BOOK_CHARACTER.NAME)
                .fetchOptional();

        LOG.info("Resultado query getBookCharacterById: \n{}",s result);
        return result;
    }
}

Lo que hacemos es nada más que injectar el bean DSLContext (por contructor). Implementamos también varios métodos para recuperar distinta información de nuestra base de datos, usando la sintaxis de jOOQ.
No necesitamos abrir la conexión ni instanciar nada, como hacíamos en el post de jOOQ. Todo nos lo gestiona Spring Boot.

6. Para automatizar la prueba, he implementado un test de integración (carga todo el contexto de Spring) para probar nuestro DAO y ver que jOOQ funciona:

package com.edwise.springbootseries.jooq.dao;

// imports...

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class BooksDaoIT {
    private static final Logger LOG = LoggerFactory.getLogger(BooksDaoIT.class);

    private static final int EXISTING_CHAR_ID = 4;
    private static final int NOT_EXISTING_CHAR_ID = 13;

    @Autowired
    private BooksDao booksDao;

    @Test
    public void getAllBookCharactersShouldReturnAllTheChars() {
        Result<Record> allBookCharacters = booksDao.getAllBookCharacters();

        allBookCharacters.stream()
                .map(record -> record.getValue(Tables.BOOK_CHARACTER.NAME))
                .forEach(name -> LOG.info("Char: {}", name));

        assertThat(allBookCharacters)
                .extracting(record -> record.getValue(Tables.BOOK_CHARACTER.NAME))
                .contains("Samwise", "Gandalf", "Frodo", "Saruman", "Aragorn");
    }

    @Test
    public void getAllBookCharactersOrderByNameShouldReturnCharsSorted() {
        Result<Record> allBookCharactersByName =
                booksDao.getAllBookCharactersOrderByName();

        assertThat(allBookCharactersByName)
                .extracting(record -> record.getValue(Tables.BOOK_CHARACTER.NAME))
                .isSorted();
    }

    @Test
    public void getBookByIdThatExistsShouldReturnValidRecord() {
        Optional<Record> bookCharacter =
                booksDao.getBookCharacterById(EXISTING_CHAR_ID);

        assertThat(
                bookCharacter
                        .orElseThrow(() -> new AssertionError("Not existing character"))
                        .getValue(Tables.BOOK_CHARACTER.NAME))
                .isEqualTo("Samwise");
    }

    @Test
    public void getBookByIdThatNotExistsShouldReturnEmptyOptionalRecord() {
        Optional<Record> bookCharacter =
                booksDao.getBookCharacterById(NOT_EXISTING_CHAR_ID);

        assertThat(bookCharacter.isPresent()).isFalse();
    }
}

Como vemos, Spring Boot facilita mucho la integración con jOOQ, gestionandolo, como siempre, con autoconfiguraciones por defecto. Si por lo que sea necesitamos configurar jOOQ con algún parametro o configuración especial, podemos hacerlo con propiedades del tipo ‘spring.jooq.propiedad’ en el appication.properties, así como definir nuestro propio @Bean de la clase de configuración de jOOQ (org.jooq.Configuration).

Como siempre, tenéis el código disponible en mi github, proyecto springboot-series-jooq.