Skip to content

Lesson 19 Practice — Standalone Service Ticket Events ​

Your task ​

A customer support team receives service tickets. Each new ticket must be saved by the ticket application, but another process also needs to know that work has arrived. We don't want ticket creation to contain the work-queue implementation.

Build a second, independent mini-application that publishes TicketOpened whenever a new ticket is saved. A separate local handler will record each new ticket in a simple work queue.

text
Customer creates ticket
         ↓
Ticket RAP app saves it
         ↓
    TicketOpened
         ↓
 Separate queue handler
         ↓
 New work-queue entry

This task is independent of both the guided Online Order example and everything before Lesson 19. You may consult the Companion, but do not copy all the Online Order source and change names blindly.

Independent work: about 3 hours. No individual chapter timings.


1. Expected outcome ​

By the end you must demonstrate:

  • a new Ticket exists in its active RAP persistence;
  • a matching TicketOpened receipt appears in a different table, written by an event handler;
  • saving a ticket draft without activation does not prove a new active-ticket event;
  • editing an existing ticket does not create a second TicketOpened record;
  • no objects from the guided Online Order application were modified.

2. New object names ​

Use ### consistently:

PurposeSuggested name
PackageZPKG_L19P_###
Active Ticket tableZSTICKET_###
Event payload abstract entityZA_TICKET_EVT_###
Work-queue tableZTICKETQ_###
Event handler classZCL_TICKET_E_###
Base root, Behavior Pool, projection, serviceGenerated, record actual names

These are deliberately different from ZONLINESHOP_###, ZORDEVT_###, and ZCL_ORDER_EVT_###.


Build the ticket producer ​

3. Create the package and persistence ​

Create the new package and the database table:

text
ZSTICKET_###

Use this baseline:

abap
@EndUserText.label : 'Service Tickets'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zsticket_### {
  key client       : abap.clnt not null;
  key ticket_uuid  : sysuuid_x16 not null;
      issue_title  : abap.char(60);

      local_created_by      : abp_creation_user;
      local_created_at      : abp_creation_tstmpl;
      local_last_changed_by : abp_locinst_lastchange_user;
      local_last_changed_at : abp_locinst_lastchange_tstmpl;
      last_changed_at       : abp_lastchange_tstmpl;
}

Activate the table and check that its Data Preview works.


4. Generate the managed RAP UI service ​

Use Generate ABAP Repository Objects on the new table; choose OData UI Service.

Record the generated names:

text
Root CDS:              __________________________
Root BDEF alias:        __________________________
Behavior Pool:          __________________________
Projection:             __________________________
OData V4 UI Binding:    __________________________

Do not assume the wizard chose the same alias as the Online Order example.

Activate/publish the generated service and launch the Fiori Elements preview. Create and activate a baseline ticket, for example:

text
IssueTitle = Printer not connecting

Confirm it exists in the active table before adding events.


5. Define the business fact ​

Your event name is:

text
TicketOpened

It's a fact in the past tense, not an action such as:

text
OpenTicket

The event must carry:

text
%key   TicketUuid
%param TicketTitle

6. Create the event abstract entity ​

Create ZA_TICKET_EVT_### as a CDS abstract entity with:

abap
@EndUserText.label: 'Ticket Opened Event Payload'
define abstract entity ZA_Ticket_Evt_###
{
  TicketTitle : abap.char(60);
}

Activate.


7. Add the event declaration ​

In the base root BDEF (not the projection), declare:

abap
event TicketOpened parameter ZA_Ticket_Evt_###;

Keep the generated create/update/delete, numbering, mapping and Draft declarations intact.


8. Enable additional save ​

For the generated managed BO, use the syntax pattern:

abap
managed with additional save with full data
implementation in class <actual_behavior_pool> unique;

Replace the placeholder with your system's generated Behavior Pool name, preserving the other global BDEF statements.

This is a header-level BDEF change, not something you put inside the root entity body.

Activate and use Quick Fix to create save_modified.


9. Raise the event when a ticket is newly created ​

Implement save_modified in the generated saver class.

Requirements:

  1. Inspect the create-<root alias> change table.
  2. Only publish if new tickets exist.
  3. Emit one event entry per newly created ticket.
  4. Supply the technical ticket key through %key.
  5. Supply TicketTitle from the generated root's IssueTitle field via %param-TicketTitle.
  6. Use RAISE ENTITY EVENT <global_root>~TicketOpened.
  7. Do not perform direct SQL to the Ticket persistence table.

Adapt the following structure to your exact generated names:

abap
METHOD save_modified.

  IF create-<root_alias> IS NOT INITIAL.

    RAISE ENTITY EVENT <global_root>~TicketOpened
      FROM VALUE #(
        FOR new_ticket IN create-<root_alias>
          ( %key               = new_ticket-%key
            %param-TicketTitle = new_ticket-IssueTitle )
      ).

  ENDIF.

ENDMETHOD.

This is a template, not paste-ready ABAP until you replace <root_alias> and <global_root> with your generator names. The create-... component is typically lowercased by ABAP, while the RAISE ENTITY EVENT name uses the global CDS entity identifier.


Build the ticket consumer ​

10. Create the queue table ​

Create:

text
ZTICKETQ_###

It must contain:

FieldTypeRule
clientabap.clntkey, not null
ticket_uuidsysuuid_x16key, not null
ticket_titleabap.char(60)payload
received_atabp_creation_tstmplhandler timestamp

Apply appropriate application-table annotations, activate it, and confirm Data Preview is initially empty.


11. Create the local event handler ​

Create a new global class:

text
ZCL_TICKET_E_###

It must have:

text
FOR EVENTS OF <your global Ticket root>

In its Local Types create a local subclass of:

text
CL_ABAP_BEHAVIOR_EVENT_HANDLER

and a PRIVATE method for:

text
<generated ticket alias>~TicketOpened

The method must accept the event table as its importing parameter.


12. Implement the queue reaction ​

Your event-handler method must:

  1. call CL_ABAP_TX=>SAVE( ) to close the active RAP modify phase before direct SQL;
  2. loop over the received event rows;
  3. read TicketUuid from %key;
  4. read TicketTitle from %param;
  5. set received_at with GET TIME STAMP FIELD;
  6. upsert one queue row per Ticket UUID using MODIFY zticketq_### FROM ...;
  7. avoid COMMIT WORK and avoid direct SQL changes to the RAP-managed Ticket table.

The Companion shows the corresponding Online Order implementation. Translate it to the Ticket root and make sure every field name matches the table you built.


Verify the entire flow ​

13. Positive test: new ticket ​

Create and activate a second ticket in the new Fiori preview:

text
IssueTitle = Laptop battery issue

Expected evidence:

text
ZSTICKET_###
→ new active ticket row

ZTICKETQ_###
→ eventually one matching ticket UUID and title

Refresh Data Preview after saving. Remember local event handling is asynchronous.


14. Negative test: no new ticket ​

Try these in the generated UI:

  • save an unfinished draft without activation, if Draft is generated;
  • view the ticket list;
  • update the title of an existing ticket, if updates are permitted.

A TicketOpened event should only be published from new active-ticket creation, not from a read or existing-ticket update.


15. Batch reasoning ​

The saver code uses a create-... table, not a single structure. If several ticket instances are created in one RAP save transaction, the expression should produce one TicketOpened event row per ticket.

You are not required to build a multi-instance EML consumer for the basic completion criteria, but you should be able to explain this behavior.


16. Diagnose a missing receipt ​

What you seeFirst thing to check
No active ticketGenerated RAP save/activation
Active ticket, event not raisedSaver header, create-<alias>, save_modified
Event raised, no queue entryHandler activation/registration, runtime error or Trial restrictions
Queue entry has blank title%param-TicketTitle mapping
Multiple queue rows for one ticketQueue key design; check Ticket UUID uniqueness

If the tenant blocks local event handling, show a breakpoint proving the producer assembled the correct event row. Report this as producer verified / consumer not verified rather than silently treating it as success.


17. Student submission ​

Demonstrate in ADT:

text
new package
active Ticket table
RAP generator output
BDEF event declaration
managed additional-save header
save_modified event raise
CDS abstract entity
local event-handler class
queue table
active ticket + matching queue receipt

Explain in your own words:

Why is it useful that the Ticket application announces TicketOpened instead of directly calling a work-queue class?

The answer should mention independent consumers and less coupling between components.


18. Completion checklist ​


19. Extra challenge ​

Replace the work-queue table's title with a deliberately shortened type, then try to log a long ticket title. Observe what your ABAP syntax/activation checks and runtime data reveal. Restore abap.char(60) and explain why a payload contract and a consumer persistence model must agree on data meaning and length.

Do not modify the guided Online Order objects during this challenge.


20. Reference ​

The pattern is based on SAP's standalone Online Shop business-event tutorial and the official Raising and Handling Business Events lesson.

The independent Ticket scenario is an adaptation for course practice. It has not been compiled in your particular BTP ABAP Trial system; use your generated aliases and instructor-tested activation steps.