Skip to Content

Hi SCN community!

It’s me again, with another contribution to Project Object.

Has it ever happened to you to be in a situation where you might be requesting the same thing over and over again to the database?

And if you’re a good developer, you avoided repetitive calls to the database implementing a buffer, correct?

Well, what I’ve got for you today is a class the will serve as a buffer for everything you want! Everything? Everything!

I can’t take full credits for this though… I got this from a guy who got this from another guy… so I have no idea who was the actual developer of this thing. I can take the credit for “perfecting” it though, and implementing some exception classes in it. So at least that 🙂

You’ll be able to find it in nugget and text version in my github, in the utilities section:

GitHub

Use example

Below is just an example of how to use this class. I am fully aware that the first “loop” is not how someone would properly perform this particular select to the database, this is meant simply as an example of how to use this class and for what.

DATA:
      db_counter TYPE i,
      lt_sbook  TYPE TABLE OF sbook,
      ls_sbook  LIKE LINE OF lt_sbook,
      ls_sbuspart TYPE sbuspart.

SELECT * FROM sbook
  INTO TABLE lt_sbook.

BREAK-POINT.

CLEAR db_counter.

LOOP AT lt_sbook INTO ls_sbook.

  SELECT SINGLE * FROM sbuspart
    INTO ls_sbuspart
    WHERE buspartnum = ls_sbookcustomid.
  ADD 1 TO db_counter.

ENDLOOP.

“check db_counter
BREAK-POINT.

CLEAR db_counter.

LOOP AT lt_sbook INTO ls_sbook.

  TRY.

      CALL METHOD zcl_buffer=>get_value
        EXPORTING
          i_name = ‘CUSTOMER_DETAILS’
          i_key  = ls_sbookcustomid.
    CATCH zcx_buffer_value_not_found.

      “If we haven’t saved it yet, get it and save it

      SELECT SINGLE * FROM sbuspart
        INTO ls_sbuspart
        WHERE buspartnum = ls_sbookcustomid.
      ADD 1 TO db_counter.

      CALL METHOD zcl_buffer=>save_value
        EXPORTING
          i_name  = ‘CUSTOMER_DETAILS’
          i_key   = ls_sbookcustomid
          i_value = ls_sbuspart.

  ENDTRY.

ENDLOOP.

“check db_counter
BREAK-POINT.

Performance remark

One last remark that I should make though… due to the high flexibility of this buffer, I think it’s not possible to have a sorted read (or, in other words, a fast read) of the value in the buffer. Therefore, if you are using a buffer with a high volume of entries, and if performance is critical, you should create a subclass and redefine the “key” with the type you are interested in particular, and also redefine the get method to replace the “LOOP” statement with a “READ” statement.

All the best!

Bruno

To report this post you need to login first.

38 Comments

You must be Logged on to comment or reply to a post.

    1. Bruno Esperança Post author

      Hi Tomas!

      The obvious advantage I see from static is that it will be available throughout the entire “runtime environment” or “run stack” or whatever it’s called.

      Don’t see any advantage for going for instances… do you?

      Thanks!

      Bruno

      (0) 
      1. Christian Guenter

        Hi Bruno,

        i think you gave the answer already.

        Bruno Esperança wrote:

        …and also redefine the get method…

        How would you redefine this method? Of course you can’t because it’s static.

        Regards Christian

        (0) 
        1. Bruno Esperança Post author

          Hi Christian,

          Yes you can. Why would you say that?

          What you can’t do, however, regardless of whether it is a static or instance method, is redefine the signature, so I guess the only way is to create a copy of the whole class for specific purposes you might need.

          Best,

          Bruno

          (0) 
            1. Suhas Saha

              I agree with you that Utility classes should have static methods.

              I have not see Bruno’s code yet, so can’t comment on it, but your statement is definitely not correct.

              If you consider CL_SALV_TABLE as a utility class for handling ALV display, figure out how the framework is defined. Or, as a matter of fact, check the RTTS classes.

              I have stopped using static methods (for reasons highlighted in the link), but do use the static constructor on a regular basis.

              BR,

              Suhas

              (0) 
              1. DEBOPRIYO MALLICK

                Hi,

                I said “Utility class should have static methods” not “Utility class must have static methods”. Should have means  good to have but not mandatory.

                Why do you think I provided the link reference –  just  to elucidate that how  instance methods can be used to build powerful frameworks of utility classes  like CL_SALV_TABLE & RTTS . If you peep inside the link, it talks about the disadvantages  of static methods.

                However, there are lot of utility classes which have only static methods . ( Check the debugger class – CL_TPDA* ) .

                Regards,

                DPM

                (0) 
                1. Suhas Saha

                  So you mean – For utility classes it’s good to have static methods?

                  There are many utility classes (part of SAP_BASIS component) which have static method. I am not saying that you can’t have them, but it depends on what the method is doing.

                  Let’s take the example of the method CL_ABAP_CODEPAGE=>FOR_LANGUAGE( ), which gives the CP for a language viz., ABAP, HTTP, JAVA. Now let’s say i would like the CP in a new language PABA (hypothetical), what would you do?

                  BR,

                  Suhas

                  (0) 
                  1. Bruno Esperança Post author

                    Do I sense some hostility in the air? It’s ok guys, have another beer 🙂

                    Anyways, I’d also like to think about the answer but I didn’t understand everything.

                    CP stands for Contains Pattern? What’s a language viz? Getting a “contains pattern” in another language? I’m guessing it doesn’t mean contains pattern 😛

                    Best,

                    Bruno

                    (0) 
          1. Christian Guenter

            Hi Bruno,

            Bruno Esperança wrote:

            Yes you can.

            and how would you do that (subclassing and redefinition of static method)? My compiler (7.31) and the above mentioned link tell me it isn’t possible.

            Compiler:

            You cannot redefine static methods. –

            Or do you mean something different?

            Christian

            (0) 
            1. Bruno Esperança Post author

              Sorry, you’re right, redefinition is only for instance methods.

              ABAP Keyword Documentation

              So I guess one possible approach would be the singleton strategy as suggested in the link Debopryio shared.

              I might look into it later and revise!!

              I can’t see any great advantages from this however 😕

              Cheers, and thanks for correcting me.

              Bruno

              (0) 
                  1. Bruno Esperança Post author

                    Hey Jacques!

                    Thank you for your contribution. As usual, the complexity of your solutions never ceases to amaze me 🙂

                    I took some time to try and understand it. It would seem that you don’t like structure types, and you used an interface instead. I would like to understand if there’s a “real” benefit. Could you maybe share why you did this?

                    On the other hand, even with the added complexity, it would seem to me that you lost on functionality compared with the approach I’ve shared (even if I want to change it to also use a factory strategy).

                    Let me explain what I mean. Imagine you have a scenario where you have a BAdI implemented multiple times (and executed multiple times), and you want to share your buffer among them, in order to avoid multiple selects to the database (or whatever you are trying to prevent). Correct me if I’m wrong, but I think with your current design this is impossible.

                    What I’m thinking about doing (when I have the time) is following the approach mentioned in the link Debopriyo shared and have a static table of buffers, and then a static “get_instance” method, which will get the buffer with the name passed as a parameter to this method (“CUSTOMER_DETAILS” in this case). This way, if a buffer has already been populated in the same memory stack, it will be reused.

                    What’s the point of adding complexity if it actually gets worse?

                    Thanks!!

                    Best,

                    Bruno

                    (0) 
                    1. Jacques Nomssi

                      Hello Bruno,

                      why? Because it is fun. Really.


                      I admit my refactoring has not added performance. It has added complexity within the server, but the CLIENT interface is simpler. It has not reduced functionality.


                      Now in our case we have two different issues:


                      1) I did not like the static method call, so I changed the API. This simplifies the interface. And if think you cannot pass the buffer reference to multiple BAdI implementations, then go ahead and create an ugly global variable for it. Other clients of the class do not have to.


                      2) I started refactoring to prepare further change in the code. The current design is the more generic one, as it only assume an “equal” relationship can be defined between the keys. But then both INSERT( ) and LOOKUP( ) need linear O(n) time.

                      The design can be improved if the keys are comparable or if you can define a hash function. It seems to me Matthew’s interface assumes the keys are CLIKE to achieve this.


                      I find local classes easy to work with. I can easily create new classes and change interfaces, try new design, and only publish tested interfaces. I constract this with global classes: they are quite rigid IMO, so I have really welcome the source-code based editor.

                      Do not take the fun out of my OO 🙂 . Ok, some of my favorite quotes:


                      From Barbara Liskov – The Power of abstraction

                      Modularity based on abstraction is the way things are done.

                      • programs are a collection of modules
                      • each module has an interface, described by a specification
                      • the connection between modules are the assumptions each module make about the other

                      From Kent Beck, in Fowler’s Refactoring book,

                      Computer Science is the discipline that believes all problems can be solved with one more layer of indirection – Dennis DeBruler

                      Refactoring tends to break big objects and methods into several smaller ones

                      • to enable sharing of logic
                      • to explain intention and implementation separately
                      • to isolate change
                      • to encode conditional logic

                      There is a price to pay: you have more things to manage, and it can make a program harder to read. But, well, there is a second refactoring game: identify indirection that isn’t paying for itself and take it out.


                      best regards.

                      JNN

                      (0) 
                      1. Bruno Esperança Post author

                        Hi Jacques!

                        Right now I only have time to comment on your point 1.

                        I went through the link you shared… but the link says the opposite to what you suggest. The article you shared states that singletons are “marginally better” than global variables. Me personally, I think this buffer class is a perfect candidate for the singleton pattern.

                        I think it’s really funny how whatever “simple” ideas I share, people always find a way to have great discussions on it and entirely different views on these ideas appear!

                        For now I’ll leave you with one of my favorite quotes 😀

                        “You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.” – Friedrich Nietzsche

                        Best,

                        Bruno

                        (0) 
                        1. Jacques Nomssi

                          Hello Bruno,

                          do you agree the CLEAR_BUFFER( ) method should take a parameter I_NAME (maybe optional?) to avoid conflict between two differents clients of the ZCL_BUFFER class ?

                          regards,

                          JNN

                          (0) 
                          1. Bruno Esperança Post author

                            Hi Jacques!

                            My idea is to change the “current” design to have a factory that takes the name as an importing parameter and returns an existing instance of that name or, if no buffer exists, creates a new one.

                            Method CLEAR_BUFFER will be an instance method, thus it will not need the name.

                            I think my reasoning is correct 😕

                            Best,

                            Bruno

                            (0) 
                  2. Bruno Esperança Post author

                    By the way, Jacques,

                    Did you check performance? One thing I’m slightly sad about is that the version without buffer is actually much faster than the version with buffer.

                    I blame the “LOOP” in the “contains” and “lookup” methods for this. If you could think of a way to have sorted access to the buffer table that would be really great. By the way, any particular reason for having two extremely similar methods like that? Couldn’t one be included in the other?

                    Thanks!

                    Best,

                    Bruno

                    (0) 
                      1. Bruno Esperança Post author

                        Hi again!

                        So, if I understand you correctly, what you’re saying is that the ideal solution would be to have the buffers defined as “type any” and their actual structure would be defined in runtime? And can you have a hashed table define in runtime?

                        That sounds amazing!! Would be really grateful if you could share something like THAT!! 😀

                        Best,

                        Bruno

                        (0) 
  1. Matthew Billingham

    This is my generic lookup interface. It uses RTTS to generate fast internal table structures, and is used for table, InfoObject master data and DSO reads.

    One day, when I have time, I’ll publish it! It needs fixing – so it throws proper exceptions for example – but it should give an idea of how it works.

    interface zif_lookup

       public .

         constants c_dateto type fieldname value ‘DATETO’. “#EC NOTEXT

       methods lookup

         exporting

           es_data type any

           eo_type type ref to cl_abap_structdescr

           e_notfound_flag type char1

         exceptions

           sql_error .


       methods set_key_val

         importing

           i_component type clike

           i_value type any .


       methods set_val_component

         importing

           i_component type clike .


       methods get_ref2_lookup

         returning

           value(rp_data) type ref to data

         exceptions

           sql_error .


       methods set_tim_val

         importing

           i_component type clike optional

           i_value type any .


       methods set_keyval_range

         importing

           it_keytab type any table .


       methods get_val_struc

         returning

           value(ro_valstruc) type ref to cl_abap_structdescr .


       methods get_notfound_flag

         returning

           value(r_notfound_flag) type flag .


    endinterface.

    (0) 
    1. Bruno Esperança Post author

      Hi Matthew,

      Thanks for chiming in 🙂

      Unfortunately, I am not skilled enough to form an idea of how your interface would work only by its definition. If you could eventually share a working example that would be great. (Or maybe I should spend some more time trying to understand it… but time seems to be a rare commodity these days!!!)

      Thanks! Best,

      Bruno

      (0) 
    1. Bruno Esperança Post author

      Most welcome Florian, thanks for taking the time to try it out 🙂

      I am planning to perform a small “uplift”, following Debopriyo’s suggestion to avoid too much static stuff. Should take care of it as soon as I have some time!

      Best,

      Bruno

      (0) 
  2. Volker Wegert

    citing RFC 1925 (10): “One size never fits all.” There are very good reasons why the system already has numerous buffering mechanisms in place and offers various ways to implement others. Statements like “class the will serve as a buffer for everything you want! Everything? Everything!” should make everyone exercise great caution. Don’t take it for granted and don’t switch of your own brain.

    (0) 
  3. Carlos Constantino

    Hi Bruno,

    Just read for the second time… and here is my method for reading data from database with a local buffer:

    How to use:

        <fs>maktx = zcl_buffer=>get( EXPORTING i_fname = ‘MAKT-MAKTX’
                                                i_vkey = <fs>matnr ).

        <fs>name1 = zcl_buffer=>get( EXPORTING i_fname = ‘KNA1-NAME1’
                                                i_vkey = <fs>kunnr ).

    i_fname = <table>-<field> that I need;

    i_vkey = table key


    The concept:

    Decompose i_fname (could be in separated parameters, but to me this makes it easier to read) and create a dynamic select statement.

    Data is stored in a static table with the following structure:

    fname; T_VALUES

    T_VALUES is a table of values (vkey; value)

    In the above scenario should be something like:

    MAKT-MAKTX -> Mat1; Material Text 1

                               Mat2; Material Text 2

    KNA1-KUNNR -> Kun1; Name1

                               Kun2: Name2

    Note that this is an incomplete custom Buffer Class. This only works when using key fields…

    (0) 
    1. Bruno Esperança Post author

      Hi Carlos 🙂

      Sounds interesting, but I think it’s a bit limited since it only works for selects to the database.

      Imagine, for example, the scenario I used it for: calling an RFC to another system.

      I think the buffer should be more generic, simply store a value and read it back.

      At the moment I have a really cool idea for another project 😉

      If you have free time do you want to collaborate? 😀

      Abraço,

      Bruno

      (0) 
      1. Carlos Constantino

        Hi Bruno,

        What you’ve mentioned is wright… although most common case is fetch data from DB.

        My implementation of BUFFER is by any means completed. I though on having a method (lets call it put) that stores data in buffer. This should be used to more complex data or more complex selections… But its still only an idea. Maybe when I really need it done 😛

        Another project? Free time? hum… I guess we could talk about it 🙂

        Abraços,

        Carlos

        (0) 

Leave a Reply