Versionsvergleich für APEX-Anwendungen

sabine_heimsathHier finden Sie die Beschreibung des Fachvortrages von Sabine Heimsath, gehalten im November 2013 im Rahmen der Deutschen Oracle Anwender Konferenz in Nürnberg:

Wer im professionellen Umfeld mit Apex arbeitet, kommt oft in die Situation,

  • dass andere Entwickler ebenfalls Änderungen in der Entwicklungsumgebung vornehmen
  • dass undokumentierte Änderungen in der Produktivumgebung vorgenommen wurden aber nicht in der Entwicklungsumgebung nachgezogen werden oder
  • dass beim Einchecken in die Versionsverwaltung ein Kommentar gepflegt werden soll, der die aktuellen Änderungen beschreibt.

In diesen Fällen kann man versuchen, sich mit bordeigenen Mitteln zu behelfen. Hier bieten sich zunächst

  • die Application History (Applikationsansicht -> Utilities -> Change History),
  • die Page History (Seitenansicht -> Utilities -> History) oder
  • der Applikationsvergleich (Application Builder -> Cross Application Reports -> Application Comparison) an.

Die Historien stoßen allerdings – auch wenn man alle verfügbaren Spalten einblendet – schnell an ihre Grenzen. Der Application Builder loggt Aktionen von Entwicklern einerseits sehr unspezifisch und andererseits auch dann, wenn eigentlich keine Änderung stattfand. So wird zum Beispiel das Springen von einem Tab zum nächsten in einer Report Definition auf jeden Fall als Änderung protokolliert, weil dabei gespeichert wird. Dadurch entstehen sehr viele irrelevante Einträge.
Die relevanten Einträge sind hingegen meistens nicht detailliert genug. Es gibt zum Beispiel keine Information dazu, welches Attribut einer Page/eines Buttons/eines Items geändert wurde, und somit gibt es auch keine Möglichkeit, den alten und den neuen Wert zu vergleichen. Der Applikationsvergleich setzt voraus, dass beide Versionen im gleichen Workspace installiert sind. Dies bietet sich innerhalb einer Entwicklungsumgebung durchaus an. Aber auch hier sind die angebotenen Informationen eher dürftig. Es werden viele falsch positive Unterschiede angezeigt, wie man schnell feststellt, wenn man versucht, vermeintliche Unterschiede aus dem Report in der Entwickler­ansicht des jeweiligen Objektes nachzuvollziehen.

Ein Lösungsansatz

Um herauszufinden, was sich tatsächlich geändert hat, bietet sich das Apex-Anwendungs-Exportfile an. Es ist leicht lesbar, wenn man PL/SQL beherrscht, und beinhaltet sämtliche Informationen, die die Apex-Applikation beschreiben. Die Objekte der Datenbankebene sind im Normalfall nicht enthalten. Man kann sie explizit als Supporting Objects exportieren, aber da sie häufig ohnehin separat versioniert werden, werden sie hier nicht betrachtet. Um den Code interpretieren zu können, muss man ein wenig „Übersetzungsarbeit“ leisten. Die Optionen, die bei der Deklaration im Application Builder angeboten werden (in der jeweils eingestellten Sprache), werden im Export mit englischen Konstantennamen ausgegeben. Die Typen der Report-Spalten werden damit z. B. zu:

Standard Report Column WITHOUT_MODIFICATION
Display as Text (based on LOV, does not save state) TEXT_FROM_LOV
Display as Text (saves state) DISPLAY_AND_SAVE
Display as Text (escape special characters, does not save state) ESCAPE_SC
Date Picker (Classic) DATE_POPUP
Date Picker DATE_PICKER
Text Field TEXT
Text Area TEXTAREA
Select List (static LOV) SELECT_LIST
Select List (named LOV) SELECT_LIST_FROM_LOV
Select List (query based LOV) SELECT_LIST_FROM_QUERY
… und weitere  

Abb. 1: Beispiel für Konstantennamen: Reportspalten-Typen

Die Apex-Exportfiles

Es gibt verschiedene Arten von Export-Files. Bei allen handelt es sich um PL/SQL-Skripte, die es er­mög­lichen, die jeweiligen Objekte (oder eine komplette Applikation) in einer anderen Umgebung neu anzulegen. Hier wird zunächst der Export einer gesamten Applikation behandelt (im Bild rot umrandet). Der Export einzelner Seiten beinhaltet keine zusätzliche Information, die nicht auch im Applikations­export enthalten ist.

exportfiles

Abb. 2: Verschiedene Exportmöglichkeiten im Apex Builder

Ein Exportfile beginnt mit Informationen zur Anwendung, zum Exportzeitpunkt und zum User. Dann folgt eine Statistik über die Bestandteile der Applikation, z. B. Anzahl der Pages, der Items, der Prozesse und weitere. Danach folgen Anweisungen zum Setzen von Umgebungsvariablen. In der Zielumgebung wird eine eventuell vorhandene Instanz der Applikation gelöscht. Dieser Teil kann in den meisten Fällen ignoriert werden. Interessant wird es etwa ab Zeile 150, denn hier wird damit begonnen, die Applikation neu auf­zu­bauen.

Die Struktur des Applikationsexports

Im folgenden „Listing“ findet sich die Struktur des Exports einer kleinen Anwendung mit Be­schrei­bung der einzelnen Aufrufe. Die Parameter werden weiter unten erläutert. Beim Analysieren der Datei sollte man wissen, dass eine Applikation häufig als ‚flow‘ referenziert wird, eine Seite als ‚page‘ oder ‚step‘ und eine Region auch als ‚plug‘ bezeichnet wird.

wwv_flow_api.create_flow Anlegen der Anwendung
wwv_flow_api.create_user_interface
wwv_flow_api.create_plugin_setting
wwv_flow_api.create_icon_bar_item Anlegen eines Elements im Menü rechts oben
wwv_flow_api.create_tab Anlegen des Standard-Tabs
wwv_flow_api.create_page Anlegen der ersten Seite
wwv_flow_api.create_page_plug Anlegen zweier Regionen
wwv_flow_api.create_page_plug
wwv_flow_api.create_page_button Anlegen zweier Buttons
wwv_flow_api.create_page_button
wwv_flow_api.create_page_branch Anlegen eines Branches
wwv_flow_api.create_page_item Anlegen eines Items
wwv_flow_api.create_page_process Anlegen eines Page Processes
wwv_flow_api.create_page
wwv_flow_api.create_flash_chart5 Anlegen eines Flash-Diagramms
wwv_flow_api.create_flash_chart5_series …mit einer Datenreihe
wwv_flow_api.create_page
wwv_flow_api.create_report_region Anlegen einer Reportregion
wwv_flow_api.create_report_columns … mit den zugehörigen Reportspalten
wwv_flow_api.create_report_columns
wwv_flow_api.create_report_columns
wwv_flow_api.create_page
wwv_flow_api.create_page_plug
wwv_flow_api.create_page_button
wwv_flow_api.create_page_da_event Anlegen eines Dynamic Action Events
wwv_flow_api.create_page_da_action … mit der zugehörigen Action

Abb. 3: Struktur einer Export-Datei Zunächst wird die Applikation mit der Prozedur wwv_flow_api.create_flow angelegt. Die Pa­ra-meter findet man im Application Builder unter Edit Application Properties in den Reitern Definition, Security und Globalization. Die Parameternamen sind ziemlich sprechend gewählt, so dass man sie den Elementen aus der GUI leicht zu ordnen kann. Das Beispiel unten zeigt dies für die Einstellungen Logging, Feedback, Primärsprache, Quelle der Applikations­sprache und die Versionsangabe zur Sicherstellung der Kompatibilität.

exportdatei

Abb. 4: Zuordnung Apex-Builder-Elemente zu Programmzeilen

Wenn die Applikation per create_flow angelegt wurde, können die Pages angelegt werden. Wie man sieht, heißen die Parameter fast genauso wie im Application Builder:

sql

Abb. 5: Definition einer Page

Auf dieser Seite befindet sich unter anderem eine Reportregion. Im gekürzten Beispiel unten erkennt man zunächst die Definition des zugrunde liegenden SQL-Statements in der Variable s, dann folgt das Anlegen des Reports mit Applikations-ID (p_flow_id), der Page-ID (p_page_id), dem Regionsnamen (p_region_name), dem Template, das hier über eine ID referenziert wird, und der Display-Sequence, die den Platz der Region in der Rendering-Reihenfolge bestimmt.

sqlreportregion

Abb. 6: Beispiel: Definition einer Reportregion (gekürzt)

Jede Spalte des Reports wird mit einem eigenen Aufruf der Prozedur (create_report_columns) definiert:

sqlregionreport2

Abb. 7: Beispiel: Definition einer Reportspalte

Neben den schon beschriebenen Parametern sieht man hier die Werte, die man im Application Builder in der Spaltendefinition zu sehen bekommt, z. B. die Spaltenüberschrift (p_column_heading), die Aus­rich­tung (p_column_alignment und p_heading_alignment), und Art der Darstellung (p_display_as), in die­sem Fall WITHOUT_MODIFICATION was der ‚Standard Report Column‘ in der GUI entspricht. Das Häkchen für ‚Show‘ wird in diesem Fall zu p_hidden_column=‘N‘.

Der Vergleich

Hat man zwei Export-Files ein und derselben Applikation, die zu unterschiedlichen Zeitpunkten exportiert wurden, ist der Vergleich recht einfach:

sqlregionreport3

Abb. 8: Beispiel: Änderungen, die an einem Page Item vorgenommen wurden

Hier sieht man, dass an dem Item mehrere Veränderungen vorgenommen wurden: Außer dem  Default Wert wurden die Definition der LOV und Text und Wert für den NULL-Wert geändert. Ein weiterer Fall:

sqlregionreport4

Abb. 9: Änderungen, die an einer Page vorgenommen wurden

Anscheinend wurde hier eine Änderung wieder rückgängig gemacht – oder der Nutzer hat zwischen den Tabs geblättert und dadurch das erneute Abspeichern der bestehenden Werte ausgelöst. Hat man zwei Export-Files einer Applikation vorliegen, zum Beispiel aus zwei verschiedenen Um­ge­bun­gen, sieht ein einfacher Textvergleich mit einem Diff-Tool am Anfang etwa so aus:

sqlregionreport5

Abb. 10: Textvergleich ohne Konfiguration: Screenshot aus Beyond Compare

Das Problem fällt sofort ins Auge: Allein durch den Export und den Import unter neuer Applikations-ID oder in einem anderen Workspace, verändern sich die IDs aller Objekte, so dass man fast nur rote Zeilen sieht. Mit einem Diff-Tool, das reguläre Ausdrücke beherrscht, kann man diese Zeilen ausblenden, um sich auf die wichtigen Unterschiede konzentrieren zu können. In diesem Fall wurde Beyond Compare 3.0 verwendet.

Konfiguration des Diff-Tools

Um in Beyond Compare die Markierung der „irrelevanten“ Zeilen zu unterdrücken, sind unter Menüpunkt
'Session'
 -> Untermenüpunkt 'Session Settings'   -> Reiter 'Importance'    -> Button 'Edit Grammar'
einige Einträge vorzunehmen. Im Reiter ‘Grammar’ sind standardmäßig schon einige Einträge für SQL-Dateien vorhanden (siehe unten). Die neu anzulegenden Einträge sind rot eingerahmt:

sqlregionreport6

Abb. 11: Beschreibung der nicht relevanten Zeilen mit regulären Ausdrücken

Hierfür klickt man den ‘New…’ Button, und legt dann nacheinander die folgenden Einträge an. Die Bezeichnungen sind frei wählbar. Ein einheitliches Präfix vereinfacht natürlich die Selektion.

Apex_ID_Line
\d{15,}\s?\+\s?wwv_flow_api.g_id_offset
Apex_Prompt_Line
prompt  .+ \d+
Apex_Upd_Line

\s{0,5}.p_last_upd_yyyymmddhh24miss => '\d+'

Diese Einträge dienen dazu, die Unterschiede zu definieren, die beim Vergleich als unwichtig eingestuft werden sollen. Nach dem Anlegen müssen im Reiter ‘Importance’ die Häkchen vor den neu angelegten Apex-Einträgen entfernt werden, damit Beyond Compare weiß, dass sie als unwichtig einzuordnen sind:

sqlregionreport7

Abb. 12: Deselektion der nicht relevanten Zeilen

Problematisch wird es, wenn auf einer Seite sehr viele Pages dazugekommen sind (oder gelöscht wurden); denn dann ist es für das Tool schwierig, die Dateien korrekt auszurichten. (Dieses Problem tritt nicht auf, wenn die Sourcen grundsätzlich mit dem ApexSplitter aufgeteilt werden.) Man kann Beyond Compare bei der Ausrichtung unterstützen, indem man bei der Definition der Grammatik bestimmte Zeilen gewichtet. Für Apex-Exporte bietet es sich zum Beispiel an, die Prompt-Zeile vor jeder Seitendefinition sehr stark zu gewichten, da die Seitennummern während der Lebenszeit einer Anwendung im Normalfall keinen großen Änderungen unterworfen sind. Diese Gewichtung kann auch im Reiter ‚Grammar‘, im unteren Teil ‚Line weights‘ über ‚New…‘ oder ‚Edit…‘ vorgenommen werden:

sqlregionreport8

Abb. 13: Ankerpunkte zum Ausrichten festlegen

Text matching:
^prompt  ...PAGE \d+:
Ganz wichtig: Nicht vergessen, im unteren Teil des Fensters ‚Session Settings‘ festzulegen, dass die Gültigkeit sich auf jede Session bezieht, damit man sich die Arbeit nicht mehrfach machen muss:

sqlregionreport9

Abb. 14: Text Compare – Session Settings dauerhaft verfügbar machen

Die Konfiguration des Diff-Tools ist damit beendet. Wenn nun die ‘unwichtigen’ Einträge mit dem Button  ausgeblendet werden, bekommen wir ein viel entspannteres Bild. Und plötzlich kann man die tatsächlichen Änderungen auf einen Blick erkennen, so wie im Beispiel unten:

sqlregionreport10

Abb. 15: Textvergleich ohne Konfiguration: Screenshot aus Beyond Compare

Was sieht man?

Beispiel 1:
Hier wurde ein Interaktiver Report angepasst und als Primary abgespeichert. Vergleicht man den vorherigen Export mit dem Export nach der Änderung, kann man erkennen, dass die Spalte ADDRESS offensichtlich ausgeblendet wurde, da sie auf der rechten Seite fehlt (die Variable rc1 wird als Parameter p_report_columns übergeben) und dass eine Sortierung angewendet wurde (p_sort_column_1 und p_sort_direction_1):

sqlregionreport11

Abb. 16: Interactive Report: Ausgeblendete Spalte und Sortierung

Nimmt man jetzt noch einen Filter hinzu, wird es richtig interessant, denn dann erscheint auf der rechten Seite ein neues Objekt, die Filterbedingung (worksheet_condition vom Typ FILTER):

sqlregionreport12

Abb. 17: Interactive Report: Filterbedingung

Man erkennt den Spaltennamen, auf den gefiltert wird (p_column_name =>’STATE’), den Operator (p_operator =>’contains’) und den Vergleichswert (p_expr =>’MO’,). Außerdem wird sogar die daraus generierte SQL-Bedingung angegeben
p_condition_sql =>'upper("STATE") like ''%''||upper(#APXWS_EXPR#)||''%'''
und man sieht, wie Apex die benutzerfreundliche Darstellung realisiert. Der Ausdruck
p_condition_display =>'#APXWS_COL_NAME# #APXWS_OP_NAME# ''MO''  ',
wird durch Substitution in der GUI zu
sqlregionreport13

Beispiel 2:
In diesem Beispiel wurden Änderungen an einer Select-Liste vorgenommen. Man sieht, dass das Item P6_CATEGORY wahrscheinlich verschoben wurde (kleinere Nummer in p_item_sequence), was aber erst im Zusammenhang mit den anderen Sequence-IDs verifiziert werden kann. Des Weiteren wurde ein NULL-Wert erlaubt (p_lov_display_null=> ‘YES’), und der Anzeige- und Rück­gabe-Wert für diesen definiert (p_lov_null_text und p_lov_null_value).
Eine Bedingung (p_display_when_type=>’CURRENT_PAGE_EQUALS_CONDITION’) sorgt dafür, dass das Item nur auf Seite 6 angezeigt wird (was bei einem Item auf Page 0 natürlich mehr Sinn ergeben würde). Im unteren Teil kann man sehen, dass zusätzlich noch Quick Picks angelegt wurden, und zwar zwei Stück jeweils mit Label und Value (p_quick_pick_label_* und p_quick_pick_value_*).

sqlregionreport14

Abb. 18: Diverse Änderungen an einem Page Item

Hier wurde der Typ eines Items von ‚Radio Group‘ auf ‚Select List‘ geändert. Außerdem wurde keine zentral definierte LOV verwendet (erkennbar an der ID), sondern eine statisch. (p_lov=> ‘STATIC2′):

sqlregionreport15

Abb. 19: Änderung der LOV-Definition an einem Page Item

Probleme und Grenzen

Nicht immer gelingt es Beyond Compare, die beiden Dateien passend auszurichten. Dann kann man dies manuell tun, indem man eine Zeile auf der linken Seite auswählt, [F7] drückt und dann mit der Maus die Zeile auf der rechten Seite anklickt, die mit der linken Zeile ausgerichtet werden soll.
Die Apex-Export-Files sind gut strukturiert, wurden aber natürlich nicht mit dem primären Ziel er­stellt, dass sie besonders gut zu vergleichen sein sollten. Daher kommt es manchmal zu Effekten wie dem, das längerer SQL- und PL/SQL-Text unter­schied­lich umgebrochen wird, wie in diesem Beispiel

sqlregionreport16

Abb. 20:Ungleicher Umbruch eines langen Statements

Als erfahrener Entwickler wird man das relativ leicht erkennen können, aber man kann auch Abhilfe schaffen, indem man nach Möglichkeit SQL in Views und PL/SQL in Packages auslagert, was auch andere Vorteile hat.
Der Vergleich von Attributen – zum Beispiel von Items – ist relativ zuverlässig.
Schwierig ist es festzustellen, ob sich eine Template-Zuordnung geändert hat, da das Template nicht über einen Namen, sondern über eine ID referenziert wird: p_plug_template=> 12319517529116625534+ wwv_flow_api.g_id_offset.
Vergleichen wir zwei Versionen einer Applikation, die mit der gleichen Applikations-ID aus dem gleichen Workspace exportiert wurden, sind die IDs der Objekte gleich, eine Änderung fällt also auf. (Wenn die entsprechenden Zeilen eingeblendet sind.) Handelt es sich allerdings um zwei Versionen mit unterschiedlichen Applikations-IDs oder aus unter­schiedlichen Workspaces, sind die IDs aller Objekte unterschiedlich. Somit sind „echte“ Änderungen einer Template-Zuordnung für uns nicht mehr erkennbar.

Fazit

Der Vergleich von Textdateien kann einem fast alle Unterschiede zwischen Applikationen zeigen. In man­chen Fällen muss man aber trotzdem im Application Builder nachsehen, was diese Unterschiede bedeuten. Dafür werden einem sehr genau die Stellen gezeigt, an denen man suchen muss, was ein großer Vorteil gegenüber den bisherigen Möglichkeiten innerhalb von Apex ist. Beyond Compare bietet durch die Regulären Ausdrücke viele Möglichkeiten, sich den Vergleich genauso zu konfigurieren, wie man ihn braucht.

Technisches
Die Beispiele stammen aus Apex 4.2.2 und 4.2.3. Bei dem verwendeten Diff-Tool handelt es sich um Beyond Compare 3.3.8.

In: APEXAuthor: Sabine Heimsath