Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

Object-oriented development is more and more often used and more common use. The advantages of object-oriented programming have spread over the world in the last years. With this triumph new methodologies and approaches to software development went along. One of these approaches are so-called "Design Patterns".

Design Patterns are more and more often used and discussed by ABAP developers. In this blog I will give you an example for the usage of the design pattern “Singleton” in BW 7.x transformations. I assume you have at least a basic knowledge of ABAP/OO and know the basic ABAP conecpts, espeicially logical lock objects. Furthermore I assume you are familiar with transforamtions and start- and endroutines.

 The idea of “Design Patterns” has been developed in the 90s by the so-called “Gang of four” Erich Gamma, Richard Helm, Ralph E. Johnson and John Vlissides to improve the object-oriented software development. They discovered repeating “patterns” of software and gave abstract solutions for these “patterns” or reusable pieces of code. For more information on design patterns I can only highly recommend the book “Design Patterns - Elements of Reusable Oject-Oriented Software” by these famous four. You can order it here .

One of these design patterns is “Singleton”. You can find an explanation of this special design pattern here in SDN wiki and also an easy sample implementation.

But where’s the link to BI? When could a Singleton class be useful in BW staging?

 You all know these from your daily work: You have your transformation between your DSOs and Cubes. You have created a code to derive e.g. the customer market from customer. Usually you solve such look-ups with a combination of “SELECT” into internal table and “READ TABLE” afterwards.

Your startroutine looks something like this:

$$ begin of 2nd part global - insert your code only below this line </pre><pre>data: gt_customer type table of /bi0/pcustomer.</pre><pre>$*$ end of 2nd part global - insert your code only before this line </pre><pre>--


</pre><pre> CLASS routine IMPLEMENTATION</pre><pre>*
</pre><pre></pre><pre>*

</pre><pre>CLASS lcl_transform IMPLEMENTATION.</pre><pre>

</pre><pre> Method start_routine</pre><pre>*

</pre><pre> Calculation of source package via start routine</pre><pre>*

</pre><pre> <-> source package</pre><pre>*

</pre><pre>METHOD start_routine.</pre><pre>=== Segments ===</pre><pre>FIELD-SYMBOLS:</pre><pre><SOURCE_FIELDS> TYPE tys_SC_1.</pre><pre>DATA:</pre><pre>MONITOR_REC TYPE rstmonitor.</pre><pre>$$ begin of routine - insert your code only below this line -</pre><pre>select * from /bi0/pcustomer into table gt_customer.</pre><pre>@</pre><pre>$$ end of routine - insert your code only before this line -</pre><pre>ENDMETHOD. "start_routine</pre><p>In the field routine you do the read table and read the attribute you need:</p><pre>CLASS lcl_transform IMPLEMENTATION.</pre><pre>METHOD compute_0CUST_MKT.</pre><pre>* IMPORTING</pre><pre>* request type rsrequest</pre><pre>* datapackid type rsdatapid</pre><pre>* SOURCE_FIELDS-CUSTOMER TYPE /BI0/OICUSTOMER</pre><pre>* EXPORTING</pre><pre>* RESULT type tys_TG_1-CUST_MKT</pre><pre>DATA:</pre><pre>MONITOR_REC TYPE rsmonitor.</pre><pre>$$ begin of routine - insert your code only below this line -</pre><pre>data: ls_customer type /bi0/pcustomer.</pre><pre>read table gt_customer into ls_customer</pre><pre>with key customer = source_fields-customer.</pre><pre>RESULT = ls_customer-cust_mkt.</pre><pre>$$ end of routine - insert your code only before this line -</pre><pre>ENDMETHOD. "compute_0CUST_MKT</pre><p>I bet you have this customer look-up not only in this transformation but in many other transformations. One solution to avoid duplicated code or coding islands is using a function module. You define your internal table as static. By the first call of the function module the select on the customer table is executed, you buffer the data in your internal table and then you do the look-up for your customer marketstomer with a “READ TABLE” statement.</p><p>Fllowing ABAP-function does the same:</p><pre>FUNCTION ZKC_LOOKUP_CUSTOMER.</pre><pre>*"

</pre><pre>""Lokale Schnittstelle:</pre><pre>" IMPORTING</pre><pre>" REFERENCE(P_CUSTOMER) TYPE /BI0/PCUSTOMER-CUSTOMER</pre><pre>" EXPORTING</pre><pre>" REFERENCE(P_CUST_MKT) TYPE /BI0/PCUSTOMER-CUST_MKT</pre><pre>*"
--


statics: gt_customer type table of /bi0/pcustomer.

data: ls_customer type /bi0/pcustomer.

if gt_customer is initial.

select * from /bi0/pcustomer into table gt_customer.

endif.

read table gt_customer into ls_customer

with key customer = p_customer.

if sy-subrc = 0.

p_cust_mkt = ls_customer-cust_mkt.

else.

p_Cust_mkt = ''.

endif.

ENDFUNCTION.

Hey, that’s fine – a good solution.

But what if you have more than one look-up? If you want to know if anyone has written a look-up for a material group from material master? You have to ask your colleague. Ideally one of your colleagues has created a function group where all look-up FMs are collected. You still have to find out the name of the right FM.

Wouldn’t it be easier if there is one central point for all of your look-ups and easy to call?

Right -  ABAP Classes can help you! You create a class “ZCL_LOOKUP_CENTRAL” and for each look-up you define a method, e.g. GET_MATERIALGROUP_FROM_MATERIAL” or “GET_CUSTOMERGROUP_FROM_CUSTOMER”. You define class private attributes for your tables. In the constructor of the class you execute “SELECT” into your class attributes. In the method you execute “READ TABLE” and return the result. 

Your class in ABAP-code would look like that:

 Class: ZCL_LOOKUP_CENTRAL

Attributes
Private Attribute
GT_CUSTOMER  Inst  customer data  TYPE TABLE_CUSTOMER

Methods

Public Methods
CONSTRUCTOR
Instance methode

method CONSTRUCTOR.
  select * from /bi0/pcustomer into table gt_customer.
endmethod.

GET_MARKET_FROM_CUSTOMER
Instance methode

Importing-Parameter
 PI_CUSTOMER TYPE /BI0/PCUSTOMER-CUSTOMER 

Exporting-Parameter
 PE_CUST_MARKET TYPE /BI0/PCUSTOMER-CUST_MKT 

method GET_MARKET_FROM_CUSTOMER.
data: ls_customer type /bi0/pcustomer.

  read table gt_customer into lS_customer
    with key customer = pi_customer.
  if sy-subrc = 0.

 pe_cust_market = ls_customer-cust_mkt.

 else.

pe_cust_market = ''.

endif.
endmethod.

In the start- or end-routine of your transformation you create an object of this class and then call the method to lookup the data.

Your transformation looks like that:

$$ begin of 2nd part global - insert your code only below this line </pre><pre>data: gt_customer type ref to zcl_lookup_central.</pre><pre>‘ end routine

METHOD end_routine.

=== Segments ===</pre><pre>FIELD-SYMBOLS:</pre><pre><RESULT_FIELDS> TYPE tys_TG_1.</pre><pre>DATA:</pre><pre>MONITOR_REC TYPE rstmonitor.</pre><pre>$*$ begin of routine - insert your code only below this line -</pre><pre>create object gt_customer.</pre><pre>loop at RESULT_PACKAGE assigning <result_fields>.</pre><pre>CALL METHOD GT_CUSTOMER->GET_MARKET_FROM_CUSTOMER</pre><pre>EXPORTING</pre><pre>PI_CUSTOMER = <RESULT_FIELDS>-customer</pre><pre>IMPORTING</pre><pre>PE_CUST_MARKET = <RESULT_FIELDS>-cust_mkt.</pre><pre>modify RESULT_PACKAGE from <result_fields>.</pre><pre>endloop.</pre><pre>$$ end of routine - insert your code only before this line -

ENDMETHOD. "end_routine

 Hey- good, this works!

But what does this mean in praxis?  For each new datapackage a new instance of this class is created. One and the same table is selected over and over again, but it’s very improbable that data has changed. So it would be good to have a “static” class. Unfortunately classes can’t be static, methods only. It would be very sufficient to have only one instance of this class. At the first call of this class the data is selected from the tables and for each next call only the results will be returned, no more select on the tables. Now we are back to Design Patterns. The pattern “Singleton” is a possible solution!

You call your class with a static class method “GET_INSTANCE”. This method is the only way to access an existing instance or to create a new one. Your constructor method has to be private.

Your “SINGLETON” class will look like that:

Class: ZCL_LOOKUP_CENTRAL

Attribute
Private Attributes
GT_CUSTOMER  Inst  customer data  TYPE TABLE_CUSTOMER
GO_LOOKUP    Stat  myself         TYPE REF TO ZCL_LOOKUP_CENTRAL

Methods

Public Methods

GET_MARKET_FROM_CUSTOMER
Description: returns market from customer
Instance method

Importing-Parameter
 PI_CUSTOMER TYPE /BI0/PCUSTOMER-CUSTOMER 
Exporting-Parameter
 PE_CUST_MARKET TYPE /BI0/PCUSTOMER-CUST_MKT

method GET_MARKET_FROM_CUSTOMER.
data: ls_customer type /bi0/pcustomer.
  read table gt_customer into lS_customer
    with key customer = pi_customer.
  pe_cust_market = ls_customer-cust_mkt.
endmethod.

GET_INSTANCE
Description: returns instance
Static Method
Exporting-Parameter
 P_INSTANCE TYPE REF TO ZCL_LOOKUP_CENTRAL 

method GET_INSTANCE.
  if go_lookup is initial.
    create object go_lookup.
  endif.
  p_InstaNCE = go_LOOKUP.
endmethod.

 

As you can see, the only difference to your old implementation is creating a new static class method for instance creation, adding a private attribute for the instance and making the public constructor method private. So you can create very easily your own singleton classes from existing ones.

In your transformation youchange one line only in your end routine, bold marked:

 

METHOD end_routine.

=== Segments ===</p><p>FIELD-SYMBOLS:</p><p><RESULT_FIELDS> TYPE tys_TG_1.</p><p>DATA:</p><p>MONITOR_REC TYPE rstmonitor.</p><p>$*$ begin of routine - insert your code only below this line -

call method ZCL_LOOKUP_CENTRAL=>GET_INSTANCE

importing p_instance = gt_customer.</p><p>loop at RESULT_PACKAGE assigning <result_fields>.</p><p>CALL METHOD GT_CUSTOMER->GET_MARKET_FROM_CUSTOMER</p><p>EXPORTING</p><p>PI_CUSTOMER = <RESULT_FIELDS>-customer</p><p>IMPORTING</p><p>PE_CUST_MARKET = <RESULT_FIELDS>-cust_mkt.</p><p>modify RESULT_PACKAGE from <result_fields>.</p><p>endloop.</p><p>$$ end of routine - insert your code only before this line -

ENDMETHOD. "end_routine

 

This solution has still one pitfall! In case of multi-user environment or parallele execution two users/processes can call the instance creation method at the same time. In this case our coding doesn’t prevent the creation of two classes. With parallel execution of the Data transfer process exactly this happens. The first data package will be exceuted by the job scheduler and the next data packages are also scheduled and executed. Depending on your settings you have towo or more jobs whol will be executed at the same time and therefore also the startroutine. To prevent this you have to work with semaphores or “lock” techniques.

One easy solution to get out of this situation is to create a SAP logical lock with according “ENQUEUE” and “DEQUEUE” FMs. I assume that you are familiar with creating lock objects, so I don’t explain it here. For more details on lock objects refer to SAP ABAP Help or to respective ABAP courses and books. Now it is very easy to prevent the parallel creation of your object. In your  “GET_INSTANCE” method, the first thing you do is try to set an exclusive lock!

If you’ve set the lock, brilliant, you’re the owner, create the object and release the lock!

IF not, well, your ENQUEUE-FM will return with error. Now wait a few seconds or as long as you assume your constructor needs to execute and try again.

Your GET_INSTANCE method should now look something like that, use of ENQUEUE- and DEQUEUE_FMs is marked bold

 

method GET_INSTANCE.

* set the exclusive lock

CALL FUNCTION 'ENQUEUE_EZ_KH_TEST'

EXPORTING

MODE_ZKH_LOCK = 'X'

** IS_LOCK =</pre><pre>* X_IS_LOCK = ' '</pre><pre>_SCOPE = '2'</pre><pre>_WAIT = 'X'</pre><pre>* COLLECT = ' '</pre><pre>EXCEPTIONS</pre><pre>FOREIGNLOCK = 1</pre><pre>SYSTEM_FAILURE = 2</pre><pre>OTHERS = 3</pre><pre>.</pre><pre>IF SY-SUBRC <> 0.</pre><pre>* do here something or raise error</pre><pre>ELSE.</pre><pre> we got the lock, everything ok

if go_lookup is initial.

create object go_lookup.

endif.

p_InstaNCE = go_LOOKUP.

* release the lock

CALL FUNCTION 'DEQUEUE_EZ_KH_TEST'

EXPORTING

MODE_ZKH_LOCK = 'X'

** IS_LOCK =</pre><pre>* X_IS_LOCK = ' '</pre><pre>_SCOPE = '3'</pre><pre>* _SYNCHRON = ' '</pre><pre>* _COLLECT = ' '</pre><pre>.</pre><pre>ENDIF.*

@

endmethod.

This solution has one little disadvantage in a “load-balanced” or “multi-server” environment. In a load-balanced environment you don’t know on which server your object creation task ends up. In this environment in combination with multi-user or parallele execution you can have one instance of your class on each server.

 

 

6 Comments
Labels in this area