Skip to Content
Author's profile photo Bruno Esperança

The last runtime buffer you’ll ever need? (OBSOLETE)

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

Assigned Tags

      38 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Tomas Buryanek
      Tomas Buryanek

      Hi Bruno,

      nice utility, but why not OO? (i.e. why static class)

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Christian Guenter
      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

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Former Member
      Former Member

      Hi Bruno,

      I agree with you that Utility classes should have static methods. But, there are some disadvantages of using static class which is listed below http://help.sap.com/abapdocu_731/en/abenstatic_class_singleton_guidl.htm.

      Regards,

      DPM

      Author's profile photo Former Member
      Former Member

      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

      Author's profile photo Former Member
      Former Member

      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

      Author's profile photo Former Member
      Former Member

      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

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Christian Guenter
      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

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog Post Author

      Or if anyone wants to revise it for me and share it, feel free 🙂

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog Post Author

      That's why there's a github, anyways 🙂

      Author's profile photo Jacques Nomssi Nzali
      Jacques Nomssi Nzali

      Hello Bruno,

      be careful what you wish for!

      JNN

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Jacques Nomssi Nzali
      Jacques Nomssi Nzali

      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

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Jacques Nomssi Nzali
      Jacques Nomssi Nzali

      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

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Matthew Billingham
      Matthew Billingham

      My interface assumes the field names are clike. The values are ANY.

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Matthew Billingham
      Matthew Billingham

      You don't want sorted access to a buffer table, you want hashed. And you get that by RTTS.

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Matthew Billingham
      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.

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Sharath Yaralkattimath
      Sharath Yaralkattimath

      Thanks Bruno for sharing,

      I am also waiting for Matthew's reply or post.

      Regards,

      Sharath

      Author's profile photo Matthew Billingham
      Matthew Billingham

      I've written my own blog. I plagiarised your title!

      How to create the only buffer you'll ever need...

      Author's profile photo Former Member
      Former Member

      How about this title?

      The Day the Conventional Buffer Stood Still..

      Author's profile photo Former Member
      Former Member

      I believe you meant Stood still according to the latest remake with Keanu, or the original one not single (abap) one of us remembers

      Author's profile photo Former Member
      Former Member

      Typo corrected 🙂

      I was referring to remake version.

      Author's profile photo Florian Henninger
      Florian Henninger

      Hi Bruno,

      this is a cool thing. Tried it --> like it! Thank you for sharing it.

      ~Florian

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Former Member
      Former Member

      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.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      This is true. I don't always use my lookup classes either.

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog Post Author

      That statement was just meant to be a reference to Full Metal Jacket (1987) - IMDb 🙂

      I guess not everyone has seen that great movie!

      Best 🙂

      Bruno

      Author's profile photo Former Member
      Former Member

      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...

      Author's profile photo Bruno Esperança
      Bruno Esperança
      Blog 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

      Author's profile photo Former Member
      Former Member

      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