Appearance
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 entryThis 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
TicketOpenedreceipt 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
TicketOpenedrecord; - no objects from the guided Online Order application were modified.
2. New object names
Use ### consistently:
| Purpose | Suggested name |
|---|---|
| Package | ZPKG_L19P_### |
| Active Ticket table | ZSTICKET_### |
| Event payload abstract entity | ZA_TICKET_EVT_### |
| Work-queue table | ZTICKETQ_### |
| Event handler class | ZCL_TICKET_E_### |
| Base root, Behavior Pool, projection, service | Generated, 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 connectingConfirm it exists in the active table before adding events.
5. Define the business fact
Your event name is:
text
TicketOpenedIt's a fact in the past tense, not an action such as:
text
OpenTicketThe event must carry:
text
%key TicketUuid
%param TicketTitle6. 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:
- Inspect the
create-<root alias>change table. - Only publish if new tickets exist.
- Emit one event entry per newly created ticket.
- Supply the technical ticket key through
%key. - Supply
TicketTitlefrom the generated root'sIssueTitlefield via%param-TicketTitle. - Use
RAISE ENTITY EVENT <global_root>~TicketOpened. - 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:
| Field | Type | Rule |
|---|---|---|
client | abap.clnt | key, not null |
ticket_uuid | sysuuid_x16 | key, not null |
ticket_title | abap.char(60) | payload |
received_at | abp_creation_tstmpl | handler 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_HANDLERand a PRIVATE method for:
text
<generated ticket alias>~TicketOpenedThe method must accept the event table as its importing parameter.
12. Implement the queue reaction
Your event-handler method must:
- call
CL_ABAP_TX=>SAVE( )to close the active RAP modify phase before direct SQL; - loop over the received event rows;
- read
TicketUuidfrom%key; - read
TicketTitlefrom%param; - set
received_atwithGET TIME STAMP FIELD; - upsert one queue row per Ticket UUID using
MODIFY zticketq_### FROM ...; - avoid
COMMIT WORKand 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 issueExpected evidence:
text
ZSTICKET_###
→ new active ticket row
ZTICKETQ_###
→ eventually one matching ticket UUID and titleRefresh 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 see | First thing to check |
|---|---|
| No active ticket | Generated RAP save/activation |
| Active ticket, event not raised | Saver header, create-<alias>, save_modified |
| Event raised, no queue entry | Handler activation/registration, runtime error or Trial restrictions |
| Queue entry has blank title | %param-TicketTitle mapping |
| Multiple queue rows for one ticket | Queue 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 receiptExplain in your own words:
Why is it useful that the Ticket application announces
TicketOpenedinstead 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.