Diesen Artikel habe ich ursprünglich im SAP Community Network veröffentlicht.

Wir kann ich die mit den Object Services (OS generierten Persistenzklassen mit Änderungsbelegschreibung (Change Documents, CDO) kombinieren?

Diese Frage wurde bereits mehrfach an verschiedenen Stellen gestellt, und bisher bin ich noch nicht auf eine Antwort gestoßen, die als Umsetzungsrichtlinie dienen könnte. Da wir die beiden Frameworks für ein Entwicklungsprojekt kombinieren müssen, habe ich beschlossen, eine kurze Machbarkeitsanalyse durchzuführen und einige Details und Fallstricke mit der Community zu teilen. Für diesen Artikel benötigen Sie ein grundlegendes Verständnis sowohl der Objektdienste als auch der Änderungsdokumente und ein recht solides Verständnis der grundlegenden Prinzipien der Objektorientierung.

Der erste wichtige Punkt ist, sich daran zu erinnern, dass Object Services und Änderungsdokumente auf unterschiedlichen Abstraktionsebenen arbeiten. Die von Object Services generierten persistenten Klassen arbeiten mit der technischen Darstellung der betreffenden Entitäten – beispielsweise dem Rechnungskopf oder einer einzelnen Position einer Rechnung. Die allgemeine Faustregel lautet „eine Tabelle, eine Klasse“. Bei Änderungsdokumenten ist das anders. Normalerweise finden Sie Änderungsdokumentobjekte für logische Objekte statt der technischen Darstellungen. Ein Änderungsbelegobjekt umfasst in der Regel mehrere Tabellendefinitionen, die zum Speichern des logischen Objekts benötigt werden. Beispielsweise finden Sie ein Änderungsbelegobjekt für eine Rechnung, das sowohl Änderungen am Rechnungskopf als auch an den entsprechenden Positionen aufzeichnet. In dieser Hinsicht ähneln Änderungsbelege Sperrobjekten. Es gibt bereits ein Dokument von Katan Patel, das einige der grundlegenden Aspekte dieses Ansatzes behandelt.

In diesem Artikel verwende ich ein grundlegendes Modell einiger beliebiger Kopfzeilendaten mit einigen beliebigen Elementen. Die Einrichtung enthält zwei Tabellen mit zugehörigen persistenten Klassen und sieht ungefähr so aus:

Beachten Sie, dass ich den Agenten, den Basisagenten und die Standardklassen- und Schnittstellenmethoden zur besseren Übersicht ausgelassen habe.

Die Object Services bieten eine Möglichkeit, die gesamte Schlüsselverwaltung in die Hände der generierten Persistenzklasse zu legen. Sie müssen lediglich den Datentyp OS_GUID für ein Primärschlüsselfeld verwenden. Dies mag in einigen Fällen praktisch sein, aber Sie sollten diese Funktion nicht verwenden, wenn Sie Änderungsdokumente basierend auf den Datentabellen aufzeichnen möchten. OS_GUID ist ein RAW-Typ, und dies verursacht alle möglichen Probleme im Änderungsdokumentengenerator und in den Berichten, die zur Anzeige der Änderungsdokumente verwendet werden. In diesem Beispiel habe ich einen zusammengesetzten Geschäftsschlüssel verwendet, der aus dem Unternehmenscode und einem beliebigen Schlüssel besteht – wahrscheinlich einer GUID, aber das ist in diesem Fall irrelevant.

Wenn die Tabellen vorhanden und aktiv sind, ist es möglich, das Änderungsbelegobjekt zu erstellen und die zugehörigen Objekte zu generieren. In diesem Fall gibt es nichts Besonderes zu beachten: Der Header wird als einzelner Eintrag übergeben, während die Elemente als Tabelle übergeben werden:

Der Funktionsbaustein zum Schreiben der Änderungsbelege kann dann mit den Standardparametern generiert werden. In diesem Fall habe ich den Standardnamen ZOSCDO_WRITE_DOCUMENT verwendet.

Bevor wir uns nun der eigentlichen Magie der Änderungsbelege zuwenden können, gibt es ein kleines Problem zu lösen. Änderungsbelege werden verwendet, um die Erstellung, Änderung und Löschung von Geschäftsobjekten aufzuzeichnen. Die Erstellung und Änderung sind einfach, aber es gibt ein Problem bei gelöschten Objekten. Die generierte Kombination aus Agent und Basis-Agent stellt ein Ereignis namens IF_OS_FACTORY~DELETED bereit, das immer dann ausgelöst wird, wenn ein Objekt gelöscht wird. Leider wird das Ereignis ausgelöst, nachdem die Objektinstanz ungültig gemacht wurde, und daher führt jeder weitere Versuch, den Geschäftsschlüssel aus dem Objekt zu erhalten, zu einer Ausnahme. Die Schnittstelle IF_OS_FACTORY bietet auch ein Ereignis mit dem Namen TO_BE_DELETED, das die Lösung für dieses Problem zu sein scheint, aber zumindest in meinem Szenario wird dieses Ereignis von den generierten Agenten nie ausgelöst. Ich konnte nur einige Hinweise auf dieses Ereignis in älteren Agentenimplementierungen finden – es sieht so aus, als wäre dieses Ereignis aus irgendeinem Grund aus der Standardimplementierung entfernt worden. Glücklicherweise ist es ziemlich einfach, es wieder zu aktivieren – ich musste nur die Methode EXT_PM_DELETED_PERSISTENT in jedem der Agenten neu definieren und die folgende Implementierung hinzufügen:

METHOD ext_pm_deleted_persistent.

  DATA: ls_backup_object_info  TYPE typ_object_info,
        l_backup_object_index  TYPE typ_index,
        lr_backup_object_iref  TYPE typ_object_iref.

  CALL METHOD super->ext_pm_deleted_persistent.

  ls_backup_object_info  = current_object_info.
  l_backup_object_index  = current_object_index.
  lr_backup_object_iref  = current_object_iref.

  RAISE EVENT if_os_factory~to_be_deleted
    EXPORTING
      object = current_object_iref.

  current_object_info  = ls_backup_object_info.
  current_object_index = l_backup_object_index.
  current_object_iref  = lr_backup_object_iref.

ENDMETHOD.

Es ist wichtig, einige der oben gezeigten Attribute zu speichern und wiederherzustellen – andernfalls würde der Lesezugriff, der später vom Event-Handler benötigt wird, diese Variablen ändern und erneut zu einem Kurzdump führen würde.

Mit diesen Vorbereitungen ist es möglich, die eigentliche Änderungsdokumenterstellung in Angriff zu nehmen. In diesem Beispiel habe ich eine separate Klasse namens ZCL_OSCDO_CHANGE_DOC_WRITER verwendet, um die Ereignisbehandlung und Datenverarbeitung zu umschließen. Innerhalb dieser Klasse verwende ich die folgenden Datentypen, um die Änderungen an den Daten zu verfolgen:

   TYPES:
      BEGIN OF t_item_data,
             item_number TYPE zoscdo_item_number,
             change      TYPE cdchngind,
             old_data    TYPE zoscdo_item,
             new_data    TYPE zoscdo_item,
           END OF t_item_data .
    TYPES:
      tt_item_data TYPE SORTED TABLE OF t_item_data
                        WITH UNIQUE KEY item_number .
    TYPES:
      BEGIN OF t_header_data,
             company_code TYPE bukrs,
             header_id    TYPE zoscdo_header_id,
             change       TYPE cdchngind,
             old_data     TYPE zoscdo_header,
             new_data     TYPE zoscdo_header,
             items        TYPE tt_item_data,
           END OF t_header_data .
    TYPES:
      tt_header_data TYPE SORTED TABLE OF t_header_data
                          WITH UNIQUE KEY company_code header_id.

Dies ist im Grunde eine In-Memory-Darstellung des Headers mit den zugehörigen Elementen. Die Klasse verfügt über ein globales Attribut GT_CHANGE_DATA TYPE TT_HEADER_DATA, das zur Verfolgung der Änderungen verwendet wird. Dieses Attribut wird mithilfe der folgenden zwei Methoden befüllt:

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_OSCDO_CHANGE_DOC_WRITER->RECORD_HEADER_STATE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IR_HEADER                      TYPE REF TO ZCL_OSCDO_HEADER
* | [--->] I_CHANGE                       TYPE        CDCHNGIND(optional)
* | [--->] I_OLD_STATE                    TYPE        ABAP_BOOL (default =ABAP_FALSE)
* | [--->] I_RECORD_CONTENTS              TYPE        ABAP_BOOL (default =ABAP_TRUE)
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD record_header_state.

    FIELD-SYMBOLS: <ls_header> TYPE t_header_data.

    DATA: ls_header TYPE t_header_data.

*   try to locate the corresponding record in the global table
    READ TABLE gt_change_data ASSIGNING <ls_header>
      WITH TABLE KEY company_code = ir_header->get_company_code( )
                     header_id    = ir_header->get_header_id( ).
    IF sy-subrc <> 0.
*     no record found - create a new one
      ls_header-company_code = ir_header->get_company_code( ).
      ls_header-header_id    = ir_header->get_header_id( ).
      INSERT ls_header INTO TABLE gt_change_data ASSIGNING <ls_header>.
    ENDIF.

*   When recording the 'old' state, we don't want to set the change indicator yet.
*   Therefore only set the change indicator if it is supplied.
    IF i_change IS SUPPLIED.
      <ls_header>-change = i_change.
    ENDIF.

*   If required, record the current state, either as 'new' or as 'old'.
    IF i_record_contents = abap_true.
      IF i_old_state = abap_true.
        ir_header->record_cdo_data( CHANGING cs_data = <ls_header>-old_data ).
      ELSE.
        ir_header->record_cdo_data( CHANGING cs_data = <ls_header>-new_data ).
      ENDIF.
    ENDIF.

  ENDMETHOD.                    "record_header_state
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_OSCDO_CHANGE_DOC_WRITER->RECORD_ITEM_STATE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IR_ITEM                        TYPE REF TO ZCL_OSCDO_ITEM
* | [--->] I_CHANGE                       TYPE        CDCHNGIND(optional)
* | [--->] I_OLD_STATE                    TYPE        ABAP_BOOL (default =ABAP_FALSE)
* | [--->] I_RECORD_CONTENTS              TYPE        ABAP_BOOL (default =ABAP_TRUE)
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD record_item_state.

    FIELD-SYMBOLS: <ls_header> TYPE t_header_data,
                   <ls_item>   TYPE t_item_data.

    DATA: ls_header TYPE t_header_data,
          ls_item   TYPE t_item_data.

*   try to locate the corresponding record in the global header table
    READ TABLE gt_change_data ASSIGNING <ls_header>
      WITH TABLE KEY company_code = ir_item->get_company_code( )
                     header_id    = ir_item->get_header_id( ).
    IF sy-subrc <> 0.
*     no header record found - create a new one
      ls_header-company_code = ir_item->get_company_code( ).
      ls_header-header_id    = ir_item->get_header_id( ).
      INSERT ls_header INTO TABLE gt_change_data ASSIGNING <ls_header>.
    ENDIF.

*   try to locate the corresponding record in the subordinate item table
    READ TABLE <ls_header>-items ASSIGNING <ls_item>
      WITH TABLE KEY item_number = ir_item->get_item_number( ).
    IF sy-subrc <> 0.
*     no item record found - create a new one
      ls_item-item_number = ir_item->get_item_number( ).
      INSERT ls_item INTO TABLE <ls_header>-items ASSIGNING <ls_item>.
    ENDIF.

*   When recording the 'old' state, we don't want to set the change indicator yet.
*   Therefore only set the change indicator if it is supplied.
    IF i_change IS SUPPLIED.
      <ls_item>-change = i_change.
    ENDIF.

*   If required, record the current state, either as 'new' or as 'old'.
    IF i_record_contents = abap_true.
      IF i_old_state = abap_true.
        ir_item->record_cdo_data( CHANGING cs_data = <ls_item>-old_data ).
      ELSE.
        ir_item->record_cdo_data( CHANGING cs_data = <ls_item>-new_data ).
      ENDIF.
    ENDIF.

  ENDMETHOD.                    "record_item_state

Die Methoden RECORD_CDO_DATA sind manuelle Ergänzungen zu den persistenten Klassen, die lediglich eine Struktur mit den Werten der Attribute füllen der Attribute füllen. Diese Methoden sind eher trivial und werden daher hier nicht angezeigt. Wenn Sie keine Methoden zu den persistenten Klassen hinzufügen möchten, können Sie diese Logik auch zur Klasse ZCL_OSCDO_CHANGE_DOC_WRITER hinzufügen.

Um die Datenstruktur zur Laufzeit zu füllen, müssen wir einige Ereignisse verarbeiten, nämlich IF_OS_FACTORY~TO_BE_DELETED und IF_OS_FACTORY~LOADED_WITH_STATE. Letzteres wird verwendet, um den „alten“ Status des Objekts zu speichern, bevor Änderungen vorgenommen wurden. Da das Ereignis für den Kopf und die Elemente identisch ist, werden die Methoden einfach in die entsprechende Referenzvariable umgewandelt und an eine der oben eingeführten Methoden weitergeleitet.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_OSCDO_CHANGE_DOC_WRITER->ON_LOADED_WITH_STATE
* +-------------------------------------------------------------------------------------------------+
* | [--->] OBJECT                         LIKE
* | [--->] WRITE_ACCESS                   LIKE
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD on_loaded_with_state.

    DATA: lr_header TYPE REF TO zcl_oscdo_header,
          lr_item   TYPE REF TO zcl_oscdo_item.

    CASE cl_abap_classdescr=>get_class_name( object ).
      WHEN co_class_name_header. " TYPE abap_abstypename VALUE '\\CLASS=ZCL_OSCDO_HEADER'
        lr_header ?= object.
        record_header_state( ir_header   = lr_header
                             i_old_state = abap_true ).
      WHEN co_class_name_item. " TYPE abap_abstypename VALUE '\\CLASS=ZCL_OSCDO_ITEM'
        lr_item ?= object.
        record_item_state( ir_item     = lr_item
                           i_old_state = abap_true ).
    ENDCASE.

  ENDMETHOD.                    "on_loaded_with_state
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_OSCDO_CHANGE_DOC_WRITER->ON_TO_BE_DELETED
* +-------------------------------------------------------------------------------------------------+
* | [--->] OBJECT                         LIKE
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD on_to_be_deleted.

    DATA: lr_header TYPE REF TO zcl_oscdo_header,
          lr_item   TYPE REF TO zcl_oscdo_item.

    CASE cl_abap_classdescr=>get_class_name( object ).
      WHEN co_class_name_header. " TYPE abap_abstypename VALUE '\CLASS=ZCL_OSCDO_HEADER'
        lr_header ?= object.
        record_header_state( ir_header         = lr_header
                             i_change          = 'D'
                             i_record_contents = abap_false ).
      WHEN co_class_name_item. " TYPE abap_abstypename VALUE '\CLASS=ZCL_OSCDO_ITEM'
        lr_item ?= object.
        record_item_state( ir_item           = lr_item
                           i_change          = 'D'
                           i_record_contents = abap_false ).
    ENDCASE.

  ENDMETHOD.                    "on_to_be_deleted

Denken Sie bei der Definition der Konstanten für den Klassennamen an das Präfix \CLASS=!

Mit diesen Methoden kann der Verfasser den unveränderten Zustand eines Objekts nach dem Laden aufzeichnen und es als gelöscht markieren. Wir benötigen noch eine Möglichkeit, neu erstellte Objekte zu verfolgen, die geänderten Objekte zu identifizieren und die entsprechenden neuen/alten Strukturen für die zukünftige Verwendung zu speichern. Dies kann mithilfe der grundlegenden Agentenmethoden erfolgen:

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_OSCDO_CHANGE_DOC_WRITER->COMPLETE_CHANGE_LIST
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD complete_change_list.

    FIELD-SYMBOLS: <lr_object> TYPE REF TO object.

    DATA: lt_objects TYPE ostyp_ref_tab,
          lr_header  TYPE REF TO zcl_oscdo_header,
          lr_item    TYPE REF TO zcl_oscdo_item.

*   add the newly created headers to the change list
    lt_objects = zca_oscdo_header=>agent->if_os_ca_instance~get_created( ).
    LOOP AT lt_objects ASSIGNING <lr_object>.
      lr_header ?= <lr_object>.
      record_header_state( ir_header   = lr_header
                           i_change    = 'I' ).
    ENDLOOP.

*   add the changed headers to the change list
    lt_objects = zca_oscdo_header=>agent->if_os_ca_instance~get_changed( ).
    LOOP AT lt_objects ASSIGNING <lr_object>.
      lr_header ?= <lr_object>.
      record_header_state( ir_header   = lr_header
                           i_change    = 'U' ).
    ENDLOOP.

*   add the newly created items to the change list
    lt_objects = zca_oscdo_item=>agent->if_os_ca_instance~get_created( ).
    LOOP AT lt_objects ASSIGNING <lr_object>.
      lr_item ?= <lr_object>.
      record_item_state( ir_item     = lr_item
                         i_change    = 'I' ).
    ENDLOOP.

*   add the changed items to the change list
    lt_objects = zca_oscdo_item=>agent->if_os_ca_instance~get_changed( ).
    LOOP AT lt_objects ASSIGNING <lr_object>.
      lr_item ?= <lr_object>.
      record_item_state( ir_item     = lr_item
                         i_change    = 'U' ).
    ENDLOOP.

  ENDMETHOD.                    "complete_change_list

Unter der Annahme, dass der erfasste Zustand vollständig ist, ist es jetzt nur noch eine Frage der einfachen Datenmanipulation, die Änderungsdokumente zu schreiben:

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_OSCDO_CHANGE_DOC_WRITER->SAVE_CHANGE_DOCUMENTS
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD save_change_documents.

    DATA: lr_persistency_manager TYPE REF TO if_os_persistency_manager,
          l_user                 TYPE syuname,
          l_tcode                TYPE sytcode,
          l_object_id            TYPE cdobjectv,
          l_item_change          TYPE cdchngind,
          l_object_change        TYPE cdchngind,
          lt_new_items           TYPE TABLE OF yzoscdo_item,
          lt_old_items           TYPE TABLE OF yzoscdo_item.

    FIELD-SYMBOLS: <ls_change>      TYPE t_header_data,
                   <ls_item>        TYPE t_item_data,
                   <ls_item_change> TYPE yzoscdo_item.

    lr_persistency_manager = cl_os_persistency_manager=>get_persistency_manager( ).
    l_user  = cl_abap_syst=>get_user_name( ).
    l_tcode = cl_abap_syst=>get_transaction_code( ).

    LOOP AT gt_change_data ASSIGNING <ls_change>.

      FREE: l_object_id, l_item_change, lt_new_items, lt_old_items.

*     determine the object ID - this refers to the header, so it's MANDT BUKRS HEADER_ID
      l_object_id(3)    = cl_abap_syst=>get_client( ).
      l_object_id+3(4)  = <ls_change>-company_code.
      l_object_id+7(32) = <ls_change>-header_id.

*     assemble the item change data
      IF <ls_change>-items IS NOT INITIAL.
        l_item_change = 'U'.
        LOOP AT <ls_change>-items ASSIGNING <ls_item>.
          CASE <ls_item>-change.
            WHEN 'I'.
              APPEND INITIAL LINE TO lt_new_items ASSIGNING <ls_item_change>.
              MOVE-CORRESPONDING <ls_item>-new_data TO <ls_item_change>.
              <ls_item_change>-kz = 'I'.

            WHEN 'U'.
              APPEND INITIAL LINE TO lt_old_items ASSIGNING <ls_item_change>.
              MOVE-CORRESPONDING <ls_item>-old_data TO <ls_item_change>.
              <ls_item_change>-kz = 'U'.
              APPEND INITIAL LINE TO lt_new_items ASSIGNING <ls_item_change>.
              MOVE-CORRESPONDING <ls_item>-new_data TO <ls_item_change>.
              <ls_item_change>-kz = 'U'.

            WHEN 'D'.
              APPEND INITIAL LINE TO lt_old_items ASSIGNING <ls_item_change>.
              MOVE-CORRESPONDING <ls_item>-old_data TO <ls_item_change>.
              <ls_item_change>-kz = 'D'.

          ENDCASE.
        ENDLOOP.
      ENDIF.

*     determine the overall change indicator
      IF <ls_change>-change IS NOT INITIAL.
        l_object_change = <ls_change>-change.
      ELSEIF l_item_change IS NOT INITIAL.
        l_object_id = 'U'.
      ENDIF.

*     update mode or direct call?
      IF lr_persistency_manager->get_update_mode( ) = oscon_dmode_direct.
        CALL FUNCTION 'ZOSCDO_WRITE_DOCUMENT'
          EXPORTING
            objectid                = l_object_id
            tcode                   = l_tcode
            utime                   = sy-uzeit
            udate                   = sy-datum
            username                = l_user
            object_change_indicator = l_object_change
            n_zoscdo_header         = <ls_change>-new_data
            o_zoscdo_header         = <ls_change>-old_data
            upd_zoscdo_header       = <ls_change>-change
            upd_zoscdo_item         = l_item_change
          TABLES
            xzoscdo_item            = lt_new_items
            yzoscdo_item            = lt_old_items.
      ELSE.
        CALL FUNCTION 'ZOSCDO_WRITE_DOCUMENT' IN UPDATE TASK
          EXPORTING
            objectid                = l_object_id
            tcode                   = l_tcode
            utime                   = sy-uzeit
            udate                   = sy-datum
            username                = l_user
            object_change_indicator = l_object_change
            n_zoscdo_header         = <ls_change>-new_data
            o_zoscdo_header         = <ls_change>-old_data
            upd_zoscdo_header       = <ls_change>-change
            upd_zoscdo_item         = l_item_change
          TABLES
            xzoscdo_item            = lt_new_items
            yzoscdo_item            = lt_old_items.
      ENDIF.

    ENDLOOP.

  ENDMETHOD.                    "save_change_documents

Der schwierige Teil der Implementierung bestand darin, einen Weg zu finden, den geänderten Status unmittelbar vor dem Speichern abzurufen und den generierten Funktionsbaustein zum richtigen Zeitpunkt auszulösen. Zu diesem Zweck habe ich mich entschieden, den Änderungsdokumenten-Ersteller als sogenannten Save Handler zu registrieren. Die Klasse muss hierfür die Schnittstelle IF_OS_CA_SERVICE implementieren, aber mit zwei Ausnahmen ist keine der Methoden erforderlich. Um einen versehentlichen Aufruf zu verhindern, habe ich einfach die Anweisung

RAISE EXCEPTION TYPE cx_os_no_implementation.

in jeder von der Schnittstelle geforderten Methode hinterlegt, mit Ausnahme der folgenden zwei Methoden.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_OSCDO_CHANGE_DOC_WRITER->IF_OS_CA_SERVICE~PREPARE_FOR_TOP_TRANSACTION
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_INVALIDATE                   TYPE        OS_BOOLEAN
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD if_os_ca_service~prepare_for_top_transaction.

    FIELD-SYMBOLS: <lr_agent> TYPE REF TO if_os_ca_service .

*   a new transaction is started - forget all previously stored change data
    FREE gt_change_data.

*   defer the actual method call to the wrapped agents
    LOOP AT gt_agents ASSIGNING <lr_agent>.
      <lr_agent>->prepare_for_top_transaction( i_invalidate ).
    ENDLOOP.

  ENDMETHOD.                    "if_os_ca_service~prepare_for_top_transaction
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_OSCDO_CHANGE_DOC_WRITER->IF_OS_CA_SERVICE~SAVE
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD if_os_ca_service~save.

    FIELD-SYMBOLS: <lr_agent> TYPE REF TO if_os_ca_service .

*   defer the actual method call to the wrapped agents
    LOOP AT gt_agents ASSIGNING <lr_agent>.
      <lr_agent>->save( ).
    ENDLOOP.

*   then complete the list of changed objects and write the change documents
    complete_change_list( ).
    save_change_documents( ).

  ENDMETHOD.                    "if_os_ca_service~save

Wie Sie sehen, ist die Implementierung ziemlich einfach: Wir überlassen die komplexen Dinge den generierten Agenten (die wir in nicht allzu ferner Zukunft in einer Tabelle namens GT_AGENTS speichern werden) und fügen einfach unsere Verarbeitungslogik hinzu.

Da wir nun die grundlegenden Strukturen für die Datenumschlüsselung eingerichtet haben, können wir damit beginnen, den Änderungsdokumenten-Writer mit den Objektdiensten zu verbinden. Der Writer muss als Ereignisbehandler registriert werden, und es wäre unklug, mehrere Instanzen zu registrieren. Aus diesem Grund habe ich es als einfaches Singleton entworfen: Die Klasse ist als CREATE PRIVATEgekennzeichnet und hat ein statisches Attribut SR_INSTANCE TYPE REF TO ZCL_OSCDO_CHANGE_DOC_WRITERsowie statische Initialisierungsmethode wie diese:

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_OSCDO_CHANGE_DOC_WRITER=>INITIALIZE
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD initialize.

    IF sr_instance IS NOT INITIAL.
      CREATE OBJECT sr_instance.
      sr_instance->register_handlers_for_agent( zca_oscdo_header=>agent ).
      sr_instance->register_handlers_for_agent( zca_oscdo_item=>agent ).
    ENDIF.

  ENDMETHOD.                    "initialize

Während der Initialisierung werden die Ereignisbehandlungsmethoden und der Speicher-Handler mit der folgenden Methode registriert:

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_OSCDO_CHANGE_DOC_WRITER->REGISTER_HANDLERS_FOR_AGENT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IR_AGENT                       TYPE REF TO CL_OS_CA_COMMON
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD register_handlers_for_agent.

    DATA: lr_persistency_manager TYPE REF TO if_os_persistency_manager.

    APPEND ir_agent TO gt_agents. " TYPE TABLE OF REF TO if_os_ca_service .

    lr_persistency_manager = cl_os_persistency_manager=>get_persistency_manager( ).
    lr_persistency_manager->register_save_manager_for_ca( i_save_manager = me
                                                          i_class_agent  = ir_agent ).

    SET HANDLER on_loaded_with_state FOR ir_agent.
    SET HANDLER on_to_be_deleted FOR ir_agent.

  ENDMETHOD.                    "register_handlers_for_agent

Nach Fertigstellung der Initialisierungsmethoden genügt ein einfacher Aufruf von

  zcl_oscdo_change_doc_writer=>initialize( ).

während der Initialisierung der Anwendung, um die Aufzeichnung von Änderungsdokumenten zu starten. In diesem Artikel wird nur das grundlegende Verfahren beschrieben, aber von hier an sollte es viel einfacher sein, der Implementierung Funktionen hinzuzufügen.