Stammbaumprüfung mit BPMN

“Zu größerer Klarheit über seine Gedanken gelangt man, indem man sie anderen klar zu machen sucht.”

Joseph Unger

Häufig bleiben interessante Erweiterungen für Anwendungen liegen, weil der Aufwand für die Ablauflogik zu hoch erscheint. Eine solche Erweiterung ist die Automatisierte Stammbaumfreigabe für den Ancestor Rest Service aus dem Beitrag REST in Peace.

Öffentlich zugängliche Stammbäume haben den eklatanten Nachteil, dass Personendaten in ihnen gespeichert werden. Sind diese Personen nur schon lang genug tot, dann können etwaige Datenschutzbelange ignoriert werden. Bei lebenden Personen ist dies nicht mehr so einfach und ohne die Zustimmung der Betroffenen müssen die Daten geschwärzt werden.

In diesem Beitrag soll die Automatisierte Stammbaumprüfung umgesetzt werden. Ein eingehender Stammbaum wird validiert und bei möglichen Fehlern eine entsprechende E-Mail an den Autoren gesendet. War die Validierung erfolgreich, dann wird der Stammbaum auf schützenswerte personenbezogene Daten geprüft. Sind solche Daten vorhanden wird eine entsprechende Nachricht an den Autoren gesendet und die Speicherung verweigert. Ansonsten wird der Stammbaum gespeichert und dies dem Autoren mitgeteilt.

Gerade diese komplexe Logik lässt das Herz des Entwickler höher schlagen. Viele Sonderfälle, Randbedingungen, Entscheidungen und Konfigurationen, die eingefügt und getestet werden müssen. Aus Projektsicht aber ein Alptraum, wenn sich die Anforderungen an den Funktionalitäten häufig ändern. Dann muss dieser Code immer wieder geändert werden, Testfälle angepasst und neue Fehler gesucht und behoben werden. Selbst wenn der Code dann endlich fehlerfrei läuft, kann er an den Anforderungen des Auftraggebers vorbeilaufen.

Eine Möglichkeit diese Probleme zu vermeiden ist die Verwendung von Geschäftsprozessen in der Modellierung und Implementierung solcher Features. Mit der Business Process Model and Notation (BPMN) können Geschäftsprozesse in einer standardisierten Darstellung erfasst werden und mit diversen Frameworks auch direkt verarbeitet werden. Ein bekannter Vertreter solcher Frameworks ist das frei verfügbare Framework Flowable. Es bietet neben den üblichen Möglichkeiten einer Prozess-Engine, einen web-basierten Designer für Geschäftsprozesse, Spring Boot Support und automatisierte Test.

Der Geschäftsprozess kann mit dem Flowable Designer modelliert werden. Die Anwendung kann mit einer einfachen Docker Kommandozeile gestartet werden.

docker run -p 8080:8080 flowable/all-in-one

Dann kann die Anwendung unter http://localhost:8080/flowable-modeler aufgerufen werden.

Ein Ergebnis für die Automatisierte Stammbaumprüfung ist in der folgenden Abbildung zu sehen.

An dieser Stelle sollte darauf hingewiesen werden, dass die Modellierung nicht in die Hände von Software Entwicklern fallen sollte. denn BPMN ist keine Programmiersprache, auch wenn die Möglichkeiten gegeben sind. Die Formulierung der Geschäftsprozesse soll auf einer domänenspezifischen Ebene gehalten werden, die der Kunde spricht. Alle Details die der Software Entwickler entwirft, gehören in die Implementierung der einzelnen Tasks.

Werden diese Ebenen vermengt, ergeben sich allzu schnell Nachteile für beide Seiten. Der Kunde beschäftigt sich nicht mehr mit seinen Geschäftsprozessen, sondern nur noch mit den Details einer speziellen technischen Ausprägung. Änderungen werden für den Kunden komplex und unübersichtlich. Die Software Entwickler ihrerseits verlieren den Überblick über ihre Freiheitsgrade in der Implementierung, weil die tatsächlichen Geschäftsanforderungen nicht mehr von technischen Schritten zu unterscheiden sind.

Ist der Geschäftsprozess als BPMN Workflow formuliert, kann die automatisierte Verarbeitung angegangen werden. Dazu werden als Nächstes die Java Implementierungen für die einzelnen Service Tasks implementiertt. Dazu werden in Flowable Implementierungen des Interface JavaDelegate verwendet. Für den Task Stammbaum validieren ist hier beispielhaft der ValidateTreeTask vorgestellt.

@Component
public class ValidateTreeTask implements JavaDelegate {
  private static final Logger LOGGER = LoggerFactory.getLogger(ValidateTreeTask.class);

  @Autowired private ValidationService service;

  @Override
  public void execute(DelegateExecution execution) {
    Optional<GedCom> gedcom = service.validate(execution.getVariable("content", String.class));
    gedcom.ifPresent(x -> execution.setVariable("tree", convertTree(x)));
    LOGGER.info("execute: {}", gedcom.isPresent());
    execution.setVariable("valid", gedcom.isPresent());
  }

  private String convertTree(GedCom gedcom) {
    try {
      return new ObjectMapper().writeValueAsString(gedcom);
    } catch (JsonProcessingException e) {
      return null;
    }
  }
}

Bei der Verwendung von Spring Boot können alle Möglichkeiten der Dependency Injection genutzt werden. Ärgerlicherweise kann Flowable nicht einfach den Spring Support in JavaDelegate Instanzen über das class Attribute einfügen, sondern es muss das delegateExpression Attribute verwendet werden. Ärgerlich deshalb, weil die Einschränkung weder verständlich, noch dokumentiert oder intuitiv ist.

Der obige Task speichert zusätzlich den eingelesenen Stammbaum als Json String in der Variablen tree. Diese Umwandlung ist nötig, weil die Klasse GedCom nicht serialisierbar ist. Die Serialisierbarkeit ist nötig, weil alle Workflow Variablen persistiert werden. Ansonsten setzt er den Wert der Variable, entsprechend dem Erfolg des Service Aufrufes auf true oder false. Diese Variable ist für die darauf folgende Fallunterscheidung im Workflow notwendig.

Der Spring Support in der Workflow Engine liefert gute Voraussetzungen für Unit Tests des Geschäftsprozesses. Nicht nur die einzelnen Services und Tasks können getestet werden, sondern auch der gesamte Workflow.

@SpringBootTest
@ExtendWith(FlowableSpringExtension.class)
@Deployment(resources = { "processes/upload-tree.bpmn20.xml" })
public class ArticleWorkflowSpringTest {
  @Autowired private RuntimeService runtimeService;

  @MockBean private ValidationService validationService;
  @MockBean private CheckPrivacyService privacyService;
  @MockBean private TreeService treeService;
  @MockBean private EmailService emailService;

  @Test
  void validTreeWithNoPrivacyInformation() {
    when(validationService.validate(any())).thenReturn(Optional.of(new GedCom()));
    when(privacyService.checkPrivacy()).thenReturn(true);

    runtimeService.startProcessInstanceByMessage("Stammbaum", singletonMap("content", "test.ged"));

    assertEquals(0, runtimeService.createProcessInstanceQuery().count());
    verify(emailService).sendMail("uploader", "success.ftl");
    verify(treeService).save(any());
  }
}

Die Zeile @ExtendWith(FlowableSpringExtension.class) sorgt für den Flowable Support im Unit Test. Die darauf folgende Zeile definiert den Workflow der getestet werden soll. Die @Deployment Annotation an der Klasse definiert den Workflow für alle Test Methoden. An der Methode annotiert, definiert sie den Workflow nur für diese Methode.

In der Test Methode wird mit Mockito das Verhalten der Mocks definiert und dann mit dem RuntimeService der Workflow gestartet. Am Ende des Tests wird verifiziert, ob der Workflow vollständig durchlaufen wurde und die gewünschten Services tatsächlich aufgerufen wurden.

Innerhalb der Spring Applikation kann die Workflow Engine wie im Unit Test verwendet werden. Je nach Anwendungsfall, kann der Workflow über eine Rest Schnittstelle, einer Message-Queue oder einer anderen Variante der Stammbaum Anlieferung, gestartet werden.

Der hier dargestellte Prozess ist nur ein sehr einfaches Beispiel für die Möglichkeiten einer Workflow Engine. Interessanter wird es, wenn mehrere Geschäftsprozesse ineinander greifen und nicht nur automatisierte Prozessschritte vorkommen. Aber das ist Stoff für weitere Beiträge.