This is a test message to test the length of the message box.
Login
|
ABAP in Practice Object Generator
Created by Software-Heroes

ABAP in Practice - Object Generator

1211

In this example, we will look at how to create a reusable generator using the XCO library to save ourselves some work for our tutorials and to automatically generate DDIC objects.

Advertising


In this article, we'll put it into practice and create a generator for creating new objects in the DDIC area. This should save us manual work in future tutorials.

 

Introduction

Sometimes you want to try something new, but you're already annoyed by the process of creating the table and generating proper data elements? Then a generator can help you.

 

Task

The task sounds very simple at first: we need a generator that creates data elements and domains for us and works with minimal configuration. This will save us some work the next time we want to create models and databases. If it is built to be reusable, we can also use it to generate mass data for each participant in training sessions.

 

Hint: We will discuss the solution in the next section. If you would like to complete the task independently first, you should pause here.

 

Solution

Let's go through the different parts of the development step by step.

 

Basic Structure

In the first step, we provide a clean basic structure for the objects. We want to provide a small framework using ABAP OO while adhering to the principles of testability and reusability. Therefore, in addition to the class, we also generate a Factory and an Injector, which we use to start almost any development process. To ensure the system works smoothly, we use our IDE Actions to generate the classes.

 

After formatting the objects, we can then move on to the actual implementation.

 

Structures

To create the objects, we should first create a configuration in the form of structures and tables. In most cases, we want to pass a structure, table, or object to the method to keep the interface small, clean, and easily extensible. In this case, we create a structure and start with the internal objects. Let's begin with the configuration for domains and data elements.

TYPES: BEGIN OF domain,
         name           TYPE sxco_ad_object_name,
         description    TYPE if_xco_cp_gen_doma_s_form=>tv_short_description,
         base_type      TYPE string,
         length         TYPE i,
         decimals       TYPE i,
         case_sensitive TYPE abap_boolean,
       END OF domain.
TYPES domains TYPE STANDARD TABLE OF domain WITH EMPTY KEY.

TYPES: BEGIN OF data_element,
         name        TYPE sxco_ad_object_name,
         description TYPE if_xco_cp_gen_dtel_s_form=>tv_short_description,
         label       TYPE string,
         domain      TYPE sxco_ad_object_name,
       END OF data_element.
TYPES data_elements TYPE STANDARD TABLE OF data_element WITH EMPTY KEY.

Later, we want to be able to create abstract entities with the data elements, so we also define configurations for the entity's field list and the entity itself.

TYPES: BEGIN OF field,
         name         TYPE sxco_cds_field_name,
         data_element TYPE sxco_ad_object_name,
         currency     TYPE string,
         unit         TYPE string,
       END OF field.
TYPES fields TYPE STANDARD TABLE OF field WITH EMPTY KEY.

TYPES: BEGIN OF abstract_entity,
         name        TYPE sxco_cds_object_name,
         description TYPE if_xco_cp_gen_dtel_s_form=>tv_short_description,
         fields      TYPE fields,
       END OF abstract_entity.
TYPES abstract_entities TYPE STANDARD TABLE OF abstract_entity WITH EMPTY KEY.

 

With this, we have defined all subordinate types and can now define the actual configuration structure. For this, we also want the package for creation, the transport, and a checkfix. We need the first two values depending on the system. We enter the configuration into the generator via the other fields.

TYPES: BEGIN OF ddic_configuration,
         package           TYPE sxco_package,
         transport         TYPE sxco_transport,
         prefix            TYPE string,
         domains           TYPE domains,
         data_elements     TYPE data_elements,
         abstract_entities TYPE abstract_entities,
       END OF ddic_configuration.

 

Hint: With the initial setup, we have already established our basic structure for the system. Therefore, every data element in our system essentially has a domain that defines its type and technical details.

 

Configuration

Since we want to relieve the caller of many configuration tasks, we can easily derive some information. The XCO framework provides us with several methods for this. For example, we can deduce from the calling object which package we are working in and whether there is an open transport request for it. To do this, we could retrieve the Callstack and determine the calling object. In this case, we keep it relatively simple and pass the SY-REPID field via the factory. However, we receive the class name as "ZMY_CLASS====CP". We write a simple function to extract this name.

calling_object = substring( val = calling_object
                            len = find( val = calling_object
                                        sub = '=' ) ).

 

We can now read in the class, or at least we assume that we are being called from a class, and retrieve the package and its name via the interface IF_XCO_AR_OBJECT. Basically, we can also access an open transport via the interface IF_XCO_CTS_CHANGEABLE if the object is assigned to one.

xco_cp_abap=>class( CONV #( calling_object ) )->if_xco_ar_object~get_package( )->name.

 

If the prefix is filled, then we want to add it to all objects, since they are usually in the same namespace. This saves time and effort during input, and we could also generate different objects for different participants.

LOOP AT ddic_configuration-domains REFERENCE INTO DATA(domain).
  domain->name = ddic_configuration-prefix && domain->name.
ENDLOOP.

LOOP AT ddic_configuration-data_elements REFERENCE INTO DATA(data_element).
  data_element->name   = ddic_configuration-prefix && data_element->name.
  data_element->domain = ddic_configuration-prefix && data_element->domain.
ENDLOOP.

 

Operation

In order to generate objects, we need an OPERATION from the XCO framework. Since we want to create new objects, we use the CREATE_PUT_OPERATION method. We then pass this from method to method to create the different types.

DATA(put_operation) = xco_cp_generation=>environment->dev_system( me->ddic_configuration-transport )->create_put_operation( ).

 

We already described the generation of artifacts with XCO in a previous article. You can find more information about the possibilities and the framework in the article.

 

Domains

What actually constitutes a domain and which types do we need? We also need a structure of constants to allow the user to specify supported types. These are the types we typically use most frequently in development.

CONSTANTS:
  BEGIN OF domain_types,
    character     TYPE string VALUE `CHAR`,
    date          TYPE string VALUE `DATN`,
    time          TYPE string VALUE `TIMN`,
    integer       TYPE string VALUE `INT4`,
    integer_long  TYPE string VALUE `INT8`,
    timestamp     TYPE string VALUE `UTCLONG`,
    currency_code TYPE string VALUE `CUKY`,
    currency      TYPE string VALUE `CURR`,
    quantity      TYPE string VALUE `QUAN`,
    unit          TYPE string VALUE `UNIT`,
    decimals      TYPE string VALUE `DEC`,
    raw           TYPE string VALUE `RAW`,
    string        TYPE string VALUE `STRING`,
    short_string  TYPE string VALUE `SSTRING`,
  END OF domain_types.

 

The actual type of structure results from our requirements and the mandatory fields of the standard. In this case, we always need a description, the package for the attachment, a name, and the type. Using the operation, we add a new domain, set the target package, and obtain the specification. We can then use the specification to set properties such as description, type, and case sensitivity.

LOOP AT ddic_configuration-domains INTO DATA(domain).
  DATA(specification) = operation->for-doma->add_object( domain-name
    )->set_package( ddic_configuration-package
    )->create_form_specification( ).

  specification->set_short_description( domain-description ).

  CASE domain-base_type.
    WHEN zif_gen_objects=>domain_types-character.
      format = xco_cp_abap_dictionary=>built_in_type->char( CONV #( domain-length ) ).
    WHEN zif_gen_objects=>domain_types-date.
      format = xco_cp_abap_dictionary=>built_in_type->datn.
    WHEN zif_gen_objects=>domain_types-integer.
      format = xco_cp_abap_dictionary=>built_in_type->int4.
    WHEN OTHERS.
      CONTINUE.
  ENDCASE.

  specification->set_format( format ).
  specification->output_characteristics->set_case_sensitive( domain-case_sensitive ).
ENDLOOP.

 

Data Elements

The data elements then look quite similar to the domains. Here we have mandatory fields such as the texts that we should fill in. This keeps the logic here quite manageable.

LOOP AT ddic_configuration-data_elements INTO DATA(data_element).
  DATA(specification) = operation->for-dtel->add_object( data_element-name
    )->set_package( ddic_configuration-package
    )->create_form_specification( ).

  specification->set_short_description( data_element-description ).
  specification->set_data_type( xco_cp_abap_dictionary=>domain( data_element-domain ) ).

  DATA(label) = COND #( WHEN data_element-label IS INITIAL
                        THEN data_element-name
                        ELSE data_element-label ).

  specification->field_label-short->set_text( CONV #( label ) ).
  specification->field_label-medium->set_text( CONV #( label ) ).
  specification->field_label-long->set_text( CONV #( label ) ).
  specification->field_label-heading->set_text( CONV #( label ) ).
ENDLOOP.

 

Abstract Entities

Let's move on to the first CDS entity, which can be somewhat more complex. The beginning is the same: we define a specification and set the description. However, we then have different types that we can create. Therefore, we need our type from the specification to further define it.

DATA(abstract_entity) = specification->add_abstract_entity( ).

 

Then we transfer our fields into the entity. Since we want to define a data element for each field, we can use simple logic here. The more complex type determination thus remains in the domain. First, we add the field using ADD_FIELD and then set the type as a data element using SET_TYPE. The difficult part is finding the right example code for how the field is added and also the type. In the first step, we tried using an expression, but this puts a comma after each record, which leads to errors with an abstract entity.

DATA(cds_field) = abstract_entity->add_field( xco_cp_ddl=>field( field-name )
  )->set_type( xco_cp_abap_dictionary=>data_element( field-data_element ) ).

 

What's still missing? The entity will throw an error if we use an AMOUNT or QUANTITY field and don't link the units together. Therefore, we have to add annotations to the fields afterward. The annotations are specified without the @ symbol, and the value is concatenated using the builder. This research also took some time, as the usage wasn't immediately obvious, and the ABAP documentation didn't contain any information on how to use the method and the values.

IF field-currency IS NOT INITIAL.
  cds_field->add_annotation( 'Semantics.amount.currencyCode' )->value->build( )->add_string( field-currency ).
ENDIF.

IF field-unit IS NOT INITIAL.
  cds_field->add_annotation( 'Semantics.quantity.unitOfMeasure' )->value->build( )->add_string( field-unit ).
ENDIF.

 

Generation

Don't forget to generate the objects at the end. Here, they are generated and activated in one step and created in the system. However, you can also create them in an inactive state first if you still have some work to do or want to deal with the errors.

RETURN put_operation->execute( ).

 

Possibilities

To achieve further optimizations in the code, we can optimize a few things to make further input easier for the user:

  • Defaults - Automatic defaults for labels, descriptions, and basic types to minimize input.
  • Basic Types - Use of basic types and data elements from the standard (SPRAS, ABAP_BOOLEAN)
  • Error Handling - The object cannot be created; a log is returned. Does the object already exist, or are required fields missing?
  • JSON - Currently, the generator uses a structure for generation; however, it is also quite easy to read and use JSON from the system or a Git repository as configuration.

 

Test

Now that we have defined the logic, we should test it and create a few objects. We use a unit test for this, but that's not best practice in this case, as a test shouldn't modify objects in the system or would require the appropriate classification. Let's populate our structure for configuration.

DATA(abstract_entities) = VALUE zif_gen_objects=>abstract_entities(
    ( name        = 'ZGEN_S_TestGeneratedStructure'
      description = 'Automatic generated'
      fields      = VALUE #( ( name = 'KeyField' data_element = 'KEY_FIELD' )
                             ( name = 'SalesVolume' data_element = 'AMOUNT' currency = 'SalesCurrency' )
                             ( name = 'SalesCurrency' data_element = 'CURRENCY' ) ) ) ).

DATA(data_elements) = VALUE zif_gen_objects=>data_elements(
    ( name = 'KEY_FIELD' domain = 'KEY_FIELD' description = 'Key' )
    ( name = 'AMOUNT' domain = 'AMOUNT' description = 'Amount' )
    ( name = 'CURRENCY' domain = 'CURRENCY' description = 'Currency' ) ).

DATA(domains) = VALUE zif_gen_objects=>domains(
    ( name = 'KEY_FIELD' base_type = zif_gen_objects=>domain_types-character length = 7 )
    ( name = 'AMOUNT' base_type = zif_gen_objects=>domain_types-currency )
    ( name = 'CURRENCY' base_type = zif_gen_objects=>domain_types-currency_code ) ).

DATA(config) = VALUE zif_gen_objects=>ddic_configuration( prefix            = 'ZGEN_DEMO_'
                                                          domains           = domains
                                                          data_elements     = data_elements
                                                          abstract_entities = abstract_entities ).

 

Then we create an instance via the factory, pass in the data, and let the system create the objects.

DATA(generator) = zcl_gen_objects_factory=>create_generator( sy-repid ).
DATA(result) = generator->generate_ddic( config ).

 

After the logic has hopefully run without major errors, we should find our objects in the current package after a refresh.

 

In the Core Data Service, the fields have been defined as desired, the relationship to the currency has been entered, and all objects have been activated. Therefore, the test is successful for us.

define abstract entity ZGEN_S_TestGeneratedStructure
{
  KeyField      : zgen_demo_key_field;
  @Semantics.amount.currencyCode: 'SalesCurrency'
  SalesVolume   : zgen_demo_amount;
  SalesCurrency : zgen_demo_currency;
}

 

Complete Example

You can find the current version of the generator in this GitHub repository. This repository is also the basis for the next article, where we will create the first objects via configuration to get started quickly.

 

Conclusion

Working with the XCO library is not always easy, and if we are not yet very familiar with the API, finding examples and the right method can take a while. In the end, we obtain an ABAP Cloud-compatible generator that will save us some work in the future.


Included topics:
QuickABAP in PracticeXCOGenerator
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Tuesday and Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


ABAP Quick - Logging Performance

Category - ABAP

What about the performance of the BAL log in the ABAP Cloud world? Let's look at three solutions and measure their performance in different scenarios.

12/19/2025

ABAP in Practice - Fiori Data incorrect

Category - ABAP

In this short practical example, we'll look at a Fiori error. Here, the data is displayed incorrectly in the UI, even though everything else appears to be correct. The trail leads us in a different direction through the RAP stack.

10/10/2025

ABAP Quick - Handling of Function modules

Category - ABAP

How do you actually handle function modules and error handling within ABAP? In this short tip, we'll also look at handling them within the context of RFC.

08/26/2025

ABAP Quick - Generic Data Types

Category - ABAP

What exactly distinguishes CLIKE from CSEQUENCE? Generic types can sometimes be a bit opaque, and as ABAP developers, we might choose a type that's too generic.

08/12/2025

BTP - Quick Deployment

Category - ABAP

Want to make Fiori Elements apps available even faster after RAP modeling? Quick Deployment is now possible.

07/18/2025