Services

Services sind in Schleupen.CS das grundlegende architektonische Strukturierungsmittel. Diese werden - wie bereits in Kommunikationsarten beschrieben - als WCF-Services implementiert. Im Folgenden werden alle wichtigen Aspekte bei der Definition von Services beleuchtet.

Grundlegende Prinzipien

Services in Schleupen.CS 3.0 liegen folgende grundlegende Prinzipien zugrunde:

  • Services sind zustandslos.
    Dies ist wichtig, um die horizontale Skalierbarkeit optimal zu unterstützen. 
  • Sind möglichst technologieneutral.
    Java-Clients können Services genauso nutzen wie C#-Clients.
  • Services haben geringe Anforderungen an die Infrastruktur.
    Sie nutzen z.B. keine verteilten Transaktionen.
  • Der Serviceschnitt beachtet Prinzipien wie beispielsweise Kohärenz.

Service-Namen

Der Name eines Services ist wichtig. Er sollte intuitiv erfassbar und selbsterklärend sein, um den Nutzer vor Überraschungen zu schützen. Um das zu erreichen sollte er:

  • widerspiegeln, was der Service tut, nicht aber wie er es tut.
  • den Namen der betroffenen Business Entity / Entities enthalten.
  • die fachliche Aktion beschreiben, die durchgeführt wird.
    Eine Vereinheitlichung kann über das Konzept der Prozessverben erreicht werden.
  • Begriffe der Anwendungsdomäne verwenden.
  • als Postfix die Servicekategorie (Entity Service, Activity Service, Process Service, Query Service, ...) nennen.
  • für gleiche Operationen auch immer die gleichen Bezeichnungen verwenden
    (nicht mal „Anlegen“ und mal „Erstellen“ verwenden).

Er sollte nicht

  • generisch benannt werden.
  • unbekannte Kürzel beinhalten.
  • beschreiben wie der Service implementiert ist, z.B. keine Hinweise auf technische Details enthalten.

Ein Beispiel für einen guten Servicenamen ist BucherAusleihenActivityService, eher ungeeignet ist beispielsweise ObjektInDatenbankSpeichernEntityService.

Diskussionswürdig ist, ob der Postfix ActivityService, der sich von einer sogenannten Servicekategorie ableitet, nachteilig ist. Wir haben uns für das Hinzunehmen des Postfix entschieden, da das Verhalten der Services abhängig von der Kategorie sehr unterschiedlich ist und daher leicht erkennbar sein sollte. 

Eine Business Entity ist ein Objekt samt Beziehung, das über einen Entity Service verwaltet wird. Es ist das Pendant zu einem Aggregat im DDD in der Schnittstellendefinition.

Wir verwenden keine Umlaute und Sonderzeichen in der gesamten Schnittstellendefinition. Also weder in dem Schnittstellennamen noch im Namensraum, bei den Operationen und verwendeten Typen sowie den Attributen.

Interface Segregation

Services in Schleupen.CS sollten für die Wiederverwendung optimiert werden, damit diese durch Dritte oder in mehreren Aufrufkontexten einfach genutzt werden können. Dies ist allerdings nicht in allen Nutzungskontexten sinnvoll, da ein Service, der beispielsweise für Massendatenverarbeitungsprozesse optimiert ist, im UI-Kontext selten sinnvoll nutzbar ist. Wir nutzen aus diesem Grund das Interface Segregation Principle (ISP)

Ziel ist es, eine optimale Implementierung für den jeweiligen Kontext bereitzustellen. Daher sind die bestehenden Services i.d.R. für den häufigsten Nutzungskontext optimiert. Andererseits bedeutet dies, dass ähnliche Services für unterschiedliche Kontexte existieren. Dies ist ein akzeptierter Trade-Off.

Auch die unten beschriebenen Servicekategorien haben eine Einfluss. Entity Services, die atomar sind, sind eher zur Wiederverwendung geeignet, Process Services, die Geschäftsprozessabschnitte umsetzen, sind zur allgemeinen Wiederverwendung in unterschiedlichen Aufrufkontexten eher ungeeignet.

Granularität und Laufzeit

Wie "groß" ein Service sein sollte, lässt sich allgemein nur schwer beantworten. Dennoch gibt es einige zu beachtende Kriterien:

  • Be chunky, not chatty
    Viele sehr kleine Service-Aufrufe, die n-mal in einer Schleife aufgerufen werden ("Chattiness"), sorgen für eine schlechtere Performance, als ein großer Aufruf mit einem "Stück" Arbeit ("Chunk").
  • Laufzeit
    Der Chunk darf aber auch nicht beliebig groß werden. Es gilt das richtige Verhältnis aus Chattiness und Chunk-Größe zu finden. Man befindet sich in einem Dilemma. Die Nachrichtengröße muss so gewählt sein, dass der Aufruf "in der Regel" deutlich unterhalb der Time-Out-Grenze abgearbeitet werden kann (Faustregel max. 50%). Da unterschiedliche Auslastungen des Systems und unterschiedliche Hardware zu stark differierenden Laufzeiten führen, ist es aber der Erfahrung überlassen, was "in der Regel" bedeutet. In diese Überlegungen ist insbesondere auch die Größe der Antwort einzubeziehen. Es schient sinnvoll, nur die wirklich benötigten Daten zu übertragen, auch wenn dies die Wiederverwendbarkeit für andere Anwendungsfälle einschränkt (siehe hierzu insbesondere auch die Überlegungen zum Bounded Context).
  • Atomarität
    Jeder Serviceaufruf sollte nur eine Funktion erfüllen, die in einer atomaren Einphasen-Transaktion durchgeführt werden kann. Hierdurch werden inkonsistente Zustände vermieden.
  • Geschäftsfunktion
    Idealerweise entspricht jede Serviceoperation einer geschäftlichen Funktionalität.

Design

Im folgenden werden weitere grundlegende Design-Prinzipien beschrieben, die wir bei der Definition der Schnittstellen beachten:

  • Typisierung: Um eine möglichst gute Nutzbarkeit und Lesbarkeit zu erreichen, werden möglichst explizite Typen verwendet und Generik vermieden.
  • Jede Service-Definition des kanonischen Servicemodell ist isoliert definiert und hat keinerlei Typ-Kopplung zu anderen Service-Definitionen.
  • Für schreibende Operation wie das Anlegen eines Buchs verwenden wir in den Schnittstellen-Definitionen Guids. Damit kann der Benutzer die Ids selber erstellen was im Fall von Asynchronität vorteilhaft ist, da diese sofort bekannt sind.
  • Eine Service-Definition hat eine hohe Kohäsion.
  • Alle Operationen eines Service weisen denselben Abstraktionsgrad auf (Single Level of Abstraction).

Servicekategorien

Services sind in Schleupen.CS immer genau einer Kategorie zugeordnet. Diese Kategorisierung ermöglicht es, dem Ersteller des Service Clients, die Eignung des Service für den eigenen Kontext einzuschätzen und die erforderlichen Randbedingungen einzuhalten.

Eine Servicekategorie definiert dessen grundsätzliches Verhalten und seinen Zweck. Sie unterstützt die Einhaltung des Principle Of Least Surprise. Dies ist aus Nutzersicht sinnvoll, da bei der Einbindung von Services ungewollte Seiteneffekte entstehen können, die es zu vermeiden gilt (zum Beispiel nicht kontrollierbare synchrone Aufrufketten). Ferner sind hierdurch Regeln ableitbar, die zu einer besseren Strukturierung der Gesamtlösung führen, siehe Serviceaufrufregeln.

Eine Servicekategorie wird über Merkmale definiert, die jeweils einen Aspekt eines Services beschreiben, z.B. die Möglichkeit des asynchronen Aufrufs. Die Merkmale haben Abhängigkeitsbeziehungen untereinander. So kann ein Service z.B. nicht gleichzeitig atomar und als Kompositum implementiert sein.

Servicekategorien und deren Default-Zuordnung in der Architektur
Process Service

Ein Process Service bildet einen automatisierten, im Hintergrund laufenden Geschäftsprozess(abschnitt) ab. Im Allgemeinen wird dieser technisch durch einen Workflow implementiert. Er hat folgende Merkmale:

  • führt mehrere fachliche Aktionen aus und bildet einen im Hintergrund laufenden Geschäftsprozess(abschnitt) ab
  • der Fokus liegt auf dem Prozess, nicht auf den Daten
  • gehört im Architekturdiagramm zur Geschäftsprozess-Schicht / Orchestrierungsebene
  • kann durch Konfiguration oder Austausch in das Customizing einbezogen werden
  • die Parameter sind typischerweise nur Ids und keine Entitäten; wenige Parameter
  • hat eine hohe Versionsstabilität
  • gibt keine Antwort mit Payload zurück
  • ist asynchron
  • ist nicht transaktional. Kompensationen müssen erstellt werden, um im Falle eines nicht erfolgreichen Abschlusses die bereits abgeschlossenen Aktivitäten rückgängig zu machen.
  • der Serviceaufruf ist zustandslos
    (Implementierung im Standard durch einen Workflow, dieser selbst ist nicht zustandslos)
  • arbeitet orchestrierend in Bezug auf die Services der Länder
  • kann transkontinental, also Baustein- oder Business Entity-übergreifend arbeiten.

Ein Beispiel eines Process Service zeigt folgende Abbildung:

Beispiel eines Process Services
Activity Service

Ein Activity Service bildet einen fachlichen Schritt, eine Aktivität eines Geschäftsprozesses ab. Er arbeitet Business-Entity-übergreifend und hat eine möglichst minimale Schnittstelle. Die typischen Merkmale sind:

  • ist aktionsgetrieben, setzt eine Aktivität um 
  • der Fokus liegt auf der Aktion, nicht auf den Daten 
  • gehört im Architekturdiagramm zur Geschäftsprozessschicht / Orchestrierungsebene
  • kann durch durch Konfiguration oder Austausch in das Customizing einbezogen werden
  • die Parameter sind typischerweise Ids und keine Entitäten; wenige Parameter
  • hohe Versionsstabilität
  • ist das Pendant zu einem Dialogschritt (Granularität!)
  • kann kompensierbar sein: jede Operation hat per Default eine Kompensationsoperation
  • wird insbesondere aus Geschäftsprozessen heraus verwendet
  • kann Antwort mit Payload enthalten
  • ist zustandslos
  • ist synchron
  • wird im Land oder einer GP-Komponente implementiert
  • arbeitet Business-Entity-übergreifend ("Indikator"!)

Ein Beispiel eines Activity Service zeigt folgende Abbildung:

Beispiel eines Activity Services
Command Service

Ein Command Service ist die asynchrone Variante eines Activity Service ohne Antwort. Er bildet in diesem Sinne also auch einen fachlichen Schritt eines Geschäftsprozesses ab. Er arbeitet Business Entity-übergreifend und hat eine möglichst minimale Schnittstelle. Ein Command Service ist einem One-Way-Service ähnlich. Wichtig ist aber, dass ein Command Service auf Messaging beruht und im Fehlerfall Retrys auf der Implementierungsseite zur Anwendung kommen. Die Wahrscheinlichkeit von Client-seitigen Timeouts ist sehr gering und wennn die Message übergeben wurde, ist das Zustellen sichergestellt. Die typischen Merkmale sind:

  • ist aktionsgetrieben, setzt eine Aktivität um 
  • der Fokus liegt auf der Aktion, nicht auf den Daten 
  • gehört im Architekturdiagramm zur Geschäftsprozessschicht / Orchestrierungsebene
  • kann durch durch Konfiguration oder Austausch in das Customizing einbezogen werden
  • die Parameter sind typischerweise Ids und keine Entitäten; wenige Parameter
  • hohe Versionsstabilität
  • ist das Pendant zu einem Dialogschritt (Granularität!)
  • kann kompensierbar sein: per Default hat jede Operation eine Kompensationsoperation
  • wird insbesondere aus Geschäftsprozessen heraus verwendet
  • hat keine Antwort
  • ist zustandslos
  • ist asynchron
  • wird im Land oder einer GP-Komponente implementiert
  • arbeitet Business-Entity-übergreifend ("Indikator"!)

Ein Beispiel eines Command Service zeigt folgende Abbildung:

Beispiel eines Command Services

In unserem Kanonischen UML-Modell wird der Service wie in der Abbildung modelliert. Generiert man hieraus eine WSDL, so wird ein PruefeResponse dazu generiert, der letzten Endes nur für den Business Event Dispatcher benötigt wird.

Query Service

Ein Query Service bildet eine Business Entity-übergreifende Suche ab. Der Anwendungsfokus liegt primär auf dem UI-Kontext.

  • der Fokus liegt auf dem Lesen von Daten und nicht auf einer Aktion wie bei den ActivityServices
  • gehört im Architekturdiagramm zur Geschäftsprozessschicht / Orchestrierungsebene
  • ist ein Baustein (durch Konfiguration oder Austausch) für das Customizing im Rahmen der Wiederverwendung
  • wird insbesondere in einem Dialogschritt verwendet
  • wird insbesondere aus UIs heraus verwendet
  • die Versionsstabilität ist geringer, da sie eine breitere Schnittstelle haben
  • ist zustandslos
  • ist synchron
  • wird im Land oder einer GP-Komponente implementiert
  • arbeitet Business Entity-übergreifend ("Indikator"!)
  • arbeitet lesend, verändert keine Daten (Kompensation irrelevant etc.)

Ein Beispiel eines Query Service zeigt folgende Abbildung:

Beispiel eines Query Services
Entity Service

Ein Entity Service kapselt die Daten einer Business Entity. Er stellt Operationen zur Verfügung um mit diesen Daten zu arbeiten. Ein Entity Service kann mehr Funktionalität als für reines CRUD erforderlich beinhalten. Ein Validierung der Daten ist häufig sinnvoll oder erforderlich.

  • der Fokus liegt auf den Daten, genauer auf der Business Entity, ist stark typisiert
  • hat optional fachliche Funktionalität in Form von Operationen, die über reines CRUD hinausgehen
  • gehört im Architekturdiagramm zur Länderschicht
  • darf nur Events auslösen, kein anderer Service darf aufgerufen werden, dieser hat somit keine Abhängigkeiten zu anderen Ländern oder Komponenten - er ist autonom
  • die Versionsstabilität ist geringer, da es eine breitere Schnittstelle gibt
  • kurz laufend
  • ist zustandslos
  • ist synchron
  • insbesondere ausgeprägt für den UI-Kontext, i.A. gekapselt hinter Dialogschritt, nicht ausgeprägt für GP-Kontext
  • wird durch ein Land bereitgestellt

Ein Beispiel eines Entity Service zeigt folgende Abbildung:

Beispiel eines Entity Services
Event Service

Ein Event Service ist ein Subscriber eines Business Events, der als WebServices implementiert ist. Er dient also der Informationsverteilung über ein fachliches Ereignis. Die Service-Beschreibung definiert die bei Eintreten das auslösenden Ereignisses aufzurufende Methode und die zu kommunizierende Information. Er ist Teil der losen Kopplung zwischen Publisher und Subscribern und ermöglicht die Implementierung der Subscriber. Die Event-Schnittstelle wird durch den Auslöser des Events definiert.

  • Enthält genau eine Operation
  • die Schnittstelle wird durch den Publisher definiert
  • ist zustandslos
  • ist asynchron
  • Fokus liegt auf dem Ereignis, nicht auf den Daten
  • der Datenumfang sollte minimal sein, z.B. Ids, da der Umgang bei Inkompatibilitäten problematisch werden kann
  • hat einen möglichst kleinen Payload, im Allgemeinen nur Ids
  • hohe Versionsstabilität
  • wird im Land oder einer GP-Komponente implementiert
  • wird im Land oder einer GP-Komponente ausgelöst
  • nutzt den Service Bus zur Entkopplung von Publisher und Subscribern.

Ein Beispiel eines Event Service zeigt folgende Abbildung:

Beispiel eines Event Services

Event Services transportieren im Allgemeinen lediglich Ids, damit diese möglichst selten inkompatibel werden. Die eigentlichen Daten können dann per Entity Service vom Subscriber (= Event Service) gelesen werden.

Auch Workflows können EventServices implementieren (im Gegensatz zum Direkt-Start durch ProcessServices)! Dies ist aus zweierlei Hinsicht häufig sinnvoll:

  • Aus einem Land / einer Provinz ist es nicht erlaubt, direkt einen ProcessService einer GP-Komponente aufzurufen. Über Events kann diese Hierarchisierung und die korrekte Abhängigkeit-Beziehung erreicht werden (vgl. Dependency Inversion).
  • Da derzeit noch keine Subworkflows derart implementiert werden können, dass übergeordnete Workflow direkt eine Response erhält, bieten sich hierfür ebenfalls Events als Lösung an.

Warum ist es kritisch, Event Services mit großem Payload auszustatten?
Betrachten wir folgenden Anwendungsfall: es wird eine neue, inkompatible Version 3.1 eines Events der ursprünglichen Version 3.0 erstellt. Für die Version 3.0 gäbe es mehrere Subscriber. Der Bereitsteller kündigt die Version 3.0 ab. Betrachten wir die Lösungsmöglichkeiten:

  1. Es wird nur die neueste Version des Events ausgelöst. Das hat zur Folge, dass alle Subscriber die neue Version mit dem Rollout des Auslösers des Events abonnieren müssen. Da dies koordiniert werden müsste ist klar, dass das kein sinnvoller Weg sein kann und eine massive Kopplung darstellen würde.
  2. Es werden beide Versionen des Events ausgelöst, die Subscriber abonnieren nur jeweils eine Version. Das zuvor beschrieben Problem ist somit gelöst, es entsteht keine unnötige Last und Subscriber können unabhängig auf neueste Version des Events wechseln. Aber es bleibt das Problem, dass Events der Version 3.0, die zum Zeitpunkt der Umstellung der Subscriber auf die neue Version 3.1 in den Queues noch vorhanden sind, nicht mehr zugestellt werden können. Diese Events sind verloren.
  3. Es werden alle Versionen des Events ausgelöst, Subscriber abonnieren für einen definierten Zeitraum beide Versionen des Events und zu einem späteren Zeitpunkt (nach Ablauf des Abkündigungszeitraums) wird das Event in der Version 3.0 nicht mehr unterstützt. Unter der Voraussetzung, dass keine DeadLetters existieren, gehen nun keine noch nicht zugestellten Events verloren. Durch die vor der Abkündigung erforderliche parallele Zustellung beim Subscriber kann es aber zu Konkurrenzsituationen kommen, da er zu dieser Zeit die Events in der Version 3.0 und 3.1 empfängt. Dies kann zeitlich auseinanderliegen. Der Subscriber muss mit dieser Situation umgehen können. Benötigt wird eine Art "Event-Idempotenz" welche an die "Aufruf-Idempotenz" erinnert, aber nicht identisch zu dieser ist.

Als Lösung hierzu wird Lösungsweg 3 empfohlen. Dabei sollte der Publisher ein Zeitstempel des Auslöse-Zeitpunkt der Message mitliefern (CreatedAt). Durch die Installation kann der Subscriber sich merken (z.B. durch ein Konfigurationsskript), ab wann er redundant Nachrichten erhalten kann. Ab diesem Zeitpunkt des doppelten Bindens an Version 3.0 und 3.1 kann er dann unterscheiden, ob er die Nachricht der Version 3.0 verwerfen kann oder nicht.

Cookie Consent mit Real Cookie Banner