RESULT-for-ABAP
An ABAP implementation of the result pattern: a method returns an object built with ok or fail instead of raising an exception for an expected failure, and several results combine into one.
A method that can succeed or fail returns a RESULT built with zcl_result=>ok or zcl_result=>fail, rather than throwing an exception for something as ordinary as a validation, or returning a bare abap_true or abap_false with no reason attached. get_value and get_error_message read the outcome back, and combine_with or combine_with_these fold several RESULTs from a batch of validations into one. A RESULT can also carry metadata as key-value pairs alongside its value or its error messages. It covers the object and how results combine; what a caller does after inspecting one is up to the calling code.
Facts
Where it sits
Who is behind it
In the atlas: maintains 2, contributes to 4@dominikpanzermaintainerLars HvamContributes to this
In the atlas: maintains 13, contributes to 92, wrote 1@larshpMarc BernardContributes to this
In the atlas: contributes to 26@mbtoolsRobertContributes to this
In the atlas: contributes to 2@rob7865413 contributors
What it declares
Every artifact
- Type
- Package
- Origin
- Independently maintained
As a package
- Where it runs
- Inside its host
- Distributed through
- abapGitCopied into the project
Connections
Turning a failure into a response a client can act on
Download
README
quoted from dominikpanzer/RESULT-for-ABAP under MIT, readRESULT for ABAP
Hi! "RESULT for ABAP" is - surprise, surprise - an ABAP implementation of the Result-Pattern. It's a way to solve a common problem: a method-call can be successful (OK) or it can fail (FAILURE) and the caller needs to know. The Result-Pattern indicates if the operation was successful or failed without the usage of exceptions. It is a quite common pattern in functional languages.
Give a Star! ⭐
If you like or are using this project please give it a star. Thanks!
Why should I use RESULT for ABAP instead of exceptions?
- Exceptions are actually only for... well, exceptional cases, like DB errors, locks etc. not for "domain errors" like validations etc. When you expect something (like the user entering wrong/invalid data) than it is actually no case for an exception. You actually seem to expect that users quite often are creative when entering data.
- Exception are often being used as a fancy form of the GOTO-statement. You often don't know where they will be caught. If they get caught at all. This has been considered harmful in 1968.
- If a method can throw an exception and the calling code does not directly catch it - was this a mistake or on purpose? Will it be catch somewhere up the callstack? You never know, so have fun analyzing the whole stack.
- Exceptions lead to hard to read code, because regular business logic is mixed with technical error handling via TRY...CATCH-blocks, for example when many different exceptions have to be caught.
- Exceptions sometimes are not really helpful, because people tend to wrap all code into a TRY...CATCH-block for CX_ROOT. This can lead to inconsistent data, because a part of the data might already have been processed and persisted. It would be actually better to shortdump.
- Exceptions tend to return only one error, but what if you have multiple errors?
- Often command-methods just return a value like "it worked," which is either ABAP_TRUE or ABAP_FALSE. But no additional error values are available which could be shown in the frontend.
- Often query-methods just return the result of a query and if the result is empty, then this represents an error. But what was the reason for the error?
- Other methods export two values: the actual value and an optional error message. But now you can only use EXPORTING and not RETURNING, because there are two parameters. This leads to hard to read code. Ideally a method should only return one value.
- You could use a structure (value, error_message) to solve that problem. RESULT for ABAP is a comfortable object oriented way of doing this - a standardized solution. Your method simply returns a RESULT. 🦖
- RESULT enables a fluent coding style compared to TRY...CATCH...ENDTRY all over the place.
- Consistently using RESULT as the name for the returning parameter of methods simplifies method definitions and significantly improves readability of your code
Okay, show me an example
Creating successful RESULTs
* create a RESULT which represents a success (OK)
result = zcl_result=>ok( ).
* another one with additional information ("value", i.e. the key of an object you created or the object itself
result = zcl_result=>ok( 100040340 ).
* read the information again. You need to know the type of the value. that's the downside of a RESULT
* get_value can give you a reference, which works smoothly on new systems
DATA new_partner TYPE bu_partner.
new_partner = result->get_value( )->*.
* or it can give you direct access via the EXPORTING-Parameter, which is comfortable on 7.5x-systems
result->get_value( importing value = new_partner ).
* a result can be generated for example based on a validator-value. FAIL_IF will create a FAILURE,
* whenever the validator returns TRUE. when the validator returns FALSE, its an OK-RESULT
result = zcl_result=>fail_if( validator_returns_false( ) ).
* when a validator returns false and you can add an error message, it will be used if the RESULT is a FAILURE,
* otherwise its an OK-RESULT, like in this example
result = zcl_result=>fail_if( this_is_true = validator_returns_false( ) error_message = 'a wild error occurred' ).
* when a validator returns true, it will be an OK-RESULT
result = zcl_result=>ok_if( validator_returns_true( ) ).
* when a validator returns true its OK. but if the validator would fails, it is a FAILURE with an error message
result = zcl_result=>ok_if( this_is_true = validator_returns_true( ) error_message = 'a wild error occurred' ).
Creating failures
* create a RESULT which indicates a FAILURE
result = zcl_result=>fail( ).
* with an error message
result = zcl_result=>fail('a wild error occurred').
* when a validator returns true
result = zcl_result=>fail_if( validator_returns_true( ) ).
* when a validator returns true + error message
result = zcl_result=>fail_if( this_is_true = validator_returns_true( ) error_message = 'a wild error occurred' ).
* when a validator returns false
result = zcl_result=>ok_if( validator_returns_false( ) ).
* when a validator returns false
result = zcl_result=>ok_if( this_is_true = validator_returns_false( ) error_message = 'a wild error occurred' ).
* reading an error message / the first error or all errors or all error messages
IF result->has_multiple_error_messages( ).
DATA(error_message) = result->get_error_message( ).
ELSE.
DATA(error_messages) = result->get_error_messages( ).
ENDIF.
* adding additional error messages to a FAILURE
zcl_result=>fail('a wild error occurred')->with_error_message( error_message ).
Combining results
Usually there are many validations at the start of a method, so you might like to combine their single RESULTs into a final big one. The typical use case here is "validate X variables and all have to be OK, otherwise it's a FAILURE so stop processing the data". So if there is at least one FAILURE, the RESULT will be a FAILURE. Otherwise the RESULT will be OK. Currently only one error message will be stored. Combined OK-RESULTs don't have a value. You can also return a table of RESULTs from you method if you need the details.
* combined RESULT is OK
DATA(result_one) = zcl_result=>ok( ).
DATA(result_two) = zcl_result=>ok( ).
result = result_one->combine_with( result_two ).
* combined RESULT is a FAILURE
DATA results TYPE zcl_result=>ty_results.
DATA(result_one) = zcl_result=>ok( ).
DATA(result_two) = zcl_result=>fail( error_message ).
DATA(result_three) = zcl_result=>fail( error_message ).
results = VALUE #( ( result_two ) ( result_three ) ).
result = result_one->combine_with_these( results ).
Adding Metadata to a RESULT
If you need more then just the one VALUE of an OK-RESULT, you can add metadata to the result. Metadata are key-value-pairs, with a unique CHAR30 key and the value being a data reference. Metadata can be added to any type of RESULT.
* Adding Metadata
DATA(structure) = VALUE zst_metadata_entry( key = 'a' value = REF #( 'random structure' ) ).
result = zcl_result=>ok( )->with_metadata( key = 'a structure' value = structure ).
result->with_metdata( key = 'band' value = 'Slayer' ).
* Reading metadata with a key
DATA(value) = result->get_metadata( 'a structure' ).
* Reading the whole key-value-table
DATA(metadata) = result->get_all_metadata( ).
Usage of a RESULT in a method
Use the RESULT as a RETURNING parameter:
METHOD do_something IMPORTING partner TYPE bu_partner
RETURNING VALUE(result) TYPE REF TO zcl_result.
...
METHOD do_something.
* 100s of lines of arcane logic
* hooray, no problems at all
result = zcl_result=>ok( 100040340 ).
ENDMETHOD.
Processing a RESULT
Use the RESULT-object for flow control as you like:
* call a method which returns a result
DATA new_partner TYPE bu_partner.
DATA(result) = do_something( partner ).
* guarding
IF result->is_failure( ).
DATA(error_message) = result->get_error_message( ).
* log / error for webservice
EXIT.
ENDIF.
new_partner = result->get_value( )->*.
* do something with partner, i.e. persistence
How to install RESULT for ABAP
You can copy and paste the source code into your system or simply clone this repository with abapGit.
Test List
I like to create a simple acceptance test list before I start coding. It's my personal todo-list. Often the list is very domain-centric, this one is quite technical.
| Test List |
|---|
| ✅ first release somehow seems to works |
✅ when FAIL_IF gets called with an optional error message "a wild error occurred", the error message gets stored when the RESULT is a failure |
✅ when FAIL_IF has been called with an optional error message "a wild error occurred", GET_ERROR_MESSAGE will return "a wild error occurred" when the RESULT is a failure |
✅ when FAIL_IF has been called with an optional error message "a wild error occurred", GET_ERROR_MESSAGE will return an exception, when the RESULT is OK |
✅ when OK_IF gets called with an optional error message "a wild error occurred", the error message gets stored when the RESULT is a failure |
✅ when OK_IF has been called with an optional error message "a wild error occurred", GET_ERROR_MESSAGE will return "a wild error occurred" when the RESULT is a failure |
✅ when OK_IF has been called with an optional error message "a wild error occurred", GET_ERROR_MESSAGE will return an exception, when the RESULT is OK |
✅ when OK_IF has been called with an optional error message "a wild error occurred", GET_VALUE a initial value when the RESULT is OK |
| ✅ when a OK-RESULT uses a table as a VALUE, the VALUE can be retrieved and has the same number of lines as the original internal table |
| ✅ update the docs 👹 |
✅ when the method WITH_METADATA( key = "name" value = "David Hasselhoff" ) gets called once, the Metadata gets stored |
✅ when the method GET_ALL_METADATA( ) gets called after WITH_METADATA( key = "name" value = "David Hasselhoff" ), it returns a table with one entry (name, David Hasselhoff) |
✅ when the method GET_METADATA( name ) gets called after WITH_METADATA( key = "name" value = "David Hasselhoff" ), it returns a single value "David Hasselhoff" |
✅ when the method GET_ALL_METADATA( ) gets called without WITH_METDATA being called before, it returns an initial table |
✅ when the method GET_METADATA( date ) gets called after WITH_METADATA( key = "name" value = "David Hasselhoff" ), it returns an initial value |
✅ when the method WITH_METADATA( key = "name" value = "David Hasselhoff" ) is called with the same key twice, no duplicates get stored and it throws |
✅ when the method WITH_METADATA is called with an initial key then that is okay |
✅ when the method WITH_METADATA is called twice with different keys ( key = "name" value = "David Hasselhoff" ) ( key = "name2" value = "David Hasselhoff" ), both values get stored |
✅ when the method WITH_METADATA( key = "name" value = value ) and value a structure, a structure will be returned by get_metadata( name ). |
| ✅ update the docs 👹 |
✅ when COMBINE_WITH gets called with two failures, both error messages get stored |
✅ when COMBINE_WITH_THESE gets called with tow failures, both error messages get stored |
✅ when GET_ERROR_MESSAGES gets called for an FAILURE with two error messages, it returns two error messages |
✅ when GET_ERROR_MESSAGE gets called on a FAILURE it returns only the first error message |
✅ HAS_MULTIPLE_ERROR_MESSAGES returns false when there is no error_message for a FAILURE |
✅ HAS_MULTIPLE_ERROR_MESSAGES returns false when there is only one error_message for a FAILURE |
✅ HAS_MULTIPLE_ERROR_MESSAGES returns true when there a multiple error_messages for a FAILURE |
✅ HAS_MULTIPLE_ERROR_MESSAGES throws when OK-RESULT |
✅ GET_ERROR_MESSAGE throws when OK-RESULT |
| ✅ when `WITH_ERROR_MESSAGE gets called with an empty message on a FAILURE, it just returns the current result |
✅ when WITH_ERROR_MESSAGE( 'a wild error occurred' ) gets called on a FAILURE, the message will be added to the list of error messages and can be retrieved with GET_ERROR_MESSAGES |
✅ when WITH_ERROR_MESSAGE( 'a wild error occurred' ) gets called on a OK-RESULT it does not do anything but return the result |
| ✅ update the docs 👹 |
✅ get_value has RETURNING and EXPORTING parameters to make handling easier on 7.5x-systems |
| 🔲 your awesome idea |
As you can see I'm usually committing after every green test or even more often. I tend to use the ´zero, one, multiple´ or the ´happy path, unhappy path´ patterns to write my tests to drive my logic.
How to support this project
PRs are welcome! You can also just pick one of the test list entries from above and implement the solution or implement your own idea. Fix a bug. Improve the docs... whatever suits you.
Greetings, Dominik
Others providing
Compare all 5 on the Error handling page →- throwthrowA minimalistic way to raise an ABAP exception, from a text passed directly or, with no argument, from the system fields a preceding MESSAGE statement filled, as a static method or through an interface.independent, abapRaises an ABAP exception from a text or the last system message
- ABAP-PRCABAP-PRCA framework for custom multi-step ABAP processes: a process class declares the states, and the framework runs each document through them and monitors the run.independent, abapFramework for multi-step ABAP processes with monitoring
- EITHER-FOR-ABAPEITHER-FOR-ABAPAn ABAP class implementing the Either pattern, holding a success value or an error from a method call in one object, with checks for which side it holds and exceptions for misuse.independent, abapABAP class implementing the Either pattern
- sap-usi-exceptionsap-usi-exceptionA shared root exception class and text getter API used across SchwarzIT's USI ABAP developments, converting an exception's text into a common format such as BAPIRET2 or SYMSG.independent, abapShared root exception class and text getter for USI developments
Details
Sources
Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.
Compatibility and provenance
abaplint
Contributors
History in the atlas
- First seen.
For your agent
- Ref
artifact:dominikpanzer-result-for-abap- JSON
- /artifacts/dominikpanzer-result-for-abap.json
- MCP
- Connecting an agent