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:
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_sbook–customid.
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_sbook–customid.
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_sbook–customid.
ADD 1 TO db_counter.CALL METHOD zcl_buffer=>save_value
EXPORTING
i_name = ‘CUSTOMER_DETAILS’
i_key = ls_sbook–customid
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
Hi Bruno,
nice utility, but why not OO? (i.e. why static class)
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
Hi Bruno,
i think you gave the answer already.
How would you redefine this method? Of course you can't because it's static.
Regards Christian
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
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
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
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
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
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
Hi Bruno,
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.
Or do you mean something different?
Christian
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
Or if anyone wants to revise it for me and share it, feel free 🙂
That's why there's a github, anyways 🙂
Hello Bruno,
be careful what you wish for!
JNN
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
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.
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
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
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
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
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
My interface assumes the field names are clike. The values are ANY.
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
You don't want sorted access to a buffer table, you want hashed. And you get that by RTTS.
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
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.
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
Thanks Bruno for sharing,
I am also waiting for Matthew's reply or post.
Regards,
Sharath
I've written my own blog. I plagiarised your title!
How to create the only buffer you'll ever need...
How about this title?
The Day the Conventional Buffer Stood Still..
I believe you meant Stood still according to the latest remake with Keanu, or the original one not single (abap) one of us remembers
Typo corrected 🙂
I was referring to remake version.
Hi Bruno,
this is a cool thing. Tried it --> like it! Thank you for sharing it.
~Florian
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
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.
This is true. I don't always use my lookup classes either.
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
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:
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...
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
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