Bringen Sie Klarheit in Ihren Monolithen mit begrenzten Kontexten

Schauen Sie sich das Video dieses Vortrags von ElixirConf 2017 unten an

Monolithische Anwendungen sind großartig, wenn Sie mit dem Aufbau Ihres Unternehmens beginnen, aber im Laufe der Zeit werden sie schwierig zu warten. Diese Codebasen werden, wenn sie wachsen, leicht zu großen Schlammkugeln.

Indiana Jones Rock

Beim Erstellen großer Anwendungen in Frameworks wie Rails beginnen die Prinzipien des Convention-over-Configuration-Designs, die Rails zu einer solchen Freude gemacht haben, im Weg zu stehen, wenn die Anwendung an Umfang zunimmt. Möglicherweise haben Sie auch die gleichen Schmerzen, wenn:

  • Refactoring ist schwierig und langwierig, da Methoden und Klassen von zu vielen anderen Klassen abhängen
  • Sie haben eine ständig wachsende Liste von Geschäftsobjekten, die schwer im Kopf zu behalten sind. Tatsächlich scheint niemand in der Lage zu sein, das System als zusammenhängendes Ganzes zu verstehen.
  • Das Ändern von Code in einem Bereich des Codes führt zu unerwarteten und unbeabsichtigten Nebenwirkungen in anderen Bereichen des Codes, da es einfach ist, globale Dienste und Objekte anzurufen.

In unserem letzten gemeinsamen Chat haben wir gemeinsam mit den Business-Experten und Ihrem Entwicklungsteam über die Entwicklung einer allgegenwärtigen Sprache gesprochen, damit Ihr Team enger zusammenarbeiten kann. Heute werden wir darauf aufbauen, indem wir neue DDD-Tools (Domain-Driven Design) einführen. Anschließend führen wir eine neue Ordnerstruktur für Ihre Rails-Apps ein, um sie auf eine Zukunft vorzubereiten, in der Ihre Anwendung weniger gekoppelt und zusammenhängender ist. Lass uns anfangen!

Lassen Sie uns über Domänen sprechen

Ein Schlüsselprinzip in DDD ist, dass die von Ihnen erstellte Software die (Geschäfts-) Domäne der Organisation, die sie erstellt, genau widerspiegeln muss. Daher müssen wir einige Hausaufgaben machen, um den Geschäftsbereich Ihrer Software zu verstehen.

Eine Domain ist das, was das Unternehmen tut, und der Kontext, wie es es tut.

Schauen wir uns unser Delorean-Beispiel aus dem vorherigen Beitrag noch einmal an. Darin wird das Unternehmen als Uber für Zeitreisen vermarktet. Daher ist seine “Domäne” (das “was es tut”) Zeitreisen-Mitfahrgelegenheit. Ebenfalls in der Domäne enthalten ist das “Wie”, wie es funktioniert – durch die Partnerschaft von Fahrern, die zeitreisende Delorean-Fahrzeuge besitzen, mit Passagieren, die Zeitreisen unternehmen möchten.

Um mehr Nuancen im Geschäftsbereich zu erhalten, führt DDD ein anderes Konzept ein, das als Subdomain bezeichnet wird:

Eine Subdomain repräsentiert die kleineren Gruppen oder Einheiten des Unternehmens, die im Alltag zusammenarbeiten, um die Geschäftsziele zu erreichen.

Delorean ist innerhalb des Unternehmens in mehrere Teams aufgeteilt. Schauen wir uns zwei von ihnen an und sehen, wofür sie verantwortlich sind:

Trip-Plattform-Team Finanz-Operations-Team
Mission Entwerfen und unterstützen Sie die Systeme, die Fahrten routen und Fahrer mit Passagieren verbinden Verwalten Sie die Systeme, an denen Finanzinstitute und Kreditkartenverarbeiter beteiligt sind
Verantwortlichkeiten
  • Passagiere mit Fahrern verbinden
  • Den Fahrer zum nächsten Ziel weiterleiten
  • Passagiere über ankommende Fahrer benachrichtigen
  • Fahrer auf neue Passagiere aufmerksam machen
  • Auszahlungen an Fahrer verarbeiten
  • Systemweite Transaktionshistorie für Audits pflegen
  • Finanzberichte erstellen
  • Kreditkartengebühren für Passagiere verarbeiten

Jede dieser beiden Gruppen animiert eine Geschäftsverantwortung oder Subdomain. Nennen wir sie Ridesharing Experience und Ecommerce.

Jetzt haben wir eine allgemeine Illustration des Unternehmens und zwei seiner Einheiten, die ihm helfen, im Alltag zu funktionieren. Die Domain und Subdomain sind Möglichkeiten, den Problembereich Ihres Unternehmens zu modellieren – und wie es sich verhält, um diese Rollen zu erfüllen. Die Chancen stehen gut, dass Ihr Business-Organigramm die Subdomains Ihres Unternehmens genau widerspiegelt. In der realen Welt sind die Abgrenzungen möglicherweise weniger klar – Teams sind möglicherweise für mehrere überlappende Subdomains verantwortlich.

Füllen wir dieses Diagramm mit ein paar weiteren Subdomains im Delorean-Geschäft:

  • Kundensupport-Subdomain: Lösen von Kundensupport-Tickets, die per E-Mail eingehen
  • Marketing-Subdomain: verwalten von Marketing-E-Mail-Kampagnen und Marketing-Gutscheincodes
  • Identitäts-Subdomain: Wie das System jeden Benutzer und seine identifizierenden Informationen verfolgt

Beschränkte Kontexte im Lösungsraum

Dieses Diagramm vor uns spiegelt nun die Geschäftsziele des Unternehmens wider, die in logische Einheiten unterteilt sind, die (hoffentlich) ihre Ziele in der realen Welt erreichen. Jetzt überlagern wir die Softwaresysteme, die diese Ziele erreichen, über dieses Diagramm. Diese Softwaresysteme werden als begrenzte Kontexte beschrieben:

Ein begrenzter Kontext ist ein System, das die Ziele des Unternehmens in der realen Welt erfüllt.

Alle unsere Softwaresysteme (wie ein Webdienst oder eine Webanwendung), die als konkrete Instanzen in der realen Welt arbeiten, werden als begrenzte Kontexte betrachtet.

Technisch gesehen ist der begrenzte Kontext in DDD-speak eine bestimmte Grenze innerhalb Ihrer Domain, die Ihr Glossar aus Ihrer allgegenwärtigen Sprache nur anwenden kann – die Idee ist, dass verschiedene Subdomains konkurrierende oder widersprüchliche Definitionen von Begriffen haben können. In diesem Beitrag werden die sprachlichen Nuancen des begrenzten Kontexts nicht näher erläutert. Weitere Informationen finden Sie in Martin Fowlers Erklärung zu begrenzten Kontexten.

Nun kommt es vor, dass bei Delorean alle diese Subdomains in einem System implementiert sind – einem großen Schlammball und einem Monolithen. Wir zeichnen ein blaues Kästchen um die Subdomains, deren Funktionen vom Softwaresystem implementiert werden. In diesem Fall beginnen wir mit unserem oben genannten Rails-Monolith:

Da es der Monolith ist, macht er im Grunde alles – und hier frisst er alle anderen Subdomains im Diagramm.

Vergessen wir nicht – wir haben ein paar andere Softwaresysteme, die wir hier nicht modelliert haben. Was ist mit all den netten Integrationen von Drittanbietern, die das Unternehmen verwendet? Dies sind auch Softwaresysteme. Wir zeichnen sie als blaue Kästchen.

Übrigens – was wir hier gezeichnet haben, ist eine Kontextkarte – ein Diagramm, das Geschäftsziele und konkrete Implementierungen von Softwaresystemen mischt. Es ist nützlich, um die Lage des Landes Ihrer Softwaresysteme zu bewerten und Abhängigkeiten zwischen Teams zu visualisieren.

Nun, das ist vernünftig und sauber, aber wir leben in der realen Welt, und Software aus der realen Welt sieht selten konsistent und kohärent aus. Wenn Sie Ihre Rails-App nach den Standardkonventionen erstellt haben, fehlen Ihrer App intern die Gruppierungen, die erforderlich sind, um Ihre App in ihren Komponenten zu visualisieren. In Wirklichkeit sieht die Delorean-Codebasis eher so aus:

Der Punkt ist – Rails erzwingt keine organisatorischen Einschränkungen für unsere Softwaresysteme – was bedeutet, dass logische Geschäftseinheiten (unsere Subdomains), die entkoppelte Schnittstellen vorschlagen, nicht im Code materialisiert werden, was im Laufe der Jahre zu Verwirrung und zunehmender Komplexität führt.

Die große Idee: Organisieren Sie Rails-Code in Modulen nach Business-Subdomain

Obwohl Ihre Ruby-Klassen in Ihrer Anwendung wahrscheinlich im globalen Namespace leben, können sie leicht in Module gezupft werden. Unser Ziel ist es, logische Gruppen von Domänencode zu erstellen, die in eigenständige Komponenten isoliert werden können.

Tatsächlich besteht eines der Ziele von domänengesteuerten Designs darin, eine Eins-zu-Eins-Zuordnung von einer Subdomain zu einem begrenzten Kontext zu haben.

OK, was bedeutet das? Lassen Sie uns zusammen mit Beispielen auf einige Empfehlungen eingehen.

Ordnerstrukturen in eine flache domänenorientierte Gruppierung invertieren

Sie erinnern sich vielleicht, dass die folgenden Rails-Konventionen uns zu Ordnerhierarchien führen, die Klassen nach Rollen gruppieren:

Verschieben wir alles in eine neue Verzeichnisstruktur: lassen Sie uns stattdessen ähnliche Funktionen nach Domäne gruppieren. Wir beginnen mit einer ersten Variante, die ich eine flache domänenorientierte Gruppierung nenne.

Klassen modulieren

Als nächstes möchten Sie die Klassen von dem modulieren, was sie zuvor waren. Da die Treiberklasse unter die Ridesharing-Domäne fällt, fügen wir sie einem Ridesharing-Modul hinzu:

Sie möchten dies für jede Klasse tun, die Sie in die flache Verzeichnisstruktur app/domains verschieben.

Verknüpfte Modelle anhand des vollständigen Klassennamens referenzieren

Zusätzlich müssen Sie Ihre ActiveRecord-Modellzuordnungen ändern, um auf die Klasse anhand ihres vollständigen, modulierten Pfads zu verweisen:

Halten Sie Controller auf dem Laufenden, wo sie ihre neu modulierten Ansichten finden

Sie müssen auch dieses kleine Bit einfügen, damit Routen vom Controller wissen, wo sie nach den Ansichten suchen müssen:

Hier ist die coole Sache: Sie müssen nicht Ihren gesamten Code auf einmal verschieben. Sie können eine kleine Domäne in Ihrer Anwendung auswählen, den ausgereiftesten Bereich Ihres Codes oder den Bereich, in dem Sie das beste Verständnis haben, und beginnen, seine Bedenken in einen einzelnen Domänenordner zu verschieben, während der vorhandene Code so lange gespeichert bleibt, bis er verschoben werden kann.

Nun haben wir einige kleine Schritte unternommen, um architektonische Klarheit in unserer Anwendung zu erreichen. Wenn wir jetzt hinschauen, haben uns unsere modularen Ordnerstrukturen geholfen, unseren Code so zu gruppieren:

Unter der Haube könnte unsere App eher so aussehen:

Was funktioniert gut mit diesem Ansatz?

  1. Es gibt weniger Rauschen in jedem Dateiverzeichnis – durch Gruppieren wie Dateien nach Domänenspezifität finden wir einen natürlichen Organisationspunkt
  2. Die Entitäten, die in jedem Domänenordner verbleiben, sind sehr zusammenhängend – sie neigen höchstwahrscheinlich dazu, miteinander zu kommunizieren und auf natürliche Weise miteinander zu erscheinen
  3. Entitäten, die nicht zusammengehören, werden jetzt getrennt (lockerer gekoppelt)
  4. Wenn Sie engineering–Teams, die entlang Subdomain-Verantwortlichkeiten arbeiten, können diese Ingenieure jetzt in einer schlankeren, isolierten Art und Weise arbeiten. Eine lockere Kopplung ermöglicht es diesen Teams, Änderungen mit der Gewissheit vorzunehmen, dass sie keine Regressionen einführen oder Konflikte in die Codebasis zurückführen
  5. Die Bühne ist jetzt auf lange Sicht bereit, um jeden dieser Domänenordner in einen unabhängigen Softwaredienst zu verschieben (mehr dazu in einem zukünftigen Blogbeitrag)

Wenn Sie weitere Anleitungen zu dieser Ordnerstruktur wünschen, habe ich eine Beispiel-App entwickelt, die diese domänenorientierte Ordnerstruktur aufweist: http://github.com/andrewhao/delorean . Werfen Sie einen Blick und lassen Sie mich wissen, was Sie denken.

Was haben wir gelernt?

In unserer gemeinsamen Zeit lernten wir Domain-driven Design Konzepte rund um Domains und Subdomains kennen. Wir lernten, unsere Softwaresysteme als begrenzte Kontexte auf einer Kontextkarte zu visualisieren, die uns die Bereiche des Systems zeigte, die als zusammenhängende Teile zusammengehören.

Als praktische Anmerkung haben wir gezeigt, wie Rails-Dateien und -Ordner “invertiert” und als Domain-First-Gruppierungen neu interpretiert werden können.

In meinem nächsten Beitrag werden wir unsere Diskussion in einem kommenden Blogbeitrag darüber fortsetzen, wie wir unseren domänenorientierten Rails-Code weiter von Domänenereignissen entkoppeln und schließlich in das Land der Microservices vordringen können.

Schreibe einen Kommentar

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