kotlin spring boot 7p

Kotlin meets Spring Boot – What’s in it for us?


Most of you might know Kotlin as a programming language designed for developing Android mobile applications. Since its first appearance in 2011, it has been challenging good old Java to a race for number 1 in the market. How big of a player it actually has become could be seen when Google announced that “the next big step that we’re taking is that we’re going Kotlin-first.” in May 2019.

Less famous is Kotlin’s use in backend applications. At SEVEN PRINCIPLES we took the opportunity to develop a new Spring Boot application and decided to give Kotlin a try. Read on for insights into features we loved about this symbiosis and issues we ran into. We will cover a brief compatibility check, data classes as DTOs, Kotlin’s null safety guarantees and the use of extension functions.


Kotlin: Compatibility

Kotlin natively runs on the JVM. That’s great! Because it allows us to use practically any library written in Java. This also holds true for the Spring framework.

class CoreApplication

fun main(args: Array<String>) {

Have a look at the Controller implementation in our code example. There is everything that you might already know from Java backend programming. Dependency injection still works by annotation-based wiring of Spring components.

class UserController(val userService: UserService) { 
    // List all users 
    fun readAll(@RequestParam("search") keyword: String? = null): List<UserDto> { 
        return userService.findAllFilteredBy(keyword).map { it.mapToDto() } 
    fun update(@RequestBody userDto: UserDto, @PathVariable("id", required = true) id: Long) { ... } 

Kotlin: Data Classes as DTOs

Data classes – as you have probably already guessed from their name – were designed for “holding data”. They ship with attribute accessors and come in handy for use as Data Transfer Objects (DTOs). DTOs should be kept free from business logic; they hide implementation details from the “world wild web” and prevent unintended manipulation of your application’s state. As you can see in our code example, that is exactly what data classes do. You can get rid of Java’s extensive getters and setters by simply declaring mutable “vars” and immutable “vals” in Kotlin’s primary class constructor. Slick, eh?

data class UserDto(
    val firstName: String?,
    val lastName: String,
    val dateOfBirth: Date?
class UserController(val userService: UserService) {
    // List all users
    fun readAll(@RequestParam("search") keyword: String? = null): List<UserDto> { ... }

    fun update(@RequestBody userDto: UserDto, @PathVariable("id", required = true) id: Long) {
        val (firstName, lastName, dateOfBirth) = userDto
        val user = userService.findById(id) ?: throw RuntimeException("No user found for id $id.")

        userService.update(user, firstName, lastName)

Small hint: Initialization of multiple variables at once with values from a data class instance makes use of “Destructuring components” and can really brighten up your day.

Kotlin: Null Safety

APIs have a huge source of uncertainty – namely the user. Whenever there is variable input into our application, input validation is needed. Kotlin has a great way of easing this task for us. As shown in the code snippet above, you must explicitly allow nullable values by denoting the corresponding variable type with a “?”. What happens if we try to send a request to our update method with an empty “lastName” attribute? It would be rejected with Http-Status “404 – Bad Request”. What happens if we try to call a function which expects a non-null value parameter with a nullable variable? The code will simply not compile. Bye bye, NullPointerExceptions! Attention should be paid when calling Java code. The Kotlin compiler uses assertions for this. But that does not prevent NPEs being thrown if the actual runtime object references a null value.

Do you like Rock ‘n’ Roll? Kotlin has it. Did you notice the “?:”-statement in the code example below? That is the so called “Elvis Operator”. It reads as “If A is null assign B”. We did happily use it for variable initialization and invoking exceptions as seen previously. When applied to a variable the Kotlin compiler automatically recognizes the corresponding value as non-null from then on.

class UserService(val userRepository: UserRepository) {
    fun findAllFilteredBy(keyword: String?): List<User> { ... }

    fun findById(id: Long): User? { ... }

    fun update(user: User, firstName: String?, lastName: String) {
        user.firstName = firstName ?: user.firstName
        user.lastName = lastName


Kotlin: Extension Functions

Extension functions allow augmenting the behavior of existing classes for which modifying the original class signature is not possible – as in the case of third-party libraries. If you tried to find an analogue in the Java world, they are basically member functions declared outside of the regular class definition. We have used extension functions for wrapping data from our business logic objects into the DTOs used for the transport layer of our application.

fun User.mapToDto(): UserDto {
    return UserDto(

Kotlin: Function Declaration

A word of warning: Without a mutual understanding for the application of extension functions their extensive use could lead to confusion; especially when collaborating in a larger team.

fun User.functionHiddenSomewhereOutOfScope(input: Any) {
    this.firstName = ""
    this.lastName = ""

Note how Kotlin allows different ways of function declaration in the code example below. Although this results in shorter code and better readability, without IDE support it is not always directly clear what the return value or purpose of the function is.

fun surpriseMe(input: Any) = input.prepareGift()

fun anythingIsAppreciated(input: Any): Any = input.prepareGift()

fun butILikeItExplicit(input: Any): Any {
    return input.prepareGift()

Kotlin: Conclusion

For the sake of brevity this article does not cover everything we delved into. All in all, we agreed that – after a time of adapting – writing Kotlin felt pretty natural and its syntax blends in neatly with Spring Boot. It simplifies many things that should have been simple from the start, but were not simple in Java, e.g. wrapping of objects and input validation. Not every feature that ships with Kotlin can yet be exploited in the context of Spring Boot. In our case, we ran into issues when it came to reactive programming and the implementation of JPA with Hibernate. If you feel tempted to gather more impressions, try the sources below for further reading.







Integrationstests mit Testcontainers und Keycloak

Wie das nach Konferenzen so ist: Es kribbelt in den Fingern und man möchte die Dinge schnell ausprobieren. Auf der diesjährigen Javaland gab es einen spannenden Talk zum Thema „Integrationstests mit Docker und Testcontainers“. Wir haben Testcontainers mal etwas genauer unter die Lupe genommen.


App-Entwicklung unter SharePoint: Anonymer Zugriff

Mit SharePoint gibt es keine öffentlichen Web-Seiten für ein anonymes Publikum. Wirklich?

Es ist schon richtig: Bei SharePoint geht es um die Zusammenarbeit von Teams im Netz. Bis in die kleinsten Verästelungen der Seiten-, Daten- und Anwendungsstruktur läßt sich komfortabel bestimmen, wer was bis zu welchen Grade ausführen, sehen oder ändern darf. Hier liegt eine der großen Stärken von SharePoint.
Aber wenn wir in speziellen Einzelfällen auf diese Stärken gerade einmal gar keinen Wert legen? Was, wenn wir eine Seite einem Publikum präsentieren wollen, das wir weder kennen noch zu kennen brauchen?
Das geht mit SharePoint nicht. Oder doch?
Das geht mit SharePoint nicht. Oder doch?
Beispiel: Während der Vorproduktion zu einem Werbefilm lädt die Casting-Direktorin Aspiranten für die zu besetzenden Rollen in ihr Studio ein. Film- und Fotoaufnahmen werden gemacht, Details zu den Bewerbern werden aufgenommen, und all diese Inhalte werden, komfortabel und benutzerfreundlich aufbereitet, auf einer SharePoint-Team-Site dem inneren Kreis der an dieser Talentsuche Beteiligten bereitgestellt.
Wir sind nun dieser „innere Kreis“, haben vielleicht einige Korrekturen vorgenommen und eine Vorauswahl unter den Aspiranten getroffen, und wollen nun unsere Seite einem größeren Publikum vorstellen: dem auftraggebenden Kunden, der Werbeagentur, oder wem auch immer. Dieses größere Publikum ist weder Teil unseres SharePoint-Teams, noch wäre es praktikabel, es dazu zu machen. Wir haben es also mit einem teilweise anonymen Publikum zu tun.
Lassen Sie uns für dieses Anwendungsbeispiel weiterhin annehmen, daß wir mit der aktuellen Online-Version von SharePoint arbeiten und unsere Erweiterungen konsequenterweise als SharePoint Framework (SPFx) WebParts, also als moderne client-seitige Web-Apps mit JavaScript-Werkzeugen und -Bibliotheken, realisieren. Mit dieser zusätzlichen Bedingung fallen bei unserem Beispiel dann auch sämtliche Lösungsansätze aus, die eine benutzerdefinierte server-seitige Instanz zur Zugriffskontrolle benötigen würden.
Die gute Nachricht ist nun, daß wir eine solche benutzerdefinierte Instanz gar nicht brauchen. Entgegen der landläufigen Meinung gibt es anonyme Zugriffe auf SharePoint-Ressourcen wie Dateien, Ordner und Seiten.
So geht’s:
Wenn SharePoint Online in der Ressourcen-Anforderung keinen rtFA- oder FedAuth-Cookie findet, wird es normalerweise das Azure Active Directory bitten, den Benutzer zu authentifizieren. Für unser Szenario mit einem teilweise anonymen Publikum brauchen wir also eine Technik, um diese Anfragen zu vermeiden.
Hierzu gibt es die Spezial-Seite guestaccess.aspx, die auch dann keine Anfrage an das Azure Active Directory sendet, wenn rtFA- oder FedAuth-Cookie fehlen. Statt dessen erwartet guestaccess.aspx einen speziellen Parameter „share“. Der Wert dieses Parameters ist ein Schlüssel für eine interne Tabelle, die solche Schlüssel auf die eigentlichen Ressourcen-Adressen abbildet. Eine gültige Anfrage an guestaccess.aspx beantwortet SharePoint mit einem Redirect auf die jeweils zugeordnete Ressource und einem FedAuth Antwortcookie.
Sendet nun der Client-Browser seine Anfrage – zusammen mit dem FedAuth-Cookie – an die Redirect-Adresse, liefert SharePoint die Ressource aus: Der Benutzer ist zwar weiterhin anonym, aber der vorhandene und für die Ressource gültige FedAuth-Cookie verhindert die sonst zwingende Anfrage an das Azure Active Directory.
Zurück zu unserem Beispiel mit der Casting-Direktorin: Das SPFx WebPart, mit dem unser Team arbeitet, hat seine Datenbasis in SharePoint Listen und Content-Typen. Die mächtige Rechteverwaltung von SharePoint stellt auf komfortable Weise sicher, daß die Anfragen, die ein Benutzer vom WebPart an die Datenbasis absetzt, so beantwortet werden, wie es den Zugriffsrechten des jeweiligen Benutzers entspricht.
Eine SharePoint Liste ist nun aber keine Ressource, für die ein „share“-Schlüssel für anonymen Zugriff, so wie für Seiten, Dateien und Ordner, erstellt werden könnte. Genauer betrachtet würde unsere Casting-Direktorin und das innere Produktionsteam dem anonymen Publikum auch gar keinen ungefilterten und ungeordneten Zugriff auf die ganze Liste geben wollen. Was unser Team nach seiner Vorauswahl und Rollenzuordnung der Aspiranten im Grunde will, ist folgendes: Eine geordnete, selektierte Liste in präsentabler Form an den auftraggebenden Kunden und die Werbeagentur übergeben.
Richtig betrachtet haben wir es in unserem Anwendungsbeispiel also gar nicht mit der Anforderung zu tun, einen anonymen Zugriff auf die eigentliche Datenbasis bereitzustellen. Was wir brauchen ist der anonyme Zugriff auf das Ergebnis einer Abfrage.
Das letzte Teil des Puzzles:
Für SPFx WebParts lassen sich im Manifest beliebige Schlüssel definieren, die im Kontext einer Seite mit Werten belegt werden können. Für das aktive WebPart sind diese Schlüssel “own properties”, also direkt verfügbare Variable.
Diese preconfiguredEntries.properties sind der letzte Baustein, den wir für unsere SharePoint-Präsentation für ein anonymes Publikum brauchen: Sie enthalten das Ergebnis einer vorher durch einen authentifizierten Nutzer ausgeführten Anfrage an die Datenbasis. Die veröffentlichte dynamische Seite trägt ihre Datenbasis sozusagen mit sich herum, womit diese Datenbasis denselben Zugriffsbedingungen unterliegt, wie die Seite selbst.
Und jetzt alles zusammen:
In unserem SPFx WebPart unterscheiden wir zwischen authentifizierten Benutzern mit Zugang zu allen Funktionen und normalem Datenbankzugriff einerseits und anonymen Gästen mit eingeschränktem Zugang und ohne Datenbankzugriff andererseits.
Authentifizierte Benutzer und anonyme Gäste
Statt eine Anfrage an die Datenbank abzusetzen, wird für anonyme Gäste der Inhalt vordefinierter WebPart Properties ausgelesen.
Anonyme Gäste erhalten den Inhalt vordefinierter WebPart Properties
Für authentifizierte Benutzer stellen wir eine Funktion bereit, die programmgesteuert neue Seiten erstellt, die unseren SPFx WebPart enthalten und diesem die gewünschten Werte für die vordefinierten WebPart Properties mitgibt. Diese Werte sind beispielsweise Antworten auf CAML-Abfragen.
Authentifizierte Benutzer erhalten eine Funktion bereit, die programmgesteuert neue Seiten erstellt
Für authentifizierte Benutzer stellen wir außerdem eine Funktion bereit, die einen Eintrag für die neu erstellte Seite in die interne /ShareLink Tabelle schreibt, und einen “share”-Token für guestaccess.aspx zurückgibt. Hinweis: Die URLs, die File.getShareLink im Beispiel unten zurückgibt, machen den “share”-Token nicht direkt sichtbar, da SharePoint einen internen Kurz-URL-Dienst anwendet.
Guest-Access-Seite und Share-Token
Die von File.getShareLink zurückgegebene URL wird dann an unser „anonymes“ Publikum – beispielsweise den auftraggebenden Kunden oder die Werbeagentur – weitergegeben. Die sonst bei SharePoint übliche Aufforderung zum Sign In wird dieses Publikum nicht sehen.

App-Entwicklung unter SharePoint: Table Joins

Joining SharePoint content für SQL Programmierer. Was geht und was nicht geht.

»Enttäuschung ist mir eine Beglückung, denn zuvor war ich getäuscht, danach ist die Täuschung aufgehoben.«
(Horst Eckert a.k.a. Janosch)

Wer Jahre mit SQL verbracht hat, und dann auf einmal SharePoint Listen und ContentTypes vor sich hat, sollte auf eine Lehrstunde gefaßt sein. Aus der SQL-Welt sind wir es gewohnt, selbst Tabellen, die in unserer Datenbank in keiner durch Constraints definierten Verbindung stehen, frei und je nach Anforderung neu miteinander zu verknüpfen. Manche mögen bei solchen Aktivitäten – vielleicht nicht ganz zu Unrecht – die Qualität des Datenbankdesigns in Frage stellen.
Für jene strengen Gemüter gibt es gute Nachrichten: Wer sich die gegenseitigen Abhängigkeiten und Bezüge seiner Daten nicht vor dem Bau seiner SharePoint Listen und ContentTypes gut überlegt, wird mit CAML – dem Äquivalent zu SQL bei SharePoint – nur wenig Freude haben. Gleich vorweg: Wer bei SharePoint nicht schon beim Datenbankdesign Relationen definiert, wird sich später seine Verknüpfungen nach einzelnen REST-Abfragen auf die beteiligten Listen/ContentTypes selbst zusammenbauen müssen.
Hier hört die Ähnlichkeit aber auch schon auf.
Eine SharePoint “List” mit ihrem zugeordneten “ContentType” ist konzeptionell verwandt mit einem SQL Table.
Ein SharePoint “Lookup” Feld ist konzeptionell verwandt mit einem SQL Foreign Key Constraint.
Hier hört die Ähnlichkeit aber auch schon auf.
“Lookup” ist das SharePoint Zauberwort für Table-Joins
“Lookup” ist das SharePoint Zauberwort für Table-Joins
Während bei einer SQL-Abfrage prinzipiell jede Tabelle mit jeder anderen Tabelle über beliebige Spalten verknüpft werden kann, ist ein SharePoint Join immer und grundsätzlich an solche Relationen gebunden, die bereits durch Lookup Felder etabliert sind.
In die SQL-Welt übersetzt hieße das: Table-Joining nur über die bereits in der Datenbank definierten Foreign Keys Constraints.
Für moderne WebParts aufbauend auf dem SharePoint Framework (SPFx) und Deployment von Features (Lists, ContentTypes, Columns) als Teil des Solution-Deployments bedeutet das: Über mögliche Join-Operations wird bereits beim XML-Design der Site columns, Site content types und Site lists entschieden.
Option 1: Database first.
Und so geht’s. Option 1: Database first.
Mit dieser Option wird die Ergebnistabelle bereits bei der Feature-Implementierung vorbereitet. Nach SQL übersetzt: Datenbankseitig wird ein View über die primäre Tabelle und deren Referenzen erzeugt, und eine Abfrage auf diesen View braucht sich um das Joining nicht mehr zu kümmern. Table-Joins erscheinen für den Abfragenden vollständig transparent.
Für den primären ContentType wird eine Lookup Column definiert, die auf den sekundären ContentType verweist. SharePoint realisiert dies intern immer über den Wert der ID-Column des sekundären ContentTypes. In SQL-Sprache: Die auf die sekundäre Tabelle verweisende Spalte der primären Tabelle wird mit einem Foreign Key Constraint an die ID-Spalte der sekundären Tabelle gebunden.
Für alle weiteren Spalten der sekundären Tabelle, die in der Ergebnistabelle benötigt werden, wird jeweils eine sekundäre Lookup-Column definiert, die auf die primäre Lookup-Column verweist.
Schema-Definition für Option 1: Database first.
Schema-Definition für Option 1: Database first.
Option 2: Code first.
Und so geht’s. Option 2: Code first.
Mit dieser Option werden alle weiteren Spalten, die aus der sekundären Tabelle benötigt werden, zur Abfragezeit durch CAML-Code deklariert.
Auch für diese Option 2 gilt: Eine Relation zwischen primärer und sekundärer List/ContentType wird Feature-seitig über eine Lookup column hergestellt. Existiert eine solche Relation nicht, kann eine Zusammenführung von Daten aus mehreren Lists/ContentTypes nicht durch eine einzelne Datenbankabfrage, sondern nur programmseitig über mehrere Einzelabfragen realisiert werden.
Wie bei Option 1 wird für den primären ContentType eine Lookup Column definiert, die auf den sekundären ContentType verweist. Die Definition der weiteren Spalten, die in der Ergebnistabelle benötigt werden, erfolgt jedoch nicht bei der Feature-Definition, sondern wird erst zur Abfragezeit über CAML-Code dynamisch vorgenommen.
CAML-Definition für Option 2
Die Namen der “ProjectedFields” können jetzt wie bei Option 1 der Liste der abgefragten hinzugefügt werden.

Data Lake und Datenschutz: Anonymisierung und Pseudonymisierung

Datenschutz im Data Lake wird zu „Muss“… Wie geht man vor?

Mit der Einführung der europäischen Datenschutz-Grundverordnung (EU-DSGVO) hat sich auch das Vorgehen beim Aufbau von Data Lakes gewandelt. Datenschutzaspekte spielen eine sehr viel größere Rolle als vorher. Themen wie Anonymisierung und Pseudonymisierung von Daten und ganzen Datenbereichen sind zu einem Muss geworden und erzwingen ein sehr durchdachtes Vorgehen. Warum ist das aber gerade beim Aufbau von Data Lakes so kritisch? Sind denn Anonymisierung und Pseudonymisierung von Daten etwas Neues? Eigentlich nein, aber im Kontext von Data Lakes in gewisser Weise schon: Der ursprüngliche Focus von solchen Systemen lag in der Vergangenheit auf dem Managen von enorm vielen heterogenen Daten. Das heißt, man kümmerte sich in der Regel zunächst um das Bereitstellen dieser Daten für Analysen und operative Zwecke in unverschlüsselter Form, was auch die Umsetzung analytischer Use Cases mit diesen Daten mit eingeschlossen hat. Warum man das gemacht hat, liegt eigentlich auch auf der Hand: Man spart sich Komplexität und kann mit Klardaten auch sehr viel einfacher umgehen, da sich Analysen und analytische Anwendungen sehr einfach mit ihnen realisieren realisieren lassen. In solchen Systemen hat man in der Regel keine große Anforderungen in Sachen Datensicherheit und Governance realisieren zu müssen geschweige denn realisiert, da Ziel und Zweck eines Data Lakes von anderen Faktoren bestimmt wurde. Dieses Bild ändert sich allerdings sehr stark in letzter Zeit.

Eine daraus folgende Anforderung, mit dem man also sehr viel stärker als früher konfrontiert ist, ist die Forderung nach Pseudonymisierung und, noch stärker, Anonymisierung der Daten oder zumindest von Datenbereichen im Data Lake. Daten, die keinen direkten zeitlichen und operativen Zweckbezug mehr haben, können nicht einfach mehr klar abgelegt werden. Es reicht auch nicht, einfach die Datenträger zu verschlüsseln. Die Daten selbst müssen in einem solchen Fall in der Regel anonymisiert oder mindestens pseudonymisiert werden. Was verbirgt sich aber hinter diesen Begriffen?

Anonymisierung, Pseudonymisierung und Analytik

Kurz gesagt bedeutet Pseudonymisierung, dass ich die Daten bei Bedarf noch auf einen Ursprung (Person, Kunde, Mitarbeiter,…) zurückführen kann, zumindest theoretisch. Bei der Anonymisierung ist das nicht mehr möglich. Eine gute Gegenüberstellung findet sich hier: Anonymisisierung und Pseudonymisierung

Für die Durchführbarkeit von Analysen ist natürlich wichtig, dass bei diesen Vorgehensweisen nicht die Beziehungen in den Daten verlorengehen. Das bedeutet, dass eine Person in einem für mich wichtigen Datenauswertungskontext immer das gleiche Pseudonym bekommt. Ist dieses nicht mehr auf die Person rückführbar – was allerdings in der Regel sehr schwer nachweisbar ist – ist die Person sogar anonymisiert. Es wird typischerweise auch gefordert, dass der Schlüssel

  • nach einem bekannten sicheren Verfahren erzeugt wird
  • nur in eine Richtung funktioniert, das kann man organisatorisch „sicherstellen“, indem der Schlüssel „weggeworfen“ oder sicher verwahrt wird
  • der Schlüssel nicht im Data Lake selbst abgelegt werden darf, sondern eine sichere Stelle außerhalb benutzt wird

Aber die Schwachstelle des Ganzen ist oben schon angedeutet: Ein Nachweis zu führen, dass solchermaßen anonymisierte Daten tatsächlich anonym sind in dem Sinn, dass unter keinen Umständen über sie ein Personenbezug hergestellt werden kann, kann sich als unmöglich oder doch sehr schwierig herausstellen. Es gibt nämlich bekannte Situationen, in denen aus den Daten selbst ohne höheren Aufwand doch ein Personenbezug hergestellt werden kann. Ein typisches Beispiel sind Mitarbeiterdaten, die aufgrund ihres Arbeitsplatzes und ihrer Einordnung in die Organisationshierarchie des Unternehmen leicht identifizierbar sind. Oder Kundendaten, die alleine aufgrund der Tatsache, dass Adressgebiete nicht hinreichend groß gewählt wurden, auf einzelne Personen rückführbar sind – zumindest indirekt und unter Umständen unter Zuhilfenahme von „hinreichend schlauen“ Verfahren. Im ersten Fall der Mitarbeiterdaten ist das sogar besonders kritisch, da hier nicht nur die Interessen des Datenschutzes selbst, sondern auch des Betriebsrats und eventuell vorhandener Betriebsvereinbarungen berücksichtigt werden müssen. Ich glaube, diese Beispiele zeigen sehr gut, in welche Fallen man hier geraten kann.

Und was ist mit Klardaten?

Klardaten dürfen nur dann im Data Lake gespeichert und verwendet werden, wenn eine Erlaubnis dazu vorliegt. Es ist also bei der Verarbeitung und Speicherung solcher Daten immer auch eine Prüfung  der „Permissions“ zu diesen Daten vorzunehmen. Die Daten müssen auch unverzüglich  bei berechtigtem Entzug der „Permissions“ gelöscht werden können. Ein solcher Widerruf oder genereller Einspruch gegen die Datenspeicherung kann beispielsweise durch einen Kundenauftrag veranlasst sein. Jetzt ist aber allein die Aufgabe, diese „Permissions“ in korrekter Weise zu verwalten eine keinesfalls triviale Aufgabe, mit der die zum Management eines Data Lakes Berechtigten in der Regel ein Problem haben. Eine mögliche Abhilfe kann hier sein, dass zwar die grundlegenden Infrastruktur zum Speichern dieser Daten seitens des Data Lakes bereitgestellt und verwaltet wird (das „System an sich“), die Benutzung von solchermaßen kritischen Bereichen aber in die Hand dafür speziell Berechtigter gelegt wird. Diese müssen dann auch zwingend die Berechtigung nachweisen – denn die Verantwortung für diese Daten liegt jetzt komplett in deren Hand! Ein Zugriff von dem normalen (und in der Regel anonymisierten) Data Lake in diese Daten ist nicht möglich und wird normalerweise technisch unterbunden. Die Datenhaltung inklusive dem Laden und Verarbeiten der Daten und das Löschen wird von den dazu Berechtigten ausgeführt und hat mit der eigentlichen Betriebsführung des Data Lakes dann „nichts zu tun“. Das erfordert natürlich ein ausgeklügeltes Sicherheits- und Mandantenkonzept. Hierauf werde ich demnächst ein Licht werfen.







Automagic Jersey API Testing

When writing or consuming REST APIs there are many excellent options to choose from. When it comes to testing parts of an API in isolation the situation is noticeably worse, though, especially in the Jersey ecosystem. Nevertheless is it useful to not only distinguish between unit and integration tests but also be able to apply both concepts when developing and testing REST APIs.

In this blog entry we will explore how unit tests relate to REST APIs in general, which tools the Jersey framework provides in this regard, the pitfalls to deal with when using them, and finally, how we may overcome its shortcomings.

Table Of Contents

Spring, anyone?

Yes, we, too, have heard of Spring. The abstractions it provides are arguably superior to Jersey’s more imperative style. This is especially true when it comes to easy testing where Spring shines, whereas Jersey does not.
Even though Jersey does provide a dedicated test framework you have to do all the work of setting it up and wiring it together yourself. If using custom filters, features and/or injection providers this becomes a tedious affair at best, and error prone or ignored at worst.

So why try making Jersey testing work regardless? Sometimes switching to Spring is not viable, e.g. for legacy projects. Some people simply like Jersey better. Having the option to write proper unit tests in Jersey is a good thing regardless of the motivation.

Unit Testing REST

Let us assume we have a very basic Jersey controller for a RESTlike ressource:

If we want to write unit tests for our controller we might trivially do so like this:

Note that this is a unit test for our Java controller method and not in fact for our REST API method.
When checking the result of our call its representation is a Java class and not a JSON string as indicated by our controller. At this point we are unit testing our implementation internals as opposed to our public contract.

To put it more bluntly we have not tested our actual REST API at all.

Jersey Test Framework


Luckily, Jersey does provide its own testing framework, including a slim in-memory application container.

Let’s try again:

Now we can make assertions about the API our actual consumers will use, and ensure our contract matches the actual responses going out.

Note that, while we did fire up an actual mini-application container, we restricted ourselves to the one controller we wanted to test. Thus we can still call this a unit test, confined to the actual unit under test.

Contrast this with e.g. RESTassured which allows testing your REST API in a very similar fashion but without allowing you to confine yourself to only parts of your application, making it more of an integration test suite.

Easy Mode?

So, it looks Jersey makes it all easy enough for us. For simple code such as above this may hold; if making use of more of Jerseys features the presented method does scale poorly, unfortunately.

There are some cases where things start to fall apart, among them mocks and providers.


Mocking parts of an application is an essential technique to ensure manageable and meaningful tests. In our example we probably want to mock our service since its logic not the thing we want to test here; we rather assume this part of our application is working as intended and concentrate on the code the test is being written for:

If we test Java code wiring this up is straightforward:

Getting Jersey to pick this up requires the following in addition:

This means a lot of boilerplate to accommodate a standard workflow. While setting up tests this way is very much possible it does result in a lot of duplicate code that does not directly contribute to our test goals but needs to be maintained regardless.


One of the powerful features Jersey provides is customised injection of parameters into methods managed by Jersey’s lifecycle. In the following snippet we let Jersey handle the controller argument annotated as @User and have it inject a proper object at the call site whenever we need it. The actual setup of the @User instance is defined by a so called Provider that we register with Jersey.

To ensure this works in our unit tests we have to actively register our provider implementation or preferably bind to a mock instance:

This, too, necessitates additional code to make it work within the Jersey Test Framework, rendering testing your API properly more verbose and brittle than writing regular unit tests.

Custom Controller Tests

Taking the above into account may make testing REST resources look like too much of a hassle to do with Jersey. Which in turn may lead to many developers just not doing it.
Luckily, Jersey allows us to hide most of that boilerplate away, requiring us to write our test setups only slightly differently than what we are used to with regular Java tests.


Applied to our example controller such a test might look similar to the following:

It is readily apparent that not having to set up the Jersey container by ourselves improves readability and maintainability of our test. All the complexity is provided by and hidden behind the generic type of our extended class.

Mocking our internal EchoService is as terse as with a regular Java class; the only difference being that we retrieve an already mocked parameter instead of passing it on in the first place. The actual definition of the mocked behaviour is done as usual.

The actual tests can then be defined as demonstrated earlier – testing REST functionality as opposed to Java code.


Combining the terseness of a unit test with the capabilities of Jersey’s test framework may look like we are having our cake and eating it, too.
It is made possible by extending the JerseyTest class provided by Jersey’s test framework and automating all of the tedious setup it requires.
At the moment this means conforming to a set of assumptions in order to work properly:

  • controller dependencies must be provided explicitly – no injection via annotation
  • controller dependencies can only be accessed by type
  • test constructors should be explicitly annotated, defaulting to the constructor with the most arguments
  • constructor arguments are always mocked automatically
  • filters and providers are always registered for all controller tests

In turn this provides us with the following facilities:

  • automatic setup of the Jersey test container with the controller under test and classes relevant to the Jersey lifecycle
  • automatic mocking of all constructor parameters
  • convenient accessor method for constructor parameters

The actual implementation is rather concise and straightforward, too:


Since Jersey is a tad picky when it comes to mixing versions of its various sub-projects here is the `pom.xml` used throughout the examples above.


DWH-Modernisierung mit dem AWS Database Migration Service

AWSLogoIm Rahmen einer Modernisierungsstrategie für die eigene BI-Umgebung steht inzwischen eine große Bandbreite verschiedenster neuer Technologien zur Verfügung. Diese sollten im Rahmen der Erarbeitung eines Konzeptes berücksichtigt werden. Die Potentiale, die die Nutzung von Clouddiensten bietet, sind definitiv eine der zu prüfenden Möglichkeiten bei der Modernisierung eines Data-Warehouses (DWH). Denn im Vergleich zu anderen neuen Technologien haben Clouddienste den Hypestatus bereits verlassen und sind auch in großen Unternehmen (z.B. Deutsche Bahn AG) inzwischen ein Teil der Gesamtstrategie der IT.

Da mit dem Wechsel der BI-Umgebung in die Cloud sowieso eine Ablösung der On-Premises Infrastruktur ein Teil der Aufgabe ist können in diesem Zuge auch gleich noch weitere Potenziale erschlossen werden. Diese können sich in Einsparpotenziale, einer Simplifizierung oder auch einer Nutzung schnellerer Technologien für die BI-Umgebung äußern. So kann man z.B. noch die folgenden Punkt andenken:

  • Umstellung auf andere z.B. lizenzfreier Data-Warehouse-Technologien wie PostgreSQL
  • Konsolidieren bestehender BI-Datenbanken durch Schemaanpassung oder Zusammenführung/Auseinanderdividieren von Data-Warehouses
  • Kontinuierliche Replikation von Daten aus dem Quell- in das Zielsystem anstatt einer ad hoc Ablösung einer Datenbank durch Einmal-Migration mit der Möglichkeit einer längerfristigen Umstellung aller Applikationen auf eine neue Datenbanktechnologie

Umsetzung in Amazon Web Services (AWS) mit dem Database Migration Service

Amazon bietet mit den Amazon Database Migration Services (AWS DMS) ein umfangreiches Toolset an, um die Migration einer lokalen BI-Umgebung (DWH oder Datenbanken) bzw. deren Replikation in die Amazon-Cloud zu vereinfachen. Die Tools sind darauf angelegt, eine Migration bzw. Replikation strukturiert und transparent zu organisieren und unterstützt bei den Herausforderungen, die bei dieser Art der Aufgabenstellung klassischerweise auftreten. Diese können beispielsweise uneinheitliche Schemaobjekte der verschiedenen BI-Datenbanktechnologien oder die Notwendigkeit der parallelen Führung einer doppelten Datenhaltung in der Übergangsphase in die Cloud sein. In AWS sind die Tools darauf ausgelegt, genau dieses zu gewährleisten.

Migration vs. Replikation in AWS

In AWS wird nicht eindeutig zwischen einer Replikation bzw. Migration unterschieden. Grundsätzlich kann eine Migration ein einmaliger Transfer einer Datenbank mit der Ablösung einer legacy Datenbank verstanden werden, während bei der Replikation eher ein Parallelbetrieb verschiedener Datenbanken verstanden wird. So können zur Erhöhung der Ausfallsicherheit durchaus DWHS oder Datenbanken repliziert werden ohne das eigentliche Ausgangssystem irgendwann abzuschalten.

Bei den meisten Ablösungen von DWHs ist es aber zumeist so, dass diese doppelt vorgehalten werden. Die auf das DWH zugreifenden Applikationen (z.B. Analytic Tools) können häufig nicht einfach parallel mit migriert werden. Daher findet auch bei einer klassischen Migration in einem gewissen zeitlichen Rahmen eine Replikation statt. Daher wird in AWS nur wenig zwischen einer Replikation und Migration unterschieden.

Im Rahmen von AWS ist es wichtig zu verstehen, dass die AWS eigenen Tools im Rahmen der Database Migration Services vor allem Migrationen bzw. Replikationen von und nach AWS unterstützen.

homogene vs. heterogene Migration

In AWS wird grundsätzlich zwischen einer homogenen und einer heterogenen Migration (bzw. Replikation) unterschieden. Diese sind interne Bezeichnungen innerhalb von AWS um zu verdeutlichen, dass nicht nur eine Migration oder Replikation innerhalb gleicher Datenbanksysteme (z.B. „homogen“ von Oracle nach Oracle). Auch ein Wechsel der Systeme ist möglich (z.B. „heterogen“ von Oracle nach postgreSQL). Jeder, der eine solche „heterogene“ Migration bereits mal organisiert hat, weiß um die Herausforderungen, die damit verbunden sind.

DMS Task vs. Schema Conversion Tool (SCT) vs. native Migrationstools

In AWS stehen verschiedene Werkzeuge für die Migration bzw. Replikation von DWHs oder einfachen Datenbanken zu Verfügung. Grob formuliert kann ein Task ein Full Load, ein Delta Load oder beides beinhalten. Als kontinuierlicher Dienst ist dieser nicht zeitgebunden, sondern repliziert kontinuierlich Daten aus einem Quellsystem in ein Zielsystem. Der Service bietet darüber hinaus auch noch weitere Funktionen um einen Task herum, wie Logs, Benachrichtigungen usw..

Migrations- und Replikationsmöglichkeiten

Auswahlmöglichkeiten für ein Task in DMS

Bei heterogenen Migrationen (siehe unten) kann das notwendige Mapping der Schemata des Quell- und Zielsystems durch das Schema Conversion Tool (SCT) organisiert werden. Dieses ist ein desktopbasierendes und kostenfreies Werkzeug für Windows, Mac und Linux. Dieses erlaubt das Folgende:

  • Kopieren eines Datenbankschemas von einer Quelle auf ein Ziel
  • Konvertieren eines Datenbank- oder Data Warehouse-Schemas
  • Analysieren einer Datenbank, um
    • die Konvertierungskomplexität zu ermitteln
    • mögliche Einschränkungen für die Ausführung auf Amazon RDS zu ermitteln
    • zu ermitteln, ob ein Lizenz-Downgrade möglich ist
  • Konvertieren von eingebettetem SQL-Code in einer Anwendung
  • Migrieren von Data Warehouse-Daten zu Amazon Redshift

Siehe https://aws.amazon.com/de/dms/faqs/ – 07.06.2017

Analyse heterogene Migration

Auswertung einer Analytik im Schema Conversion Tool mit einer Übersicht (links) und einer Detailauswertung (rechts)

Man beachte, dass für die heterogene Migration bzw. Replikation nur bestimmte DWH- bzw. Datenbanksysteme unterstützt werden. Dies sind im Folgenden:

Quelldatenbank Zieldatenbank auf Amazon RDS
Oracle Database Amazon Aurora, MySQL, PostgreSQL, MariaDB
Oracle Data Warehouse Amazon Redshift
Microsoft SQL Server Amazon Aurora, Amazon Redshift, MySQL, PostgreSQL, MariaDB
Teradata Amazon Redshift
IBM Netezza Amazon Redshift
Greenplum Amazon Redshift
HPE Vertica Amazon Redshift
MySQL und MariaDB PostgreSQL
PostgreSQL Amazon Aurora, MySQL, MariaDB
Amazon Aurora PostgreSQL

https://aws.amazon.com/de/dms/ – 26.05.2017

Die Entwicklung geht aber immer weiter. Seit 10. April 2017 wird auch NoSQL-Datenbank MongodDB als Quellsystem sowie Amazon DynamoDB als Sourcesystem unterstützt.

Das SCT migriert die Daten nicht direkt über das Tool, bietet aber eine direkte Integration in ein DMS Task an, so dass mit dem definierten Mapping innerhalb von SCT die Migration bzw. Replikation innerhalb des Task möglich ist.

Zudem stehen auch in AWS die nativen Migrationstools wie Export/Import oder Datapump (Oracle) oder auch pg_dump (potgreSQL) zur Verfügung, unabhängig davon, ob die Datenbank im Rahmen von RDS oder auf einer EC2-Instanz läuft.


Grundsätzlich werden die einzelnen Tasks in AWS DMS nicht in Rechnung gestellt, d.h. prinzipiell kann eine virtuell unendliche Anzahl von Tasks eingerichtet werden ohne dass es zu einem finanziellen Mehraufwand kommt. Auch das Schema Conversion Tool ist lizenzkostenfrei. Wie üblich bei AWS ist nur die für die Migration benötigte Infrastruktur kostenpflichtig. Kostenfaktor sind vor allem zwei AWS Dienste:

  • Die Migrations- und Replikationstasks werden auf Replikationsinstanzen ausgeführt, deren Betrieb kostenpflichtig ist.
  • Sind AWS-externe Datenbanken an der Migration beteiligt, die via DierectConnect oder VPN an AWS angebunden sind, treten weitere Kosten durch den Datentransfer für die Migration und Replikation auf.

Die aktuelle Preisgestaltung kann direkt von der AWS Webseite abgerufen werden: https://aws.amazon.com/de/dms/pricing/


Eine Modernisierungsstrategie für ein Datawarehouse sollte die Migration in die Cloud beinhalten. Als Erweiterung sollte aber auch die Ablösung lizenzgebundener DWH-Systeme durch lizenzfreier Systeme mit angedacht werden, alleine schon um Kostenvorteile zu erwirtschaften. In AWS steht auch das notwendige Werkzeugset zur Verfügung um dieses schnell und transparent zu planen. Mit dem Einsatz des Schema Conversion Tools können die erwarteten Aufwände und Risiken schnell analysiert werden. Man springt so nicht ins kalte Wasser.

Durch die Replikationsmöglichkeit in AWS DMS besteht auch die Möglichkeit, eine Migration nicht ad hoc zu vollziehen, sondern über einen längeren Zeitraum um die neue Umgebung testen zu können. Dies bietet so die Möglichkeit auch DWHs und Datenbanken abzulösen die von einer umfangreichen Anzahl von Applikationen genutzt werden und deren ad hoc Umstellung auf eine neue Datenbanktechnologie nur mit hohem Aufwand bzw. Risiko verbunden ist.

Zudem erlaubt es diese eine einfache Skalierung von Datenbankservern (auch weltweit verteilt), Einrichtung hochverfügbarer Infrastrukturen sowie on-the-fly Sicherung auf räumlich verteilten Standorte.

Container-Deployment auf AWS

Eines der fantastischen Phänomene, welche die zunehmend Cloud-zentrierte IT-Welt in jüngerer Zeit maßgeblich beeinflusst, sind zweifelsohne die Amazon Web Services (AWS). Natürlich beschäftigen auch wir bei 7P uns entsprechend eifrig mit der Thematik und unseren Kunden bleibt die Popularität der AWS nicht verborgen. So ist es wenig verwunderlich, dass der Begriff und die Möglichkeit eines Einsatzes in der Amazon Cloud bei praktisch jedem neuen Projekt fällt bzw. diskutiert wird. Höchste Zeit uns der Frage auch hier im Blog zu widmen.
Als erster Artikel zum Thema AWS auf diesem Blog, richtet sich dieser eher an Leser, die wenig bis keine Vorerfahrung haben. Weiterlesen