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>--
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
8 | |
5 | |
5 | |
4 | |
4 | |
4 | |
4 | |
4 | |
3 | |
3 |