Anwendungen strukturiert aufbauen

Für die Struktur von Code in der objektorientierten Programmierung (OOP) eignet sich der Einsatz der SOLID-Prinzipien. Doch wie steht die Sache bei der Anwendungsarchitektur?

Know-how  –  1 Kommentare
Anwendungen strukturiert aufbauen
Anzeige

Die Frage, wie sich die Codearchitektur strukturieren lässt, scheint zunächst nicht einfach zu beantworten. Zu unterschiedlich sind Anwendungen, zu differenziert deren jeweilige Umgebungen, als dass es das eine pauschale Vorgehensmuster gäbe. Selbstverständlich gibt es Richtlinien, an denen man sich orientieren kann, allerdings beschreiben sie stets nur einen Ausschnitt des Gesamtbilds.

Elementar für die Entwicklung moderner Web- und Cloud-Angebote sind in dem Kontext die als 12 Factor Apps bekannten Regeln des Cloud-Dienstleisters Heroku. Sie sind im Folgenden mit 12FA abgekürzt. Ohne auf all ihre Aspekte im Detail eingehen zu wollen, zeigen die Empfehlungen rasch, in welche Richtung man gedanklich gehen muss: So sind insbesondere die Forderung nach statuslosen Prozessen zentral, die sich schnell starten und sauber beenden lassen. 12FA verlangt das explizite modellieren sämtlicher Abhängigkeiten – sei es mittels Paketverwaltung oder als externe, über eine Konfiguration beschriebene und angeforderte Dienste. Ein so entstandenes Modell ist eine Grundlage für den Einsatz von Microservices.

Auf den ersten Blick scheinen Letztere lediglich eine neue Bezeichnung für das zu sein, was vor rund 15 Jahren der Begriff SOA beschrieb, nämlich Webservices. Tatsächlich geht der Gedanke von Microservices aber weit über die Ideen der Service-oriented Architecture beziehungsweise REST (Representational State Transfer) hinaus, denn er beschreibt Details wie die Infrastruktur und den Umgang mit ihr als Teil des Services.

Der englische Entwickler Martin Fowler beantwortet die Frage nach einer Erklärung der Microservice wie folgt:

"In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies."

Unabhängig von dieser Definition gibt es den Ansatz, vom eigentlichen Wort auszugehen. Ein Service ist eine unabhängige, isolierte Einheit, die eine bestimmte Aufgabe für ihre Umgebung erfüllt. Sie agiert dabei autonom vom Rest ihrer Umwelt und ist idealerweise statuslos. Wenn man so will, ist eine Funktion die kleinste denkbare Einheit eines Service.

Alle bislang genannten Bedingungen erfüllt beispielsweise die in nahezu jeder Plattform verfügbare Funktion zur Berechnung des Sinus: Sie ist unabhängig, isoliert vom Rest der Plattform lauffähig, agiert autonom, ohne Abhängigkeit von anderen Funktionen und ist üblicherweise statuslos implementiert. Dennoch spricht man in dem Fall von einer Funktion und nicht von einem Service oder Dienst.

Der Grund dafür ist, dass Dienste eigenständige Prozesse darstellen. Eine Funktion hingegen kommt zusammen mit vielen weiteren Funktionen in seinem Kontext zum Einsatz. Würde man die Funktion als eigenständigen Prozess modellieren, ließe sich tatsächlich von einem Dienst reden.

Der Begriff micro ist dagegen nicht so klar umrissen, allerdings macht er klar, dass es um eine kleine Einheit geht. Mit klein geht üblicherweise der Begriff fokussiert einher, denn wenn sich Entwickler auf wenig beschränken müssen, lassen sich ohnehin nicht mehr zahlreiche Themen zugleich angehen.

Zusammengefasst beschreibt der Gedanke von Microservices also kleine, eigenständige Einheiten, die isoliert und unabhängig voneinander arbeiten, und sich auf eine einzige Aufgabe konzentrieren. Das erinnert ein bisschen an das Konzept der Lego-Bausteine, die für sich genommen klein sind und nur eine einzige Aufgabe erfüllen. In beiden Fällen entsteht das eigentliche Potenzial aus der Kombinierbarkeit der Elemente.

Schlägt man den Bogen zu den aus der objektorientierten Welt bekannten SOLID-Prinzipien (siehe Exkurs "SOLID im Überblick" am Ende des Artikels), fällt auf, dass sich einige von ihnen hervorragend auf den Architekturansatz übertragen lassen. Am Stärksten gilt das vermutlich für das S, also das Single Responsibility Principle (SRP). Es besagt, dass jede Klasse nur eine einzige Verantwortung beziehungsweise Aufgabe haben sollte. Der US-amerikanische Informatiker Robert C. Martin, der unter anderem das Buch "Clean Code" geschrieben hat, hat das Prinzip wie folgt umrissen:

"There should never be more than one reason for a class to change."

Der Satz lässt sich auf Dienste übertragen: Sie sollten nur eine einzige Verantwortlichkeit im fachlichen Sinne haben und sich nur ändern, wenn es ihre fachliche Kernkompetenz betrifft. Insofern gilt SRP auch für Dienste und ist ein erster Indikator, wie Software zu schneiden sein könnte, um einen Monolithen in kleinere Einheiten zu zerlegen.

Der Fokus auf den fachlichen Aspekten spielt bei all dem eine zentrale Rolle, da ein Dienst primär eine fachliche Einheit darstellt und erst sekundär eine technische. Genau das meint auch Martin Fowler wenn er schreibt "services are built around business capabilities".

SRP allein genügt jedoch nicht. Ein weiterer Grund für schlechten, unüberschaubaren und damit langfristig kaum wartbaren Code ist die typische Redundanz, die man in vielen Projekten vorfindet. Das dafür gültige Rezept ist nicht Bestandteil der SOLID-Prinzipien und heißt "Don't Repeat Yourself". Es besagt, dass man immer, wenn man etwas zum zweiten Mal umsetzt, überlegen sollte, wie sich beide Vorkommen gemeinsam handhaben lassen.

Auf Code einer Klasse bezogen heißt das, dass man sich wiederholenden Code in eine gemeinsame Methode auslagern sollte. Bei der Architektur einer Anwendung bietet es sich an, Funktionen, die an unterschiedlichen Stellen genutzt werden, in einen gesonderten Dienst (oder zumindest ein gemeinsam verwendetes Modul) auszulagern. Man kann sogar soweit gehen zu sagen, dass sich DRY auch auf die Infrastruktur anwenden lässt: In dem Moment, in dem man einen zweiten Server so aufsetzt, dass er einem anderen ähnelt, sollte man darüber nachdenken, den Vorgang zu automatisieren. Damit wird er nicht nur skalierbar, sondern vor allem auch reproduzierbar und ist zudem dokumentiert. Das Vorgehen verhindert Snowflake Server und führt zu einem höheren Grad an automatisiert verwalteter Infrastruktur.

Zu guter Letzt muss noch gelten, dass Dienste unabhängig voneinander fungieren. Das lässt sich mit SRP und DRY noch nicht garantieren, allerdings gibt es dafür einen weiteren Baustein der SOLID-Prinzipien: Das I steht für das Interface Segregation Principle und besagt, dass eine Schnittstelle nur das absolute Minimum an zwingend notwendigem Funktionsumfang bereitstellen sollte, um eine Aufgabe zu erledigen. Alles darüber hinaus gehört in eine gesonderte Schnittstelle.

Bei Diensten verhält es sich genauso: Bietet jeder über seine Schnittstelle nur genau die fachlichen Fähigkeiten an, die zum Bewältigen einer Aufgabe erforderlich sind, werden Dienste unabhängiger voneinander und lassen sich im Bedarfsfall zugleich leichter kombinieren, ohne unnötigen Ballast mitzuschleppen. Robert C. Martin hat das wie folgt formuliert:

"Clients should not be forced to depend upon interfaces that they do not use."

Mit anderen Worten bedeutet das, dass ein Dienst, der auf einen anderen zurückgreift, sich dadurch nicht an weitere Funktionen binden sollte, sondern nur an das, was tatsächlich fachlich relevant ist.

Anzeige