In June 2020 we have launched a brand new documentation portal which consolidates all the knowledge, feature documentation, comprehensive guides, video tutorials, troubleshooting, release notes, API documentation and more. Please, use it as a single reference point about SAP Cloud SDK. From now on it's the most complete and up to date source of information about the product.
Disclaimer: This blog post is only applicable for the SAP Cloud SDK version of at most 2.19.2. We plan to continuously migrate these blog posts into our List of Tutorials. Feel free to check out our updated Tutorials on the SAP Cloud SDK.
This deep dive explains how to consume BAPIs inside SAP S/4HANA On-Premise from your cloud application that is developed using the SAP Cloud SDK.
Note:
- This post is part of a series. For a complete overview visit the SAP Cloud SDK Overview.
- This tutorial requires access to an SAP ERP On-Premise System.
Goal of this blog post
In order to enrich your cloud application with business data from S/4HANA it makes sense to integrate your cloud app with S/4HANA. The preferred approach is to use OData services from the respective business domain, but calling BAPIs is nevertheless helpful. The SAP Cloud SDK provides an easy-to-use API that you can use in your cloud app to develop BAPI calls.
After recapping the BAPI concept and its programming model, we will present an example cloud application and use it as reference to explain how to utilize this API.
The approach outlined in this deep dive only works for S/4HANA On-Premise. SAP S/4HANA Cloud supports BAPI invocation via the virtual data model for BAPIs, check out the
respective step 11 of this blog series.
In order to execute the presented example app in the SAP Cloud Platform, you have to establish a connection to your S/4HANA On-Premise system via the SAP Cloud Connector.
The BAPI programming model
In the SAP ecosystem, the notion of Business Application Programming Interfaces (BAPI) is an established and prominent channel for reading/writing business objects and triggering business processes.
Besides the underlying BAPI-specific source code each BAPI consists of several meta data:
- Function Module Name
- Function Group
- Short Text
- Function Module Documentation
- Parameters (Importing, Exporting, Tables)
- Return table
Function Module Name, Function Group, and Documentation
The function module name represents the name of the BAPI.
Since ABAP as programming language is not case-sensitive, the name appears to be in upper case whereas words are separated by underscore. The prefix BAPI indicates that the respective function module is remote-enabled and follows the BAPI proramming model.
The function group organizes several BAPIs that are logically related into one set. For instance, all BAPIs that deal with the business object cost center reside in the same function group.
The function module documentation is accessible in transaction SE37 (Function Builder) and explains the functionality of the respective BAPI. It covers noteworthy information about the BAPI parameters and its exceptions.
Parameters
Commonly one BAPI consists of importing, exporting, and tables parameters.
The distinction between importing and exporting depends on the data flow and the perspective:
|
Input Parameter
for BAPI |
Output Parameter
for BAPI |
BAPI perspective |
Importing |
Exporting |
Caller perspective |
Exporting |
Importing |
When the BAPI caller provides input data for the BAPI call, this parameter is considered as importing from the BAPI perspective. In contrast, the BAPI caller considers this parameter as exporting.
Apart from that, tables parameters can be used for both input and output purposes. One table parameter can be prefilled with data by the caller. This data is consumable by the BAPI. After processing the content of the table can be adjusted by the BAPI and the caller can consume this updated content.
Return table
Function modules that are not exposed as BAPIs use exceptions to indicate error situations. BAPIs do not use this mechanism. Instead each BAPI reports each noteworthy information in the return table. The return table is a dedicated tables parameter with name RETURN. As this parameter is not marked as optional, the BAPI caller must supply it. During BAPI processing success messages, warnings, and error messages along with their message class and message number are recorded.
Transaction handling: Commit and rollback
Complex business objects or business processes require the call of multiple BAPIs inside one transaction. The term transaction refers here to the logical unit of work on the business application level. In order to keep your business data integer and consistent, you either want all BAPIs inside one transaction to be successful or none.
For instance, assume that you are purchasing a new product and before creating a purchase order, you need to create a material master for this product. In case the purchase order creation fails, the material master must not be created also, i.e. the two entities must be considered inside one transaction.
After all BAPIs in one transaction have been executed, the created/changed/deleted entities must be committed or rolled back. If all BAPIs ran successfully, a special commit BAPI must be invoked to persist all changes on the database. If at least one BAPI reported an error situation, everything must be rolled back and thus not be persisted on the database. Rolling back also includes removing potential locks on business objects.
Example: BAPI for creating multiple Cost Centers
Let us apply above theory considering the BAPI for creating Cost Centers:
As importing parameter this BAPI expects the Controlling Area which the new Cost Centers should belong to. Apart from that you can specify further flags, such as if you want to conduct a test run.
The new Cost Centers you want to create are passed in the tables parameter named COSTCENTERLIST. This parameter is marked as mandatory, i.e. the BAPI caller must supply it. This applies also to return table represented by the mandatory tables parameter RETURN.
How SAP Cloud SDK simplifies BAPI execution
API overview
The SAP Cloud SDK provides the class BapiQuery which is your main entry point in order to formulate a BAPI call in your Java code. It provides possibilities to specify the BAPI function name, the commit handling as well as all importing, exporting, and tables parameters.
The method execute of the class BapiQuery returns an instance of BapiQueryResult which allows access to the returned data.
Convenient deserialization of Java objects
Commonly you represent the business objects of your application domain by creating respective Java classes. Assuming your cloud app comes from the cost controlling area, you will likely create Java classes such as Cost Center or Activity Type.
Typically you have to manage proper deserialization of such object instances for your BAPI call yourself. The SAP Cloud SDK simplifies this aspect for you. We utilize the annotation ElementName to indicate the ABAP field name of a Java field. By this means the SDK is capable to automatically create object instances of your entity classes out of a BAPI result.
For instance, consider one BAPI call to lookup Cost Centers from a certain Controlling Area. The SDK allows you to obtain the returned Cost Center data in a Java list by one method call. The deserialization is managed for your under the hood.
Transparent transaction handling
The BAPI programming model requires the BAPI caller to explicitly trigger the commit or rollback of the requested changes at the end of the logical unit of work. Technically this involves calling the respective BAPI:
- BAPI_TRANSACTION_COMMIT
- BAPI_TRANSACTION_ROLLBACK
The SAP Cloud SDK covers this aspect for you transparently, thus you do not need to take care of this at all. You only have to specify the commit flag when invoking the method execute on the BapiQuery instance.
The SDK automatically checks whether the executed BAPIs ran successfully or not. If so, it calls the commit BAPI, otherwise it executes the rollback BAPI. Everything is managed under the hood.
Example application: Creation of Cost Centers
Idea of this example application
Using this example application we aim to demonstrate the invocation of BAPI functions in your S/4HANA On-Premise system. It is constructed on purpose to showcase the different features of BAPI invocation in one simple app. This example is meant to provide you with entry pointers to implement your own app if you need to call BAPIs.
Based on a given Controlling Area, we retrieve a list of all Cost Centers in this respective Controlling Area. Then we select one particular Cost Center, retrieve all detailed fields ot it and we conclude by creating several copies of this Cost Center under different names. This program flow includes the execution of the following BAPIs:
- BAPI_COSTCENTER_GETLIST1
- BAPI_COSTCENTER_GETDETAIL1
- BAPI_COSTCENTER_CREATEMULTIPLE
The first two BAPIS are read-only, i.e. they do not have transactional behaviour. In contrast, the latter one creates new Cost Centers and, therefore, requires an explicit commit to persist the changes on the database.
Link to example application
You can access this example application here:
https://github.com/SAP/cloud-s4-sdk-examples/tree/master/Cost-Center-Creation-Neo
Represent Cost Center as Java class
Following the object-orientied programming paradigm it makes sense to represent entities from the application domain as Java classes. Since this example application deals with Cost Centers, it contains the Java class CostCenter.
@Getter
@ToString
public class CostCenter {
@ElementName("CO_AREA")
private ControllingArea controllingArea;
@ElementName("COSTCENTER")
private String id;
@ElementName("NAME")
private String name;
@ElementName("DESCRIPT")
private String description;
//other fields omitted for readability
}
Using the annotation ElementName you can map specify the ABAP name that corresponds to the respective Java field. For instance, the Controlling Area member in the Java class refers to the ABAP field CO_AREA.
Moreover, note the annotations Getter und ToString from Lombok which reduce the boilerplate code of writing getter and toString methods.
Retrieve Cost Centers from Controlling Area
In order to invoke the BAPI for retrieving Cot Centers we create an instance of the BapiQuery class.
At first, we parameterize this instance with the exporting parameter (we now consider the perspective of the BAPI caller) for the Controlling Area. As this exporting parameter is a simple field, i.e. no structure or table type, we use the method withExporting and specify its ABAP field name, the data type, and the given value.
Additionally, we must mention the tables parameter to obtain the Cost Center list. For this purpose we call the method withTable and pass the name of the tables parameter and its line type. As the Cost Center list is considered as output of this BAPI, we call the method end directly.
As the return table is always a mandatory parameter, we call the method withTableAsReturn and pass the lien type of the BAPI parameter RETURN.
The method execute takes an instance of ErpEndpoint as input and sends the request to the S/4HANA On-Premise system.
private List<CostCenter> retrieveCostCenterList(final RequestParameters reqParameters, final ErpConfigContext erpConfigContext) throws QueryExecutionException {
final BapiQueryResult resultGetCostCenters = new BapiQuery("BAPI_COSTCENTER_GETLIST1")
.withExporting("CONTROLLINGAREA", "BAPI0012_GEN-CO_AREA", reqParameters.getControllingArea())
.withTable("COSTCENTERLIST", "BAPI0012_CCLIST").end()
.withTableAsReturn("BAPIRET2")
.execute(erpConfigContext);
return resultGetCostCenters.get("COSTCENTERLIST").getAsCollection().asList(CostCenter.class);
}
After the response from S/4HANA was received, we get an instance of BapiQueryResult.
We can fetch the list of Cost Center instances by using the method get on this result object. Since we know that this parameter is neither a structure nor a simple field, we must use the method getAsCollection which allows to call asList. Note that we pass the class type of our CostCenter class. As a consequence, the SAP Cloud SDK deserializes the BAPI result into a list of CostCenter instances. From there we can now easily access them in a type-safe manner.
Retrieve details of particular Cost Center
Based on user input, we will now obtain details of one particular Cost Center within the given Controlling Area. The Cost Center is identified by the parameter costCenterIndex passed from the outside.
We formulate another BapiQuery and pass the two exporting parameters for the Controlling Area and the Cost Center ID using the method withExporting.
The output data of this BAPI call is the Cost Center details. In the ABAP dictionary this is represented as a structure. For this purpose we utilize the method withImportingFields and pass the field name along with the name of the ABAP structure.
As known we call withTableAsReturn and execute and return the Cost Center details as instance of BapiQueryResult.
private CostCenter retrieveCostCenterDetails(final RequestParameters reqParameters, final ErpConfigContext erpConfigContext, final List<CostCenter> costCenterList) throws QueryExecutionException {
CostCenter chosenCostCenter = costCenterList.get(reqParameters.getCostCenterIndex());
final BapiQueryResult resultCostCenterDetail = new BapiQuery("BAPI_COSTCENTER_GETDETAIL1")
.withExporting("CONTROLLINGAREA", "BAPI0012_1-CO_AREA", chosenCostCenter.getControllingArea())
.withExporting("COSTCENTER", "BAPI0012_1-COSTCENTER", chosenCostCenter.getId())
.withImportingFields("COSTCENTERDETAIL", "BAPI0012_CCOUTPUTLIST").end()
.withTableAsReturn("BAPIRET2")
.execute(erpConfigContext);
return resultCostCenterDetail.get("COSTCENTERDETAIL").getAsObject().as(CostCenter.class);
}
The deserialization into the Java class CostCenter happens when we access the output parameter COSTCENTERDETAIL using get, getAsObject and as.
Create multiple Cost Centers
Considering that we have fetched all fields of the chosen Cost Center, we will proceed with creating copies of it under a different name.
The BAPI for creating multiple Cost Centers expects the Controlling Area as exporting parameter (from the perspective of the BAPI caller).
Note that we set the commit flag as second constructor argument of the class BapiQuery besides the function name to. This will trigger the subsequent call of the commit BAPI.
The actual Cost Centers to be created are passed as tables parameter named COSTCENTERLIST.
When we want to utilize a tables parameter to provide input to the BAPI call, we hold the returned object of withTable in a reference and call the method row for every new table entry.
private BapiQueryResult createCostCenterCopies(final RequestParameters reqParameters, final ErpConfigContext erpConfigContext, final CostCenter detailedCostCenter) throws QueryExecutionException {
final BapiQuery queryCreate = new BapiQuery("BAPI_COSTCENTER_CREATEMULTIPLE", true)
.withExporting("CONTROLLINGAREA", "BAPI0012_GEN-CO_AREA", reqParameters.getControllingArea())
.withTableAsReturn("BAPIRET2");
Table<BapiQuery> costCenterTables = queryCreate.withTable("COSTCENTERLIST", "BAPI0012_CCINPUTLIST");
for (int i = reqParameters.getNamingSuffixStartCounter(); i < (reqParameters.getNamingSuffixStartCounter() + reqParameters.getNumberOfCreations()); i++) {
costCenterTables.row()
.field("COSTCENTER", "KOSTL", reqParameters.getNamingPrefix() + i)
.field("NAME", "KTEXT", detailedCostCenter.getName())
.field("DESCRIPT", "KLTXT", detailedCostCenter.getDescription() + " " + i)
.field("VALID_FROM", "DATBI", convertJodaDateToErpDate(LocalDate.now()))
.field("VALID_TO", "DATAB", convertJodaDateToErpDate(LocalDate.now().plusYears(1)))
.field("PERSON_IN_CHARGE", "VERAK", detailedCostCenter.getPersonInCharge())
.field("COSTCENTER_TYPE", "KOSAR", detailedCostCenter.getType())
.field("COSTCTR_HIER_GRP", "KHINR", detailedCostCenter.getHierarchyGroup())
.field("COMP_CODE", "BUKRS", detailedCostCenter.getCompanyCode())
.field("BUS_AREA", "GSBER", detailedCostCenter.getBusinessArea())
.field("CURRENCY", "WAERS", detailedCostCenter.getCurrency())
.end();
}
return queryCreate.execute(erpConfigContext);
}
In this example we create as many entries as requested by the parameter numberOfCreations.
Inside one row we can supply all necessary fields using the method field to pass the field name, its ABAP type and the value.
Finally we call the method execute to send the BAPI request to S/4HANA On-Premise.
Invocation of remote-enabled function modules
The concepts described in this deep dive apply to the invocation of remote-enabled function modules (RFM) too. However, there are few differences:
- You have to use the classes RfcQuery and RfcQueryResult.
- RFMs may not utilize the notion of a return table and use exceptions to indicate error situations
- RFMs may use changing parameters. For this purpose the SDK provides methods such as withChanging.
Conclusion
In this deep dive we have recapped the concepts of the BAPI programming model. Based on this theoretical knowledge we have presented the advantages of using the SAP Cloud SDK for BAPI calls and applied it to an example application that creates Cost Centers.
Stay tuned for upcoming blog posts about the SAP Cloud SDK.