Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SAP and Kotlin

SAP and Kotlin

The SAP Java Connector is used to develop SAP-compatible software components which are executed on the JVM.
Using the connector you can call ABAP from Java and vice versa. In this talk I show how the use of Kotlin helps to develop these components. Coroutines are used for scalability and a DSL is used to create a simplified version of the Connector API.

cjastram

April 10, 2018
Tweet

Other Decks in Programming

Transcript

  1. Agenda • SAP • Extending SAP Systems • Using a

    Kotlin DSL to access SAP Systems • Using Coroutines to create scalable components • Q and A
  2. Caution • Creating a DSL and using Coroutines worked out

    of the box • I did not get a deeper understanding of the internals
  3. SAP • Enterprise Resource Planning (ERP) System • Financials and

    Controlling FI CO • Production Planning PP • Human Resources HR • Sales and Distribution SD • Archiving, Customs Management (Zoll) and XXX, XXXX • Uses ABAP as the main programming language Allgemeiner Berichtsaufbereitungsprozessor Advanced Business Application Programming
  4. SAP Pros and Cons Pros • Market share leadership: too

    big to fail in the near future • A global solution for more than 40 countries including legal requirements • Software scope and industry expertise • ABAP for writing business logic and interacting with modules written in Java and .NET Cons • Complex pricing, high TCO and recurring upgrades • New technologies only available for new releases after long ramp up phases • ABAP for developing infrastructure code like connecting mobile devices to SAP • Limited number of Open Source libraries for ABAP, commercial libraries are expensive
  5. Use case for a Java extension (1) • Customer wants

    to create predictions for sales volumes using Neural Networks • Customer wants to optimize the distribution of pallets on trucks using Constraint Logic Programming (CLP) • No ABAP libraries available • .NET and JAVA libraries available
  6. Use case for Java extension (2) • Customer uses an

    old SAP release • Service technicians use a mobile client based on Lotus Notes • Customer wants to send push Notifications to technicians from the SAP system • Customer wants to send basic informations to technicians from the SAP system • Google Firebase provides a sync layer for free / small amount • No ABAP libraries available • JAVA libraries available
  7. SAP Java Connector JCo The SAP Java Connector (SAP JCo)

    is a middleware component that enables the development of SAP-compatible components and applications in Java. SAP JCo supports communication with the SAP Server in both directions: Inbound: Java calls an ABAP Function Module Outbound: ABAP calls Java a Function Module implemented in JAVA
  8. SAP Java Connector JCo Pros • Free of charge for

    SAP customers • Rock solid software • Excellent APIs for SAP Busines Objects Cons • Complicated API • Verbose coding SAP System RFC Library JNI Layer RFC Middleware Middleware Interface JCo Java API Java Application
  9. SAP Function Modules • A Function Module in ABAP is

    similar to a public static method in JAVA • The APIs of an SAP System are defined by Function Modules • You can write your own Function Modules
  10. ABAP DATA: ls_option TYPE rfc_db_opt, lt_options TYPE TABLE OF rfc_db_opt,

    lt_fields TYPE TABLE OF rfc_db_fld, ls_tab512 TYPE tab512, lt_tab512 TYPE TABLE OF tab512. ls_option-text = 'MSGNR = ''100'''. APPEND ls_option TO lt_options. CALL FUNCTION 'RFC_READ_TABLE' EXPORTING query_table = 'T100' rowcount = 5 TABLES options = lt_options fields = lt_fields data = lt_tab512 EXCEPTIONS table_not_available = 1 … OTHERS = 0. IF sy-subrc = 0. LOOP AT lt_tab512 INTO ls_tab512. WRITE / ls_tab512-wa. ENDLOOP. ENDIF.
  11. Java JCoDestination destination = JCoDestinationManager.getDestination("NPL_DEMO"); JCoFunction function = destination.getRepository().getFunction("RFC_READ_TABLE"); function.getImportParameterList().setValue("QUERY_TABLE",

    "T100"); function.getImportParameterList().setValue("ROWCOUNT", 5); JCoTable optionsTable = function.getTableParameterList().getTable("OPTIONS"); optionsTable.appendRow(); optionsTable.setValue("TEXT", "MSGNR = '100'"); try { function.execute(destination); JCoTable table = function.getTableParameterList().getTable("DATA"); for ( int i=0; i < table.getNumRows(); i++) { table.setRow(i); System.out.println( table.getValue("WA")); } } catch (AbapException e ) { System.out.println("ERROR: " + e.getMessage() ); }
  12. Kotlin DSL sapCall("FIREBASE") { rfcFunction("RFC_READ_TABLE") { importing { this["QUERY_TABLE"] =

    "T100" this["ROWCOUNT"] = 5 } table("OPTIONS") { this.appendRow() this["TEXT"] = "MSGNR = '100'" } execute() onSuccess { table("DATA").forEach { println(it["WA"]) } } onAbapException { e -> println("ERROR ABAP Exception: ${e.message}") } } }
  13. How does it work? • Use functions having a lambda

    with a receiver as an argument • Inside the body of the function literal, the receiver object passed to a call becomes an implicit this, so that you can access the members of that receiver object without any additional qualifiers, or access the receiver object using a this expression. • This behavior is similar to extension functions, which also allow you to access the members of the receiver object inside the body of the function.
  14. How To (Rule of thumb) • Write an example of

    your DSL in pseudo code • Describe the class and the methods you need in each lambda • Implement the classes and functions top down
  15. Step 1 sapCall("FIREBASE") { Destination.rfcFunction("RFC_READ_TABLE") { importing { this["QUERY_TABLE"] =

    "T100" this["ROWCOUNT"] = 5 } table("OPTIONS") { this.appendRow() this["TEXT"] = "MSGNR = '100'" } execute() onSuccess { table("DATA").forEach { println(it["WA"]) } } onAbapException { e -> println("ERROR ABAP Exception: ${e.message}") } } }
  16. The code (simplified) fun sapCall(name : String, block : Destination.()

    -> Unit ) { Destination(name).block() } class Destination(destinationName: String, val destination : JCoDestination = JCoDestinationManager.getDestination(destinationName)) { fun rfcFunction(name : String, block: RfcFunction.() -> Unit) { RfcFunction(this.destination, name).block() } }
  17. Step 2 sapCall("FIREBASE") { rfcFunction("RFC_READ_TABLE") { RfcFunction.importing { this["QUERY_TABLE"] =

    "T100" this["ROWCOUNT"] = 5 } RfcFunction.table("OPTIONS") { this.appendRow() this["TEXT"] = "MSGNR = '100'" } RfcFunction.execute() RfcFunction.onSuccess { table("DATA").forEach { println(it["WA"]) } } RfcFunction.onAbapException { e -> println("ERROR ABAP Exception: ${e.message}") } } }
  18. The code (simplified) class RfcFunction(val destination: JCoDestination, name: String) {

    protected val importingParameters: ImportingParameters protected val exportingParameters: ExportingParameters private val tableParameters: JCoParameterList init { importingParameters = ImportingParameters(function.importParameterList) exportingParameters = ExportingParameters(function.exportParameterList) tableParameters = function.tableParameterList } fun onSuccess(init: () -> Unit) { ... } fun execute(): RfcFunction { try { this.successfull = false function.execute(destination) this.successfull = true }catch(e: AbapException) { this.abapException = e } catch (e: Throwable) { this.throwable = e } return this } fun onAbapException(init: (e: AbapException) -> Unit) { ... } fun exporting(init: ExportingParameters.() -> Unit): ExportingParameters { exportingParameters.init() return exportingParameters } fun importing(init: ImportingParameters.() -> Unit): ImportingParameters { importingParameters.init() return importingParameters } }
  19. Kotlin DSL sapCall("FIREBASE") { rfcFunction("RFC_READ_TABLE") { importing { ImportingParameter.this["QUERY_TABLE"] =

    "T100" ImportingParameter.this["ROWCOUNT"] = 5 } table("OPTIONS") { this.appendRow() this["TEXT"] = "MSGNR = '100'" } execute() onSuccess { table("DATA").forEach { println(it["WA"]) } } onAbapException { e -> println("ERROR ABAP Exception: ${e.message}") } } }
  20. The code class ImportingParameters(private val parameters: JCoParameterList?, private val writeonlyStructure:

    WriteonlyStructure = WriteonlyStructure(parameters)) { fun structure(structureName: String, init: WriteonlyValues.() -> Unit): WriteonlyValues { var writeonlyStructure = WriteonlyStructure(this.parameters!!.getStructure(structureName)) writeonlyStructure.init() return writeonlyStructure } operator fun set(name: String, value: String) { writeRecord!!.setValue(name, value) } operator fun set(name: String, value: BigInteger) { writeRecord!!.setValue(name, value) } operator fun set(name: String, value: Boolean) { writeRecord!!.setValue(name, value) } operator fun set(name: String, value: Char) { writeRecord!!.setValue(name, value)} operator fun set(name: String, value: CharArray) { writeRecord!!.setValue(name, value) } operator fun set(name: String, value: Short) { writeRecord!!.setValue(name, value) } operator fun set(name: String, value: Int) { writeRecord!!.setValue(name, value) } operator fun set(name: String, value: Long) { writeRecord!!.setValue(name, value) } operator fun set(name: String, value: Float) { writeRecord!!.setValue(name, value) } ... }
  21. Why do I use Coroutines? • Coroutines help me to

    execute functions in parallel • Coroutines help to call asynchronous functions in a deterministic order
  22. Kotlin Coroutines • Coroutines are experimental in Kotlin 1.1+ •

    The API is defined in kotlin.coroutines.experimental as the API might change in the future • The API will move to kotlin.coroutines when the API is finished • Warnings on experimental status are disabled via Gradle apply plugin: 'kotlin' kotlin { experimental { coroutines 'enable' } } apply plugin: 'kotlin' kotlin { experimental { coroutines 'enable' } }
  23. Coroutines From the documentation • “Basically, coroutines are computations that

    can be suspended without blocking a thread” • “.. coroutines can not be suspended at random instructions, but rather only at so called suspension points, which are calls to specially marked functions” • “Coroutine suspension is almost free”
  24. Coroutines Implementation 1. Extension to the Kotlin compiler • New

    Keyword: suspend for function • Compilation of coroutines to State Machines 2. Library • Functions to create coroutines: Coroutine Builder • Functions to wrap existing asynchronous APIs with coroutines • Functions to resume a suspended coroutine 3. Runtime • Dispatching of coroutines to Threads
  25. 2 Asynchronous Calls + 1 Thread Time call first service

    callback from service Blocking Blocking call next service with result from first service callback from service
  26. 2 Asynchronous Calls with Coroutine Time call a service callback

    from service to resume Suspend Suspend call next service callback from service
  27. 12 Asynchronous Calls + 6 Coroutines + 1 Thread •

    Coroutines are light-weight • You can create lots of coroutines
  28. Simple Benchmark Threads versus Coroutines Thread val time = measureTimeMillis

    { val n = AtomicInteger() val jobs = List(N) { thread(start=true ) { n.addAndGet(1) } } jobs.forEach { it.join() } } println(time) Coroutine val time = measureTimeMillis { val n = AtomicInteger() val jobs = List(N) { launch { n.addAndGet(1) } } jobs.forEach { it.join() } } println(time)
  29. Coroutines versus Threads -2000 0 2000 4000 6000 8000 10000

    12000 14000 16000 18000 1 10 100 1000 10000 100000 1000000 10000000 Threads /Coroutines Coroutine ST Coroutine MT Threads 1 50 19 10 10 44 23 5 100 52 24 46 1000 70 34 175 10000 120 88 1367 100000 192 179 12972 1000000 782 785 10000000 16026 14586 Time (ms) Number ofThreads/Coroutines
  30. • Use coroutines to perform computations in parallel • Use

    suspending functions to execute async calls in a deterministic order Rule of thumb
  31. Wrapping existing APIs Reading values from Firebase in Java final

    CountDownLatch latch = new CountDownLatch(1); final List fcmInfoList = new ArrayList<TestItem>(); FirebaseDatabase.getInstance().getReference("fcm/info").addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { for(DataSnapshot childSnapshot : snapshot.getChildren() ){ fcmInfoList.add(childSnapshot.getValue(TestItem.class)); } latch.countDown(); } @Override public void onCancelled(DatabaseError error) { latch.countDown(); throw error.toException(); } }); latch.await(); ... Further Processing
  32. Wrapping existing APIs Writing a Kotlin adapter for Coroutines for

    Firebase suspend fun <T> DatabaseReference.readList(clazz: Class<T>): List<T> = suspendCoroutine { continuation -> val list = ArrayList<T>() addListenerForSingleValueEvent(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { try { for (ds in snapshot.children) { list.add(ds.getValue(clazz)) } continuation.resume(list) } catch (e: Throwable) { continuation.resumeWithException(e) } } override fun onCancelled(error: DatabaseError) { continuation.resumeWithException(error.toException()) } }) }
  33. Wrapping existing APIs Now you can write val job =

    launch{ val list = FirebaseDatabase.getInstance().getReference("fcm/info").readList(TestItem::class.java) ... Further Processing } job.join()
  34. Wrapping existing APIs Some Firebase functions return an ApiFuture: Wrap

    It! suspend fun <T> ApiFuture<T>.await(): T = suspendCoroutine { continuation -> ApiFutures.addCallback(this, object : ApiFutureCallback<T> { override fun onFailure(t: Throwable) { continuation.resumeWithException(t) } override fun onSuccess(result: T) { continuation.resume(result) } }) }
  35. Wrapping existing APIs Now you can write val job =

    launch{ val list = FirebaseDatabase.getInstance().getReference("fcm/info").readList(TestItem::class.java) ... Further Processing } job.join()
  36. Summary • Kotlin is easy to use • Kotlin DSLs

    help to simplify the use of APIs, which are not easy to use • Coroutines help to execute code in parallel and suspending functions and coroutines help to execute asynchronous calls in a synchronous fashion.