Hier 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 Entwickleransicht 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 ermöglichen, 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 Applikationsexport enthalten ist.
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 aufzubauen.
Die Struktur des Applikationsexports
Im folgenden Listing findet sich die Struktur des Exports einer kleinen Anwendung mit Beschreibung 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 Para-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 Applikationssprache und die Versionsangabe zur Sicherstellung der Kompatibilität.
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:
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.
Abb. 6: Beispiel: Definition einer Reportregion (gekürzt)
Jede Spalte des Reports wird mit einem eigenen Aufruf der Prozedur (create_report_columns) definiert:
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 Ausrichtung (p_column_alignment und p_heading_alignment), und Art der Darstellung (p_display_as), in diesem 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:
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:
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 Umgebungen, sieht ein einfacher Textvergleich mit einem Diff-Tool am Anfang etwa so aus:
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:
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_Lines{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:
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:
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:
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:
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):
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):
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
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ückgabe-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_*).
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‘):
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 erstellt, dass sie besonders gut zu vergleichen sein sollten. Daher kommt es manchmal zu Effekten wie dem, das längerer SQL- und PL/SQL-Text unterschiedlich umgebrochen wird, wie in diesem Beispiel
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 unterschiedlichen 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 manchen 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.