
Der Anti-Corruption Layer (ACL) ist ein Muster (Pattern) aus dem Strategic Design im Domain-driven Design (DDD) von Eric Evans.
Ursprung
Im Domain-driven Design ist der ACL eine Schicht zwischen zwei oder mehr Bounded Contexts, also Domänen, und ermöglicht die Kommunikation zwischen diesen. Verschiedene Bounded Contexts können von verschiedenen Teams oder Gewerken verantwortet werden. Das führt dazu, dass ein Team oftmals ein unvollständiges Verständnis davon und wenig Kontrolle darüber hat, was in einem anderen Bounded Context passiert. Ein ACL trennt also verschiedene Domänen voneinander.
Der ACL soll verhindern, dass die Einführung fremder Konzepte aus einem anderen Bounded Context die eigene Domäne „verschmutzt“. Er wendet damit Schaden ab und ist eine Stütze der Modularisierung.
EIn ACL ist fachlich begründet und geht daher über ein einfaches Entwurfsmuster hinaus. Programmier-Entwurfsmuster können auf rein technischer Ebene mit einer überschaubaren Anzahl an Code-Zeilen veranschaulicht werden. Die Sinnhaftigkeit eines ACL beurteilen zu können, bedeutet die fachliche Expertise zu besitzen und einen Blick auf das große Ganze zu haben. Daher ist es auch schwer, ein gutes Beispiel für einen ACL als Code herunterzuschreiben.
Als der Begriff geprägt worden war, wurde ein ACL als eine Komposition verschiedener Entwurfsmuster beschrieben. Ein ACL ist eine Kombination aus Fassaden, Adaptern, Kommunikations- und Transportmechanismen. An anderer Stelle wird erwähnt, dass ein ACL üblicherweise als eine Menge an Services, manchmal aber auch als Entity implementiert wird.
Verwendung über DDD hinaus
Vermutlich hat der recht kämpferisch klingende Namen dazu geführt, dass der Begriff des Anti-Corruption Layer auch über DDD hinaus populär wurde. Denn er lässt sich ideal verwenden, um gute, moderne Software an schlecht gewachsene Legacy-Software anzubinden. Ich habe gut und modern kursiv geschrieben, da sich hierüber trefflich streiten lässt.
Ein ACL in diesem Sinne ist oftmals ein alleinstehender Service, der Qualitätsdefizite adressieren oder veraltete Bibliotheken, Protokolle oder Konzepte aus einer Software heraushalten soll. Hierbei kann nicht nur eine Rolle spielen, dass eine Software wartbar, frei von Sicherheitslücken in den verwendeten Abhängigkeiten (Dependencies) und homogen im Design gehalten werden soll, sondern dass auch konkrete Fehler durch Seiteneffekte bei der Anbindung an das Altsystem abgewandt werden, indem der ACL als Puffer dient und damit die restliche Software nicht betroffen ist Als Fehlerursache vorstellbar ist z.B., dass die direkte Anbindung zu einer sehr hohen Last (IO, CPU) oder einem hohen Speicherbedarf führt und dadurch die gesamte Software in Mitleidenschaft zieht.
Ich würde zuerst versuchen, derartige Probleme organisatorisch zu lösen. Nur wenn dies nicht möglich ist, würde ich einen ACL als technische Lösung in Betracht ziehen. Aus meiner Sicht stellt ein solcher fast immer ein Eingeständnis dar, dass es kommunikative Probleme gibt, die nicht gelöst werden können (oder wollen). Bei der Einführung eines ACL muss man sich ausreichend Gedanken machen, damit dieser auch eine Verbesserung darstellt und nicht nur zusätzliche Komplexität oder Schlimmeres bedeutet:
- Wird der ACL als eigenständiger Service umgesetzt? Spielt Skalierung eine Rolle?
- Handelt es sich um eine vorübergehende oder dauerhafte Lösung?
- Sind Endanwender direkt betroffen? Spielen Latenzen eine Rolle?
- Adressiert der ACL eine bestimmte API oder ein ganzes System? Ist er uni- oder bidirektional?
Konkretes Beispiel eines Anti-Corruption Layers
In einem vergangenen Projekt habe ich einen Anti-Corruption Layer mit eingeführt. Ein altes Legacy-System, das ich mit gewartet habe, sollte an einen neuen Stammdaten-Katalog angebunden werden. Beide Systeme wurden von verschiedenen Bereichen verantwortet. Während in dem einen Bereich ein agiles Mindset vorherrschte, war der andere sehr konservativ unterwegs. Die Kommunikation war alles andere als optimal, mehrere Versuche einen regelmäßigen Austausch zwischen den Gewerken zu etablieren, sind gescheitert.
Das Design der Katalogschnittstelle wurde nicht gemeinsam ausgearbeitet, sondern aus dem bereitstellenden Bereich vorgegeben. Zur Integration wurde neben einer Dokumentation auch eine Testinstanz bereitgestellt. Die Schnittstelle wurde als Feed implementiert. Der Feed enthielt eine Liste von Update-Nachrichten zu Produkten. Um ein Update aus dem Feed zu entfernen, musste das Update über die angegebene ID per HTTP-Aufruf bestätigt werden.
Die Schnittstelle erwies sich für das Team, welches die Legacy-Software daran anbinden wollte, als wenig brauchbar. Problematisch war vor allem die große Menge an Updates. Offenbar wurde unregelmäßig und unangekündigt ein Vollexport veranlasst, welcher zu Milliarden von Update-Nachrichten führte, so dass die Legacy-Software permanent mit der Verarbeitung beschäftigt war und nicht hinterherkam. Viele Updates erschienen darüber hinaus unbegründet. So wurden anscheinend Updates erzeugt, wenn sich Attribute änderten, die für die konsumierende Software nicht von Interesse und in einigen Fällen auch nicht einmal Teil des Feeds waren.
Die monolithische Legacy-Software brachte ein selbst entwickeltes Jobsystem mit sich. Darüber hinaus beinhaltete die Software ein ausgeklügeltes, vielseitig konfigurierbares Benachrichtigungssystem. Über dieses wurden unter anderem Belege generiert, Endkunden über anstehende Lieferungen informiert und Adressaktualisierungen propagiert. Mit einem HTTP-Feed hingegen konnte das System nicht viel anfangen. Das Legacy-System war nicht mandantenfähig, d.h. jeder Kunde besaß seine eigene Instanz mit je einer relationalen Datenbank.
Eine herkömmliche Integration in das System hätte bedeutet, dass der Feed über das Jobsystem konsumiert werden würde. Da jeder Job eine maximale Laufzeit hat, hätte dies nur in Inkrementen erfolgen können. Ein Lauf hätte eine feste Anzahl an Updates verarbeitet oder die Verarbeitung nach einer abgelaufenen Zeit beendet. Durch die Menge an Updates wäre das System permanent unter Last und Dead Locks auf der relationalen Datenbank waren bereits ein bekanntes Problem. Schlimmer aber noch, jede der über hundert Instanzen hätte die gleichen Updates verarbeitet und die Infrastruktur weit über ein gesundes Maß unter dauerhafte Last gesetzt.
Als Lösung haben wir einen neuen Service als ACL etabliert. Dies hatte folgende Vorteile:
- Der Service konnte mit modernen Werkzeugen entwickelt werden und musste sich nicht dem historischen Framework und der veralteten Architektur des Legacy-Systems unterordnen.
- Der Service konnte unnötige Updates zentral wegfiltern. Durch einen Vergleich gegen den letzten Stand konnte zentral entschieden werden, ob es sich aus Sicht des Altsystems um ein tatsächliches Update handelte oder nicht.
- Der Service konnte den Instanzen des Altsystems jeweils nur die Updates zu denjenigen Produkten zur Verfügung stellen, die sie auch in ihrer Datenbank hatten. Da viele Instanzen nur bis zu 10% des gesamten Katalogs kannten, war dies ein enormer Vorteil.
- Der Service konnte darüber hinaus Nicht-Verfügbarkeiten, lange Latenzen etc. beim Aufruf des Katalogs zentral behandeln und aus den Instanzen des Altsystems raushalten.
- Der Service konnte die Belange der Domänen des Altsystems berücksichtigen, so dass er sich optimal in das Job- und Benachrichtigungssystem integrieren ließ.
Insgesamt hat sich ein ACL in diesem Fall als große Bereicherung erwiesen. Statt einer dauerhaften zusätzlichen Last auf dem System und gehäuften Dead Locks wurde erreicht, dass die Updates einmal am Tag in wenigen Sekunden bis Minuten durchlaufen konnten.