App Entwicklung für Atlassian Jira Server mit Active Objects

Dockerisierte Integration Tests mit Testdaten gegen echte Datenbanken

Comsysto Reply
Feb 6, 2019 · 6 min read

Von Bernhard, Software Engineer @comsysto

Einleitung

Dieser BlogPost gibt weiterführende Informationen zu Active Objects — dem Objekt Relationalen Mapper (ORM) von Atlassian — und setzt Grundlagen in diesem Gebiet voraus.

Am besten folgendes Tutorial durchgehen, dort werden die Basics von ActiveObjects erklärt:

ActiveObjects — Kurzeinleitung

Hier meine eigene Pro/Contra Liste zu ActiveObjects:

PRO:

  • Isolation — App sieht nur eigene Tabellen — Keine Tabellen von anderen Apps
  • ORM — Object Relationales Mapping ala JPA/Hibernate in reduzierter Form.
  • Echte Datenbanken — Unabhängig von der eingesetzten Datenbank lassen sich Abfragen definieren
  • Streaming API — gut für große Datensätze (fast batch queries — read only objects)
  • INNER und LEFT Joins — Left und Inner Joins bequem möglich

CONTRA:

  • Keine Subselects — Muss auf API Ebene bspw. durch Stream API gelöst werden
  • Kein Compound Unique Index — Es kann nur eine Column Unique sein. Unique Index über mehrere Spalten nicht unterstützt.
  • Keine Möglichkeit Joins zu beeinflussen — nur zwei Möglichkeiten LEFT, INNER
  • Komische DB spezifische Eigenheiten — bspw. manuelles Column-Quoting für PostgreSQL nötig

Unterstützte Datenbanken bei JIRA sind Stand heute:

  • MySQL
  • PostgreSQL
  • Microsoft SQL Server
  • Oracle

Obwohl von der spezifischen Datenbankengine abstrahiert wird, so muss man dennoch auf deren Eigenheiten achten. Hierzu gehört bspw. Column-Quoting bei PostgreSQL und das Case-Sensitive-Like-Query Problem auf das wir später eingehen.

Im Folgenden werde ich kurze isolierte Kapitel zu Best Practices bereitstellen. Dabei sind diese Best Practices teilweise von mir erdacht und nach bisherigem Kenntnisstand so zu empfehlen.

BestPractice #1: Keep Tablenames short (max. 30)

So wird aus ‘LogEvent’ bspw. ‘AO_AEF786_LOG_EVENT’ was bereits 19 Zeichen sind. Sind die Tabellennamen zu lang, fährt die Anwendung nicht hoch und wirft Exceptions.

BestPractice #2: Sub-Selects mit WHERE IN (…)

Dabei ist es leider nicht möglich einen Array/Liste von Werten an ActiveObjects zu übergeben, sondern man muss selbst die ‘Fragezeichen’ erzeugen, die in ihrer Anzahl zu den WHERE Parametern passen müssen.

Daher ist folgendes Pattern sehr nützlich:

Es wird also sichergestellt, dass für jeden whereParam auch ein Fragezeichen erzeugt wird. Hardcodiert würde der select so aussehen:

Ist nicht schön, aber funktioniert.

BestPractice #3: CaseInsensitive WHERE LIKE

Daher wollen wir mit ‘SELECT WHERE FOO LIKE “%bar%”’ eine Suche in der Datenbank vornehmen. Vorbedingungen/Empfehlungen sind dabei:

  • @Index auf der Spalte
  • DatenTyp VARCHAR(255)

Das Problem ist dabei, dass die LIKE Queries bspw. bei PostgreSQL case-sensitive sind. Sprich eine Suche nach ‘Foo’ liefert andere Ergebnisse als ‘foo’.

Will man also eine case-insensitive Suche, dann muss man Lower-Case Shadow Spalten anlegen, mit lower-case Werten befüllen und auf diesen Spalten mit LIKE suchen. Die Entity mit Index und Shadow-Spalten sieht dabei bspw. so aus:

Speichern und Suchen sieht so aus:

Ist nicht schön, aber funktioniert.

BestPractice #4 — WHERE FOO IS NULL

BestPractice #5 — ORDER BY QUOTING

BestPractice #6 — String und Datentyp VARCHAR(255)

Will man aber längere Strings persistieren, dann muss man die Unlimited-Annotation auf dem Setter verwenden, kann dann aber keinen Index auf dieses Feld legen.

BestPractice #7 — Lazy vs. Eager

Will man bspw. pagination nutzen und es reicht einem ReadOnlyEntityProxys zu erhalten, sollte man auf die Streaming-API setzen.

BestPractice #8 — Joins

  • Variante 1: Left join via @OneToMany Annotationen.
  • Variante 2: Inner join via query.join() Abfrage.

Je nach Anwendungsfall mag es sinnvoll sein statt dem Left Join mal einen Inner Join zu verwenden. Ich habe bspw. in meiner App den Fall, dass ich alle Customfields haben will, die entsprechende Permissions haben. Hier ist ein Inner Join ideal, da ich wirklich nur die Customfields bekomme, die auch Permissions haben. Bei einem Left Join müsste ich in Memory noch die Customfields herausfiltern, die keine Permissions haben. Da ich die Pagination in die Datenbank verlagern will, ist das der Richtige Ansatz.

BestPractice #9 — Migration Tasks

Als Entwickler will ich sogenannte Migration Tasks laufen lassen, welche meine Datenbasis nach einem App-Update ändert. Bspw. Daten von Tabelle1 nach Tabelle2 schaufeln. In einer reinen Spring Data JPA Application würde ich evtl. einfach einen Service mittels @PostConstruct auf die Geschichte loslassen.

In JIRA ist aber das Problem, dass zum Zeitpunkt an dem ein PostConstruct ausgeführt wird die Persistenzschicht (ActiveObjects) noch nicht geladen ist. Es folgen NullPointerExceptions.

Daher ist mein Ansatz ein EventListener auf das ‘OnPluginEnabled Event’, denn sobald dieses Event getriggert wird, weiß ich, dass die Persistenzschicht bereit ist. Im Listener setze ich einen Cluster-Lock (nur für JIRA DataCenter) und starte die Migration Tasks.

Das sieht in etwa wie folgt aus.

BestPractice #10 — Dockerized Database Integration Tests

Im obigen Beispiel starten wir mit Docker eine PostgreSQL Datenbank und geben dem ‘atlas-unit-test’ Befehl die entsprechenden Parameter mit, um gegen diese Datenbank zu testen.

Das komplette Setup sieht man auf GitHub: https://github.com/comsysto/poc-dockerized-database-integration-tests-atlassian-plugin

Fazit

Originally published at comsystoreply.de.

comsystoreply

Innovation through insight.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store