← Back to tutorials

RAP from scratch · Lesson 2 of 6

RAP data model: tables, CDS View Entities and associations

Create the tables for a library, load test data and define the Root View Entity with its associations: the foundation of any RAP business object.

También disponible en español →

Every RAP application is built in layers, bottom-up:

  1. Data: database tables and CDS View Entities (this lesson).
  2. Projection and service: what is exposed to the outside (lesson 3).
  3. Behavior: what can be done with the data, and under which rules (lesson 4).

We keep the library from the previous lesson: authors and books.

1. The tables

In ADT: New → Other ABAP Repository Object → Dictionary → Database Table.

Authors, a simple lookup table:

@EndUserText.label : 'Library: authors'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zlib_author {
  key client    : abap.clnt not null;
  key author_id : abap.numc(6) not null;
  name          : abap.char(60);
  country       : abap.char(2);
}

Books, which will become the RAP business object:

@EndUserText.label : 'Library: books'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zlib_book {
  key client            : abap.clnt not null;
  key book_uuid         : sysuuid_x16 not null;
  book_id               : abap.numc(8);
  title                 : abap.char(80);
  author_id             : abap.numc(6);
  pages                 : abap.int4;
  isbn                  : abap.char(17);
  rating                : abap.int1;
  @Semantics.amount.currencyCode : 'zlib_book.currency_code'
  price                 : abap.curr(11,2);
  currency_code         : abap.cuky;
  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;
}

Three things to notice:

  • Technical key book_uuid: RAP can generate it on its own when a book is created (managed numbering). The number the user sees is book_id, the semantic key.
  • @Semantics.amount.currencyCode: every CURR field needs its reference currency field.
  • The five administrative fields (local_created_by … last_changed_at) use the abp_* data elements. RAP fills them automatically and needs them for optimistic locking (ETag) and, later on, for draft mode. Add them from day one.

2. Test data

A console class to fill the tables. Run it with F9 whenever you want to start over:

CLASS zcl_lib_fill_data DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
ENDCLASS.

CLASS zcl_lib_fill_data IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    DATA authors TYPE STANDARD TABLE OF zlib_author WITH EMPTY KEY.
    DATA books   TYPE STANDARD TABLE OF zlib_book WITH EMPTY KEY.

    DELETE FROM zlib_book.
    DELETE FROM zlib_author.

    authors = VALUE #(
      ( author_id = '000001' name = 'Ana Rojas'   country = 'CL' )
      ( author_id = '000002' name = 'Carlos Soto' country = 'AR' )
      ( author_id = '000003' name = 'Elena Vidal' country = 'ES' ) ).

    books = VALUE #(
      ( book_id = '00000010' title = 'Birds of the South' author_id = '000001' pages = 320
        isbn = '978-0-0000-0010-1' rating = 4 price = '24.90' currency_code = 'USD' )
      ( book_id = '00000011' title = 'Mountain Rivers'    author_id = '000001' pages = 180
        isbn = '978-0-0000-0011-1' rating = 5 price = '18.50' currency_code = 'USD' )
      ( book_id = '00000012' title = 'The Last Train'     author_id = '000002' pages = 410
        isbn = '978-0-0000-0012-1' rating = 3 price = '21.00' currency_code = 'USD' )
      ( book_id = '00000013' title = 'Winter Tales'       author_id = '000003' pages = 120
        isbn = '978-0-0000-0013-1' rating = 4 price = '12.00' currency_code = 'EUR' ) ).

    LOOP AT books ASSIGNING FIELD-SYMBOL(<book>).
      TRY.
          <book>-book_uuid = cl_system_uuid=>create_uuid_x16_static( ).
        CATCH cx_uuid_error.
          out->write( 'Could not generate the UUID' ).
          RETURN.
      ENDTRY.
    ENDLOOP.

    INSERT zlib_author FROM TABLE @authors.
    INSERT zlib_book FROM TABLE @books.

    out->write( |{ lines( authors ) } authors and { lines( books ) } books loaded| ).
  ENDMETHOD.
ENDCLASS.

It is the same VALUE #( … ) syntax from lesson 1, now typed against the real tables. The client field is left out: the system sets it.

3. The author view

A simple CDS View Entity on top of zlib_author. It will be the target of an association:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Library: authors'
define view entity ZI_LibAuthor
  as select from zlib_author
{
  key author_id as AuthorId,
      name      as Name,
      country   as Country
}

Field names go from snake_case (table) to CamelCase (CDS). That is the convention you will see across RAP code.

4. The book Root View Entity

The root entity is the head of the business object: everything else (behavior, projection, service) hangs from it. By convention it carries the R_ prefix; the projection will use C_ and a reusable interface, I_.

@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
@EndUserText.label: 'Library: books'
define root view entity ZR_LibBook
  as select from zlib_book
  association [0..1] to ZI_LibAuthor as _Author
    on $projection.AuthorId = _Author.AuthorId
  association [0..1] to I_Currency   as _Currency
    on $projection.CurrencyCode = _Currency.Currency
{
  key book_uuid             as BookUuid,
      book_id               as BookId,
      title                 as Title,
      author_id             as AuthorId,
      pages                 as Pages,
      isbn                  as Isbn,
      rating                as Rating,
      @Semantics.amount.currencyCode: 'CurrencyCode'
      price                 as Price,
      @Consumption.valueHelpDefinition: [ {
        entity.name: 'I_CurrencyStdVH',
        entity.element: 'Currency',
        useForValidation: true
      } ]
      currency_code         as CurrencyCode,
      @Semantics.user.createdBy: true
      local_created_by      as LocalCreatedBy,
      @Semantics.systemDateTime.createdAt: true
      local_created_at      as LocalCreatedAt,
      @Semantics.user.localInstanceLastChangedBy: true
      local_last_changed_by as LocalLastChangedBy,
      @Semantics.systemDateTime.localInstanceLastChangedAt: true
      local_last_changed_at as LocalLastChangedAt,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at       as LastChangedAt,

      // Public associations
      _Author,
      _Currency
}

Associations

  • association [0..1] to ZI_LibAuthor as _Author on … declares the relationship: each book has at most one author. The _ prefix is a convention.
  • $projection.AuthorId refers to the field as named in this view (AuthorId), not in the table.
  • An association is lazy: no JOIN happens until someone uses it (for example, _Author.Name).

Typical mistake: declaring the association and forgetting to list it at the end (_Author, _Currency). The association exists, but nobody outside the view can use it.

Value help on the currency

@Consumption.valueHelpDefinition connects the field to I_CurrencyStdVH, a standard value help view. With useForValidation: true, the Fiori UI will also reject currencies that do not exist.

Administrative @Semantics annotations

They tell RAP which field is “created by”, “changed at” and so on. With them, the framework fills those fields automatically once we add behavior.

5. Check the result

Activate everything (Ctrl+Shift+F3), open ZR_LibBook and press F8 (Data Preview):

  • you should see the four books;
  • right-click a row → Follow Association → _Author to jump to the book’s author.

If you see the data and the navigation works, the data model is ready. In the next lesson we expose it as an OData service and open it in a Fiori app without writing a single line of UI5.