Slide 1

Slide 1 text

SAP and Kotlin Cord Jastram, SAP Consultant [email protected]

Slide 2

Slide 2 text

Agenda • SAP • Extending SAP Systems • Using a Kotlin DSL to access SAP Systems • Using Coroutines to create scalable components • Q and A

Slide 3

Slide 3 text

Caution • Creating a DSL and using Coroutines worked out of the box • I did not get a deeper understanding of the internals

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

What can we do? Use Java libraries to pimp your SAP

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

First Task Connect SAP to a Java program running on a JVM

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Second Task Call asynchronous Firebase methods Call lots of methods

Slide 13

Slide 13 text

Connect a mobile device to SAP Firebase SAP Connector

Slide 14

Slide 14 text

Challenges Firebase SAP Connector

Slide 15

Slide 15 text

What can we do? KOTLIN !

Slide 16

Slide 16 text

Solution Firebase SAP Connector

Slide 17

Slide 17 text

An internal DSL for the SAP JCo

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Definition

Slide 20

Slide 20 text

Import Parameters

Slide 21

Slide 21 text

Export Parameters

Slide 22

Slide 22 text

Tables Parameter

Slide 23

Slide 23 text

Exceptions

Slide 24

Slide 24 text

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.

Slide 25

Slide 25 text

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() ); }

Slide 26

Slide 26 text

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}") } } }

Slide 27

Slide 27 text

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.

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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}") } } }

Slide 30

Slide 30 text

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() } }

Slide 31

Slide 31 text

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}") } } }

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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}") } } }

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Kotlin DSL in IntelliJ Editor

Slide 36

Slide 36 text

DSL Code Completion Importing Parameter • Writeonly Interface • Minimal API

Slide 37

Slide 37 text

Java Code Completion Importing Parameter • Full API • Useless methods

Slide 38

Slide 38 text

Real Life SD_SALESDOCUMENT_CREATE

Slide 39

Slide 39 text

Real Life: 14 Input Parameters

Slide 40

Slide 40 text

Real Life: 39 Table Parameters

Slide 41

Slide 41 text

Real Life: 130 Fields for a Structure

Slide 42

Slide 42 text

Coroutines

Slide 43

Slide 43 text

Why do I use Coroutines? • Coroutines help me to execute functions in parallel • Coroutines help to call asynchronous functions in a deterministic order

Slide 44

Slide 44 text

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' } }

Slide 45

Slide 45 text

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”

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Coroutines versus Threads

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

12 Asynchronous Calls + 6 Threads • Threads are expensive • Upper limit of threads

Slide 50

Slide 50 text

2 Asynchronous Calls with Coroutine Time call a service callback from service to resume Suspend Suspend call next service callback from service

Slide 51

Slide 51 text

12 Asynchronous Calls + 6 Coroutines + 1 Thread • Coroutines are light-weight • You can create lots of coroutines

Slide 52

Slide 52 text

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)

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

An Example suspension point from IDE coroutine builder

Slide 55

Slide 55 text

Helper Functions

Slide 56

Slide 56 text

An Example

Slide 57

Slide 57 text

An Example

Slide 58

Slide 58 text

An Example

Slide 59

Slide 59 text

An Example

Slide 60

Slide 60 text

An Example

Slide 61

Slide 61 text

An Example

Slide 62

Slide 62 text

An Example

Slide 63

Slide 63 text

An Example

Slide 64

Slide 64 text

An Example

Slide 65

Slide 65 text

An Example

Slide 66

Slide 66 text

An Example

Slide 67

Slide 67 text

An Example

Slide 68

Slide 68 text

An Example

Slide 69

Slide 69 text

• Use coroutines to perform computations in parallel • Use suspending functions to execute async calls in a deterministic order Rule of thumb

Slide 70

Slide 70 text

Wrapping existing APIs Reading values from Firebase in Java final CountDownLatch latch = new CountDownLatch(1); final List fcmInfoList = new ArrayList(); 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

Slide 71

Slide 71 text

Wrapping existing APIs Writing a Kotlin adapter for Coroutines for Firebase suspend fun DatabaseReference.readList(clazz: Class): List = suspendCoroutine { continuation -> val list = ArrayList() 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()) } }) }

Slide 72

Slide 72 text

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()

Slide 73

Slide 73 text

Wrapping existing APIs Some Firebase functions return an ApiFuture: Wrap It! suspend fun ApiFuture.await(): T = suspendCoroutine { continuation -> ApiFutures.addCallback(this, object : ApiFutureCallback { override fun onFailure(t: Throwable) { continuation.resumeWithException(t) } override fun onSuccess(result: T) { continuation.resume(result) } }) }

Slide 74

Slide 74 text

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()

Slide 75

Slide 75 text

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.