Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
jrgkraus
Active Contributor
An application has its types: table types, structures, even types for references to classes may be useful. In order to make types accessible to all classes of our application, it's obvious that they should go to a central point, which could be a class or an interface.

I personally prefer a class for types, because an interface has a different usage: it is made for being implemented by another class. Types are only declarations that should be available from different classes. But all what I am going to talk about works for interfaces and classes as well.

So we start our application in our brand new packet, let's name it ZAPP.

The types class


class zcl_app_types definition.
public section.
types awesome_data_line type some_structure_from_ddic.
types awesome_data_lines type standard table of awesome_data_line with empty key.
endclass.

Now we are ready to use it:
class zcl_app_backend definition.
public section.
methods do_the_magic_on_table
importing data_line type zcl_app_types=>awesome_data_lines.

methods do_the_magic_on_line
importing data_line type zcl_app_types=>awesome_data_line.

private section.
data awesome_data_lines type zcl_app_types=>awesome_data_lines.
endclass.


Use a shorter alias for the type class


But how can we make it slimmer? It's pretty noisy having always the ZCL_APP_TYPES as a prefix for each type. In real live, the name may be much longer (real live example: ZCL_MM00_PURCH_HISTORY_TYPES). With interfaces, we could use aliases to map types to local names. But this means creating a new alias for each new type of the local class which is quite noisy too.

With a type class, we can do a mapping with a simple trick: creating a data object with reference to the class. The data object offers all public static elements such as types. Using this, the class transforms like this:
class zcl_app_backend definition.

public section.
data types type ref to ZCL_APP_TYPES.

methods do_the_magic_on_table
importing data_line type types->awesome_data_lines.

methods do_the_magic_on_line
importing data_line type types->awesome_data_line.

private section.
data awesome_data_lines type types->awesome_data_lines.
endclass.

 

Types from other APIs


Now we have types-> as a prefix for all types of out application-own types class. When we use types from other API applications, the name extends a bit:
data common_types type ref to zcl_abap_common_types.
...
methods get_binary_lines
returning(result) type common_types->ty_binary_lines.

 

When use types classes


I personally keep all types that are used in more than one class in my types class. In other words: no public types if not in the type class. However, there are always exceptions to the rule. In DB_ACCESS classes that are intended to only transfer data from the data base to the application, I sometimes define my result table directly in the interface:
interface zif_app_db_access.

types settings type standard table of zdb_pp01_001 with empty key.

methods read_settings
returning(result) type settings.

This is OK for me as long as I never see
type zif_app_db_access=>settings

in my code. As soon a the type is needed in other classes than the DB_ACCESS class, I would transfer it to zcl_app_types.

Where is the benefit?


To me, it is very simple to just use types->... to have access to the types that my app is using. Using the editor's code completion I can see directly every type already available. Since every type is in one class, no ambiguous names occur. (think of zif_db_access=>ty_data, zcl_app_backend=>ty_data...). Distinguishing between types and variables is also easier for the types look always like type->data_lines while the variable appears as data_lines only.

The shorter naming extends also legibility because code lines are kept shorter. Each set of types can have a clear name that replaces the global name of the class.
34 Comments
Andre
Explorer
Hi Jörg,

I personally use a lot the types declaration within a class but I have to disagree with you on one point: "When use types classes - I personally keep all types that are used in more than one class in my types class".

If you need to use the same type in more than one place, I would always suggest creating it in SE11.

 

Cheers,

André

 

 
jrgkraus
Active Contributor
0 Kudos
To me, a DDIC type is useful when using it in user interaction. Otherwise defining texts for labels in different sizes and languages seems needless to me. I prefer to define types that serve for program logic and not for user interaction directly in ABAP code. This is also less time-consuming.
Jelena
Active Contributor
I agree with this philosophy. If it's a purely "technical" type, i.e. we don't need any semantic definitions and don't plan taking advantage of anything associated with dictionary types (i.e. it won't be anything user-facing or used in a service definition that can be generated, and such) then there seems to be no advantage in using dictionary. Granted, SE11 has better "discoverability" but it also means it can easily become polluted, especially when similarly named elements are used in different contexts with different definitions.

It's all about the use case, plain put.

Thanks for the blog!
Sandra_Rossi
Active Contributor
0 Kudos
I guess one argument in favor of defining a type in the DDIC is to avoid having errors during the transport of repository objects, by transporting the DDIC objects first in a dedicated request (as far as a DDIC type does not refer to a class or an interface...)

But there is the Transport Check tool to avoid those errors too...
Michael_Keller
Active Contributor
As a note: If a class isn't a type class and you discover a lot of types statements in it, then this can be a hint that the class has too many tasks (see "Separation of concerns").
former_member610590
Participant
0 Kudos
I think for NetWeaver is not the best option to use types in class. DDIC is the more preferable.

The main reason: when the types in class or interface is changing , the program where it is used is  forced to regenerate and it could cause the syntax errors (LOAD_PROGRAMM_MISMATCH) during the transporting.

Another option is DTO (but it is differ from types).
former_member610590
Participant
0 Kudos
what is the difference between DDIC-structure and public type of interface?

in both cases it is in server memory, but in DDIC it is more manageable and clear to check and compare.
Sandra_Rossi
Active Contributor
0 Kudos
What is DTO?
jrgkraus
Active Contributor
Sincerely I do not get your point Wouldn't a change of a ddic type also cause a regeneration of the program? Whether you have types in DDIC or a class, the repo objects must be always aligned  before running the program.

In case of a type class with 100 types you have one repo object to align. When each type is in ddic you have 100 repo objects to align. I prefer the first one.
former_member610590
Participant
DTO on wiki

 

Data Transfer Object sample:
CLASS zcl_order_conf_dto DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .

PUBLIC SECTION.
DATA mv_kunnr TYPE kunnr READ-ONLY.

METHODS set_customer
IMPORTING iv TYPE kunnr.

METHODS set_any_other_field
IMPORTING ivr TYPE REF TO char10.

METHODS get_any_other_field
RETURNING VALUE(rv) TYPE REF TO char10.

PROTECTED SECTION.
PRIVATE SECTION.
DATA mv_any_other_field TYPE REF TO char10.

ENDCLASS.



CLASS zcl_order_conf_dto IMPLEMENTATION.
METHOD set_customer.
mv_kunnr = iv.
ENDMETHOD.

METHOD set_any_other_field.
mv_any_other_field = ivr.
ENDMETHOD.

METHOD get_any_other_field.
rv = mv_any_other_field.
ENDMETHOD.

ENDCLASS.

 

 

 
former_member610590
Participant
in case class will be used in several reports/classes/functions - all this functions would be regenerated even if you change .

in case you have 100 separate objects - small change will cause regeneration only where exactly it  one changed object is used.

For global types abap_help mostly tells us that declaration is up to context, but provide recommendation about separation of concerns and semantics meaning.

ABAP declaration

 
jrgkraus
Active Contributor
I understand the point. But I do not see the disadvantage - when a change comes with transport order, why should i be concerned whether 10 or 40 programs are regenerated?

As described above, the type classes are application-exclusive. So the where-used lists of them are not too big.

Another thing is the name: a ddic type can have a 30 character name. Depending on your naming conventions (or even worse a name space) few space may remain for a clear and descriptive name.

Having the type in a class, you can use all of the 30 characters available to the name. Since the type class is application-exclusive, no prefixes or namespaces are needed.

 
former_member610590
Participant

But I do not see the disadvantage – when a change comes with transport order, why should i be concerned whether 10 or 40 programs are regenerated?

yes, you are correct. it depends on the situation. And sometimes we should not care about this. Agree. But sometimes we should.

Having the type in a class, you can use all of the 30 characters available to the name. Since the type class is application-exclusive, no prefixes or namespaces are needed.

? However in DDIC I could put also documentation and make reference to data element.

 

PS. Actually I am using both types in class/interface and DDIC and agree with you point but trying to provide another approach.

Mostly I am using types in class + DTO and it provides more semantic information for code understanding.

abo
Active Contributor
0 Kudos
lars.hvam what are your thoughts on this? Especially the part on public types: "no public types if not in the type class", is it an approach we could adopt?
larshp
Active Contributor
I have not read the full blog, but it seems like a anti-pattern, the types class will become a god object

Define the types in the interfaces where they belong with the logic/abstraction, define types in ddic if its used in databases or dynpros(for texts)
jrgkraus
Active Contributor
0 Kudos
I am creating own types classes for each application. Why should these have the smell of a god class?
larshp
Active Contributor
0 Kudos
If an application is small, it will not matter, but applications tend to grow over time.

Take abapGit as an example, would it be considered an application? It has around 530 TYPES definitions.
jrgkraus
Active Contributor
It's true that one should not have one class with 500 types in it. As applications grow, things should be drilled down to smaller entities. So do types classes. Typically, I add sub packages when applications grow big. so I can divide also my types classes.
jrgkraus
Active Contributor
? However in DDIC I could put also documentation and make reference to data element.

For a reader of my code, the documentation is several clicks away.

Say, we have an application in a package ZMM01_MRP_UTIL. Using a types class, I can name a needed type freely
methods read_storage_loc_mrp_parms
importing number type matnr
returning value(result) type types->storage_loc_mrp_parameters.

Using a DD type, I have to take care of the name in order to avoid conflicts with other application. It could lead to a coding like this:
methods read_storage_loc_mrp_parms
importing number type matnr
returning value(result) type /xxx/st_mm01_mrp_util_stl_prms.

Personally, I find the first example better to read.

References to data elements can be made in type declarations too.

 
former_member628138
Discoverer
0 Kudos

This code throws error:

class zcl_app_backend definition.

public section.
data types type ref to ZCL_APP_TYPES.

methods do_the_magic_on_table
importing data_line type types->awesome_data_lines.
...

That's because the object "types" is not instantiated in the defintion section. I don't know whether such issue depends on de abap version (I'm using 7.4 sp6)

Anyway I've used this method in the implementation section but only after starting the "types" object in the constructor:

  METHOD constructor.
super->constructor( ).
me->types = NEW #( ).
....
ENDMETHOD.

 

I found that having a class as a types and constants container has some nice advantages if you need some semi-constant data. For instance:

CLASS ZCL_MY_TYPES IMPLEMENTATION.

METHOD constructor.
"t_some_static_range is defined in the public section
me->t_some_static_range = value #(
( sign = 'I' option = 'EQ' low = 'some_static_value_1' )
( sign = 'I' option = 'EQ' low = 'some_static_value_2' )
).
ENDMETHOD.

ENDCLASS.

* Then create the object:

data(o_types) = new zcl_my_types( ).

* And use it in any place:

if v_some_vble in o_types->t_some_static_range.
...
endif.



 

 

jrgkraus
Active Contributor
0 Kudos
I am on 750 SP 19 - here it works.

However - meanwhile I found out that generating the test frame for classes (when pressing F8 from the editor) does not work with declarations like this. It seems that ABAP (in my version) is able to access the types of a non-instantiated object variable, but the code generator is not able to handle it.

Therefore, I switched to creating the types class as abstract and derive a local class-relevant class from it:
class zcl_sd37_billing_print_types definition
public
abstract
create public .

public section.
types: ....


*(class-relevant local types of consumer class)

class types definition inheriting from zcl_sd37_billing_print_types final.
endclass.

*(class coding)
public section.
methods read
importing linked_orderid type zcl_sd37_billing_print_types=>linked_orderid

private section.
data linked_orderid type types=>linked_orderid.




Note that with this approach (as shown abovd), you will have to use the full class name in the public section
former_member610590
Participant
0 Kudos
Personally, I find the first example better to read.

 

I think it depends on whether the method public or private.

In case you have incapsulated logic (or group of class) - it would be very nice to have type in interface or class as you specified.

But in case you have many-points to use type (structure or data element) - it would be more clear to use DDIC-structure.

Another score for DDIC is parameters in RFC/UPDATE TASKs and OData-services. It is impossible there to use interfaces types. and in that case where used list would show that object is part of some integration. in case of interface type you would not find it.
Sergiu
Contributor
0 Kudos

How to reference to a parameter of a method when the type is not defined in the class?

 

class zcl_app definition.
public section.
methods do_the_magic_on_table
importing data_lines type standard table of mara with empty key.

 

Something like this:

data: data_lines_from_method like zcl_app=>do_the_magic_on_table=>data_lines.

 

jrgkraus
Active Contributor
0 Kudos
Why should you abuse a parameter definition as a type defintion? Define your type in the types class:

 
class zcl_app_types.

public section
types mara_Lines type standard table of mara with empty key.

then refer to the type from both the method definition and the data line.
JaschaS
Explorer
0 Kudos
I use the same approach, but I am facing a problem on how to correctly determine the domain at runtime of a type that uses your kind of "alias".

Say, I have a class y_owner with one type and a setter method with an input parameter of that class type.
class y_owner definition
public
create public .

public section.

types:
type type wcb_owner_type.

methods:
set_type
importing iv_type type y_owner=>type.

WCB_OWNER_TYPE is a domain with fixed values. I want to write a unit test for setting valid values, and for that I want to get all fixed domain values of "type" (WCB_OWNER_TYPE).

Before I can do that, I need the domain name of the field, so WCB_OWNER_TYPE.

But I can't get it through RTTI, because the absolute name is obfuscated by the class type:

RTTI of iv_type


This also leads to the fact that it is not recognized as a DDIC type, despite it actually being one, but underneath that "class type".

I don't want to hardcode the domain name into my tests and I also want to keep using the "class type".

Any idea? I'm on 7.56.
jrgkraus
Active Contributor

Hello,

you can use the public attribute HELP_ID of the class cl_abap_typedescr to determine the domain at run time. See my example (note that I use the abstract class approach mentioned in my comment above):

*&---------------------------------------------------------------------*
*& Report zp_tmp_sapblog_types
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
report zp_tmp_sapblog_types.

class types definition inheriting from zcl_sapblog_types abstract.
endclass.

class app definition.
public section.
methods main
returning value(result) type types=>my_vbtyp.

endclass.

class app implementation.

method main.
result = '>'.
endmethod.

endclass.


class test definition for testing duration short risk level harmless.
public section.

private section.
data cut type ref to app.

methods setup.

methods test_main for testing.
endclass.

CLASS test IMPLEMENTATION.

METHOD setup.
cut = new #( ).
ENDMETHOD.

METHOD test_main.
DATA: domain_values TYPE STANDARD TABLE OF dd07v.
data(result) = cut->main( ).

data(descr) = cast cl_abap_elemdescr( cl_abap_typedescr=>describe_by_data( result ) ).
data(dom_name) = conv ddobjname( descr->help_id ).

call function 'DDUT_DOMVALUES_GET'
exporting
name = dom_name
tables
dd07v_tab = domain_values.

cl_abap_unit_assert=>assert_table_contains( table = domain_values
line = result ).
ENDMETHOD.

ENDCLASS.
JaschaS
Explorer
0 Kudos
Thanks, Jörg! I had noticed the help_id, but could not be certain that it always contains the domain.
JaschaS
Explorer
0 Kudos

@jrgkraus I've unfortunately found that not to be true all the time. For variables typed by data element LVC_VALUE, the help_id of the cl_abap_elemdescr doesn't contain the domain name TEXT128, but instead contains LVC_VALUE again.

Do you know of any other alternative to get the domain through RTTI? I'm still on 7.56, on-prem.

shais
Participant
0 Kudos

@JaschaS ,

In case of DDIC type, you may use CL_ABAP_ELEMDESCR->GET_DDIC_FIELD( )-DOMNAME

JaschaS
Explorer
0 Kudos

@shais Yes, thanks, for DDIC I'm aware of that. But I'm looking for non-DDIC too.

shais
Participant
0 Kudos

Non-DDIC don't have a domain 🙂
You might be looking for something else.

JaschaS
Explorer
0 Kudos

@shais Ok, I was a bit unprecise there, let me rephrase it 😊

Back in my post from 04-28-2023 4:17 PM, I described the problem I'm coming from. I used the approach that is demonstrated in this blog post. But getting the domain of a variable that is defined in such way doesn't work, even if the underlying type is in fact a DDIC type.

I've prepared a code example, trying to get the domain from either help_id or get_ddic_field()

Spoiler
class ycl_0000ca_rtts definition
  public final
  create public.

  public section.
    " The data element keyflag has a same-named domain with two fixed values
    types test_type1 type keyflag.
    " LVC_VALUE is based on domain text128
    types test_type2 type lvc_value.

    class-data test_variable1 type ycl_0000ca_rtts=>test_type1.
    class-data test_variable2 type ycl_0000ca_rtts=>test_type2.

    interfaces if_oo_adt_classrun.
endclass.

class ycl_0000ca_rtts implementation.
  method if_oo_adt_classrun~main.
    data elemdescr type ref to cl_abap_elemdescr.

    " Test help_id for type 1:
    elemdescr ?= cl_abap_datadescr=>describe_by_data( test_variable1 ).
    out->write( |Help ID: { elemdescr->help_id } (should be keyflag)| ).
    " OK - this works because data element and domain have incidentally the same name

    " Test help_id for type 2:
    elemdescr ?= cl_abap_datadescr=>describe_by_data( test_variable2 ).
    out->write( |Help ID: { elemdescr->help_id } (should be text128)| ).

    " The following raises exception no_ddic_type
*    data(domain_name) = elemdescr->get_ddic_field( )-domname.
  endmethod.
endclass.

So whatever help_id is, it's not the domain.

And get_ddic_field( ) doesn't work, although deep down, the variable is really based on DDIC.

Probably the information gets lost, because that's how the actual types look in the debugger:

JaschaS_0-1712920967816.png

 

shais
Participant

@JaschaS ,
Oh, I see.
Basically this is because the help ID returns the data element and not the domain (or the table field in case you have used a table field reference in your type definition. e.g. VBAK-VBELN).

Anyway, the following code should do the trick:

 

elemdescr ?= cl_abap_datadescr=>describe_by_data( test_variable2 ).
if elemdescr->help_id is not INITIAL.
  elemdescr ?= cl_abap_datadescr=>describe_by_name( elemdescr->help_id ).
  domname = elemdescr->get_ddic_field( )-domname.
endif.

 

 

JaschaS
Explorer

@shais 

Smart! Thank you! Now I just hope that help_id really is the data element and doesn't turn out to be something else again in some cases. But a short test left me optimistic 😊

If SAP could add that as a method to cl_abap_elemdescr, that would be perfect 😄