Desplegar docker una app Spring Boot 1.5.x desde Gradle

Utilizaremos el plugin gradle-docker para gradle.

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '1.5.17.RELEASE'
    id 'com.palantir.docker' version '0.20.1'
    id 'com.palantir.docker-run' version '0.20.1'
}

...

jar {
    baseName = 'my-spring-boot-app'
    version = '1.0.0'
}

docker {
    dependsOn build
    name "my-docker-registry:5043/my-image-name"
    tags jar.version, 'latest'
    files jar.archivePath
    buildArgs(['JAR_FILE': jar.archiveName])
}

dockerRun {
    name "my-container-name"
    image "my-docker-registry:5043/my-image-name:${jar.version}"
    ports '8080:8080'
    env 'JAVA_OPTS': '-Xms128m -Xmx512m -server', 'SPRING_PROFILES_ACTIVE': 'my-spring-prifle'
    network 'my-docker-network'
    daemonize true
}

Dockerfile

FROM openjdk:8-jre-alpine
ARG JAR_FILE
COPY ${JAR_FILE} /app.jar
ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar
EXPOSE 8080

Operaciones

Construir la imagen de docker

./gradlew clean docker dockerTag

REPOSITORY                              TAG     IMAGE ID            CREATED             SIZE
my-docker-registry:5043/my-image-name   latest  457475efc55d        9 minutes ago       145MB
my-docker-registry:5043/my-image-name   1.0.0   457475efc55d        9 minutes ago       145MB

Construir y ejecutar el contenedor

./gradlew dockerRun

Ver el estado del contenedor

./gradlew dockerRunStatus
> Task :dockerRunStatus
Docker container 'my-container-name' is STOPPED.

Ver el estado de la red

./gradlew dockerNetworkModeStatus
> Task :dockerNetworkModeStatus
Docker container 'my-container-name' is configured to run with 'my-docker-network' network mode.

Detener el contenedor

./gradlew dockerStop

Eliminar el contenedor

dockerRemoveContainer

Push imagen

./gradlew dockerPush

Spring Testing. Mostrar información de las peticiones

Cuando usamos MockMvc para testear controladores de Spring, es muy útil disponer del máximo de información referente a la petición realizada desde el test. Una forma muy sencilla es indicarle al «builder» que queremos que muestre en la salida del test dicha información.

import org.junit.Before;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;

@RunWith(MockitoJUnitRunner.class)
public abstract class MyTestClass {

    MockMvc mvc;

    @Mock
    MyRepository myRepository;

    @Before
    public void initialize() {

        mvc = MockMvcBuilders.standaloneSetup(new MyController(new MyService(myRepository)));
                .alwaysDo(print()) // Esta es la línea importante
                .build();
    }
}

Esto hace que para cada petición se se hace desde un test unitario via MockMvc muestre una información detallada de la misma:

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /my-request-path
       Parameters = {my-request-param=[my-request-param-value]}
          Headers = {my-header=[my-header-value]}

Handler:
             Type = com.example.MyController
           Method = public org.springframework.http.ResponseEntity<java.util.List<com.example.MyResponseClass>> com.example.MyController.myMethod(com.example.MyRequestBodyClass)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[application/json;charset=UTF-8]}
     Content type = application/json;charset=UTF-8
             Body = [{"field":"value"}]
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Tener en cuenta que hasta versión 5.0 de Spring Framework no se muestra el contenido de la request tal y como se puede leer en el siguiente jira:

https://jira.spring.io/browse/SPR-14717

Mejorar la salida de los tests de una aplicación Spring Boot

Cuando hago aplicaciones con SpringBoot abruma un poco la cantidad de información que este muestra en el log cuando está activado el debug.

El tema es que el nivel de log lo suelo configurar a través del fichero application.properties pero cuando se ejecutan tests de JUnit donde no interviene Spring para nada este valor no se recoje y por defecto muestra todo a nivel DEBUG.

Añadiendo este fichero logback-test.xml en la ruta src/test/resources conseguiremos que los mensajes del framework Spring se muestren con nivel WARN mientras que los del paquete de mi aplicación (en el ejemplo: com.my.package) se muestren con el nivel más alto TRACE.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml" />
    <logger name="org.springframework" level="WARN"/>
    <logger name="com.my.package level="TRACE"/>
</configuration>

De esta forma la salida (standard output) de los tests queda muchísimo más limpia y prácticamente se limita a los mensajes própios de mi aplicación.

Gradle. Buscar nuevas versiones de las librerías que usa tu proyecto

La tarea de buscar manualmente si existen nuevas versiones de las librerías que usamos en un proyecto puede ser muy tediosa. El plugin de gradle gradle-versions-plugin nos permite elaborar un informe acerca de las nuevas versiones de librerías que usemos.

En el fichero build.gradle de la carpeta raíz de nuestro proyecto Gradle tendremos que:

1. Asegurarnos que tiene definido el repositorio jcenter.
2. Añadir, en el apartado buildscript -> dependencies la dependencia del plugin gradle-versions.
3. En la sección allprojects indicar que este plugin estará disponible para todos los proyectos.

El siguiente trozo de código muestra las líneas implicadas.

buildscript {
    
    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.github.ben-manes:gradle-versions-plugin:0.20.0"
    }
}

allprojects {

    apply plugin: "com.github.ben-manes.versions"
}

A continuación, ya podemos extraer un informe donde se nos indicará qué librerías que estamos utilizando disponen de una versión más actual a la utilizada por nuestro proyecto. En mi caso, como se trata de un proyecto Android lo haré sobre el módulo app.

./gradlew app:dependencyUpdates

La salida de este comando para mi proyecto es la siguiente:

> Task :app:dependencyUpdates 

------------------------------------------------------------
:app Project Dependency Updates (report to plain text file)
------------------------------------------------------------

The following dependencies are using the latest milestone version:
 - android.arch.persistence.room:compiler:1.1.1
 - android.arch.persistence.room:runtime:1.1.1
 - com.android.support:appcompat-v7:28.0.0
 - com.android.support:cardview-v7:28.0.0
 - com.android.support:design:28.0.0
 - com.android.support.test:runner:1.0.2
 - com.android.support.test.espresso:espresso-core:3.0.2
 - com.github.PhilJay:MPAndroidChart:v3.1.0-alpha
 - com.google.code.gson:gson:2.8.5
 - com.google.truth:truth:0.42
 - com.google.truth.extensions:truth-java8-extension:0.42
 - com.jakewharton:butterknife:9.0.0-rc1
 - com.jakewharton:butterknife-compiler:9.0.0-rc1
 - junit:junit:4.12
 - org.eclipse.paho:org.eclipse.paho.android.service:1.1.1
 - org.mockito:mockito-core:2.23.0

The following dependencies have later milestone versions:
 - com.android.support.constraint:constraint-layout [1.1.3 -> 2.0.0-alpha2]
     http://tools.android.com
 - com.android.tools.build:aapt2 [3.2.1-4818971 -> 3.4.0-alpha03-5013011]
     https://developer.android.com/studio
 - com.android.tools.lint:lint-gradle [26.2.1 -> 26.4.0-alpha03]
     https://developer.android.com/studio
 - com.google.dagger:dagger-android [2.15 -> 2.19]
     https://github.com/google/dagger
 - com.google.dagger:dagger-android-processor [2.15 -> 2.19]
     https://github.com/google/dagger
 - com.google.dagger:dagger-android-support [2.15 -> 2.19]
     https://github.com/google/dagger
 - com.google.dagger:dagger-compiler [2.15 -> 2.19]
     https://github.com/google/dagger
 - io.reactivex.rxjava2:rxandroid [2.0.2 -> 2.1.0]
     https://github.com/ReactiveX/RxAndroid
 - io.reactivex.rxjava2:rxjava [2.1.16 -> 2.2.3]
     https://github.com/ReactiveX/RxJava
 - org.eclipse.paho:org.eclipse.paho.client.mqttv3 [1.1.1 -> 1.2.0]
     http://www.eclipse.org/paho

Gradle updates:
 - Gradle: [4.6 -> 4.10.2 -> 5.0-rc-3]

Generated report file build/dependencyUpdates/report.txt


BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed

Spring. Hacer que HttpLogginInterceptor muestre mensajes en el log con nivel DEBUG en lugar de INFO

Si usas okhttp en alugna aplicación Spring (o Java) hay una clase HttpLoggingInterceptor que nos permite, en tiempo de desarrollo, volcar toda la información acerca de las peticiones que hace el cliente http en el log de la aplicación.

Por defecto, HttpLoggingInterceptor muestra los mensajes en nivel de INFO, en mi caso, prefería que se mostrasen con nivel de debug.

@Configuration
public class HttpClientModule {
    
    private static final Logger logger = LoggerFactory.getLogger(HttpClientModule.class);

    @Bean
    public OkHttpClient okHttpClient(@Value("${debug.requests}") boolean debug) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        if (debug) {
            // La siguiente línea es la importante
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(logger::debug); 
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(interceptor);
        }
        return builder.build();
    }
}