Dependency Management unter SpringBoot 2

Fügt man als Dependency spring-boot-starter-jooq ein, dann werden alle Abhängigeiten sowie Jooq als Abhängigkeiten eingefügt. Soweit so gut. Aber was ist, wenn man eine neuere Version von Jooq (zum Beispiel aufgrund eines Bugs) verwenden möchte?

Override Version

Unter Gradle kann man mit ext [jooq.version] die zu verwendende Version vorgeben.

// Override supported Jooq version 3.11.9
ext['jooq.version'] = '3.11.11'

Nun wird die aktuelle Version von Jooq 3.11.11 verwendet.

Die neue in Memory Datenbank

Microstream ist die neue in Memory Datenbank für Java. Aus dem Hause XDev stammt diese revolutionäre Datenbank. Nachdem im Frühjahr das Produkt umbenannt worden ist, heißt es nicht mehr JetstreamDB sondern Microstream. Die Microstream Datenbank ist kostenlos und kann frei verwendet werden.

Was ist Microstream?

Microstream ist ein Framework das Java Objekte ultraschnell serialisieren und desialisieren des kann. Das macht sich Microstream zu nutze und umgeht damit unvermeidbare Netzwerklatenzen, die ansonsten bei klassischen RDBMs entstehen.

Wie funktioniert Microstream?

Es gibt eine Root Entität. Diese enthält alles was in der Datenbank gespeichert werden soll. Es gibt also hier kein Datenbank Schema oder ähnliches. Die Java Objekte sind die Entitäten. Das war es. Alles was sich also in dem Objektgraph unterhalb der Root Entität befindet, kann persistiert werden.

Was ist mit Queries?

Abfragen, wie man es bei einer normalen RDBMs kennt, gibt es nicht. Und braucht man auch nicht. Man verwendet einfach das Streaming API von Java und erreicht Wahnsinnig schnelle Abfragen in Millisekunden Bereich.

Demoanwendung

Es soll hier in einer kleinen Demoanwendung die Verwendung der Microstream API veranschaulicht werden. Ich verwende hier das SpringBoot Framework in der aktuellsten Version 2.1.4. Die Anwendung erzeugt bei jedem Start eine neue Einladung. Die Einladungen bestehen aus einer ID und einem Namen. Super einfach. Die ID wird hierbei immer um einen erhöht. Der Name ist hier statisch. Um den Code vom unnötigen Boilerplate frei zu halten, verwende ich Lombok.

Abhängigkeiten einbinden

Es muss das Repository von microstream eingebunden werden. Die Group ID .heißt one.microstream und es muss das Artefakt storage.embedded in der Version 01.00.00-MS-RC1 Hier das Gradle Buildscript:

repositories {
    mavenCentral()
    maven { url 'https://repo.microstream.one/repository/maven-releases/' }
}


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation('one.microstream:storage.embedded:01.00.00-MS-RC1')
    annotationProcessor('org.projectlombok:lombok')
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

Die Entität

Die Invocartion stellt die Entität des Repositories dar. Sie besteht aus der ID und dem Namen der Einladung.

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Invocation {
    private Long id;

    private String name;
}

Pretty simple, aber erfüllt für die Demo genau seinen zweck.

Insert new Invocation

Der CommandLineRunner wird beim Starten des SpringBoot Container ausgeführt. Es sind für die Verwendung der in Memory Datenbank zwei statische Variable nötig. Die eine enthält das Root des Objektgraphen hier ROOT genannt und eine STORAGE die für das Persistieren auf der Festplatte.

Wie gelange jetzt die Daten in den Objektgraphen?

@Component
@Log
public class InitDatabase implements CommandLineRunner {
    // to stop spring boot application
    @Autowired
    private ConfigurableApplicationContext cac;

    // this is the ROOT of object graph
    private static Reference<InvocationRepository>ROOT = 
        X.Reference(new InvocationRepository());

    // Storage manager
    private static EmbeddedStorageManager STORAGE = 
        EmbeddedStorage.start(ROOT);

    @Override
    public void run(String... args) throws Exception {
        // load all into a local list
        var rootData = ROOT.get();
        List<Invocation> invocations = rootData.getInvocations();

        // print size of Repository
        log.info("size: " + rootData.size());

        // add a new Incocation to Repository
        Invocation invocation = new Invocation();
        invocation.setId(Long.valueOf(rootData.size()+1));
        invocation.setName("Test");
        invocations.add(invocation);
        invocations.forEach(i-> log.info(i.toString()));

        // store
        STORAGE.store(ROOT.get().getInvocations());

        // close
        STORAGE.shutdown();

        // shutdown SpringBoot container
        cac.close();
    }
}

Das Repository

Das Repository ist sehr einfach gehalten.

public class InvocationRepository {
    private final List<Invocation> invocations = new ArrayList<>();

    /**
     * get a List of Invocations
     * @return List of Invocation
     */
    public List<Invocation> getInvocations() {
        return invocations;
    }


    /**
     * Size of Repository
     * @return
     */
    public int size() {
        return invocations.size();
    }
}

Einschränkungen

Leider gibt es mit dem Classloader ein Problem, wenn man die DevTools von SpringBoot mit einbindet. Daher habe ich diese aktuell noch nicht eingebunden.

Was ist JIB?

Das Google Container Tool JIB ist ein Werkzeug um einfach aus einer Anwendung ein Docker Image zu erzeugen. Docker Images sind im Prinzip auch nur Archive, so dass man diese auch ohne Docker bauen kann. Und genau tut das Tool JIB von Google. Es gibt jeweils ein Plugin für Maven und für Gradle. In diesem Beispiel konzentriere ich mich auf die Gradle Variante von JIB.

Setup

Für das JIB Plugin muss Gradle in mindestes der Version 4.6 vorhanden sein. Die aktuelle Version 1.0.2 wird wie gewohnt eingefügt.

plugins {
  id 'com.google.cloud.tools.jib' version '1.0.2'
}

Der Erste Build

Ein beherzigtes gradle jib erstellt uns ein Docker Image der Anwendung.

gradle jib

Den Default Server ändern

Benutzt eine Anwendung den TomCat, dann muss man das Basis Image ändern. Der Default nimmt JIB eine auf Jetty basierendes Distroless Image. Hier nun ein Beispiel für die Verwendung von TomCat 8.5, ein auf alpine basieredes Image.

jib {
  from.image = 'tomcat:8.5-jre8-alpine'

  // ROOT muss angepasst werden
  container.appRoot = '/usr/local/tomcat/webapps/ROOT'
}

Alternative

Mit Source 2 Image (S2I) von OpenShift steht hierzu auch eine Alternative zur Verfügung. Diese habe ich aber noch nicht evaluiert. Die Quellen findet man hier: https://github.com/openshift/source-to-image

Milestone von Spring Boot 2.2.0M1

Um neue Feature zu testen, kann man auf die Milestone Repositories zurückgreifen. Da das Spring Boot Framework über ein Plugin eingebunden wird, muss das dem PluginManagement mit geteilt werden. Dazu muss man in der settings.gradle das Snapshot und/oder Milestone Repository hinzufügen. Ansonsten wird Spring Boot 2.2.0M1 Version von gradle nicht gefunden.

Plugin

Folgende Änderungen an der settings.gradle Datei sind nötig, um die Milestone Variante zu finden…

pluginManagement {
    repositories {
        maven { url 'https://repo.spring.io/snapshot' }
        maven { url 'https://repo.spring.io/milestone' }
        gradlePluginPortal()
    }
    resolutionStrategy {
        eachPlugin {
            if (requested.id.id == 'org.springframework.boot') {
                useModule("org.springframework.boot:spring-boot-gradle-plugin:${requested.version}")
            }
        }
    }
}

Dependencies

Jetzt kann regulär die Version 2.2.0.M1 eingebunden werden.

plugins {
    id 'java'
    id 'eclipse'
    id 'org.springframework.boot' version '2.2.0.M1'
    id 'io.spring.dependency-management' version "1.0.7.RELEASE"
}

Szenrio

Gegeben sei eine Domain die bei dem DynDNS Provider duckdns.org gehostet wird. Es sind aber als Einschränkung bei DuckDNS nur maximal 5 SubDomains erlaubt.

Eine Lösung – Pfade verwenden

Eine Lösung kann sein, dass man eine SubDomain verwendet, um über die Pfade den Dienst mit Hilfe des Reverse Proxys aufzulösen.

Hier nun der Server Abschnitt um einen Dienst der in dem lokalen Netz auf der IP Addresse 192.168.2.2 Port 4711 lauscht. Diesen werden wir auf der Domain xxx.duckdns.org/service bekannt machen.

server {
    listen 80;
    server_name xxx.duckdns.org;

    #
    # Serve service on path http://xxx.duckdns.org/service and map it to 192.168.2.2:4711
    #
    location /service/ 
    {
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;

        proxy_pass              http://192.168.2.2:4711/;

        proxy_read_timeout      600s;
        proxy_send_timeout      600s;
    }
}

Die proxy_pass Anweisung ist hier der wichtige Teil. Hiermit bestimmt man wo der Dienst erreichbar ist.

Spring Tool Suite 4.2.0

Hier kurz die wichtigsten Änderungen der neuen Version vom 28.03.2019.

  • Bislang gab es keine Möglichkeit der VM für den Language Server Procol process Parameter zu übergeben. Dieses kann notwendig sein, wenn zum Beispiel der Heap zu klein ist.

    -Dboot.ls.custom.vmargs=-Xmx2G

    Kann man den Heap für das LSP auf 2G anheben.

  • Stark verbesserte Performance. Der Cache der Symboleindizierung des LSP wird nun über Neustarts des LSPs hinweg gespeichert.

  • Die interne Kommunikation mit dem JDT wurde ersetztz, was auch zu einer erhöhten Performance durch zeit- und Speichersparmaßnahmen geführt hat.
  • Update auf Eclipse 2019-03.
  • In den Lauch-Configs wird JMX Unterstüzung nun per default gesetzt.
  • Ein out of sync Fehler bei den Live Hoover wurde behoben.
  • Verschiedene Fehler in der Navigation in den Live Bean wurde behoben.
  • Weitere kleinere Eclipse Probleme wurden behoben.

Die Ausgabe des Logos von Jooq verhindern

Beim Starten eines Queries wird normalerweise das Logo von Jooq ausgegeben. Das ist auch nicht weiter schlimm, es ist nur beim Debuggen der Anwendung manchmal ein wenig störend, weil es viel Platz auf der Konsole einnimmt.

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@  @@        @@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@        @@@@@@@@@@
@@@@@@@@@@@@@@@@  @@  @@    @@@@@@@@@@
@@@@@@@@@@  @@@@  @@  @@    @@@@@@@@@@
@@@@@@@@@@        @@        @@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@        @@        @@@@@@@@@@
@@@@@@@@@@    @@  @@  @@@@  @@@@@@@@@@
@@@@@@@@@@    @@  @@  @@@@  @@@@@@@@@@
@@@@@@@@@@        @@  @  @  @@@@@@@@@@
@@@@@@@@@@        @@        @@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@  @@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  Thank you for using jOOQ 3.11.9

Dank geht an dieser Stelle an Lukas Eder für dieses tolle Werkzeug.

Wie kann nun das Logo abgeschaltet werden? Dazu muss nur die Property org.jooq.no-logo auf true gesetzt werden. Ich löse das in dem ich eine Klasse JooqConfig mit einem Bean erstelle. Die Komponente wird so beim Component Scan gefunden und die Property gesetzt, sodass beim Starten kein Logo angezeigt wird.

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class JooqConfig {
    @Bean
    private void disableLogo() {
        System.setProperty("org.jooq.no-logo", "true");
    }
}

Credentials auslagern

Neben der Möglichkeit die Credentials mit dem Gradle Plugin Credentials zu verwalten, kann man auch zum Beispiel dieses in eine weitere Datei auszulagern. Hierfür bietet sich YAML an.

Hier die jooq.yaml

---
jdbc:
  password: secret
  username: usr

In dem Buildscript jooq.gradle lesen wir die Daten aus dem YAML ein. Das SnakeYaml nutze ich hierfür um die cfg zu definieren.

...
jooq {
  def cfg = new org.yaml.snakeyaml.Yaml().load(new File("" + rootProject.projectDir + "/jooq.yml").newInputStream())
...

Weiter unten im Abschnitt xxx können jetzt die Credentials aus dem jooq.yaml angegeben werden…

...
    jdbc {
      driver = 'org.mariadb.jdbc.Driver'
      url = 'jdbc:mariadb://server:3306/database'
      username = cfg.jdbc.username
      password = cfg.jdbc.password
    }
...

Installation

Docker besteht unter Arch Linux und Manjaro nur aus der Komponente docker. Damit man nicht als Root den Docker Daemon steuer muss, kann man einen benutzer in die Gruppe docker. Aufnehmen.

# Install Docker
yaourt --noconfirm -S docker
# add user to group docker
sudo usermod -aG docker $USER

Jetzt muss der User sich neu anmelden, damit die geänderten Gruppenrechte ziehen.

Hierbei muss sudo usermod -aG docker $USER als der User der Docker verwenden soll ausgeführt werden, da $USER auasgelesen wird. Danach muss der User sich neu einloggen, damit er die Gruppenrechte bekommt oder der User direkt angegeben werden.

Änderungen ohne Logout übernehmen

Mit su – BENUTZER kann eine Shell präsentieren die so aussieht, als wenn man sich auf der Virtuelle Konsole angemeldet hat.

su - $USER

Somit hat man die neu hinzugefügten Gruppenrechte und man kann mit Docker direkt loslegen.

Gradle Publish

Mit gradle publish kann ich ein Artefakt auf das Nexus Repository schieben, um es dort für andere Anwednungen vorzuhalten. Dazu binde ich per apply from: ein weiteres Skript ein, dass die Aufgabe erledigt.

// Include Gradle Skript um auf ein Nexus Repository zu deployen.
apply from: 'publishToNexus.gradle'

Das Skript publishToNexus.gradle sieht wie folgt aus:

// add sources
task sourcesJar(type: Jar) {
    from sourceSets.main.allJava
    classifier = 'sources'
}

def nexusUser = "MrY@gmail.com"
def nexusPassword = "MySecret"

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
            artifact sourcesJar
            groupId 'de.pfau'
        }
    }
    repositories {
        maven {
            credentials {
                username nexusUser
                password nexusPassword
            }
            url "${nexusUrl}/repository/edvpfau"
        }
    }
}

Es liegt in der Natur der Sache, dass das Plugin die Creditials benötigt. Es muss sich ja gegenüber dem Repository Authentifizieren. Nun ist es aber keine gute Idee das Skript so im SCM abzulegen, da jetzt jedem, der Zugriff auf die Sourcen hat, es möglich ist auch auf das Nexus Repository Zugriff zu erlangen.

Credentials Plugin für Gradle

Das Gradle Credentials Plugin löst genau dieses Problem, indem es eine vershlüsselte Version ablegt und dieses auf Anfrage dem Skript zur Verfügung stellt. Dazu muss man nur die Zeile wo das Nexus Passwort festgelegt wird abändern.

def nexusPassword = credentials."${nexusUser}"

Das Password bekannt machen

Damit wir jetzt auf das Passwort zugreifen können, muss dieses gesetzt werden. Mit dem Task addCredentials kann ein Value für ein Key, hier der Benutzername bestehend aus der Emailaddresse, gesetzt werden.

./gradlew addCredentials -PcredentialsKey="MrY@gmail.com" -PcredentialsValue="mySecred"

So kann das Gradle Buildscript gepusht werden, nur derjenige der dieses Feature benötigt auch die Credentials kennen muss.