architecture-components.html 23.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<p>Als Software-Architektur bezeichnen wir ein abstraktes Modell, aus dem insbesondere eine strukturierte und hierarchische Anordnung der Bauteile eines Anwendungssystems und eine Beschreibung der Beziehungen dieser Teile untereinander hervorgeht <a href="#cite-Bal11">[Bal11]</a>. Mit dem Begriff Architektur (lat. <i>architectura</i>, Baukunst) verbinden wir zunächst allgemein die Gestaltung von Raum, was in der Praxis häufig dem Entwurf von Bauwerken entspricht. Durch den Architekturentwurf entsteht ein Modell, das Vorgaben für die spätere Konstruktion des Bauwerks macht. In der Softwaretechnik wird der Architekturbegriff mit gleicher Semantik genutzt, wobei die Bauteile entweder generisch als Komponenten (z.B. in der UML) oder als Module (z.B. in Java) bezeichnet werden. Es folgt eine entsprechende Definition der <a href="https://ieeexplore.ieee.org/document/4278472">IEEE</a>.</p>

<div class="cite"><a href="https://ieeexplore.ieee.org/document/4278472">"Architecture is the fundamental organization of a system, embodied in its components, their relationships to each other and the environment, and the principles governing its design and evolution." (ISO/IEC/IEEE 42010:2011)</a></div>

<h4>Prinzipien des Software Engineering</h4>

<span>Der Zweck einer Software-Architektur ist an den allgemeinen Prinzipien des Software Engineering ausgerichtet. Zu diesen Prinzipien zählen nach Ghezzi et al. <a href="#cite-GJM03">[GJM03]</a>:</span>
<ul>
<li><b>Abstraktion</b>: Eine Architektur soll einen ganzheitlichen Überblick auf ein Anwendungssystem bieten und muss dazu verallgemeinern. Wichtige Aspekte sind zu identifizieren und darzustellen, unwichtige Aspekte auszublenden. Das Abstraktionsniveau ist auf den Zweck der Darstellung angepasst. Abstraktion ist grundsätzlich erforderlich, um Komplexität zu beherrschen.</li>
<li><b>Modularisierung</b>: Ein komplexes System wird in Komponenten/Module aufgeteilt. Dabei erlaubt Hierarchisierung, dass Module ihrerseits wieder in feinere Teilmodule zerlegt werden können, und Kapselung, dass ein Modul die Details seiner Implementierung in der Sichtbarkeit nach außen bewusst begrenzt. Bei der Aufteilung der Module wird eine hohe Kohäsion und eine geringe Kopplung angestrebt. Hohe Kohäsion heißt, dass innerhalb des Moduls ein enger Zusammenhang zwischen seinen Teilen besteht, z.B. viele Abhängigkeiten der Klassen innerhalb eines Moduls. Geringe Kopplung heißt, dass zwischen den Modulen über deren nach außen sichtbare Schnittstellen möglichst wenig Abhängigkeiten bestehen.</li>
<li><b>Trennung von Zuständigkeiten</b> (engl. <i>Separation of Concerns</i>): Es wird angestrebt, dass ein Modul möglichst für einen abgegrenzten Aspekt zuständig ist. Wenn die Aspekte klar getrennt sind, kann die Verantwortlichkeit für die Entwicklung der Module ebenso aufgeteilt und parallelisiert werden. Da das ganzheitliche Architekturmodell als Kommunikationsmittel für verschiedene Stakeholder dient, sollte es aus unterschiedlichen aspekt-orientierten Sichten betrachtet werden können. Ein früher Vorschlag diesbezüglich ist das <a href="https://en.wikipedia.org/wiki/4%2B1_architectural_view_model">"4+1 Architecural View Model"</a>, das folgende 4 zueinander konsistente Sichten auf eine Architektur vorschlägt und diese anschließend in ausgewählten Anwendungsszenarien zur Veranschaulichung zusammenführt (+1):
<ul>
	<li><i>Logical View</i>: Die Funktionalität des Systems aus Sicht des Anwenders wird dargestellt, z.B. als Sequenz- oder Klassendiagramm in der UML.</li>
	<li><i>Development View</i>: Die hierarchische Struktur und Abhängigkeiten des System werden aus Sicht des Entwicklers dargestellt, z.B. als Komponenten- oder Paketdiagramm in der UML.</li>
	<li><i>Process View</i>: Das dynamische Verhalten des Systems zur Laufzeit wird dargestellt, d.h. insbesondere Parallelität und Synchronisation, z.B. als Aktivitätsdiagramm in der UML.</li>
	<li><i>Physical View</i>: Die Verteilung der Komponenten auf Laufzeitumgebungen, insbesondere auf virtuelle und dedizierte Server, aus Sicht des für den Betrieb zuständigen Systemadministrators wird dargestellt, z.B. als Verteilungsdiagramm in der UML.</li>
</ul>
</li>
<li><b>Allgemeingültigkeit</b> (engl. <i>Generality</i>): Zu Beginn eines Softwareprojekts stellt sich die Frage, wie individuell die Architektur des zu konstruierenden Systems sein muss. Wenn ein bewährtes Framework bereits als passende Architekturvorlage vorliegt, ist es grundsätzlich empfehlenswert darauf aufzubauen. Bei der Wiederverwendung von erprobten Komponenten kann i.d.R. davon ausgegangen werden, dass der Aufwand für eine eigene Implementierung deutlich höher ausfällt als die Einarbeitungszeit in die API der wiederverwendeten Komponente. Außerdem können durch Wiederverwendung ansonsten häufig auftretende Schwachstellen vermieden werden. Die Vor- und Nachteile eines individuellen Architekturentwurfs gegenüber einer adaptierten Standardarchitektur sind sorgfältig abzuwägen. Es ist zu beachten, dass die Architektur eines neues Systems sich durch die eigenen Entwurfsentscheidungen manifestiert.</li>
<li><b>Inkrementalität</b>: Inkrementalität basiert auf der Annahme, dass im Verlauf der Entwicklung Änderungen nicht zu verhindern sind und impliziert damit iteratives Vorgehen. Solange das Anwendungssystem weiterentwickelt wird, ist auch dessen Architektur i.d.R. nicht dauerhaft stabil. Die Evolution eines Anwendungssystems führt zu Unterschieden in der sogenannten präskriptiven und deskriptiven Architektur. Die präskriptive Architektur beschreibt, was durch die Architekten zur Entwurfszeit geplant war, während die deskriptive Architektur ausdrückt, was tatsächlich zur Laufzeit im Betrieb beobachtet werden kann, z.B. durch dynamische Code-Analyse mittels geeigneter <a href="https://www.gartner.com/reviews/market/apm">Application Performance Monitoring-Werkzeuge</a> wie <a href="https://www.appdynamics.com/">AppDynamics</a>, <a href="https://www.dynatrace.de/">Dynatrace</a> oder <a href="https://newrelic.com/">New Relic</a>. Wir sprechen von Software-Erosion, wenn der Soll-Zustand (präskriptiv) und der Ist-Zustand (deskriptiv) der Architektur stark voneinander abweichen.</li>
<li><b>Früherkennung von Änderungen</b> (engl. <i>Anticipation of Change</i>): Aufbauend auf der inkrementellen Entwicklung eines Systems gilt es potentielle zukünftige Änderungen möglichst früh zu antizipieren und in der Architektur zu berücksichtigen.  Eine gute Architektur ist flexibel erweiterbar, aber weiterhin möglichst einfach zu handhaben. Die Ursachen für Änderungen sind sehr verschieden, z.B. Beseitigung von Fehlern (korrektive Wartung), Verbesserung nicht-funktionaler Eigenschaften (perfektive Wartung), Anpassung der Funktionalität wegen sich ändernder Bedingungen (adaptive Wartung).</li>
<li><b>Genauigkeit und Formalität</b>: Es geht hier nicht darum Kreativität zu verhindern, sondern das Ergebnis kreativer Entwurfsphasen möglichst präzise, z.B. in standardisierten Notationen, zu erfassen. Genauigkeit schafft Vertrauen für einen Entwurf. Formalität ist Genauigkeit in höchstem Maße, wobei jede Organisation bzw. jedes Team hier ein adäquates Maß für sich definieren muss. Es stellen sich dabei Fragen wie: Ist eine Verifikation für bestimmte Algorithmen erforderlich? Wie systematisch werden Testdaten und Testfälle erzeugt? Wie gründlich ist die Dokumentation von Aktivitäten?</li>
</ul>

<h4>Komponentenbasierte Entwicklung</h4>

<p>Komponentenbasierte Entwicklung drückt das Paradigma aus, dass Software Engineering zu einer echten Ingenieurwissenschaft macht. Die zentrale Idee besteht in dem oben genannten Prinzip der Modularisierung, d.h. der hierarchischen Aufteilung des Systems in seine Komponenten, und der damit verbundenen Wiederverwendbarkeit von Komponenten in anderen Projekten. Damit entsteht Analogie zu klassischen Ingenieurdisziplinen, in denen es üblich ist, zuvor geprüfte/zugelassene Bauteile und bewährte Verfahren wiederzuverwenden. Für die Wiederverwendbarkeit ist es wichtig, dass jede Komponente möglichst einen klar definierten Aspekt implementiert und kapselt. Es kann mehrere Komponenten geben, die demselben inhaltlichen Zweck dienen. Wenn für diese Komponenten eine gemeinsame Schnittstelle spezifiziert ist, sind sie als alternative Implementierungen leicht austauschbar. Die Verknüpfung von mehreren Basiskomponenten zu einer zusammengesetzten Komponente oder schließlich zu einem Gesamtsystem bleibt eine architektonische Aufgabe, in der sich das <a href="#unit-composite" class="navigate">Entwurfsmuster Kompositum</a> wiederfindet.</p>

<img src="media/architecture_composite_pattern.png" style="width:450px">
<label>Komponentenbasiertes System als Kompositum</label>

<p>Es stellt sich nun die Frage, was genau eine Software-Komponente ausmacht. Dazu wird folgend eine verbreitete Definition von Clemens Szyperski et al. <a href="#cite-SGM02">[SGM02]</a> zitiert und diskutiert.</p>

<div class="cite"><a href="https://de.wikipedia.org/wiki/Komponente_(Software)">"A software component is a unit of composition with contractually specified interfaces and explicit context dependencies only. A software component can be deployed independently and is subject to composition by third parties." (Clemens Szyperski et al.)</a></div>

<ul>
<li><i>Unit of Composition</i>: Eine Komponente ist Bauteil innerhalb einer hierarchischen Systemarchitektur. Die Granularität einer Komponente ist nicht festgelegt. Eine Komponente könnte nur eine einzelne Methode implementieren – oder ein komplexes Subsystem, das verschiedene andere Komponenten umfasst.</li>
<li><i>Specified Interfaces</i>: Eine Komponente spezifiziert ihre Schnittstelle nach außen. Andere Komponenten oder ein menschlicher Anwender können nur über diese Schnittstelle mit der Komponente interagieren. Das Verhalten der Komponente ist dabei entsprechend dem sogenannten <a href="https://en.wikipedia.org/wiki/Design_by_contract">"Design-by-Contract-Prinzip"</a> zugesichert. Dazu zählen z.B. die Typisierung von Input- und Return-Argumenten, die Angabe möglicher Exceptions und ggf. spezieller Vor- und Nachbedingungen. Alles was nicht in der Schnittstelle beschrieben ist, stellt sich für den Aufrufer als <i>Black Box</i> dar.</li>
<li><i>Explicit Context Dependencies</i>: Eine Komponente hängt von definierten Prämissen bezüglich ihres Ausführungskontextes ab, die zur Laufzeit permanent erfüllt werden müssen. Diese Abhängigkeiten können z.B. enthalten:
<ul>
<li>Erforderliche Schnittstellen, die zur Laufzeit von anderen Komponenten im System bereitgestellt und implementiert werden (s. <a href="unit-dependency-injection" class="navigate">Dependency Injection</a>)</li>
<li>Verfügbarkeit bestimmter Ressourcen wie Datenbanken oder Dateisystempfade</li>
<li>Konkrete Anforderungen an eine Laufzeitumgebung (z.B. JVM-Version), an ein Betriebssystem oder an Treiber zur Kommunikation mit Peripheriegeräten</li>
</ul>
</li>
<li><i>Independent Deployment</i>: Eine nicht weiter unterteilte Basiskomponente ist durch den Anspruch begrenzt, dass jede Komponente unabhängig von anderen Komponenten bereitgestellt werden kann, was i.d.R. auch unabhängige Lauffähigkeit impliziert. An dieser Stelle wird auf das folgende Kapitel zu <a href="unit-microservices" class="navigate">Microservices</a> verwiesen.</li>
<li><i>Third-party Composition</i>: Komponenten, die durch Dritte erstellt worden sind, können wiederverwendet werden. Es erfolgt eine explizite Rollentrennung zwischen Anbieter und Anwender einer Komponente. Ein Anbieter fungiert als Entwickler, der die Funktionalität einer Komponente implementiert, deren Schnittstelle und Abhängigkeiten angibt und diese in einem Repository veröffentlicht, aus dem zukünftige Anwender die Komponente (ggf. lizenzpflichtig) abrufen können. Ein Anwender kann seinerseits als Architekt arbeiten, indem er verschiedene Basiskomponenten so zusammenstellt, dass eine höherwertige Komponente entsteht, für die er selbst als Anbieter auftritt. Die folgende Abbildung skizziert die verschiedenen Rollen in der komponentenbasierten Entwicklung. Eine Person oder ein Team kann natürlich auch mehrere Rollen gleichzeitig einnehmen.</li>
</ul>

<img src="media/architecture_component_based_dev.png" style="width:750px">
<label>Rollen in der komponentenbasierten Entwicklung</label>

<h4>Komponentenmodelle</h4>

<p>Im Rahmen der komponentenbasierten Entwicklung werden wir uns in der Praxis für ein spezifisches Komponentenmodell entscheiden. Ein Komponentenmodell spezifiziert, wie Komponenten und deren Komposition textuell (z.B. in einer Programmiersprache) oder grafisch (z.B. in UML-Notation) abgebildet werden. Folgende Definition fasst zusammen, was wir von einem Komponentenmodell erwarten können <a href="#cite-LW07">[LW07]</a>.</p>

<div class="cite"><a href="http://lig-membres.imag.fr/donsez/ujf/m2r/glcs/composants/lau.pdf">"	A software component model is a definition of (1) the semantics of components, that is, what components are meant to be, (2) the syntax of components, that is, how they are defined, constructed, and represented, and (3) the composition of components, that is, how they are composed or assembled." (Kung-Kiu Lau et al.)</a></div>

<p>Aktuelle Komponentenmodelle sind z.B. <a href="https://docs.microsoft.com/de-de/dotnet/">Microsoft .NET</a>, <a href="https://jcp.org/en/jsr/detail?id=376">Java Platform Module System (JPMS)</a>, <a href="https://www.osgi.org/developer/specifications/">OSGi</a> und <a href="https://spring.io/">Spring</a>. Zu frühen Komponentenmodellen, die heute veraltet sind, gehören <a href="https://jcp.org/en/jsr/detail?id=220">Enterprise JavaBeans (EJBs)</a>, <a href="https://www.omg.org/spec/CCM/4.0/">OMG CORBA Component Model (CCM)</a> und <a href="https://docs.microsoft.com/de-de/windows/desktop/com/">Microsoft COM</a>. Auch die UML bietet ein Komponentenmodell an, das nicht wie die vorherigen auf eine konkrete Implementierung sondern auf die Spezifikation, Dokumentation und Kommunikation eines Architekturentwurfs ausgerichtet ist. Die folgende Abbildung zeigt wie Komponenten in einem UML-Komponentendiagramm dargestellt werden.</p>

<img src="media/architecture_uml_component_model.png" style="width:720px">
<label>Notation des UML-Komponentendiagramms</label>

<p>Eine Komponente kann in der UML durch andere Teilkomponenten oder durch Klassen realisiert werden. Beides sind im Sinne der UML sogenannte <i>Classifier</i>. In der obigen Abbildung wird die Komponente <code>Provider</code> durch die Teilkomponenten <code>PartA</code> und <code>PartB</code> realisiert, wobei <code>PartA</code> wiederum durch die Klassen <code>ClassA1</code> und <code>ClassA2</code> realisiert wird. Weitere Realisierungen sind nicht angegeben. Die Komponente <code>Provider</code> wird manifestiert durch das ausführbare Artefakt <code>provider.jar</code>. Das Komponentendiagramm enthält keine Information darüber, in welcher Laufzeitumgebung und auf welchem Host dieses Artefakt bereitgestellt wird. Diese Information würde in der Sicht eines zum Gesamtmodell zugehörigen Verteilungsdiagramms dargestellt werden. Die Komponente <code>Provider</code> bietet nach außen die Schnittstelle <code>ProvidedInterface</code> an, die von der Komponente <code>Client</code> genutzt wird. Intern wird diese Schnittstelle in der Teilkomponente <code>PartA</code> realisiert. Eine anschauliche Notationsbeschreibung des UML-Komponentendiagramms findet sich in der <a href="https://docs.microsoft.com/visualstudio/modeling/uml-component-diagrams-reference?view=vs-2015#reading-component-diagrams">Dokumentation zu Microsoft Visual Studio</a> sowie in den  UML-Lehrbüchern, die im Kapitel <a href="#unit-0-2" class="navigate">Objektorientierung und UML</a> referenziert werden. 

<h4>Java Platform Module System (JPMS)</h4>

<p>Im Gegensatz zur UML erlaubt es das <a href="https://www.informatik-aktuell.de/entwicklung/programmiersprachen/java-9-das-neue-modulsystem-jigsaw-tutorial.html">Modulsystem in Java (JPMS)</a> nicht, dass Komponenten hierarchisch ineinander verschachtelt werden. In Java können lediglich Packages als Realisierung der Module wie gewohnt hierarchisch untergliedert werden. Auf Ebene der Module können Abhängigkeiten explizit definiert werden (<code>requires &lt;module></code>). Die Sichtbarkeit der modulinternen Realisierung kann ebenso explizit nach außen freigegeben werden (<code>export &lt;package></code>).</p>

<p>Das folgende Code-Beispiel zeigt die beiden Java-Module <code>client</code> und <code>simpleRegression</code>, wobei letzteres eine einfache lineare Regression implementiert. Als Input für das Regressionsmodell dient eine Datenreihe mit 2D-Koordinaten im JSON-Format, d.h. zusammengehörige x- und y-Werte (Zeilen 13-14). Das Modul <code>client</code> ist ausschließlich vom Modul <code>simpleRegression</code> abhängig, dessen Abhängigkeiten ihm aber verborgen bleiben (Zeile 3). Es ist damit weitgehend entkoppelt. Der Zugriff auf das Package <code>regression</code> ist nur möglich, weil es explizit exportiert wird (Zeile 7 im Modul <code>simpleRegression</code>). Das Modul <code>simpleRegression</code> ist von folgenden Modulen abhängig: <a href="http://commons.apache.org/proper/commons-math/">commons.math3</a> zum Trainieren des Regressionsmodells, <a href="https://github.com/google/gson">gson</a> zum Konvertieren von JSON in Java Objekte und <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/module-summary.html">java.desktop</a> aufgrund der Verwendung der Klasse <code>Point</code> für 2D-Koordinaten (Zeilen 3-5). Weiterhin ergibt sich durch das Modul <code>gson</code> eine transitive Abhängigkeit zum Modul <code>java.sql</code> (Zeile 6).</p>

<ul class="nav nav-tabs" id="jpms-tabs" role="tablist">
  <li class="nav-item"><a href="#jpms-tabs-client" class="nav-link active" data-toggle="tab" role="tab">Client</a></li>
  <li class="nav-item"><a href="#jpms-tabs-regression" class="nav-link" data-toggle="tab" role="tab">SimpleRegression</a></li>
</ul>
<div class="tab-content" id="jpms-tabs-content">
  <div class="tab-pane show active" id="jpms-tabs-client" role="tabpanel">
	<pre><code class="language-java line-numbers">// module-info.java
module client {
    requires simpleRegression;
}

// Client.java
package client;
import regression.SimpleRegressionModel;

class Client {

    public static void main(String[] args) {
        String json = "[{'x': 2, 'y': 4}, {'x': 4, 'y': 3}, {'x': 3, 'y': 6}, {'x': 9, 'y': 7}, {'x': 7, 'y': 8}]";
        SimpleRegressionModel model = new SimpleRegressionModel(json);

        System.out.println("Regression function: y = f(x) = " + model.getSlope() + "x + " + model.getIntercept());
        System.out.println("R² = " + model.getDeterminationCoefficient());

        double x = 5;
        System.out.printf("Prediction: y = f(%.1f) = %.1f", x, model.predict(x));
    }
}
</code></pre> 
  </div>
  <div class="tab-pane" id="jpms-tabs-regression" role="tabpanel">
	<pre><code class="language-java line-numbers">// module-info.java
module simpleRegression {
    requires gson;
    requires commons.math3;
    requires java.desktop;
    requires transitive java.sql; // gson depends on java.sql
    exports regression; // exports package
}

// SimpleRegressionModel.java
package regression;
import com.google.gson.Gson;
import org.apache.commons.math3.stat.regression.SimpleRegression;
import java.awt.Point;

public class SimpleRegressionModel {

    Gson gson = new Gson(); // dependency to Google GSON
    SimpleRegression model = new SimpleRegression(); // dependency to Apache Commons Math

    public SimpleRegressionModel(String json) {
        Point[] points = gson.fromJson(json, Point[].class); // dependency to Java Desktop (Point class)
        for (Point p : points) model.addData(p.x, p.y);
    }

    public double getIntercept() { return model.getIntercept(); }

    public double getSlope() { return model.getSlope(); }

    public double getDeterminationCoefficient() { return model.getRSquare(); }

    public double predict(double x) { return model.predict(x); }
}
</code></pre> 
  </div>
</div>

<code></code>
</p>

<p>Die gezeigten Code-Beispiele zu JPMS finden sich im Verzeichnis <a href="/architecture/jpms" class="repo-link">/architecture/jpms</a> des Modul-Repository. Das folgende UML-Komponentendiagramm veranschaulicht die Abhängigkeiten der Module aus dem obigen Code-Beispiel. Es wird deutlich, wie das JPMS dazu beiträgt, Abhängigkeiten zwischen Komponenten explizit zu gestalten und ggf. einzuschränken.</p>

<img src="media/architecture_jpms.png" style="width:600px">
Jens Ehlers's avatar
Jens Ehlers committed
146
147
148
149
150
151
152
153
154
155
156
157
<label>UML-Komponentendiagramm zur Veranschaulichung des Java Platform Module System (JPMS)</label>

<h4>Kapselung auf Ebene von Klassen</h4>

<span>Bisher ist nur über Modularisierung und Kapselung auf Ebene von Komponenten gesprochen worden. Im Feinentwurf werden die Komponenten durch Klassen realisiert, die ebenfalls interne Daten- und Verhaltensstrukturen in Form von privaten Attributen und Methoden voreinander verstecken können. Dazu dienen in objektorientierten Sprachen insbesondere die Sicherbarkeitsmodifizierer <code>private</code> und <code>protected</code>. Wenn auf eine Klasse regelmäßig von außen zugegriffen wird, ist eine stabile Schnittstelle wichtig. Der Zugriff kann in diesem Fall sinnvoll über Getter- und Setter-Methoden gekapselt werden, um die zugrundeliegenden internen Strukturen verändern zu können, ohne den Zugriff von außen zu beeinflussen. Grundsätzlich stellt sich die Frage, vor welchen äußeren Zugriffen die internen Strukturen gekapselt werden sollen.</span>
<ul>
  <li>Vor Zugriffen aus Methoden in anderen Klassen im selben Modul?</li>
  <li>Oder vor Zugriffen aus anderen Modulen durch externe Anwender, die das eigene Modul als abhängige Bibliothek einsetzen?</li>
</ul>
<p>Im letzteren Fall kann der Aufwand, eine Veränderung der Schnittstelle durchzusetzen, sehr hoch sein, weil die externen Anwender etwaige Änderungen aus Trägheit ablehnen und ggf. als Anwender abspringen, oder weil sie aufgrund ihrer Vielzahl gar nicht vollständig bekannt sind, usw. Es werden aber auch viele Klassen entwickelt, die nur teamintern für eine kleine Anwendung oder ein kleines Modul genutzt und nicht nach außen über eine API weitergegeben werden.  Wenn die Anzahl der Zugriffe auf die Strukturen einer Klasse übersichtlich ist und relativ einfach per Refactoring auch an den abhängigen Stellen geändert werden kann, ist zu überlegen, ob Getter und Setter tatsächlich erforderlich sind. Der strukturelle Aufbau der Klasse soll hier weiterhin einfach bleiben und nicht die Narben der Versionsgeschichte der Klasse abbilden. <i>Keep it simple!</i> Getter und Setter bleiben stets sinnvoll, wenn sie zusätzliche Funktionalitäten übernehmen und diese vom Aufrufer auch erwartet werden. Sichtbarkeit kann seit Java 9 aber z.T. sinnvoller auf Modulebene als auf Klassenebene eingeschränkt werden.</p>

<p>Das Projekt <a href="https://projectlombok.org/">Lombok</a> ermöglicht es in Java, Getter und Setter ohne weitere Funktionalität, die lediglich <i><a href="https://en.wikipedia.org/wiki/Boilerplate_code">Boilerplate-Code</a></i> darstellen, über die Verwendung von Annotationen wie <code>@Getter</code> und <code>@Setter</code> zur Kompilierungszeit zu generieren. In der verwendeten Entwicklungsumgebung muss ein entsprechendes Plugin (s. hier für <a href="https://projectlombok.org/setup/intellij">IntelliJ IDEA</a>) installiert werden, damit die Lombok-Annotationen auch während der Entwicklung aufgelöst werden können.</p>