Skip to content

Teacher Notes — Lesson 1

Student-facing files:

  1. Lesson 1 — Database Basics, ABAP SQL, and Code Organization
  2. Lesson 1 Practice — Airport Reader and Runner

This revision intentionally reinforces the students' existing OOP knowledge.

1. Architectural goal

Do not leave students with:

text
Executable Class

SELECT everywhere

Database

Teach:

text
Runner

Reader

ABAP SQL

Persistence

This is a teaching architecture, not the final RAP application architecture.

2. Why "Reader" and not "Service"

Do not call the data-access class Service or Business Service.

In RAP, business service has a specific meaning involving service exposure. Using "service class" here would create terminology debt.

Reader is deliberately narrow:

text
GET_ALL
GET_BY_CURRENCY
GET_BY_ID

3. Why not "Repository"?

Repository can imply broader persistence responsibilities such as create, update, and delete.

Lesson 1 is read-only, so Reader is clearer.

4. Why organize code this early?

Students already know OOP. If the first database lesson puts all SQL into IF_OO_ADT_CLASSRUN~main, the course accidentally teaches them to abandon the separation they just learned.

The Runner/Reader split reinforces:

  • single responsibility;
  • collaboration between objects;
  • method boundaries;
  • typed contracts;
  • keeping data-access details out of presentation/execution code.

Do not oversell this as a formal architectural pattern.

5. Runtime architecture

Experienced developers may ask, "Where is the DB connection?"

Use:

text
ADT

ABAP backend

ABAP SQL / database interface

standard database connection

system database

Safe wording:

For ordinary ABAP SQL against system-managed data, the application developer does not create a JDBC/ODBC-style connection in the class.

Do not say "ABAP has no database connections." Advanced connection/external-data scenarios exist.

6. SQL Console in the lesson

Place the ADT SQL Console between Data Preview and the Reader class.

Recommended flow:

text
Data Preview

inspect records

SQL Console

run the ABAP SQL query interactively

Reader class

use the query in organized application code

Current ADT documentation states that SQL Console can be opened from an ABAP project, Data Preview, a table editor, or a CDS editor. It displays the executed query, result, and query statistics. When opened from Data Preview, it can use the most recent Data Preview query. SQL Console supports the newer ABAP SQL syntax.

Use this query in the demonstration:

abap
SELECT FROM /dmo/carrier
  FIELDS carrier_id,
         name,
         currency_code
  ORDER BY carrier_id

Then compare it with the Reader implementation:

abap
SELECT FROM /dmo/carrier
  FIELDS carrier_id,
         name,
         currency_code
  ORDER BY carrier_id
  INTO TABLE @carriers.

The comparison makes INTO TABLE concrete: SQL Console owns and displays its query result, while application code needs an ABAP target for the result.

Keep the terminology precise: this is the ADT SQL Console executing ABAP SQL in the ABAP development environment. Do not describe it as a direct SAP HANA SQL client.

7. Suggested timing

SectionTime
Persistent data / Data Preview15 min
SQL Console exploration10 min
Runtime DB architecture10 min
ABAP SQL overview10 min
Why Runner + Reader10 min
Create Reader types + GET_ALL20 min
Create Runner + call Reader15 min
GET_BY_CURRENCY15 min
GET_BY_ID + found handling15 min
Architecture recap10 min

8. Carrier Reader reference implementation

abap
CLASS zcl_carrier_reader_### DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.

    TYPES:
      BEGIN OF ty_carrier,
        carrier_id    TYPE /dmo/carrier-carrier_id,
        name          TYPE /dmo/carrier-name,
        currency_code TYPE /dmo/carrier-currency_code,
      END OF ty_carrier,

      tt_carriers TYPE STANDARD TABLE OF ty_carrier
        WITH EMPTY KEY.

    METHODS get_all
      RETURNING VALUE(carriers) TYPE tt_carriers.

    METHODS get_by_currency
      IMPORTING
        currency TYPE /dmo/carrier-currency_code
      RETURNING
        VALUE(carriers) TYPE tt_carriers.

    METHODS get_by_id
      IMPORTING
        carrier_id TYPE /dmo/carrier-carrier_id
      EXPORTING
        carrier    TYPE ty_carrier
        found      TYPE abap_bool.

ENDCLASS.


CLASS zcl_carrier_reader_### IMPLEMENTATION.

  METHOD get_all.

    SELECT FROM /dmo/carrier
      FIELDS carrier_id,
             name,
             currency_code
      ORDER BY carrier_id
      INTO TABLE @carriers.

  ENDMETHOD.


  METHOD get_by_currency.

    SELECT FROM /dmo/carrier
      FIELDS carrier_id,
             name,
             currency_code
      WHERE currency_code = @currency
      ORDER BY carrier_id
      INTO TABLE @carriers.

  ENDMETHOD.


  METHOD get_by_id.

    CLEAR:
      carrier,
      found.

    SELECT SINGLE FROM /dmo/carrier
      FIELDS carrier_id,
             name,
             currency_code
      WHERE carrier_id = @carrier_id
      INTO @carrier.

    IF sy-subrc = 0.
      found = abap_true.
    ENDIF.

  ENDMETHOD.

ENDCLASS.

9. Carrier Runner reference implementation

abap
CLASS zcl_sql_l01_### DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

ENDCLASS.


CLASS zcl_sql_l01_### IMPLEMENTATION.

  METHOD if_oo_adt_classrun~main.

    DATA(reader) = NEW zcl_carrier_reader_###( ).

    DATA(all_carriers) = reader->get_all( ).
    out->write( all_carriers ).

    DATA currency TYPE /dmo/carrier-currency_code VALUE 'USD'.

    DATA(filtered_carriers) =
      reader->get_by_currency( currency = currency ).

    out->write( filtered_carriers ).

    DATA carrier_id TYPE /dmo/carrier-carrier_id VALUE 'LH'.

    reader->get_by_id(
      EXPORTING
        carrier_id = carrier_id
      IMPORTING
        carrier    = DATA(carrier)
        found      = DATA(found)
    ).

    IF found = abap_true.
      out->write( carrier ).
    ELSE.
      out->write( 'Carrier not found' ).
    ENDIF.

  ENDMETHOD.

ENDCLASS.

Use demo values that actually exist in the tenant.

10. Why public result types?

The Runner needs a clear ABAP contract for what the Reader returns.

ty_carrier and tt_carriers provide that contract without exposing every persistence column.

Do not go deeply into DTO/domain-model terminology yet.

11. Why WITH EMPTY KEY?

For these simple read results, no ABAP internal-table lookup key is needed.

Do not confuse:

text
database table key

with:

text
ABAP internal table key

They are different concepts.

12. Why does GET_BY_ID return found?

Returning only a structure would make "not found" indistinguishable from an initial structure.

A simple abap_bool keeps the first lesson explicit without introducing custom exceptions or references.

13. Where should sy-subrc be checked?

Keep sy-subrc inside the Reader immediately after the SELECT SINGLE that sets it.

Do not make the Runner inspect sy-subrc from a query that happened inside another method.

14. Assignment expected architecture

Students should first run the basic airport query in ADT SQL Console, then create:

text
ZCL_SQL_L01_PRACTICE_###

ZCL_AIRPORT_READER_###

/DMO/AIRPORT

The SQL Console step confirms the query independently of the class structure. The Reader then owns the corresponding SQL in application code.

The Runner should contain no direct airport SELECT.

15. Airport Reader reference solution

abap
CLASS zcl_airport_reader_### DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.

    TYPES:
      BEGIN OF ty_airport,
        airport_id TYPE /dmo/airport-airport_id,
        name       TYPE /dmo/airport-name,
        city       TYPE /dmo/airport-city,
        country    TYPE /dmo/airport-country,
      END OF ty_airport,

      tt_airports TYPE STANDARD TABLE OF ty_airport
        WITH EMPTY KEY.

    METHODS get_all
      RETURNING VALUE(airports) TYPE tt_airports.

    METHODS get_by_country
      IMPORTING
        country TYPE /dmo/airport-country
      RETURNING
        VALUE(airports) TYPE tt_airports.

    METHODS get_by_id
      IMPORTING
        airport_id TYPE /dmo/airport-airport_id
      EXPORTING
        airport    TYPE ty_airport
        found      TYPE abap_bool.

ENDCLASS.


CLASS zcl_airport_reader_### IMPLEMENTATION.

  METHOD get_all.

    SELECT FROM /dmo/airport
      FIELDS airport_id,
             name,
             city,
             country
      ORDER BY airport_id
      INTO TABLE @airports.

  ENDMETHOD.


  METHOD get_by_country.

    SELECT FROM /dmo/airport
      FIELDS airport_id,
             name,
             city,
             country
      WHERE country = @country
      ORDER BY airport_id
      INTO TABLE @airports.

  ENDMETHOD.


  METHOD get_by_id.

    CLEAR:
      airport,
      found.

    SELECT SINGLE FROM /dmo/airport
      FIELDS airport_id,
             name,
             city,
             country
      WHERE airport_id = @airport_id
      INTO @airport.

    IF sy-subrc = 0.
      found = abap_true.
    ENDIF.

  ENDMETHOD.

ENDCLASS.

16. Airport Runner reference solution

abap
CLASS zcl_sql_l01_practice_### DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

ENDCLASS.


CLASS zcl_sql_l01_practice_### IMPLEMENTATION.

  METHOD if_oo_adt_classrun~main.

    DATA(reader) = NEW zcl_airport_reader_###( ).

    DATA(airports) = reader->get_all( ).
    out->write( airports ).

    DATA country TYPE /dmo/airport-country VALUE 'US'.

    DATA(country_airports) =
      reader->get_by_country( country = country ).

    LOOP AT country_airports INTO DATA(airport).
      out->write(
        |{ airport-airport_id } - { airport-city } - { airport-name }|
      ).
    ENDLOOP.

    DATA airport_id TYPE /dmo/airport-airport_id VALUE '...'.

    reader->get_by_id(
      EXPORTING
        airport_id = airport_id
      IMPORTING
        airport    = DATA(one_airport)
        found      = DATA(found)
    ).

    IF found = abap_true.
      out->write( one_airport ).
    ELSE.
      out->write( 'Airport not found' ).
    ENDIF.

  ENDMETHOD.

ENDCLASS.

17. Architecture questions — expected answers

What is the difference between SQL Console and the Reader?

SQL Console is an ADT development tool for executing and analyzing a query interactively. The Reader is application code that owns data-access logic and returns typed ABAP data to its caller.

Why does the Runner not contain SELECT?

Because the lesson separates execution/presentation from data access.

What is the Reader responsible for?

Reading data and encapsulating the ABAP SQL needed for that read.

What is the Runner responsible for?

Creating objects, calling Reader methods, and displaying/processing returned data.

Is the Reader a RAP business service?

No. It is a small OOP data-access class for this lesson.

Why no connection string?

Because the class executes in the ABAP backend, which provides the standard database access infrastructure for ordinary ABAP SQL.

Why return typed ABAP data?

To provide a clear method contract and keep presentation outside the Reader.

Why put WHERE in the Reader?

The Reader owns the query and should ask the database for only relevant rows.

Why SELECT SINGLE in GET_BY_ID?

Because the complete key identifies at most one row.

Why check sy-subrc in the Reader?

Because it immediately follows and interprets the SQL statement executed there.

18. Transition to Lesson 2

Lesson 1:

text
Runner

Reader

/DMO/CARRIER

Lesson 2 can become:

text
Runner

Reader

ZI_CARRIER_###

/DMO/CARRIER

The Runner does not need to care whether the Reader reads a table or a CDS entity.

That provides a concrete reason for the data-access boundary.

19. Do not teach yet

Avoid:

  • service classes;
  • RAP service definitions;
  • RAP service bindings;
  • OData;
  • table creation;
  • INSERT;
  • UPDATE;
  • DELETE;
  • repository-pattern theory;
  • dependency-injection frameworks;
  • joins and advanced SQL.

20. SAP references