JVM-Sprachen im Mix - JAXenter
Java Blend

JVM-Sprachen im Mix

Eckard Buchner
Schlumpfparade

Nachdem wir bisher auf mögliche Probleme eingegangen sind, soll ein Beispiel zum Abschluss zeigen, dass mehrsprachige Projekte durchaus funktionieren können: Nehmen wir an, eine Bank hätte uns mit der Entwicklung einer Geldwäschepräventionslösung beauftragt. Finanzinstitute sind laut Geldwäschegesetz zum Einsatz solcher Lösungen verpflichtet und müssen Verdachtsfälle an die Behörden (FIU) melden. Geldwäsche kann auf vielerlei Arten betrieben werden. Beschränken wir uns hier auf den klassischen Fall, der im englischen Smurfing (smurf = Schlumpf) genannt wird: Da bei Bareinzahlungen über 15 000 EUR eine Identifikation des Einzahlenden und eine Datenspeicherung von 5 Jahren zwingend erforderlich ist, werden einfach innerhalb eines kürzeren Zeitraums mehrere Bareinzahlungen unterhalb dieses Betrags getätigt. Das Geld wird so unbemerkt in den Geldkreislauf eingeschleust [21]. Um dies zu verhindern, sind bei Banktransaktionen folgende Regeln zu prüfen:

  • Transaktion wurde bar getätigt (Bareinzahlung)
  • Betrag liegt unterhalb 15 000 EUR und oberhalb einer bestimmten Schwelle für Bagatellbeträge
  • Mehrere solche Einzahlungen innerhalb eines bestimmten Zeitraums ergeben in der Summe mehr als 15 000 EUR

Idealerweise sind die Regeln vom Geldwäschebeauftragten der Bank ohne Programmieraufwand änderbar. Zum Beispiel sollte der Zeitraum oder der Schwellwert für Bagatellbeträge variabel sein.

Für jede Komponente der Anwendung nutzen wir die Stärken der jeweiligen Sprache: Die Metaprogrammierung in Groovy erlaubt die Entwicklung einer eigenen, vereinfachten Regelsyntax in Form einer DSL. Darin führen wir Schlüsselwörter aus der Fachsprache des Anwenders ein, die ihm das Verständnis und die Anpassung der Regeln erleichtern sollen. Für das „Model“, d. h. die Datenstrukturen und die Analyseroutinen auf diesen Daten, nutzen wir die funktionalen Eigenschaften von Scala. Am Ende stellen wir mit einem JUnit-Test in Java sicher, dass wir unseren Code in eine vielleicht schon bestehende Java-Anwendung integrieren können. Abbildung 1 zeigt die wichtigsten Klassen und Methoden.

Abb. 1: Geldwäschebeispiel
PruefeTransaktion() {
  Wenn(BetragZwischen(2500,15000)) {
    Wenn(BarEinzahlungen(Zeitraum: 10) > 15000) {
      GeldwaescheVerdacht(
        Meldung : "Smurfing-Verdacht!",
        Score   : 10
      )
    }
  }
}
(smurfingCheck.aml)

Das Skript bildet die oben besprochenen Regeln zur Smurfing-Erkennung ab. Es wird als Groovy-Skript geladen und interpretiert. Die Hauptmethode PruefeTransaktion() wird dabei dem Skriptobjekt dynamisch eingepflanzt und von einer Hilfsklasse implementiert (Listing 7).

Listing 7

class TransactionCheck {
  def script
  def checkDelegate = new ScriptDelegate()

  def TransactionCheck(String resourceName) {
    script = new GroovyShell().parse(
                          TransactionCheck.getResource(resourceName).text)
    script.metaClass {
      PruefeTransaktion = {
        Closure closure ->
        closure.delegate = checkDelegate
        closure()
      }
    }
  }

  def checkTransaction(Account account, Transaction transaction) {
    checkDelegate.nextTransaction(account, transaction)
    script.run()
  }

  List alerts() {
    checkDelegate.alerts
  }
}
(TransactionCheck.groovy)

Alle im Skript verwendeten Methoden werden vom ScriptDelegate implementiert. Die Klasse prüft und übersetzt die Parameter aus dem Prüfskript und gibt die Aufrufe an das Model weiter. Dazu bekommt der ScriptDelegate die zu prüfende Transaktion vor jeder Skriptausführung übergeben. Listing 8 zeigt die Scala-Klassen für Transaktionen und Konten.

Listing 8

class Transaction(val date: Date, val amount: BigDecimal, val cash: Boolean) {} 
(Transaction.scala)

class Account(val id : String) {
  val transactions = new ListBuffer[Transaction]

  def cashDeposits(earliest: Date): Double = {
    val sumFn = (d: Double, t: Transaction) => {
      val amount = t.amount.doubleValue
      if (t.cash
              && amount > 0
              && t.date.after(earliest))
        d + amount
      else
        d
    }

    transactions.foldLeft(0.0)(sumFn)
  }
} 
(Account.scala)

Die Transaktionen besitzen nur einige Attribute. Beachten Sie, dass amount aus Kompatibilitätsgründen als java.math.BigDecimal (statt scala.BigDecimal) deklariert ist. Die Kontoklasse berechnet mit der Funktion cashDeposits() für einen bestimmten Zeitraum die Summe der Bareinzahlungen. Dazu verwenden wir die foldLeft()-Methode aus den Scala Collections mit einer geeigneten Filterfunktion. Aufgerufen wird die Funktion in der Methode BarEinzahlungen() im ScriptDelegate wie folgt:

double BarEinzahlungen(Map filter) {
    Date earliest = new Date().minus(filter.Zeitraum)
    account.cashDeposits(earliest)
  }

Aus dem Parameter Zeitraum, den der Anwender im Skript festgelegt hat, wird ein Datum errechnet und an die Methode im Model weitergegeben. Den vollständigen Quellcode finden Sie auf der Heft-CD. Testen wir nun unsere kleine Anwendung mit einem JUnit-Test (Listing 9).

Listing 9

public class TransactionCheckTest {
    @Test
    public void testSmurfing() {
        // create check class
        TransactionCheck tc = 
              new TransactionCheck("smurfingCheck.aml");

        // create an account
        Account account = new Account("0001");

        // create and check first transaction - 5 days ago
        Transaction t1 = 
              new Transaction(todayMinus(5), new BigDecimal("9000.00"), true);
        account.transactions().$plus$eq(t1);
        tc.checkTransaction(account, t1);
        assertEquals(0, tc.alerts.size());

        // create and check second transaction - yesterday
        Transaction t2 = 
              new Transaction(todayMinus(1), new BigDecimal("8000.00"), true);
        account.transactions().$plus$eq(t2);
        tc.checkTransaction(account, t2);
        assertEquals(1, tc.alerts.size());
    }

    private Date todayMinus(int days) {
        return DefaultGroovyMethods.minus(new Date(), days);
    }
} 
(TransactionCheckTest.java)

Führt man den Test aus, wird der Smurfing-Alarm ausgelöst; die Beträge sind im Test passend gewählt. Wie man sieht, arbeiten alle Sprachen friedlich zusammen, wenn wir von dem seltsamen Aufruf der

psenv::pushli(); eval($_oclass[„plus“]); psenv::popli(); ?>

psenv::pushli();$env->object_param[0]=““; eval($_oclass[„eq“]); psenv::popli(); ?>

-Methode bei den Konten einmal absehen. Das könnte man in Scala durch eine Hilfsmethode addTransaction() beseitigen. Darauf wurde hier aber verzichtet.

Fazit

Der gemischte Betrieb mehrerer Sprachen in einem Projekt ist eine spannende Angelegenheit, wenn man über die Verwendung von Java-Klassen und -Bibliotheken aus den neuen Sprachen hinausgeht. Zwar generieren alle Compiler Bytecode, das heißt aber nicht, dass die Funktionalität deswegen in jeder beliebigen Sprache zur Verfügung steht. Der Artikel hat einige fortgeschrittene Sprachfeatures gezeigt, die nur in der jeweiligen Sprache nutzbar sind. Auf jeden Fall empfiehlt es sich, über eine komponentenweise Trennung der Sprachen nachzudenken. Bei Verwendung z. B. in einem OSGi Framework ist es unerheblich, in welcher JVM-Sprache die einzelnen Komponenten realisiert sind.

Eckard Buchner ist seit 1997 Softwareentwickler bei der TONBELLER AG und seit 2006 Projektleiter und Produktverantwortlicher in der Entwicklung für den Bereich Geldwäschebekämpfungslösungen. 2008 absolvierte er das St. Gallener Management Seminar und beschäftigt sich aktuell intensiv mit der Java-Sprachentwicklung und der Einführung agiler Ansätze und Techniken.

Geschrieben von
Eckard Buchner
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.