Freitag, 6. April 2012

ABAP-Code pragmatisch entkoppeln

Vor über einer Dekade hat sich SAP von der Ein-System-Philosophie verabschiedet. Das brachte neue Herausforderungen in der Software-Logistik mit sich: Die Abhängigkeiten der Softwareteile müssen modelliert werden, was post-hoc ein schwieriges Unterfangen ist. Auf Kundenseite gibt es ähnliche Aufgaben, aber in kleinerem Masstab. Ich zeige in diesem Blog einen Weg, wie man sie pragmatisch lösen kann.

Bis zum Ende der 90er Jahre, als mit der HR-Entkopplung und dem ATP-Server die ersten Schritte hin zu einem neuen Paradigma unternommen wurden, waren die gesamten Unternehmensabläufe in einem einzigen System integriert. Ein R/3-System enthält Module für Produktionsplanung, Projektverwaltung, Personalwirtschaft, Vertrieb, Versand, Fakturierung, Controlling, Finanzbuchhaltung und vieles mehr. Das war zweifellos ein grosser Vorteil: Wenn beispielsweise die Auftragserfassung im selben System läuft wie das Controlling, können Kosten und Erlöse ohne Verzug verbucht werden. Auch Kunden, die nur mit einem bestimmten Modul produktiv gehen, haben all diese Module in ihrem Repository: die nicht benutzten Module laufen leer mit oder sind mit Softwareschaltern deaktiviert.

R/3 ist zwar ein System – es wird aber mit mehreren physischen Rechnern realisiert. Neben den Client-Rechnern gibt es einen Datenbankserver und eine beliebige Anzahl von Applikationsservern, auf denen die Anwendungslogik läuft - wodurch das System skalierbar wird.[1]

Im neuen Paradigma, das SAP etwa seit Beginn des Jahrtausends verfolgte, gibt es nicht mehr ein System, sondern viele. Es gibt ein CRM-System für die Kundensicht und ein SRM-System für die Lieferantensicht des Unternehmens, es gibt ein zentrales MDM-System für die Stammdaten, einen APO für die Planung und Optimierung der Beschaffung, eine Billing Engine zur Erstellung von Fakturen, eine Pricing Engine für die Preisfindung, ein PI-System, das die Verbindung all dieser Systeme untereinander und mit Drittsystemen gewährleistet, ein BI-System für die Analytik, u.v.a.m.

Einige dieser Engines können koexistieren, d.h. in derselben SAP-Instanz laufen, andere nicht. Ein BI-System zum Beispiel ist grundsätzlich so anders konfiguriert, dass es nicht mit einem ERP-System zusammen laufen kann.

Die verschiedenen Systeme müssen untereinander Informationen austauschen. Vor allem aber können die verschiedenen Systeme aus ganz unterschiedlichen Softwareschichten zusammengesetzt sein. Eine Datenbanktabelle, ein Datentyp, ein Programm, eine Klasse kann in einem System existieren, im anderen nicht. Beim Entwurf der Schnittstellen muss also darauf geachtet werden, dass sowohl Quell- als auch Zielsystem einer Nachricht die Datentypen der Schnittstelle kennen. Und auch für die Softwareentwicklung wird es schwieriger, wenn an einem Ort entwickelt, aber auf mehreren verschiedenen Systeme verteilt werden soll, die sich nicht nur durch einen Korrekturlevel unterscheiden, sondern in denen bestimmte Pakete gar nicht vorhanden sind.

Wenn man die Aufgabe hat, eine bestimmte Funktionalität in einem einzigen System zum Laufen zu bekommen, macht man sich wenig Gedanken um Software-Abhängigkeiten. Es geht ja nur um ein System: Was da ist, wird verwendet. Man fragt sich nicht, ob Teile der Entwicklungen auch in ein anderes System transportierbar wären, das zum Beispiel keine SAP_APPL-Schicht hat.

So vergehen die Jahre, und wechselnde Projekt-Teams reichern das eine System um immer neue kundeneigene Logik an. Programmierrichtlinien, Paketkonzepte, Namenskonventionen usw. helfen, wurden aber mit unterschiedlicher Disziplin eingehalten. Um sie nicht neu implementieren zu müssen, wurden Objekte, die schon existieren, grundsätzlich wiederverwendet. Das ist zwar gut und richtig, aber schafft Abhängigkeiten - die man lange nicht im Focus hatte. Entkopplungsfragen spielten keine Rolle.

Und irgendwann stellt man sich die Frage: Welchen Bestand von (kundeneigener) Software wollen wir auf jedem neu eingerichteten SAP-System immer zur Verfügung haben?

Für eine Modellierung der Abhängigkeiten mit Paketen ist es zu spät. Die Pakete enthalten entweder viel zu viele oder viel zu wenig Objekte. Der Aufwand, die Paketzuordnungen in den Objektkatalogeinträgen einer Dekade umzuhängen, wird von niemandem finanziert. Eine pragmatische Lösung ist gesucht.

Und es gibt eine: Transportstücklisten. Sie erlauben es, transportierbare Objekte orthogonal zu allen bestehenden Paketkonzepten zu einer Gruppe zusammenzufassen. Eine Transportstückliste ist ähnlich einem Transportauftrag, hat aber zwei wesentliche Unterschiede:

  1. Sie kann nicht freigegeben werden - sie hat kein Zielsystem und ist für immer im Status Änderbar. Sie kann aber - wie ein ganz normaler Auftrag auch - in einen Transportauftrag kopiert werden, dient also als Kopiervorlage für Transporte.

  2. Sie ist selbst ein Entwicklungsobjekt (Typ LIMU COMM), gehört also einem Paket an und kann in einen Auftrag aufgenommen und transportiert werden.



Transportstücklisten, die mit Transaktion SE01 gepflegt werden können, lösen das Entkopplungsproblem auf pragmatische Weise: Sie stellen ein dauerhaftes Gefäss dar, in das immer neue Objekte aufgenommen werden können und das so gepflegt werden kann wie andere Entwicklungsobjekte. Ich habe zum Beispiel für mein BSP-Framework eine Stückliste ZMGB_BASIS_BSP, in die ich bei Weiterentwicklung des Frameworks die neu hinzugekommenen Objekte - die aus ganz verschiedenen Paketen kommen - aufnehme:



Die Stückliste ZMGB_BASIS_BSP weist keine Abhängigkeiten zur Komponente SAP_APPL auf. Die einzigen Objekte aus SAP-Software, die verwendet werden, liegen in den Komponenten SAP_BASIS und SAP_ABA. Diese sind in praktisch allen SAP-Systemen vorhanden - in BW-Systemen ebenso wie in ERP, SRM, CRM oder PMR.

Wie kontrolliere ich diese Abhängigkeiten? Hierzu habe ich einen Report Z_DECO_TRAN geschrieben, der die Abhängigkeiten in der Objektliste eines Transportauftrags untersucht (der Link führt zum Quellcode, dazu ist im Programm nur noch ein PF-Status LIST mit den Funktionscodes BACK, CANC, EXIT, DETAIL und REFRESH anzulegen).

Hier ein Beispiel des Selektionsbildes:



Mit diesen Selektionen findet der Report beispielsweise im Transport (oder der Stückliste) enthaltene Datenelemente, die auf Domänen verweisen, die nicht in den Komponenten SAP_BASIS oder SAP_ABA enthalten sind. Oder Domänen, deren Entitätentabelle fehlt. Oder Datenelemente, deren Suchhilfe fehlt. Oder Programme, die Code aufrufen, der nicht im Transport und nicht in SAP_ABA und SAP_BASIS enhtalten ist.

Nach Hinzunahme neuer Objekte in die Stückliste beseitige ich durch iterativen Aufruf von Z_DECO_TRAN alle Abhängigkeiten, bis die Abhängigkeitsliste im Report leer wird.

Es gibt verschiedene Typen von Abhängigkeiten:

  • Fehlende Kundenobjekte. Wenn ich ein kundeneigenes Datenelement habe, das auf eine kundeneigene Domäne verweist, muss eben auch diese Domäne zum Auftrag dazugenommen werden.

  • Ungünstig gewählte Standardtypen. Typisches Beispiel: Beim Entwickeln suchten Sie einen Tabellentyp "Tabelle von Feldnamen" zum Basistyp FIELDNAME. Über den Verwendungsnachweis hatten Sie FIELDNAME_TAB gefunden und selbst verwendet. Was meinen Sie wohl, in welcher Komponente FIELDNAME_TAB liegt? Er gehört zur Komponente FI-GL-GL, Sachkonten!!! Das Problem ist, dass auch ein SAP-Anwendungsentwickler sich eben schnell einen Tabellentyp von Feldnamen anlegt, wenn er ihn braucht. Wenn Sie den wiederverwenden, haben Sie ein Problem. Die Lösung ist in diesem Fall: Einen Basistyp wie ALFIELDNAMES oder TTFIELDNAME zu verwenden.

  • Schlecht geschnittene Klassen. Wenn eine Klasse zu viele Aufgaben übernimmt, hat sie auch zu viele Abhängigkeiten. Ein eigenes Beispiel: Ich wollte die Klasse ZCL_PARAM als Model in einer BSP-Applikation einsetzen und hatte ihr daher das Interface ZIF_MODEL eingepflanzt. Dadurch wurde die Klasse mit dem BSP-Framework verkoppelt, was nicht immer erwünscht ist. Meine Lösung war, mit Komposition zu arbeiten: Die BSP-Anbindung durch Implementierung des Interfaces ZIF_MODEL habe ich nun in eine eigene Klasse ZCL_PARAM_MODEL ausgelagert, die eine Read-Only-Referenz go_param auf die ZCL_PARAM-Instanz enthält.



Ich beanspruche mit dem Report keine Vollständigkeit. Wenn nur 95% der statischen Abhängigkeiten gefunden werden, reicht mir das als Arbeitshilfe schon aus. Der Rest kann nur durch Quertransport in das gewünschte System ermittelt werden. Auch in dieser Transportphase sind dann weitere Iterationsschritte nötig.

Dazu kommen dynamische Abhängigekeiten - dynamisch aufgerufener Code, dynamisch erzeugte Strukturen usw. Dynamische Abhängigkeiten können prinzipiell nicht gefunden werden, sie werden erst durch Ausführung des Codes im Zielsystem offenbar. Ein weiterer Grund, warum automatische Tests, insbesondere Modultests so wichtig sind. Das blosse Durchlaufen des Codes deckt schon Fehler auf, die die Syntaxprüfung allein nicht finden kann.




[1] Es ist ein gefährlicher, aber leider mancherorts lebendiger Irrglaube, dass man alle Performanceprobleme durch Hinzufügen weiterer Applikations-Server lösen könnte. Programme, die nicht effizient mit den Ressourcen umgehen, werden schlechte Laufzeiten bringen – der Entwickler wird seinen Kopf auch nicht retten können, wenn man ihm hundert weitere Applikationsserver dazuschaltet.

Kommentare :

Guillaume Garcia hat gesagt…

Hi,
I really like your posts. May I ask to add a translation widget so as to reach a larger audience? (for instance Google Translation tool that can be added as a wwidget to your Blogger site).
Thans in advance and keep posting!

Best regards,
Guillaume

Guillaume Garcia hat gesagt…

Hi,
Actually, I built up a Yahoo pipes! for the same. Hope you don't mind:
http://pipes.yahoo.com/guillaumegarcia13/ruediger_plantiko_english

Best regards,
Guillaume

Rüdiger Plantiko hat gesagt…

Dear Guillaume,

thanks for the suggestion. I readily added the gadget. However, the "translation" merely can give a rough idea of what I meant. I simply have no time to perform a proper translation.

I considered writing future blogs in English from the beginning - at least those dealing with the art of programming - but this wouldn't serve my native-German readers who appreciate a German language blog...

Thank you in any case for your positive feedback on my blog.

And I have nothing against transmitting the content through pipes.yahoo.com - as long as nobody expects to get Oxford English from a machine ...

Best regards,
Rüdiger

Guillaume Garcia hat gesagt…
Dieser Kommentar wurde vom Autor entfernt.
Guillaume Garcia hat gesagt…

Hi,

Actually, the translation German to English is quite good! (I would not say the same for German to French... I am a native French-speaker).
http://translate.google.com/translate?hl=en&sl=de&tl=en&u=http%3A%2F%2Fruediger-plantiko.blogspot.com%2F2012%2F04%2Fabap-code-pragmatisch-entkoppeln.html&anno=2

I totally understand your approach as I am also blogging in my native language. ;)

Best regards,
Guillaume

Rüdiger Plantiko hat gesagt…

Guillaume,

thanks for putting my attention to your blog. I subsrcibed YABON.

Regards,
Rüdiger

Tobias Topyla hat gesagt…

Hallo Rüdiger,

an dieser Stelle nochmals Kudos für einen weiteren gelungenen Artikel! Dein Blog ist eine Schatzkiste, vielen Dank - ich hoffe es gibt hiervon ein Backup. (-:

Nehmen wir an, eine neue Lösung soll gebaut werden. Es bietet sich grundsätzlich eine Möglichkeit der strikten Entkopplung durch eine Prüfung der Einträge im Objektkatalog (Tabelle TADIR) vor dem Aufruf an. Sinnvollerweise hat die SAP einen dynamischen Methodenaufruf bereits mit der Einführung von ABAP Objects schon unter 46 vorgesehen:

http://help.sap.com/abapdocu_70/de/ABENNEWS-46-OBJECTS-DYNAMIC.htm

In diesem Zusammenhang ist es natürlich auch insbesondere z.B. beim Einsatz eines BAPI hilfreich auf den hierfür vorgesehenen Hilfsbaustein FUNCTION_EXISTS nicht zu verzichten. Beim RFC gegen eine SM59 DESTINATION kann hinter EXCEPTIONS der optionale Zusatz MESSAGE ebenfalls Auskunft über Verfügbarkeit geben.

Aus Sicht der Ressourcenschonung genügt es sicherlich aber schon sehr oft einfach die Laufzeitstruktur SY und das Feld SAPRL abzufragen. Glücklicherweise gibt die Tabelle CVERS bei Detailfragen Aufschluss darüber ob bestimmte Teile einer Business Logik en-bloc überhaupt aktiv geschaltet werden können (Stichwort SAP_APPL und dessen Versionsstand).

Beachtet man all dies bei der Entwicklung, dann ist es durchaus möglich final einen Transport eines Release auszuliefern, der im Prinzip "überall" und bei jedem meiner Kunden auf allen unterschiedlichsten Kisten zunächst ohne Zusatzaufwand direkt eingespielt werden kann und dann auch bei der ersten Demo mit hoher Wahrscheinlichkeit lauffähig ist.

Als Nachteil sehe ich hauptsächlich den Verlust der Möglichkeit bei der Fehleranalyse einen regulären Workbench Verwendungsnachweis durchzuführen. Leider ist dieser für dynamische Aufrufe ungeeignet da es sich wohl um ein rein statisches Text parsing handelt und es meines Wissens kein SAP Ersatzwerkzeug hierfür gibt. Als Lösung für dieses Problem kann eine eigene SE91 Nachrichtenklasse erzeugt werden die alle dyn. aufgerufenen Objekte listet. Durch Einhaltung von Namenskonventionen gemäß Suchhilfe SCTSOBJECT wäre es auch nicht nötig mehr als den tatsächlichen TADIR-OBJ_NAME einzutragen. In diesem Fall wäre nach jedem dyn. Aufruf ein Fragment für den Verwendungsnachweis einzufügen wie z.B.: IF 2 EQ 3. MESSAGE e005. ENDIF. Idealerweise verkürzt dargestellt durch Einsatz eines ABAP Makrobefehls.

Aus meiner Sicht muss "graceful degradation" gerade bei portablen Geschäftsanwendungen für den handelsüblichen ABAP Entwickler eine sehr hohe Priorität haben so, dass die von Dir beschriebenen Problemsituationen erst gar nicht auftreten.
Leider driftet Theorie und Praxis aber immer sehr weit auseinander und es werden durch faule Programmierer wohl möglich einige Millionen Arbeitsplätze geschaffen darunter auch meiner. (-:

Viele Grüße
Tobias Topyla

Rüdiger Plantiko hat gesagt…

Hallo Tobias,

danke für Deinen Kommentar!

Du prangerst die Faulheit Deiner Kollegen an. Nun ist aber Faulheit, wie wir seit Larry Wall wissen, eine der drei grössten Tugenden von Entwicklern (die anderen beiden sind Ungeduld und Hybris). Die Faulheit stand an der Wiege der genialsten Programme.

Ich weiss aber natürlich, was Du meinst. Es gibt eine wenig clevere Art von Faulheit, die das Ausmass der zu leistenden Arbeit in Wirklichkeit erhöht, statt es zu erniedrigen. Andererseits gibt es auch einen irregeleiteten Fleiss: wenn man mit grossem Grimm und Disziplin abwegige Ziele verfolgt. Das Kriterium dafür, seine Energie richtig einzusetzen, kann nur eines sein: Dient man dem Ziel, den produzierten Code änderbar zu halten.

Um eine Tabelle separat zu pflegen, in der ich - wie eine Maschine - alle verwendeten Funktionsbausteine aufliste, bin ich allerdings auch zu faul - und halte das für eine sinnvolle Art von Faulheit. So etwas kann die Maschine viel besser als ich, und die erzeugte Liste ist obendrein noch garantiert vollständig.

Der erwähnte Report Z_DECO_TRAN listet für alle verwendeten Objekte ja auch die Softwarekomponente auf, der sie angehören. Mit welchem Release das verwendete Objekt darin eingeführt wurde, ist m.E. schwer herauszufinden. So ist das Mindestrelease gemäss CVERS schwer abzufragen. Jedenfalls erhält man eine Liste der Abhängigkeiten. Man könnte einen zweiten Report schreiben, der diese Liste nimmt und die Existenz der in einer solchen Liste aufgeführten Objekte prüft. Den lässt man als erstes im Kundensystem laufen, in das man seine Software einspielen möchte.

Die Software, die auf allen möglichen SAP-Systemen laufen soll - mit oder ohne SAP_APPL, SAP_RETAIL u.ä. - entwerfe ich so, dass sie nur auf die Basiskomponenten SAP_APPL und SAP_BASIS zugreift. Die verschiedenen Komponentenstände sind für mich kein Thema - hier unterscheide ich mich wohl von einem SAP-Berater, der von Firma zu Firma weiterzieht und mit den verschiedensten Releaseständen konfrontiert ist. Als fest Angestellter hat man auf einer überschaubare Familie von SAP-Installationen zu arbeiten und kennt den kleinsten Nenner der Komponentenversionen.

Für den Vorschlag, BAPIs oder Methoden, deren Namen man statisch kennt, nach einem vorherigen Existenzcheck dynamisch aufzurufen, nur um den Syntaxfehler beim Import in ein System zu vermeiden, das diese Methode nicht kennt, kann ich mich nicht erwärmen (bzw. bei Funktionsbausteinen und externen Prozeduraufrufen: den Fehler der erweiterten Syntaxprüfung).

Erstens weil der dynamische Aufruf dafür nicht gedacht ist. Er ist gedacht für Aufrufe von Methoden, deren Name erst zur Laufzeit bekannt ist. Verwendet man ihn für eigentlich statische Aufrufe, verdunkelt man den Code und verschlechtert seine Lesbarkeit.

Zweitens, weil die Fehlermeldungen der Syntaxprüfung / erweiterten Syntaxprüfung ja gut und richtig sind. Es ist gut, wenn solche Fehler so früh wie möglich bemerkt und gemeldet werden ("crash early"). Und die Compilezeit ist jedenfalls früher als die Laufzeit.

Drittens vergibt man sich den Verwendungsnachweis. Das ist schlimm, denn den Verwendungsnachweis brauche ich beim Programmieren, vor allem beim Ändern von Code oft. Dieses Werkzeug sollte ich mir nicht mutwillig selbst kaputtmachen. Der Verwendungsnachweis kann nur statische, keine dynamischen Funktionsaufrufe berücksichtigen. Das liegt nicht daran, dass der Verwendungsnachweis etwa schlecht implementiert wäre, sondern es geht grundsätzlich nicht. Manche Funktionsnamen werden ja erst zur Laufzeit gebildet, z.B. durch String-Konkatenation aus verschiedenen Bestandteilen - oder es fliessen Benutzereingaben oder Informationen aus anderen Systemen ein.

Der Compiler macht übrigens keine Textsuche im Quelltext, wie Du mutmassest, sondern geht über Indextabellen wie CROSS, WBCROSSGT, WBCROSSI, die beim Aktivieren von Code in einem Hintergrundstep aktualisiert werden. Dabei wird der Code in der vom Compiler erzeugten Form zugrundegelegt.

Viele Grüsse,
Rüdiger

Rüdiger Plantiko hat gesagt…

Nachtrag: Die Ermittlung des Releasestands, zu dem ein Baustein in eine Komponente hineinkam, ist auch nicht schwierig zu ermitteln: TADIR-CRELEASE.

So könnte man eine Abwandlung des Z_DECO_TRAN bauen, die für alle verwendeten Softwarekomponenten den minimal erforderlichen Releasestand aufführt.

Somit reicht es, im neuen Zielsystem einmal "System->Status, Detail Komponentenversionen" aufzurufen und die dort aufgeführten Releasestände mit den erforderlichen zu vergleichen.

Das wäre die einfachste Lösung!