Was wäre, wenn man das Thema Customer-Exit-Kapselung noch einmal von Grund auf neu denkt? Customer-Exit-Variablen gibt es in SAP BW seit jeher, und in fast jedem System sieht ihre Implementierung ähnlich aus: eine zentrale Stelle, in der die Logik aller Variablen nebeneinander steht. In BW/4HANA ist diese Stelle der BAdI RSROA_VARIABLES_EXIT_BADI (und ganz früher war es das Include zum Funktionsbaustein EXIT_SAPLRRS0_001).

Was einen guten Exit auszeichnet

  • Kapselung. Jede fachliche Logik steht für sich. Wer die Berechnung des Vormonats ändern will, liest und ändert nur diese Logik, nicht eine Sammelimplementierung mit Hunderten Zeilen und einem CASE über alle Variablennamen.
  • Stabilität gegen Änderungen. Ein Fehler in einem Bereich darf nicht das gesamte System treffen. Ein Laufzeitfehler in einer einzelnen Variable darf nicht jede Query zum Absturz bringen, in der der Exit aufgerufen wird.
  • Auswechselbarkeit. Welche Variable welche Logik nutzt, muss sich ändern lassen, ohne Code anzufassen. Eine neue Variable mit bekannter Logik ist dann ein Eintrag, kein Transport mit Programmänderung.

Was es bisher gab

Schon für den alten CMOD-Exit gab es Ansätze, die über das Include hinausgingen: ein Funktionsbaustein je Variable, dazu eine Tabelle, die Variablennamen und Baustein verbindet. Der Exit selbst wird zum Verteiler, der den Baustein dynamisch aufruft. Das bringt Kapselung je Variable und entkoppelt den Transport. Die Logik blieb aber an die Variable gebunden: Zwei Variablen (V1 gibt den Vormonat zurück, V2 den Vor-Vormonat) mit nahezu identischem Code bedeuten zwei Bausteine.

Was dieser Ansatz anders macht

Das Framework implementiert den BAdI genau einmal, als schlanken Dispatcher. Alles Fachliche liegt daneben. Neben den „normalen“ Customer-Exit-Klassen bilden Logikklassen einen Teil der Klassen ab:

  • Logikklassen statt Variablenbausteine. Klassen sind nach Logik geschnitten, nicht nach Variable. Eine Logik wird einmal programmiert und von beliebig vielen Variablen genutzt:
    • Tag verschieben, etwa „heute minus 7 Tage“ als Startdatum einer Auswertung.
    • Periode verschieben, etwa der Vormonat zum eingegebenen Monat als Vergleichszeitraum.
    • Wert einer anderen Variable übernehmen, etwa das Enddatum eines eingegebenen Zeitraums als Stichtag.
    • Attribut aus Stammdaten lesen, etwa das Land zum eingegebenen Buchungskreis.
  • Parameter und Referenzvariablen im Customizing. Was die Logik konkret tun soll, steht in einer Tabelle: die Verschiebung, die Einheit, die Variable, deren Eingabe als Basis dient. Abhängigkeiten zwischen Variablen sind damit sichtbar, ohne Code zu lesen, und werden dem OLAP-Prozessor gemeldet.
  • Ein Kontextobjekt. Die Logik sieht nicht die rohen BAdI-Parameter, sondern einen Kontext, der Referenzvariable, Parameter und Tagesdatum liefert. Das macht jede Logik mit ABAP Unit testbar, ohne eine Query auszuführen.
  • Fehlerverhalten je Variable. Im Customizing steht, ob ein Fehler als Warnung, als Abbruch oder still behandelt wird. Ein Laufzeitfehler in einer Logik wird abgefangen; die übrigen Variablen laufen weiter.
  • Prüfungen als eigene Bausteine. Eingabeprüfungen nach dem Variablenbild sind eigene Klassen, die je Query oder für alle Queries zugeordnet werden, etwa „höchstens zwölf Monate“ oder „nicht in der Zukunft“.

Ein kurzes Beispiel

Zwei Variablen sollen aus einer eingegebenen Geschäftsperiode die Vorperiode und die Vor-Vorperiode ableiten. In einer Sammelimplementierung stehen dafür zwei Blöcke, die sich nur in einer Zahl unterscheiden:

CASE i_vnam.
  WHEN 'ZV_FISCPER_M1'.
    " Referenz lesen, Präfix der Geschäftsjahresvariante abtrennen,
    " eine Periode zurück, Jahreswechsel behandeln, Präfix wieder davor …
  WHEN 'ZV_FISCPER_M2'.
    " dasselbe noch einmal, mit zwei Perioden
ENDCASE.

Mit dem Framework gibt es eine Klasse für zwei Customer-Exit-Variablen, und so sind die zwei Einträge im Customizing:

Variable Logikklasse Referenzvariable Parameter Ergebnis bei Eingabe K42026001
ZV_FISCPER_M1 ZCL_BWV_L_PERIOD_OFFSET ZV_FISCPER_IN OFFSET=-1;IOBJ=FISCPER K42025012
ZV_FISCPER_M2 ZCL_BWV_L_PERIOD_OFFSET ZV_FISCPER_IN OFFSET=-2;IOBJ=FISCPER K42025011

Eine dritte Variable für die Vorjahresperiode ist lediglich ein weiterer Eintrag mit OFFSET=-12.

Ein zweites Beispiel

Dieselbe Idee mit Tagen. Drei Variablen sollen ein Datum liefern: die letzten sieben Tage und die letzten vierzehn Tage, jeweils ab heute gerechnet, und das eingegebene Datum ein Jahr zurück. Die ersten beiden brauchen keine Eingabe und laufen vor dem Variablenbild, die dritte liest die Eingabevariable und läuft danach. Alle drei nutzen dieselbe Logikklasse; der Unterschied steht in Referenzvariable und Parametern:

Variable Logikklasse Referenzvariable Parameter Step Ergebnis
ZV_CALDAY_M7 ZCL_BWV_L_DATE_OFFSET – OFFSET=-7;UNIT=D 1 heute minus 7 Tage
ZV_CALDAY_M14 ZCL_BWV_L_DATE_OFFSET – OFFSET=-14;UNIT=D 1 heute minus 14 Tage
ZV_CALDAY_VJ ZCL_BWV_L_DATE_OFFSET ZV_CALDAY_IN OFFSET=-1;UNIT=Y 2 Eingabedatum im Vorjahr

Dass der 29. Februar ein Jahr zurück auf den 28. Februar fällt, ist einmal in der Klasse gelöst und getestet. Keine der drei Variablen enthält dafür eine Zeile Code.

Ein drittes Beispiel

Nicht nur Zeit lässt sich ableiten. Ein Bericht soll nach dem Land und der Hauswährung des eingegebenen Buchungskreises filtern und zur eingegebenen Kostenstelle den Verantwortlichen anzeigen, ohne dass der Anwender das selbst eingibt. In der Sammelimplementierung stünde dafür ein SELECT auf die Stammdatentabelle je Variable, mit eigenem Lesen der Eingabe und eigener Fehlerbehandlung. Im Framework erledigt das eine Logikklasse, die zum Wert der Referenzvariable ein Attribut aus den Stammdaten liest. Welches Merkmal und welches Attribut, steht in den Parametern:

Variable Logikklasse Referenzvariable Parameter Eingabe Ergebnis
ZV_COUNTRY_CC ZCL_BWV_L_MD_ATTR ZV_COMP_CODE_IN IOBJ=0COMP_CODE;ATTR=0COUNTRY 1000 DE
ZV_CURRENCY_CC ZCL_BWV_L_MD_ATTR ZV_COMP_CODE_IN IOBJ=0COMP_CODE;ATTR=0CURRENCY 1000 EUR
ZV_RESP_CCTR ZCL_BWV_L_MD_ATTR ZV_COSTCENTER_IN IOBJ=0COSTCENTER;ATTR=0RESP_PERS 4711 Personalnummer des Verantwortlichen

Die Klasse leitet die Stammdatentabelle aus dem Namen des Merkmals ab und liest die aktive Version; dass die Kostenstelle an den Kostenrechnungskreis geklammert ist, ändert daran nichts. Fehlt zum eingegebenen Wert ein Stammdatensatz, meldet sie das als fachlichen Fehler, und das Customizing entscheidet, ob der Bericht mit Warnung weiterläuft oder abbricht. Ein weiteres Attribut, etwa die Region, ist wieder nur ein Eintrag.

Wie Dispatcher, Factory, Kontext und Customizing im Einzelnen zusammenspielen und wie eine Logikklasse aussieht, zeigt der zweite Artikel dieser Reihe: Technisches Konzept und Beispielimplementierung.