Herzlich willkommen auf dem Blog der exensio GmbH

exensio ist ein unabhängiges Software-Unternehmen von erfahrenen IT Beratern und Architekten. Zu unseren Kunden zählen namhafte deutsche Großunternehmen.

exensio verbessert das Kommunikations- und Informationsmanagement von Unternehmen. Auf Basis sinnvoller und zweckmäßiger Technologien erarbeiten wir flexible und übergreifende Lösungen für webbasierte Informationssysteme. Abhängig von den Kundenanforderungen realisieren wir Web Applikationen sowie Lösungen auf Basis von Web Content Management- und Portalsystemen. Integrativ, wirtschaftlich und anwenderfreundlich!

Hier gelangen Sie zur exensio GmbH.

Donnerstag, 3. November 2016

Preisbindung für rezeptpflichtige Medikamente – Warum sich Pharma-politische Entscheidungen auf unsere IT Lösungen auswirken?

Vor kurzem hat der Europäische Gerichtshof die Preisbindung für verschreibungspflichtige Medikamente in Deutschland gekippt (siehe u.a. http://www.tagesschau.de/wirtschaft/eugh-arzneimittel-103.html). Was steckt dahinter? In Deutschland ist die Preisbildung für verschreibungspflichtige Arzneimittel gesetzlich geregelt (Arzneimittelpreisverordnung). Dahinter steckt der Grundgedanke, dass Patienten das gleiche Medikament in jeder Apotheke zum gleichen Preis erhalten. Kranke sollen in ihrer Notlage nicht noch Preise vergleichen müssen. Zudem soll mit dem System die Grund- und Notfallversorgung mit Medikamenten sichergestellt werden.

D.h. deutschen Apotheken ist es für rezeptpflichtige Arzneimittel nicht erlaubt, Rabatte zu gewähren. Dies gilt auch für Versandapotheken.

Mit dem Urteil des EuGH ist diese Preisbindung für ausländische Versandapotheken nun aufgehoben worden. Der Aufschrei der deutschen (Versand-)Apotheken blieb natürlich nicht aus, wenn ab sofort über den ausländischen Apotheken-Versandhandel die gleichen Medikamente zu günstigerem Preis erworben werden können und in Deutschland aber die Preisbindung gilt.

2 Lösungsszenarien liegen auf der Hand: Entweder wird nun auch für deutsche (Versand-) Apotheken die Preisbindung aufgehoben oder der Versandhandel für rezeptpflichtige Medikamente wird verboten. Zu letzterer Variante gibt es bereits einen Vorstoß durch den Gesundheitsminister (siehe u.a. https://www.welt.de/newsticker/dpa_nt/infoline_nt/schlaglichter_nt/article159106082/Groehe-will-Versandhandel-mit-Arzneien-auf-Rezept-verbieten.html).

Wieso beschäftigen wir von exensio uns mit diesem Thema? exensio erstellt IT Lösungen in unterschiedlichen Bereichen von Pharmaunternehmen. Da wir die geschäftlichen Abläufe und Hintergründe verstehen müssen, ist es für uns wichtig, immer ein Ohr an den aktuellen fachlichen Entwicklungen zu haben.

So hat exensio für einen unserer Pharma Kunden ein PIM (Product Information Management) System implementiert, mit dem unter anderem sämtliche Preise für alle Präparate verwaltet werden. Dazu gehören der Hersteller Abgabepreis (HAP), der Apotheken Einkaufspreis (AEP) sowie der Apotheken Verkaufspreis (AVP).

Der Hersteller kann diese Preise regelmäßig (alle 14 Tage) ändern und meldet diese der IFA (Informationsstelle für Arzneimittelspezialitäten). Mit Hilfe unserer PIM Lösung können diese Preisanpassungen auf elektronischem Wege automatisiert übermittelt (und Rückmeldungen verarbeitet) werden.

Je nach Lösung des oben beschriebenen Konflikts würde dies eine Änderung für unser Modul "Preisänderungsmeldung" implizieren. Warten wir ab, wer sich durchsetzt.

Mittwoch, 19. Oktober 2016

Integration mit Scrapy und Tweepy

In diesem Posting gehe ich auf die Integration von Webinhalten mittels Scrapy und Tweepy ein. Ich zeige, wie man einen Scrapy-Spider bei Srapinghub betreibt und wie man Tweets auf Twitter postet.

Problemstellung

Eine Webseite (in diesem Fall im Internet) stellt Content zur Verfügung, der potentiell für eine breite Masse von Interesse ist. Die Usability der Webseite stellt eine hohe Hürde für viele User dar. Das beginnt beispielsweise am fehlenden Responsive Design, geht weiter über mangelnde Teilbarkeit und endet bei nicht vorhandener, moderner Funktionalität. Die Ursachen für die Mängel sind vielschichtig und reichen von nicht vorhandenem Budget bis zur falschen Wahl des verwendeten Frameworks bzw. veralteter Technologie. Ähnlich verhält es sich mit der Ablösung des Systems, die eigentlich folgerichtig wäre. Die üblichen Gründe für den Weiterbetrieb sind z.B. fehlendes Bewußtsein der Mängel, eingeschränktes KnowHow bzgl. neuer Technologien oder nicht vorhandenes Budget.

Das bestehende System wird trotz der Mängel vom Betreiber intensiv genutzt. Ziel des Projekts ist die einfache Integration der im Quellsystem zur Verfügung gestellten Inhalte in moderne Technologien, hier gezeigt anhand von Twitter. Die Vorteile der vorgestellten Lösung sind geringe Integrationskosten, cloudbasierte Lösung, die kaum Verwaltungsaufwand erfordert, bestehende Arbeitsprozesse seitens des Betreibers bleiben unverändert. Durch die Veröffentlichung auf Twitter wird der zur Verfügung gestellte Inhalt auf einfachste Weise einem breiten Publikum zugänglich gemacht. Zugleich lassen sich sämtliche Vorteile der Plattform nutzen (Teilbarkeit, Usability, Responsive Design).

Projektübersicht und Ablauf

Das Quellsystem verfügt über keinerlei Schnittstelle oder API, so dass entschieden wurde, die zur Verfügung gestellten Inhalte mit Screen Scraping Technologie auszulesen. Die Wahl fiel auf das Framework Scrapy [1], da es einfach in der Handhabung ist und es sich günstig in der Cloudlösung Scrapinghub [2] betreiben lässt.
Aus den oben genannten Gründen wurde für die Wiederveröffentlichung der erfassten Inhalte der Mikrobloggingdienst Twitter [3] gewählt. Durch die zuvor genannte Festlegung der Web Scraping Technologie (Scrapy benutzt Python) musste ein auf Python basiertes Tool gefunden werden. Hier bot sich die Verwendung von Tweepy [4] an, einer einfach zu benutzenden Python Bibliothek für die Twitter API.
Abgerundet wird die Lösung durch den Einsatz von TinyURL [5], einem Kurz-URL-Dienst, der sich sehr einfach in Python integrieren lässt. Dieser Schritt ist notwendig, um die 140-Zeichen Grenze von Twitter einzuhalten und dennoch sinnvolle Inhalte zu posten.

Der Scrapy-Spider wird nach seiner Erstellung in den Srapinghub geladen. Dort wird ein scheduled Job angelegt, der z.B. stündlich ausgeführt wird. Die durch den Spider erfassten Inhalte werden im Spider selbst anhand von Selektoren verarbeitet und in Items aufgeteilt. Alle Items werden anschließend an eine Pipeline übergeben. In der Pipeline sind weitere Bearbeitungsschritte möglich, wie z.B. Aufarbeitung von Inhalten oder Veröffentlichung als Tweet.

Im Spider wird der Request auf die angegebene URL ausgeführt und die erfassten Daten schrittweise verarbeitet. Im ersten Schritt werden die erfassten IDs mit den bereits bearbeiteten IDs verglichen. Items mit IDs, die bereits bekannt sind, werden ausgefiltert. Auf diese Weise wird Persistenz erreicht und es wird verhindert, dass bereits bearbeitete Inhalte erneut veröffentlicht werden.
Anschließend werden alle nicht gefilterten Items an die Pipeline durchgereicht, siehe Code Listing 1:
Die Pipeline umfasst zwei Schritte.
  • Kurz-URL anlegen
    Für die Erzeugung der Kurz-URL bot sich TinyURL auch deswegen an, weil weder ein Account erforderlich ist, noch eine eigene Bibliothek installiert werden muss. Dies kann einfach mit den Modulen requests und json erledigt werden, siehe Code Listing 2:
  • Tweet posten
    Bevor der erfasste Inhalt getweetet werden kann, soll ein Hashtag hinzugefügt werden. Und natürlich muss die verkürzte URL zum eigentlichen Inhalt eingefügt werden. Schließlich muss noch kontrolliert werden, dass der gesamte Tweet nicht länger als 140 Zeichen lang ist. Sollte der Text die Grenze überschreiten, muss gekürzt werden, siehe Code Listing 3:

Hands on...

Zur Vorbereitung der Arbeitsumgebung gehört die Installation von Scrapy, Tweepy und weiteren Tools. Unter Linux also:
sudo pip install Scrapy
sudo pip install tweepy
sudo pip install shub
shub [6] installiert den Scrapinghub Command Line Client. Der Client stellt ein praktisches Tool für die Entwicklung und das Deployment des Projekts auf Scrapinghub dar.Zuerst muss ein Projektverzeichnis angelegt werden. Mit dem Kommando:
scrapy startproject myProject
erstellt Scrapy das komplette Projektverzeichnis inkl. aller zusätzlicher Dateien. Die Datei settings.py enthält alle Einstellungen auf Projektebene. Die Datei pipelines.py enthält die oben definierten Pipelines. Und in der Datei items.py werden die Attribute unseres Items festgelegt.

Im nächsten Schritt wird mit dem folgenden Kommando ein Scrapy-Spider angelegt und seine Start-URL definiert:
scrapy genspider mySpider start.url
Scrapy erstellt in diesem Fall die Datei mySpider.py, mit einem Grundgerüst eines Scrapy-Spiders. Die Datei lässt sich anschließend in einem Editor bearbeiten. Für das Grundgerüst unterstützt Scrapy Templates und Profile. Mehr dazu ist in der Hilfe jedes Befehls zu finden (scrapy genspider -h).

Ist der Spider ausreichend an die eigenen Bedürfnisse angepasst, lässt er sich mit diesem Kommando starten:
scrapy crawl mySpider
Bei diesem Aufruf findet die Ausgabe mit dem DEBUG Loglevel auf stdout statt. Damit lässt sich also prima überprüfen, was der Spider im Detail macht. Dort sieht man insbesondere auch die verwendeten Umgebungsvariablen, die angewendeten Einstellungen (bestehend aus globalen Scrapy-Einstellungen, Projekteinstellungen und Spider-Einstellungen), die Attribute unserer Items und die verwendeten Pipelines.

Bevor das Projekt nach Scrapinghub geladen werden kann, müssen evtl. vorhandene Abhängigkeiten zu Fremd-Modulen berücksichtigt werden. Scrapinghub erlaubt die Möglichkeit, externe Bibliotheken zu definieren [7]. Ich habe mit dem folgenden Kommando die Abhängigkeiten in meinem Projekt ermittelt:
pip freeze > requirements.txt
Anschließend verweise ich in der Datei scrapinghub.yml auf meine soeben erstellte Datei requirements.txt, siehe Code Listing 4: Jetzt sind alle Bedingungen erfüllt und das Projekt kann nach Scrapinghub geladen werden. Für das Deployment dient der Befehl:
shub deploy
Beim ersten Aufruf dieses Befehl wird ein Wizard gestartet, mit dessen Hilfe die benötigen Einstellungen für den Upload beu Scrapinghub vorgenommen werden. Nach erfolgtem Deployment sieht eine typische Ausgabe so aus:
Packing version 1.0
Deploying to Scrapy Cloud project "myProjectId"
{"status": "ok", "version": "1.0", "spiders": 1, "project": myProjectId}
Run your spiders at: https://app.scrapinghub.com/p/myProjectId/
Da das Deployment erfolgreich war, folgt jetzt der große Moment, in dem der Spider das erste Mal gestartet wird. Der Spider kann entweder über die Weboberfläche von Scrapinghub gestartet werden, oder wieder über den Command Line Client. Das Kommando dafür ist:
shub schedule mySpider
Der Aufruf wird typischerweise quittiert durch die folgende Ausgabe, die Hinweise auf das Logging enthält:
Spider mySpider scheduled, job ID: myProjectId/1/1
Watch the log on the command line:
    shub log -f 1/1
or print items as they are being scraped:
    shub items -f 1/1
or watch it running in Scrapinghub's web interface:
    https://app.scrapinghub.com/p/myProjectId/job/1/1

Tipp: Persistenz zwischen Läufen eines Spiders

Häufig wird in der Scrapinghub Dokumentation und in Foren zum Thema Persistenz auf das Addon DeltaFetch [8] verwiesen. Dieses Addon ist dazu geeignet, das Erfassen von URLs zu vermeiden, die bereits früher analysiert wurden. Ist der eigene Spider also so angelegt, dass die zu erfassenden URLs aus der Start-URL gescrapt werden, dann ist DeltaFethc in der Lage zu unterscheiden, ob die eingelesenen URLs in einem vorigen Lauf schon ausgelesen wurden. In diesem Fall werden diese URLs nicht erneut bearbeitet.
Im aktuellen Projekt werden allerdings keine URLs erfasst, sondern IDs die sich in Div-Tags befinden und die keine eigene URL besitzen. Deshalb war es für die Persistenz erforderlich, eine eigene Lösung zu entwickeln. Als Basis dient das Addon DotScrapy [9], das die Funktion data_path() zur Verfügung stellt. Umgesetzt wurden die Funktionen setLastRunId() und getLastRunId(), siehe Code Listing 1.

Fazit

Mit der vorgestellten Lösung lassen sich Webinhalte von schwer zugänglichen Webseiten auf einfache, günstige und schnelle Weise einem großen Publikum zur Verfügung stellen. Die Lösung ist vollständig cloudbasiert, erfordert also keinerlei eigene Administration. Durch die Screen Scraping Technik müssen weder bestehende Prozesse, noch bestehende Systeme angepasst werden.

Links

[1] Scrapy
[2] Scrapinghub
[3] Twitter
[4] Tweepy
[5] TinyURL
[6] Scrapinghub Command Line Client
[7] Dependencies and External Libraries in Scrapinghub
[8] Scrapinghub Addon DeltaFetch
[9] Scrapinghub Addon DotScrapy

Donnerstag, 13. Oktober 2016

Grails, good to know: Working with the Build Test Data Plugin

There are several choices for creating test data when working with Grails. Beside the manual creation of domain objects it is possible to use one of the existing plugins. Before Grails 3.0 one of the good choices was the Fixture Plugin for specifying domain test data. This plugin is not ported to newer Grails versions and therefore no longer an option. Another good choice is the Build Test Data Plugin which is in the focus of this post.

After referencing the plugin via the build.gradle file, new domain objects can be created easily by calling the build() method on a domain class. The following listing demonstrates the usage by creating two new entities of the type project. All required attributes of the entities are populated automatically or can be alternatively specified explicitly as shown for the second entity.

The documentation provides more information on the general usage of the plugin. The following sections provide two best practices on using some less known features of the Build Test Data Plugin.

Testing REST APIs


Nowadays many Grails applications provide REST APIs that have also run through the automatic test cycles. Assuming in our case that an existing project from an external system should be imported via REST, it is necessary for a test to construct a post request that contains all necessary attributes and especially an ID. When calling the standard build() method on the project entity and then converting it to JSON, the object will already be saved in the database before sending it via REST to the application. This can be avoided by calling the buildWithoutSave() method instead of the build() method. An example for the usage is shown in the following code snippet:

Creation of custom identities


The ID of the entity is available after creating a new entity via the build() method, when using the standard settings for domain classes. Depending on the requirements it might be necessary to use custom IDs for the database that are assigned manually. This can be specified in the mapping block of a domain class, as shown below:
To avoid the manual assignment of IDs in test cases it is possible to enhance the TestDataConfig file. This file of the BuildTestData Plugin can be used to define static or dynamic values for the attributes of entities when creating test data. In our case we assume that UUIDs are assigned as IDs to the domain classes. We want to make sure that an artificial ascending UUID is assigned to every created entity. This can be done by defining a closure that generates the assignment code of an ID for each domain class which is shown in the following listing.


A simple test for verifying the creation of project entities and its correct UUID assignment can look like shown below.

Mittwoch, 5. Oktober 2016

Migration von Grails 1.3.7 nach 3.1.9 - Testing - Teil 3

Dies ist der dritte Beitrag über die Migration einer Grails Applikation. Im ersten Teil habe ich mich mit allgemeinen Schwierigkeiten und Lösungen befasst und im Zweiten Teil eine selbstentwickelte Lösung zum Erstellen von Testdaten vorgestellt. In diesem Beitrag werde ich speziell die Tests beleuchten.

Testarten

In der existierenden Applikation gibt es eine übersichtliche Anzahl von Unit-Tests. Der größte Teil der Integrationstests besteht aus Controller- Integrationstests und nur wenigen Service- Integrationstests. Als funktionale Tests wurden historisch bedingt die Frameworks Geb und Webtest eingesetzt.
In früheren Versionen konnte das Testing Framework Spock über ein Plugin eingebunden werden, während es in neueren Versionen direkt in Grails integriert ist. Auch Geb muss nicht mehr als Plugin eingebunden werden sondern wird direkt unterstützt. Webtest ist ein Plugin das zum automatischen Testen von Web-Applikationen implementiert wurde, jedoch nicht mehr weiter entwickelt wird und deshalb nicht mehr verwendet werden kann.

Unit Tests

Da schon in der ursprünglichen Version das Test Framework Spock verwendet wurde, konnten die Unit Tests ohne große Schwierigkeiten migriert werden. Hauptsächlich sind Funktionsaufrufe überflüssig, die durch Annotationen ersetzt wurden.

Integrationstests

Es wird empfohlen Controller möglichst als Unit Tests zu realisieren und Services mit Integrationstests zu testen. Sämtliche Versuche, die bestehenden Controller-Integrationstests zu migrieren, blieben erfolglos. Man kann ein Objekt eines Controllers erstellen, jedoch war es nicht möglich, diesen mit allen Abhängigkeiten zu initialisieren. Schwierigkeiten hierbei waren die Hibernate-Session und HTTP-Sessions, die nicht korrekt funktionierten, so dass bspw. ein Service keine Daten der Datenbank abfragen oder die HTTP- Session bearbeiten konnte.
Wir haben uns dazu entschlossen, den Empfehlungen zu folgen und die Tests zu ändern. Das bedeutet für die Controller neue Unit Tests zu schreiben und für die Services Integrationstests. Ich glaube, dass durch diesen Schritt eine höhere Robustheit gegenüber kommenden neueren Versionen geschaffen wird, da jetzt ausschließlich native Bordmittel von Grails verwendet werden.

Beim Migrieren der Service-Integrationstests macht sich vor allem Grails' Umstellung auf Spring- Boot bemerkbar, das seit der Version 3.0 verwendet wird [1]. Denn ab jetzt wird die setupSpec Funktion von Spock, der ersten Testklasse, vor dem Start der Applikation ausgeführt, was deren Nutzen stark einschränkt. Beispielsweise ist das Anlegen oder Laden von Daten aus der Datenbank, die zum Testen verwendet werden sollen, nicht mehr möglich, denn sämtliche Hibernate- Funktionen sind nicht verfügbar. Weiter wird der where Block, der in Spock verwendet werden kann, vor dem Ausführen der setup- Methode ausgewertet. Somit sollten dort statische Werte verwendet werden, wie String, int oder boolean. Es ist möglich, eine ähnliche Funktionsweise wie die setupSpec Funktion nachzubilden.
Dazu wird eine statische boolean Variable initialized = false angelegt. In der setup-Funktion wird zusätzlich zu dem Code, der vor jeder Testmethode ausgeführt werden soll, eine if- Condition verwendet um Code zu kapseln, der nur ein einziges mal zu Beginn ausgewertet werden soll. Hierzu wird mit !initialized überprüft, ob dieser Teil bereits einmal durchlaufen wurde.  Am Ende der if- condition wird die Variable initialized auf true gesetzt und somit wird dieser Codeteil nicht noch einmal ausgeführt. Das nachfolgende Codebeispiel veranschaulicht das Prinzip.


Funktionale Tests

Da in der ursprünglichen Version ebenfalls schon Geb verwendet wurde, können diese Tests ohne größere Anstrengungen migriert werden. Auch hier sind nur vereinzelt kleinere Änderungen durchzuführen. Zum Beispiel müssen alle Testklassen mit der Annotation @Integration markiert werden, oder der contextPath muss bei der Url Definition der pages erweitert werden, da Grails diesen nicht mehr standardmäßig auf den Namen der Applikation setzt [2].
Das dem Grails Webtest Plugin zu Grunde liegende Projekt Canoo Webtest wird nicht mehr entwickelt und somit ist auch das Grails Plugin nicht mehr aktuell und funktionsfähig. [3] [4] Es gibt somit keine Alternative außer der Neu-Implementierung dieser Tests mit Geb. Ich habe eine - wenn auch sehr umständliche - Möglichkeit gefunden, die alten Webtests gegen die migrierte Applikation auszuführen. Der Vorteil hierbei ist, dass die neue Applikationen während der Migration auf die richtige Funktionalität geprüft werden kann. Somit hat man die Gewissheit, dass sie wie gewünscht funktioniert, auch wenn noch keine umfangreichen neu implementierten Tests vorhanden sind.
Dazu lässt sich in der Konfigurationsdatei webtest.properties mit den folgenden drei Zeilen einstellen, welcher Server getestet werden soll. Da dadurch die alte Applikation gestartet wird, muss darauf geachtet werden, dass sie mit einem anderen Port wie die Neue startet, da es sonst zu Fehlern kommt.

Fazit

Die Migration der Tests ist in unserem Fall der größte Teil Umstellung auf Grails 3.1.9. Da es in früheren Versionen noch mehrere Test-Möglichkeiten gab und auch Grails sich stark weiter entwickelt hat, ist es leider nicht immer möglich, alte Tests zu migrieren - ein großer Teil der Tests muss komplett neu geschrieben werden.
Ich hoffe ich kann mit dieser Blog-Artikel-Reihe über die Migration einer großen Grails Applikation einigen Interessierten einige Arbeit ersparen, indem sie auf die aufgezeigten Lösungen zurückgreifen.

Links

Dienstag, 27. September 2016

Migration von Grails 1.3.7 nach 3.1.9 - Erstellen von Testdaten - Teil 2

Wie schon im ersten Teil dieser Reihe beschrieben, stand ich während der Migration einer Applikation der Version 1.3.7 auf 3.1.9 vor dem Problem, dass das Plugin Fixtures, welches zum Erstellen und Einspielen von Testdaten verwendet wurde, nicht mehr weiterentwickelt wird. Hier will ich nun darstellen, welche Anforderungen wir an eine neue Lösung hatten, welche Möglichkeiten sich boten und was schließlich dazu führte, eine eigene Lösung zu implementieren. Abschließend werde ich das Prinzip meiner Lösung näher erläutern und Codebeispiele geben.

Anforderungen

Eine besondere Funktionalität des Fixtures-Plugins ist die Möglichkeit, den einzelnen Testdaten-Instanzen einen eindeutigen Namen zu geben und diese in Test Cases zu referenzieren. Da Testdaten vorhanden waren, die natürlich wieder verwendet werden sollten, ist es zusätzlich von Vorteil, wenn die Daten möglichst ähnlich dargestellt werden. Damit ist es möglich mit Hilfe der Suchen und Ersetzen Funktion der IDE wiederkehrende Muster zu finden und diese an das neue Format anzupassen. Eine weitere Anforderung war die Robustheit gegenüber Änderungen. Zum Beispiel sollte es keine großen Anpassung der Fixtures nach sich ziehen, wenn einem Attribut der Constraint bindable:false hinzugefügt wird. Diese Änderung würde bei der Erstellung einer Instanz erzwingen, dass dieses Attribut nicht innerhalb einer Map übergeben wird, sondern einzeln gesetzt werden muss.

Alternativen

Das Grails-Plugin Build-Test-Data wurde für die Erstellung von Testdaten implementiert. Es analysiert automatisch Constraints einer Domainklasse und erstellt valide Instanzen. Möglich wäre es, die schon vorhandenen Daten zu verwenden und daraus Instanzen zu erstellen - jedoch nicht, ohne eine eigene Erweiterung die Daten zu speichern und wieder zu verwenden.

Da ohne eine eigene Implementierung keine Möglichkeit gegeben war, kam die Idee auf, die einzelnen Instanzen manuell per Konstruktor-Aufruf zu erstellen. Hierzu müsste jedoch die Syntax der Test-Daten stark geändert werden. Auch könnte es zu Problemen führen, wenn es Änderungen an den Constraints gibt. Wird, wie oben beschrieben, zu einem Attribut der Constraint bindable:false hinzugefügt, müssten alle Instanzen der Domainklasse angepasst werden.

Da keine der Lösungen zufriedenstellend verwendet werden konnte, habe ich mich dazu entschlossen, eine eigene Lösung zu implementieren, um Test-Daten zu erstellen. Mit der eigenen Implementierung ist es wieder möglich, die Testdaten in Test Cases zu referenzieren.

Implementierung der eigenen Logik

Es soll die Möglichkeit geboten werden, Daten zu definieren, die automatisch gespeichert werden. Für komplexere Beziehungen soll es möglich sein, separate Daten zu beschreiben, die zunächst erstellt und nicht explizit gespeichert werden sollen, da diese von anderen Daten abhängen und dort ein kaskadierendes Speichern notwendig ist. Abschließend kann eine Funktion implementiert sein, in der beliebige Operationen für Modifikationen ausführbar sind.

Data-Klasse

Da alle Klassen einem bestimmten Aufbau folgen müssen, um eine korrekte Funktionsweise zu gewährleisten, habe ich eine abstrakte Oberklasse implementiert, von der geerbt wird. Diese besitzt folgende leere Funktionen, die dazu verwendet werden um zum einen die Daten auszulesen und zum andern ggf. spezielle Beziehungen herzustellen:
  • getPreData: Diese Methode muss überschrieben werden und eine Map mit Daten zurückgeben.
  • getData: Diese Methode muss ebenso eine Map der Daten zurück geben.
  • post: Diese Methode kann überschrieben werden.
Beide get Methoden müssen eine Map zurückgeben, in der die Daten enthalten sind. Als Schlüssel wird jeweils der Name verwendet, unter dem die Testdaten Instanz referenziert werden kann, und als Wert eine Map, die die Attribute und deren Werte enthält. Zusätzlich muss der Schlüssel domainName enthalten sein, der die Domainklasse definiert. Da der Aufbau der neuen Daten ähnlich dem der Fixtures ist lassen sich diese schnell passend migrieren. Wie nachfolgend zu sehen müssen vor allem die Klassen sowie Methoden-Deklarationen erweitert werden.


Sollen Beziehungen zwischen Instanzen hergestellt werden, realisiert man diese mit Closures. Dabei wird, wie oben zu sehen eine Referenz auf die gewünschte Instanz zurückgeben. Da die Closure erst beim Erstellen der Daten ausgewertet wird, muss die Reihenfolge beachtet werden, in der man Daten erstellt. Bei Beziehungen, die mit belongsTo markiert sind, greift ein kaskadierendes Speichern, das die abhängige Instanz mitspeichert. Hierfür wurde die Methode getPre geschaffen, die es ermöglicht, Daten zu erstellen, die nicht explizit gespeichert werden, aber nach dem Erstellen der Hauptdaten auf Persistenz überprüft werden. In der Methode post kann man beliebige Operationen ausführen. In der migrierten Applikation sind in wenigen Domainklassen Hilfsfunktionen implementiert um Beziehungen zwischen Instanzen herzustellen. Dabei wird nicht nur die Referenz gespeichert sondern auch Attributwerte an verschiedensten Stellen in der Datenbank geändert. Es bietet sich an dies, wie in dem nachfolgenden Codeausschnitt zu sehen, in der post Methode zu verwenden.


DataLoader-Klasse

DataLoader ist eine abstrakte Klasse, die die Map data enthält, in der alle erstellten Instanzen gespeichert werden. Mit der Funktion loadDataClass ist es möglich, alle Daten, die in der übergebenen Klasse definiert sind, zu erstellen und speichern. Tritt ein Fehler in einer der Daten Instanzen auf, wird eine Exception geworfen. Zuerst wird ein Objekt der übergebenen Klasse erstellt und die preData sowie data ausgelesen. Sind preData vorhanden, werden diese vor den Hauptdaten erstellt, anschließend gespeichert und die preData auf Persistenz geprüft. Zum Schluss wird die Funktion post ausgeführt. Im folgenden Sequenzdiagramm ist der Ablauf bildlich darstellt:



Eine Exception wird erzeugt, wenn
  1. ein Name zur Referenzierung der Daten doppelt verwendet wird
  2. keine Beziehung (null) hergestellt werden kann
  3. das Speichern einer Instanz nicht möglich ist
  4. ein preData-Aufruf nicht gespeichert wurde
Der folgende Code- Block enthält einen ersten Entwurf der Logik, wie das Erstellen der Daten realisiert werden kann.


Fazit

Diesd Implementierung funktioniert in dem migrierten Projekt sehr gut, wenngleich noch einige Verbesserungen möglich sind. Zum einen könnte die Performance verbessert werden und zum anderen könnten die Implementierung dahingehend verbessert werden, dass eine Fehlverwendung vermieden wird.


Freitag, 23. September 2016

SVN und Mac OS X und Umlaute

Hat man unter Mac OS X schon einmal ein Subversion Repository ausgecheckt, das Dateien mit Umlauten im Namen enthält kennt man wahrscheinlich das Problem: Es werden scheinbar ohne Grund Dateien als geändert und als unversioniert angezeigt. Es gibt auch erst einmal keine offensichtliche Lösung für das Problem.

Das Problem

Mac OS X speichert Umlaute in Dateinamen als sogenannte precomposed characters (auf Deutsch etwa: "zusammengesetzte Buchstaben"). Das bedeutet, dass ein "ü" nicht als einzelner Buchstabe gespeichert wird, sondern als Zusammensetzung aus einem "u" und dem Zeichen für zwei Punkte über dem Buchstabe ("¨"). Das ist an sich völlig in Ordnung und so vorgesehen. Allerdings gibt es Programme (wie Subversion), die beim Vergleich von Dateinamen diesen Umstand nicht berücksichtigen. Für Subversion sind das Zeichen "ü" und das zusammengesetzte Zeichen "ü" aus zwei Zeichen unterschiedliche Buchstaben.

Fügt man mit Windows eine Datei mit Umlauten in ein Subversion Repository hinzu und checked dieses auf OS X aus, sieht das Ergebnis von svn status so aus: Man kann die Dateien weder commiten noch reverten. Der Zustand lässt sich nicht ändern. Das Problem ist im Subversion Bugtracker seit 2005 gemeldet. Es existiert auch eine Seite im Subversion-Wiki zu diesem Thema. Nur leider ist bisher noch keine Lösung im offizielle Subversion-Code gelandet (Zum Zeitpunkt der Erstellung diese Artikels ist 1.9.4 die aktuelle Version von Subversion).

Die Lösung

An der Fehlermeldung im Subversion-Bugtracker ist ein Patch angehängt, der das Problem behebt. Mit dem Paketmanager Homebrew kann dieser relativ einfach in Subversion eingebaut werden. Ein Nebeneffekt ist, dass man ab dann eine relativ aktuelle Version von Subversion verwendet.

Um den Patch einspielen zu können muss man natürlich erst einmal Homebrew installieren. Wie das geht steht auf der oben verlinkten Seite. Mit den folgenden Schritten kann man Subversion patchen:
  1. Im Terminal mit brew edit subversion die Subversion-Formel zum anpassen öffnen
  2. Unterhalb von patch :DATA muss die URL und der Hash des Patches eingetragen werden. Standardmäßig wird unter OS X der Editor VIM verwendet. Mit der Taste i kann man in den Eingabemodus wechseln. Die Datei sieht an dieser Stelle dann so aus:
  3. Die Datei speichern und schließen. In VIM drückt man zuerst ESC um den Eingabemodus zu verlassen und gibt dann :x ein und bestätigt mit Enter
  4. Speichern und Subversion bauen mit brew install --build-from-source subversion
  5. Terminal schließen und neu öffnen und mit svn --version überprüfen ob Version 1.9.4 oder neuer läuft

Update von Subversion

Mit Homebrew kann man im Terminal mit dem Befehl brew update und dann brew upgrade alle installierten Programme aktualisieren.

Bei brew update öffnet sich bedingt durch die obige Anpassung manchmal der Standardeditor mit einer Merge-Nachtricht. Es muss dann einfach gespeichert und der Editor beendet werden, damit Homebrew das Update beendet. Es handelt sich hier um einen normalen Git-Merge.

Wird eine neue Version von Subversion installiert (z.B. durch brew upgrade), wird automatisch die vorkompilierte Version verwendet. Diese muss dann durch eine selbstkompilierte Version ersetzt werden: brew reinstall --build-from-source subversion. So wird automatisch wieder der Patch eingespielt.

Nun kann man ohne Probleme mit Subversion-Repsitories arbeiten, die Datei mit Umlauten enthalten.

IntelliJ

IntelliJ kann glücklicherweise so eingestellt werden, dass die neue gepatche Version von Subversion verwendet wird. Dazu muss man in den Einstellungen auf die Homebrew-Version von Subversion wechseln (/usr/local/bin/svn):
Jetzt sollte auch IntelliJ mit Umlauten in Repositories umgehen können.

Subversion GUI Tools

Bei fast allen Subversion GUI Tools (z.B. Versions, Cornerstone oder SmartSVN) ist es leider so, dass diese eingebaute Subversion-Clients verwenden. Diese sind in der Regel leider nicht gepatcht (siehe z.B. den FAQ von Cornerstone) und so zeigen diese Programme auch immer Dateien als geändert an, obwohl sich dort nichts geändert hat.