Skip to content

Lesson 1 Companion ​

Use this companion when you want to go beyond the main lesson, review the architecture, compare your implementation with a reference solution, or understand why a particular design was chosen.

How to use this companion

Try the lesson and assignment first. Then use this file to check your reasoning, study a complete implementation, and review the architectural ideas in more depth.

Related Lesson 1 files:

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

This companion reinforces the OOP knowledge used in the lesson.

1. Architectural goal ​

The architecture to avoid is:

text
Executable Class
      ↓
SELECT everywhere
      ↓
Database

The lesson uses:

text
Runner
  ↓
Reader
  ↓
ABAP SQL
  ↓
Persistence

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

2. Why "Reader" and not "Service" ​

Reader is a more precise name here than 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? ​

Because the course has already introduced OOP, putting all SQL into IF_OO_ADT_CLASSRUN~main would discard the separation of responsibilities established earlier.

The Runner/Reader split reinforces:

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

Treat this as a simple separation of responsibilities, not as a formal architectural pattern.

5. Runtime architecture ​

If you have worked with other application stacks, you may ask, "Where is the DB connection?"

The runtime path is:

text
ADT
 ↓
ABAP backend
 ↓
ABAP SQL / database interface
 ↓
standard database connection
 ↓
system database

A precise description is:

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

This does not mean that ABAP has no database connections; advanced connection and external-data scenarios also exist.

6. SQL Console in the lesson ​

The lesson places the ADT SQL Console between Data Preview and the Reader class.

The flow is:

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.

Start with this query in SQL Console:

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 study time ​

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.

DTO and domain-model terminology is not needed at this stage.

11. Why WITH EMPTY KEY? ​

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

Keep the following concepts separate:

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? ​

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

The Runner should not inspect sy-subrc from a query that happened inside another method.

14. Assignment architecture ​

You 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 — Introduction to ABAP CDS evolves this into:

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. Topics that come later ​

These topics are deliberately deferred:

  • 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 ​