Die vorliegende Bachelorarbeit behandelt die Fragestellung, ob die Anwendung der Clean-Architecture-Prinzipien auf die moderne Android-Entwicklung Vorteile mit sich bringt. Dafür wurde eine Beispielanwendung mit zunehmendem Einbringen dieser Prinzipien in mehreren Ausführungen entwickelt, um diese anschließend miteinander vergleichen und Auswirkungen festhalten zu können.
Um die Forschungsfrage zu beantworten, wurde zuerst eine Android-Applikation in der Programmiersprache Kotlin ohne Beachtung der Clean-Architecture-Prinzipien mit der Model-View-ViewModel-Architektur entwickelt. Anschließend erfolgte die
Entwicklung zweier weiterer Ausführungen der Applikation mit sukzessiver Anwendung der wichtigsten Clean-Architecture Prinzipien, damit die Veränderungen der oben aufgelisteten Eigenschaften schrittweise verglichen werden konnten. Spezifisch wurde auf die Auswirkungen beim Erstellen von Testfällen, das Erweitern des Quellcodes um zusätzliche Funktionen und das Verstehen der Softwareprojektstruktur Bezug genommen.
Während die Testbarkeit und die Erweiterbarkeit anhand des Quellcodes und auf Modellierungsebene untersucht werden konnten, erfolgte für die Evaluierung der Lesbarkeit eine quantitative Studie zur Verständlichkeit der Projektstruktur und des Quellcodes. Die Untersuchung wies eine schrittweise und deutliche Verbesserung der Applikation hinsichtlich der Erweiterbarkeit und Testbarkeit auf. Für die Erweiterbarkeit zeigte sich, dass das Einbringen von ausgewählten Funktionen, um die die Applikation erweitert werden sollte, ohne eine Steigerung der Komplexität erfolgen konnte, während sich das Erstellen von Testfällen zur Validierung der Funktionalitäten als übersichtlich und einfach erwies. Die Umfrage zur Lesbarkeit, die lediglich von Personen mit Programmiererfahrung durchgeführt wurde, ergab, dass die Struktur und der Quellcode der endgültigen Applikation für die befragten Personen am verständlichsten war.
Inhaltsverzeichnis
Kurzfassung
Vorwort
Inhaltsverzeichnis
Abbildungsverzeichnis
Tabellenverzeichnis
Abkürzungsverzeichnis
1. Einleitung
1.1. Motivation
1.2. Problemstellung
1.3. Abgrenzung
1.4. Ziel der Arbeit
1.5. Vorgehen
1.6. Ausblick
2. Theoretische Grundlagen
2.1. Softwarearchitektur
2.1.1. Definition Softwarearchitektur
2.1.2. Bedeutung von Softwarearchitektur
2.2. Clean-Architecture-Prinzipien nach Robert C. Martin
2.2.1. SOLID-Prinzipien
2.2.2. Schichten entkoppeln
2.2.3. Grenzlinien
2.2.4. Anwendungsfälle
2.2.5. Entitäten
2.2.6. Architektur
2.2.7. Beispiel
2.2.8. Clean Architecture bei clientseitiger Software
2.3. Native Android-Appentwicklung
2.3.1. Android Studio
2.3.2. Lebenszyklen
2.3.3. Layouts
2.3.4. Frameworks und Tools
2.4. Programmiersprache Kotlin für native Android-Appentwicklung
2.4.1. Kotlin vs Java
2.4.2. Asynchrone Programmierung
2.4.3. Jetpack Compose
2.5. Architekturen für Android-Entwicklung
2.5.1. Model-View-Controller
2.5.2. Model-View-ViewModel
2.5.3. Vergleich
2.6. Bewertung von Softwarearchitektur
2.6.1. Wartbarkeit
2.6.2. Testbarkeit
2.6.3. Erweiterbarkeit
2.6.4. Lesbarkeit
3. Problemanalyse
4. Lösungskonzept
4.1. Android-Applikation
4.1.1. Basis Applikation
4.1.2. Modifizierte Applikation
4.1.3. Endgültige Applikation
4.1.4. Applikationsstrukturen
4.1.5. Realisierungstechniken
4.2. Bewertung der Testbarkeit
4.3. Bewertung der Erweiterbarkeit
4.4. Bewertung der Lesbarkeit
5. Evaluierung
5.1. Testbarkeit
5.1.1. Basis Applikation
5.1.2. Modifizierte Applikation
5.1.3. Endgültige Applikation
5.2. Erweiterbarkeit
5.2.1. Basis Applikation
5.2.2. Modifizierte Applikation
5.2.3. Endgültige Applikation
5.3. Lesbarkeit
5.3.1. Umfrageergebnisse Basis Applikation
5.3.2. Umfrageergebnisse Modifizierte Applikation
5.3.3. Umfrageergebnisse Endgültige Applikation
5.3.4. Evaluation der Umfrageergebnisse
5.4. Zusammenfassende Gesamtevaluierung
6. Zusammenfassung und Ausblick
6.1. Erreichte Ergebnisse
6.2. Ausblick
6.2.1. Übertragbarkeit der Ergebnisse
6.2.2. Erweiterbarkeit der Ergebnisse
A. Anhang Umfrage
B. Anhang Android-Applikationen
Kurzfassung
Im Bereich der Softwareentwicklung ist die Entwicklung von Smartphone Applikationen in den letzten Jahren zunehmend bedeutsamer geworden. Moderne Programmiersprachen ermöglichen die Entwicklung immer umfangreicherer Applikationen, die dadurch jedoch auch an Komplexität gewinnen. Daher ist es sehr wichtig, eine gute Softwarearchitektur als Grundpfeiler für eine erfolgreiche Anwendung einzusetzen. Das Ziel der vorliegenden Arbeit ist es, zu beantworten, ob und welche Vorteile durch die Anwendung der Clean-Architecture-Prinzipien von Robert Cecil Martin auf die bei moderner Android-Entwicklung häufig angewandte Model-ViewViewModel-Architektur entstehen. Dazu werden die Auswirkungen auf folgende Qualitätsattribute näher betrachtet:
- Testbarkeit
- Erweiterbarkeit
- Lesbarkeit
Um die Forschungsfrage zu beantworten, wurde zuerst eine Android-Applikation in der Programmiersprache Kotlin ohne Beachtung der Clean-Architecture-Prinzipien mit der Model-View-ViewModel-Architektur entwickelt. Anschließend erfolgte die Entwicklung zweier weiterer Ausführungen der Applikation mit sukzessiver Anwendung der wichtigsten Clean-Architecture-Prinzipien, damit die Veränderungen der oben aufgelisteten Eigenschaften schrittweise verglichen werden konnten. Spezifisch wurde auf die Auswirkungen beim Erstellen von Testfällen, das Erweitern des Quellcodes um zusätzliche Funktionen und das Verstehen der Softwareprojektstruktur Bezug genommen. Während die Testbarkeit und die Erweiterbarkeit anhand des Quellcodes und auf Modellierungsebene untersucht werden konnten, erfolgte für die Evaluierung der Lesbarkeit eine quantitative Studie zur Verständlichkeit der Projektstruktur und des Quellcodes. Die Untersuchung wies eine schrittweise und deutliche Verbesserung der Applikation hinsichtlich der Erweiterbarkeit und Testbarkeit auf. Für die Erweiterbarkeit zeigte sich, dass das Einbringen von ausgewählten Funktionen, um die die Applikation erweitert werden sollte, ohne eine Steigerung der Komplexität erfolgen konnte, während sich das Erstellen von Testfällen zur Validierung der Funktionalitäten als übersichtlich und einfach erwies. Die Umfrage zur Lesbarkeit, die lediglich von Personen mit Programmiererfahrung durchgeführt wurde, ergab, dass die Struktur und der Quellcode der endgültigen Applikation für die befragten Personen am verständlichsten war.
Vorwort
Die vorliegende Bachelorarbeit behandelt die Fragestellung, ob die Anwendung der Clean-Architecture-Prinzipien auf die moderne Android-Entwicklung Vorteile mit sich bringt. Dafür wurde eine Beispielanwendung mit zunehmendem Einbringen dieser Prinzipien in mehreren Ausführungen entwickelt, um diese anschließend miteinander vergleichen und Auswirkungen festhalten zu können.
Die Idee für mein Thema kam mir während des letzten Jahres, als ich eine AndroidApplikation entwickelt und mich mit der Frage nach einer geeigneten Softwarearchitektur beschäftigt habe. Im Rahmen dieses Projektes bin ich auf die Prinzipien von Clean Architecture gestoßen, die ich erfolgreich anwenden konnte. Subjektiv empfand ich erhebliche Vorteile gegenüber bisher angewandter Architekturen und Designprinzipien, konnte aber keine Gegenüberstellungen finden, die dies belegten. Daher wollte ich mich mit der Fragestellung in meiner Arbeit intensiver auseinandersetzen, um die vermuteten Vorteile zu bestätigen.
Teilweise wies die durchgeführte Forschung Schwierigkeiten auf, weshalb ich meine Vorgehensweise mehrmals anpassen und vom anfänglich geplanten Weg abweichen musste. So war beispielsweise anfangs die Einbringung einer empirischen Erhebung nie vorgesehen und auch ergaben sich während der Arbeit geeignetere Bewertungsverfahren als ursprünglich vorgesehen.
Ich möchte mich bei Philipp Lackner bedanken, der mit seinem YouTube-Kanal und seinem unheimlich detaillierten Wissen viele hilfreiche Tools und Techniken zur Android-Entwicklung vermittelt. Diese halfen bei der komplikationslosen Einbringung verschiedener Implementierungstechniken, die zwar nur bedingt mit der Forschungsfrage zu tun hatten, aber dennoch wichtig für die Gesamtvollständigkeit waren.
Ich wünsche Ihnen viel Freude beim Lesen dieser Bachelorarbeit.
Matthias Kerat
Welzheim, 22.08.2022
Abbildungsverzeichnis
2.1. Umfang von Softwarearchitektur - Darstellung nach [46, S. 4]
2.2. Softwarearchitektur als Brücke - Darstellung nach [13, S. 94]
2.3. Verstoß gegen das SRP - Darstellung nach [36]
2.4. Logdatei Klasse - Darstellung nach [36]
2.5. Erfüllung des SRP - Darstellung nach [36]
2.6. Verstoß gegen das OCP - Darstellung nach [42, S. 12]
2.7. Erfüllung des OCP - Darstellung nach [42, S. 17]
2.8. Verstoß gegen ISP - Darstellung nach [38, S. 103]
2.9. Erfüllung des ISP - Darstellung nach [38, S. 104]
2.10. Dependency-Inversion-Prinzip - Darstellung nach [16, S. 201]
2.11. Geschäftsregeln und Datenbank - Darstellung nach [38, S. 182]
2.12. Komponentenaufteilung - Darstellung nach [38, S. 183]
2.13. Darlehen Entität - Darstellung nach [38, S. 202]
2.14. Clean Architecture - Darstellung nach [38, S. 212]
2.15. CA Beispiel - Darstellung nach [38, S. 217]
2.16. Drei Schichten - Darstellung nach [6, S. 3]
2.17. Aktivitätslebenszyklus - Darstellung nach [32]
2.18. Layout als XML-Datei - Darstellung nach [28]
2.19. Laden eines Layouts - Darstellung nach [28]
2.20.Instanz eines Viewobjekts - Darstellung nach [28]
2.21. Retrofit-Architektur - Darstellung nach [22, S. 3]
2.22. Hilt Moduldefinition - Darstellung nach [26]
2.23. Room-Komponenten - Darstellung nach [29]
2.24. Datenklassen - Darstellung nach [12, S. 25]
2.25. Erweiterungsfunktion in Kotlin - Darstellung nach [5, S. 42]
2.26. Kotlin Coroutine - Darstellung nach [17, S. 431f.]
2.27. Composable - Darstellung nach [33]
2.28. Composable-Zustand - Darstellung nach [31]
2.29. Composable-Ansicht - Darstellung nach [31]
2.30. MVC-Entwurfsmuster - Darstellung nach [45, S. 7]
2.31. Android-Implementierung von MVC - Darstellung nach [35, S. 13] . .
2.32. MVVM-Entwurfsmuster - Darstellung nach [53, S. 36]
2.33. ViewModel - Darstellung nach [34]
2.34. View - Darstellung nach [34]
4.1. Hauptansicht Applikation
4.2. Dialog zum Ändern des Namens
4.3. Dialog zum Hinzufügen eines Einkaufsgegenstands
4.4. Dialog zum Suchen verschiedener Einkaufsgegenstände
4.5. Entwurf Basis Applikation
4.6. Entwurf Modifizierte Applikation
4.7. Grenzlinien Endgültige Applikation
4.8. Entwurf Endgültige Applikation
4.9. Schichtenaufteilung der Endgültigen Applikation
4.10. Basis Applikation
4.11. Modifizierte Applikation
4.12. Endgültige Applikation
4.13. Erweiterte Historienfunktion
5.1. Pseudoimplementierung ShoppingRepository
5.2. Realisierung CSVParser zu Testzwecken
5.3. Testklasse für ShoppingModel
5.4. Testklasse für RemoveShoppingltemFromListUseCase
5.5. UML-Teildiagramm für Modifikation Historie
5.6. UML-Teildiagramm für Modifikation Benutzerinformation
5.7. UML-Teildiagramm für Modifikation Historie
5.8. UML-Teildiagramm für Modifikation Benutzerinformation
5.9. UML-Teildiagramm für Modifikation Historie
5.10. UML-Teildiagramm für Modifikation Benutzerinformation
5.11. Alte Testklasse
5.12. Angepasste Testklasse
5.13. Ergebnis Programmiererfahrung
5.14. Ergebnis Softwarearchitekturwissen
5.15. Bewertung Basis Applikation
5.16. Bewertung Modifizierte Applikation
5.17. Bewertung Endgültige Applikation
A.1. Umfrage Einleitung
A.2. Frage zur Programmiererfahrung
A.3. Frage zum Softwarearchitekturverständnis
A.4. Fragen zur Basis Applikation
A.5. Fragen zur Modifizierten Applikation
A.6. Fragen zur Endgültigen Applikation
Tabellenverzeichnis
5.1. Zusammenfassung der Umfrageergebnisse
5.2. Ergebnisse der Teilevaluierungen
Abkürzungsverzeichnis
Model-View-ViewModel
Clean Architecture
Softwarearchitektur
Single-Responsibility-Prinzip
Open-Closed-Prinzip
Liskovsche Substitutionsprinzip
Interface-Segregation-Prinzip
Dependency-Inversion-Prinzip
Dependency Injection
Java Virtual Machine
Model-View-Controller
Architecture-level modifiability analysis
1. Einleitung
Durch die mittlerweile regelmäßige Benutzung von mobilen Endgeräten im Alltag oder zu Geschäftszwecken, gewann das Erstellen von Smartphone-Applikationen in Softwareentwicklerkreisen in den letzten Jahren stark an Bedeutung. Während es noch vor einem Jahrzehnt eine große Herausforderung war, ein Applikationsprojekt überhaupt aufzusetzen, ist die Erstellung kleinerer Applikationen heutzutage mit Hilfe von ausgereiften Entwicklungsumgebungen und etwas Zeitaufwand sogar für Nicht-Entwickler möglich. Für gelernte Softwareentwickler hingegen bieten diese Entwicklungsumgebungen die Möglichkeit, immer umfangreichere Anwendungen zu entwickeln und so komplexe Geschäftsanforderungen zu realisieren. Dabei spielt die angewandte Softwarearchitektur eine große Rolle. Sie bestimmt, wie verschiedene Quellcode-Dateien zu Komponenten zusammengefasst werden und miteinander kommunizieren sollen, mit dem Ziel, eine möglichst übersichtliche und einfache Struktur zu wahren, sodass eine gute Erweiterbarkeit und Instandhaltung möglich wird. Denn die rasante Veränderung der Umgebung und das ständige Hinzukommen von neuen Anforderungen in nahezu allen Bereichen, erfordert auch für bereits erstellte Anwendungen eine gute Anpassungsfähigkeit, weshalb ein anfänglich stabil gesetzter Grundpfeiler essentiell ist. Wird anfangs ein wackeliges Softwaregerüst erstellt, das nach und nach immer mehr Last in Form von neuen Funktionalitätsanforderungen tragen muss, besteht eine große Chance, dass diese Last irgendwann nicht mehr getragen werden kann und einen Einsturz verursacht.
1.1. Motivation
In der modernen Arbeitswelt und derer immer knapper werdenden Zeit- und Effizienzvorgaben, kommt es häufig vor, dass das Kernprodukt einer SmartphoneApplikation für bessere Konkurrenzfähigkeit schnell auf den Markt gebracht werden muss, ohne dass bereits sämtliche Funktionalitäten vorhanden sind. Bei “fertigen” Applikationen ergeben sich im Laufe der Produkteinsatzzeit oftmals neue Anforderungen auf Grund von zusätzlichen Kundenwünschen oder zu Zwecken der Prozessoptimierung. In beiden Fällen bedarf es einer Änderung des bestehenden Quellcodes, um neue Funktionalitäten oder Anforderungen realisieren zu können. Im Gegensatz zur Neuerstellung einer Smartphone-Applikation, ist ein Eingriff in den Quellcode einer bereits bestehenden weitaus komplexer und mit mehr Aufwand verbunden. Entweder muss sich der Entwickler nach potentiell längerer Zeit wieder in die Struktur und die Details des Projektes hineindenken, oder es ist wegen personeller Veränderungen ein anderer, neuer Entwickler dafür zuständig, der mit der Software noch nicht vertraut ist. Dieser erbrachte Aufwand schließt dabei nicht aus, dass es nach erfolgreicher Einarbeitung grundsätzlich schwierig ist, die bestehende Software komplikationslos um neue Anforderungen zu erweitern. Möglicherweise besteht eine enge Verdrahtung zwischen verschiedenen Komponenten, die Änderungen an bestimmten Stellen erschweren oder sogar eine Kettenreaktion an weiteren Änderungsanforderungen verursachen. Falls dieser Vorgang zu komplex wird, kann der Entwickler zwar versuchen, das Problem zu umgehen, muss dafür jedoch häufig an falschen Stellen Quellcode einfügen oder Verzweigungen einführen. Die Umsetzung der darauffolgenden Anforderungen kann dann noch schwieriger ausfallen und größere Problemumgehungen erfordern. Dadurch wird die Komplexität der Software kontinuierlich hochgeschaukelt, bis sie irgendwann überhaupt nicht mehr erweitert werden kann.
Ein weiterer Punkt beim Erstellungsprozess einer Smartphone-Applikation ist die Einbringung von Testfällen, die potentielle Szenarien bei der Benutzung der Anwendung simulieren und damit validieren, dass bestimmte Funktionalitäten auch wirklich das tun, was sie sollen. Erfolgen nach der Einbringung der Testfälle Änderungen an bereits bestehender Funktionalität, kann anschließend validiert werden, ob die Änderung einen Fehler verursacht hat oder ob die Richtigkeit des Systems weiterhin gegeben ist. Es ist im Interesse eines Entwicklers, dass Testfälle einfach und übersichtlich implementiert werden können, da sie das Produkt selbst im Bezug auf die Fertigstellung nicht voranbringen. Benötigt das Erstellen von Tests mehr Zeit, als die eigentliche Entwicklung, entstehen dabei erhebliche Kosten. Testfälle müssen bei zusätzlicher Funktionalität der Software gegebenenfalls auch angepasst werden, falls neue Szenarien durch die Änderung hinzukommen. Deshalb ist es auch hier wichtig für den Entwickler, dass Testfälle nicht zu kompliziert sind und sich leicht erweitern lassen.
Damit die bereits erwähnten Probleme während des Lebenszyklus einer SmartphoneApplikation minimiert werden können, benötigt der Entwickler verschiedene Richtlinien zur Erstellung, die für die Erhaltung wichtiger Qualitätsmerkmale der Software sorgen. Dafür wird eine geeignete Softwarearchitektur mit Entwicklungsprinzipien benötigt.
1.2. Problemstellung
Wegen der Tools, die bei der Android-Entwicklung zur Verfügung stehen, eignet sich die Anwendung der Model-View-ViewModel (MVVM)-Architektur sehr gut. Sie gibt vor, wie verschiedene Komponenten miteinander interagieren und welche Logik welchen Bereichen zugeordnet werden soll (vgl. [10]). Ferner gibt Robert C. Martin in seinem Buch Clean Architecture[38] einige Architekturprinzipien vor, die beim Entwicklungsprozess dabei helfen sollen, Erweiterbarkeit, Testbarkeit und Lesbarkeit von Software zu verbessern. Die vorliegende Arbeit beschäftigt sich mit der zusätzlichen Einbringung der Clean Architecture (CA)-Prinzipien zur standardmäßigen MVVM-Architektur bei Android-Applikationen. Außerdem werden die daraus resultierenden Auswirkungen hinsichtlich verschiedener Qualitätsmerkmale, wie zum Beispiel Erweiter-, Test- und Lesbarkeit untersucht.
1.3. Abgrenzung
Softwarearchitektur kommt prinzipiell in allen Bereichen zum Einsatz, in denen Software erstellt wird und ist somit nicht auf ein spezielles Gebiet beschränkt. Die vorliegende Arbeit beschäftigt sich mit der Fragestellung, wie sich verschiedenen Entwicklungsprinzipien auf die Softwarearchitektur in der modernen AppEntwicklung auswirken. Insbesondere wird dabei auf die Entwicklung von AndroidApplikationen, die einen großen Teil des Marktes ausmachen, eingegangen. Eine Übertragung auf Softwareerstellungsprozesse für andere Smartphone-Betriebssysteme ist durchaus möglich, wird in dieser Arbeit jedoch nicht thematisiert. Zudem kann nicht garantiert werden, dass sich die entstehenden Erkenntnisse der eingesetzten Prinzipien und Techniken auf gänzlich andere Bereiche, wie beispielsweise das Entwickeln von Web- oder Desktopanwendungen, übertragen lassen.
1.4. Ziel der Arbeit
Das Ziel der Forschung ist es, herauszufinden, ob und welche Vorteile durch die zusätzliche Anwendung der CA-Prinzipien auf eine mit der MVVM-Architektur erstellte Applikation entstehen. Es kann durchaus sein, dass die Initialapplikation wegen der MVVM-Eigenschaften bereits sporadisch die Anwendung einzelner Prinzipien aufweist. Die Forschung zielt jedoch auf die Einbringung aller relevanten Prinzipien ab. Zudem soll eine schrittweise Verbesserung gezeigt werden, die aus der sukzessiven Einbringung verschiedener CA-Prinzipien erfolgt.
1.5. Vorgehen
Um die Forschungsfrage zu beantworten, wird zuerst eine Beispielapplikation für Android nach Definition der MVVM-Architektur erstellt. Dabei können gegebenenfalls wegen der Beachtung der MVVM-Eigenschaften vereinzelnd bereits einzelne CA-Prinzipien vorkommen. Anschließend wird die Applikation sukzessive modifi- ziert, sodass nicht alle Prinzipien auf einmal angewandt werden. Dadurch können Effekte einzelner Prinzipien detailliert betrachtet und Verschleierungen potentieller Inkonsequenzen verhindert werden. Insgesamt wird es nach der Entwicklung drei verschiedene Applikationen geben, die miteinander verglichen werden können. Jede von ihnen besitzt einen zur vorherigen Applikation erhöhten Anwendungsumfang von CA-Prinzipien. Um einen aussagekräftigen Vergleich zu ermöglichen, sollen für jede Applikation drei wichtige Qualitätsmerkmale herangezogen werden:
- Erweiterbarkeit
- Testbarkeit
- Lesbarkeit
Die Erweiter- und Testbarkeit wird anhand des Quellcodes und der Entwurfsdiagramme beurteilt. Für die Lesbarkeit soll eine quantitative Erhebung erfolgen, die circa 15-25 Personen mit Programmierkenntnissen umfassen soll. Da die Wartbarkeit eines Softwaresystems Testbarkeit, Erweiterbarkeit und Lesbarkeit umfasst[8, S. 3-5] und Stakeholder daran interessiert sind, den Kostenfaktor der Wartbarkeit möglichst gering zu halten[4, S. 130], eignen sich diese Qualitätsmerkmale sehr gut für die Beurteilung der einzelnen Applikationen. Nach dem Vorliegen der Ergebnisse der einzelnen Applikationen werden diese miteinander verglichen und interpretiert, um die Forschungsfrage beantworten zu können.
1.6. Ausblick
Die vorliegende Bachelorarbeit beginnt mit den theoretischen Grundlagen, die wichtig sind, um die spätere Forschung nachvollziehen zu können. Dabei wird ausführlich auf einzelne CA-Prinzipien eingegangen, verschiedene Techniken und Tools für die nativen Android-Entwicklung erläutert und Bewertungsmöglichkeiten zu einer bestehenden Softwarearchitektur vorgestellt. Anschließend werden in der Problemanalyse Schwierigkeiten aufgeführt, die beim Versuch, die Forschungsfrage zu beantworten, auftreten können. Im darauffolgenden Kapitel Lösungskonzept wird eine detaillierte Vorgehensweise der Forschung vorgestellt. Insbesondere beinhaltet dieses Kapitel eine ausführliche Erläuterung, die Modellierung und die visuelle Veranschaulichung der drei verschiedenen Applikationen, die im anschließenden Kapitel bezüglich ihrer Softwarequalitätsmerkmale evaluiert werden. Bevor die Ergebnisse am Ende dieser Arbeit zusammengefasst werden, erfolgt eine Gesamtevaluierung, bei der die Teilevaluierungen der einzelnen Applikationen gegenübergestellt werden.
2. Theoretische Grundlagen
2.1. Softwarearchitektur
2.1.1. Definition Softwarearchitektur
Verglichen mit traditioneller Software von vor einigen Jahrzehnten, welche normalerweise eine sequentielle Abfolge von Maschinenanweisungen oder eine Kombination von Datenstrukturen und Algorithmen darstellte, ist heutige Software weitaus komplexer und schwieriger zu kontrollieren. Softwarearchitektur (SA) ist ein aufstrebendes Thema im Bereich der Softwareentwicklung, das helfen soll den Erfolg eines solchen Softwareproduktes zu gewährleisten.[44, S. 1] Die Industrie hat sich in der Vergangenheit schwer getan, SA genau zu definieren. Für einige Architekten ist sie der Entwurf eines Systems, andere wiederum definieren sie als Roadmap für den Erstellungsprozess eines Systems. Eine weitere Art SA zu sehen, wird in Abbildung 2.1 illustriert. In dieser Definition umfasst SA die Struktur eines Systems, Architekturmerkmale, Architekturentscheidungen und Designprinzipien. Bei der Struktur des Systems geht es um den Architekturstil, wie beispielsweise Mikroservices, Mikrokernel oder verschiedene logisch zusammengehörende Schichten. In der nächsten Dimension liegen die Architekturmerkmale, die die Erfolgskriterien des Systems festlegen. Dazu gehören Attribute wie Verlässlichkeit, Testbarkeit, Skalierbarkeit und Elastizität. Architekturentscheidungen als nächster Faktor legen verschiedene Regeln fest, wie ein System konstruiert werden soll. Zum Beispiel könnte eine Entscheidung sein, dass nur die Business- und Dienstschicht auf Daten in der Datenbank zugreifen dürfen. Direkte Datenbankaufrufe von der Präsentationsschicht werden damit bei Einhaltung der Entscheidung verhindert. Der letzte Faktor ist durch die Designprinzipien definiert, die ähnlich wie die Architekturentscheidungen verschiedene Richtungen vorgeben, jedoch nicht restriktiv sind. Sie geben Richtlinien vor, die dem Entwickler Spielraum für verschiedene Implementierungsmöglichkeiten gewähren. So kann sich dieser bei der Implementierung einer asynchronen Nachrichtenübertragung zum Beispiel zwischen den Kommunikationsprotokollen REST oder gRPC entscheiden. [46, S. 3-8]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.1.: Umfang von Softwarearchitektur - Darstellung nach [46, S. 4]
2.1.2. Bedeutung von Softwarearchitektur
Ein kritischer Punkt beim Erstellen eines komplexen Softwaresystems ist die dafür angewandte SA, die die Organisation von unterschiedlichen miteinander interagierenden Komponenten festlegt. Durch die richtige SA können verschiedene Schlüsselanforderungen, wie beispielsweise Performance, Verlässlichkeit und Skalierbarkeit an das System erfüllt werden. In den letzten Jahren wurde immer mehr realisiert, dass die Auswahl der SA ein entscheidender Erfolgsfaktor für die Entwicklung eines Softwareproduktes ist. In Abbildung 2.2 ist zu sehen, dass die SA normalerweise als Brücke zwischen den Anforderungen an ein System und dessen Code fungiert, was verschiedene wichtige Eigenschaften für die Realisierung der Anforderungen hervorhebt und gleichzeitig detaillierte Implementierungsstrukturen versteckt.[13]
Aus technischer Sicht gibt es drei fundamentale Gründe für die Wichtigkeit von SA:
- Kommunikation zwischen Stakeholdern Hier repräsentiert die SA eine Abstraktion des Systems, sodass eine Grundlage für gemeinsames Verstehen, Verhandeln und Sich-einig-Werden geschaffen wird. Jeder Stakeholder ist an verschiedenen Systemeigenschaften, die von der SA betroffen sind, interessiert. Zum Beispiel ist für einen Benutzer eine hohe Zuverlässigkeit und Verfügbarkeit wünschenswert, während für den Kunden das Einhalten des Zeitplans und die Kosten von Bedeutung sind. Die SA stellt entscheidende Grundlagen zur Verfügung, damit über diese Anforderungen verhandelt werden kann und ein besseres Management möglich wird.[2, S. 27]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.2.: Softwarearchitektur als Brücke - Darstellung nach [13, S. 94]
- Frühe Designentscheidungen SA legt die frühesten Designentscheidungen fest, die sich dann auf die verbleibenden Entwicklungs-, Einsatz- und Wartungsprozesse auswirken. Neben der Beschreibung des Systems wird auch indirekt die Organisationsstruktur des Softwareprojekts festgelegt. Darunter fallen Planung, Budget und Kommunikation im Entwicklerteam. Außerdem können Fehler im frühen Stadium erkannt und entsprechend korrigiert werden, was bei einem bereits fortgeschrittenen Projekt unter Umständen einen erheblichen Mehraufwand verursachen würde.
- Übertragbare Abstraktion eines Systems Durch SA wird die Struktur eines Systems und das Zusammenwirken verschiedener Komponenten abstrakt betrachtet. Details der Struktur und des Zusammenspiels verschiedener Komponenten ist konkret noch nicht fassbar. Dadurch kann eine Übertragung auf andere Systeme mit ähnlichen Anforderungen erfolgen.[2, S. 26-29]
2.2. Clean-Architecture-Prinzipien nach Robert C. Martin
Um ein Softwaresystem das erste Mal zum Laufen zu bringen, benötigt es keine gute SA. Die Schwierigkeit besteht darin, den Lebenszyklus des Systems zu sichern. Darunter fallen einfaches Verstehen und Entwickeln, komplikationslose Instandhaltung und problemloses Deployment. Von all diesen Eingenschaften ist die Instandhaltung die kostenintensivste. Hierbei muss der Quellcode, um eine geeignete Implementierungsstelle zu finden, oder einen Fehler zu beheben, meistens zuerst aufwändig durchforstet werden. Zusätzlich entstehen Risikokosten, die durch die Modifizierung oder das Einführen neuer Funktionen entstehen können.[38, Kapitel 15]
2.2.1. SOLID-Prinzipien
Die SOLID-Prinzipien sind fünf Designprinzipien für SA, die bei einer Einhaltung dem Entwickler dabei helfen, eine gute Software zu erstellen:
- Single-Responsibility-Prinzip
- Open-Closed-Prinzip
- Liskovsches Substitutionsprinzip
- Interface-Segragation-Prinzip
- Dependency-Inversion-Prinzip
Vorteil der Anwendung dieser Prinzipien ist eine auf lange Sicht wiederverwendbare, stabile, wartbare und testbare Software.[36] Die SOLID Prinzipien sind zentraler Baustein der Clean-Architecture-Prinzipien.[38, Teil 5]
Single-Responsibility-Prinzip
Das Single-Responsibility-Prinzip (SRP) besagt, dass es nie mehr als einen Grund geben sollte, eine Klasse zu ändern. Wenn eine Klasse beispielsweise dafür zuständig ist, eine Bestätigungsmail zu erstellen und diese auch zu versenden, dann besitzt sie zwei verschiedene Verantwortungsbereiche. Ändert sich eine dieser beiden Anforderungen, muss die ganze Klasse geöffnet und modifiziert werden, obwohl nur bestimmte Teile mit der eigentlichen Änderungsanforderung zu tun haben. Durch die Limitierung auf einen Verantwortungsbereich pro Klasse wird das Veränderungsrisiko, das Entwickler reduzieren möchten, minimiert. Ein weiter Vorteil dieses Prinzips ist das einfachere Testen der Klasse, da sie normalerweise weniger private Funktionen und Abhängigkeiten besitzt und weniger Testfälle geschrieben werden müssen. Auch die Wartbarkeit wird durch das einfachere Verstehen der Klasse positiv beeinflusst.[42, S. 3-10] In Abbildung 2.3 ist die Klasse Kunde zu sehen, die gegen das SRP verstößt. Die Verantwortung der Klasse liegt sowohl beim Hinzufügen des Kunden in eine Datenbank als auch bei der Behandlung eines eventuell auftretenden Fehlers. In diesem Fall das Abspeichern eines Fehlers in einer Logdatei. Um den Verstoß aufzulösen, sollte die Verantwortung für die Fehlerspeicherung in einer Logdatei ausgelagert werden. Die neue Klasse und ihre Funktion zum Behandeln des Fehlers kann dann von der Kundenklasse aufgerufen werden. Abbildung 2.4 und 2.5 stellen diese Modifikation dar. Sowohl die Klasse Logdatei als auch die Kundenklasse haben nun jeweils eine einzige Verantwortung und erfüllen das Kriterium des SRP.[36]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.3.: Verstoß gegen das SRP - Darstellung nach [36]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.4.: Logdatei Klasse - Darstellung nach [36]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.5.: Erfüllung des SRP - Darstellung nach [36]
Open-Closed-Prinzip
Das Open-Closed-Prinzip (OCP) besagt, dass eine Klasse offen für Erweiterungen und geschlossen für Modifikationen sein soll.[36] Zur Erläuterung dieses Prinzips wird eine Kodiererklasse betrachtet, die als Eingabe Daten bekommt und ein entsprechendes Kodierformat, in welches die Daten kodiert werden sollen. Die Klasse prüft in einer Verzweigung, welches Format verwendet werden soll und erzeugt dann den entsprechenden Kodierer, der die Daten kodiert. Abbildung 2.6 zeigt die Kodiererklasse. Ändern sich die Anforderungen und die Kodiererklasse soll ein neues Format, zum Beispiel Yaml, unterstützen, muss sie durch eine zusätzliche Verzweigungsanweisung modifiziert werden, was gegen das OCP verstößt.[42, S. 11-15]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.6.: Verstoß gegen das OCP - Darstellung nach [42, S. 12]
Damit die Kodiererklasse dem OCP nachkommt, muss die Verantwortlichkeit für die Auswahl des richtigen Kodierers ausgelagert werden. Das kann mit Hilfe einer Schnittstelle für den Kodierer, die die Kodierfunktion vorgibt, erfolgen. Verschiedene spezifische Kodierer werden als Klasse definiert, realisieren diese Schnittstelle und können von einer Klasse, zum Beispiel einer Fabrik, die als einzige Verantwortlichkeit das Zurückgeben des richtigen Kodierers besitzt, erzeugt werden. In Abbildung 2.7 ist die überarbeitete Kodiererklasse zu sehen. Wird nun ein neues Kodierformat benötigt, kann dafür eine neue Klasse, die die entsprechende Schnittstelle realisiert, erzeugt und zusätzlich von der Fabrikklasse verwaltet werden, ohne dass die Kodiererklasse modifiziert werden muss. Sie ist also offen für zusätzliche Kodierformate und geschlossen für Modifizierungen, da diese in der Klasse nicht mehr notwendig sind.[42, S. 15-17]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.7.: Erfüllung des OCP - Darstellung nach [42, S. 17]
Liskovsches Substitutionsprinzip
Das Liskovsche Substitutionsprinzip (LSP) besagt, dass eine Unterklasse die Instanz einer Oberklasse ersetzen kann, ohne dass sich das Verhalten des Programms ändert. Ein Verstoß gegen dieses Prinzip liegt zum Beispiel vor, wenn eine Kontoklasse mit Gebühren von einem Konto ohne Gebühren erbt. Das Konto mit Gebühren könnte in diesem Fall nicht die Oberklasse, sprich das Konto ohne Gebühren, ersetzen, ohne das Verhalten des Programms zu verändern. Beide Klassen weisen ein spezielles Verhalten auf und sollten daher von einer abstrakten Kontoklasse erben, um dem LSP zu entsprechen.[51, S. 4] Ein weiteres Beispiel ist der Aufbau eines Dateisystems. Dafür wird eine Dateischnittstelle mit den Funktionen Dateiname ändern und Dateibesitzer ändern erzeugt, die von den Klassen Lokaledatei und Dropboxdatei realisiert wird. Für die Implementierung innerhalb der Dropboxklasse gilt jedoch, dass der Besitzer nicht geändert werden darf, weswegen diese ihre Oberklasse, also die Dateischnittstelle, nicht beliebig ersetzen kann, ohne dass sich das Programmverhalten verändert und ein Verstoß gegen das LSP vorliegt. Durch die Definition einer zusätzlichen Schnittstelle für Dateien, die es erlauben, ihren Besitzer zu ändern, kann dieser Verstoß aufgehoben werden. Die neu angelegte Schnittstelle erweitert die Dateischnittstelle und definiert die Änderungsfunktion für den Besitzer, die aus der alten Schnittstelle entfernt wird. Nun kann eine lokale Datei die neue Schnittstelle realisieren und die Dropboxdatei die alte, die nur noch die Funktion für das Ändern des Namens vorgibt. Beide konkreten Klassen könnten nun ohne negative Nebenwirkungen ihre Oberklassen im Programm ersetzen.[42, S. 33-38] Bereits ein einfacher Verstoß gegen dieses Prinzip kann die SA verunreinigen und eine Vielzahl weiterer Probleme, wie beispielsweise das notgedrungene Einführen von Verzweigungen, hervorrufen.[38, S. 101]
Interface-Segregation-Prinzip
Das Interface-Segregation-Prinzip (ISP) gibt vor, dass viele spezifische Schnittstellen für Clients besser sind, als eine generelle Schnittstelle, die von allen benutzt wird. Wird gegen dieses Prinzip verstoßen, sind Klassen weniger gut benutz- und übertragbar. [37] Die jeweiligen Schnittstellen sollten nur Methoden definieren, die aus Sicht der Clients gebraucht werden.[42, S. 55] In Abbildung 2.8 ist eine Verfehlung des ISP zu sehen. Verwenden die Benutzerklassen nur die jeweilige Operation, die die gleiche anhängende Zahl wie sie selbst haben, besitzt der Quellcode, obwohl er die anderen Operationen nicht benutzt, eine Abhängigkeit gegenüber diesen. Falls die geerbte Klasse eine Abhängigkeit gegenüber einer Datenbank aufweist, weil diese in den für eine der erbenden Klassen irrelevanten Operationen benötigt wird, und diese zur Laufzeit versagt, dann ist auch die erbende Klasse davon betroffen, obwohl sie überhaupt keinen Bezug zur Datenbank hat.[38, S. 103f.] Um dieses
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.8.: Verstoß gegen ISP - Darstellung nach [38, S. 103]
Problem zu lösen, können die einzelnen Operationen in Schnittstellen aufgelöst werden. Dieser Vorgang wird in Abbildung 2.9 gezeigt. Der Quellcode von Benutzer1 ist nach dieser Änderung nur noch von der Schnittstelle BenutzerlOperationen und operationl abhängig. Außerdem würde eine Modifikation an der Klasse Operationen, die für Benutzer1 irrelevant ist, bei statisch typisierten Programmiersprachen keine Neukompilierung und kein erneutes Deployment von Benutzer1 verursachen.[38, S. 104]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.9.: Erfüllung des ISP - Darstellung nach [38, S. 104]
Dependency-Inversion-Prinzip
Das Dependency-Inversion-Prinzip (DIP) besagt, dass Module von höheren Ebenen nicht von Modulen niedrigerer Ebenen abhängig sein sollten. Um eine Abhängigkeit zu invertieren, muss das Modul der höheren Ebene eine Schnittstelle definieren, mit der es arbeitet. Danach kann ein Modul der niedrigeren Ebene diese Schnittstelle realisieren.[39] Außerdem geht geht es darum, dass sich Abhängigkeiten auf Abstraktionen statt auf Konkretionen beziehen sollen. Der Grund, warum dieses Prinzip so wichtig ist, ist, dass sich konkrete Dinge viel häufiger ändern als abstrakte. Abstraktionen können normalerweise leicht erweitert werden, ohne dass dies eine Modifikation erfordert[37], während konkrete Elemente während des Entwicklungsprozesses sehr häufig Anpassungen unterliegen.[38, S. 108] Eine Produktliste, die von einem Produktservice, der einen Server für die Kommunikation benötigt, abhängig ist, kann als höherstehendes Modul angesehen werden, das eine Abhängigkeit zu einer detailreichen Serviceklasse besitzt. Um dem DIP gerecht zu werden, muss die Produktliste von einer Abstraktion abhängig sein. Die Abstraktion, zum Beispiel eine Schnittstelle, gibt dann vor, was ein Produktservice zu tun hat und beschreibt keine Details zur Funktionsweise. So kann sich hinter der Schnittstelle eine beliebige konkrete Implementation des Produktservices befinden. Wird beispielsweise in Zukunft der Produktservice geändert und benutzt eine lokale Datenbank statt einem Server, müssen die konkrete Klassen, die von der Abstraktion abhängig sind, nicht verändert werden. Gleichzeitig kann der Produktservice in verschiedenen anderen Kontexten der Anwendung verwendet werden, auch wenn an diesen Stellen eine andere Technik zum Erhalten der Produkte notwendig ist. Hierfür muss lediglich eine neue konkrete Klasse, die diese Schnittstelle realisiert, definiert werden. Zusätzlich steigt die Testbarkeit der Produktliste, da für den Produktservice auch eine Testfallimplementierung erstellt werden kann, die zur Laufzeit des Tests an die Produktliste übergeben wird. Abbildung 2.10 zeigt die Produktliste, die die Schnittstelle, mit der sie arbeitet, vorgibt und somit von einer Abstraktion abhängig ist. Zusätzlich wird damit auch die Abhängigkeit invertiert, da Produktservice zur Schicht der Produktliste gehört und nun eine Abhängigkeit für die konkreten Implementierungen in der darunter liegenden Schicht darstellt.[16, S. 198-201] Das DIP ist eines der Kernprinzipien bei Clean Architecture, wo es auch als Abhängigkeitsregel bezeichnet wird.[38, S. 110]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.10.: Dependency-Inversion-Prinzip - Darstellung nach [16, S. 201]
2.2.2. Schichten entkoppeln
Benutzerschnittstellen werden bei Anwendungen oft ohne Auswirkungen auf die Geschäftslogik verändert. Daher sollten Bestandteile, die die Benutzerschnittstellen betreffen, von der Geschäftslogik, also den Anwendungsfällen, getrennt werden. Somit kann die Benutzeroberfläche ohne Auswirkungen auf die Anwendungsfälle, die weiterhin deutlich erkennbar bleiben, verändert werden. Ähnlich verhält es sich mit der Datenbank und deren technischen Details. Diese haben weder mit den Anwendungsfällen noch mit der Benutzerschnittstelle etwas zu tun und sollten deshalb vom System getrennt werden, um unabhängige Modifikationen problemlos zu ermöglichen. Das System wir durch diese Entkopplung in horizontale Schichten aufgeteilt und schafft die Grundlage für das SRP.[38, S. 168f.]
2.2.3. Grenzlinien
Grenzlinien sollten zwischen Bereichen gezogen werden, die nicht voneinander abhängig sind. Beispielsweise spielt für die Benutzerschnittstelle die Datenbank keine Rolle und diese ist wiederum für die Geschäftsregeln nicht von Belang. Zudem ist für viele Entwickler die Datenbank vermeintlich sehr stark mit den Geschäftsregeln gekoppelt, jedoch müssen diese von den Details der Datenbank, wie Schema oder Sprache, keine Kenntnis haben. Die Datenbank stellt vielmehr ein Werkzeug zur Verfügung, das durch die Geschäftsregeln indirekt genutzt werden kann. Dadurch wird es möglich, dass die Datenbank austauschbar hinter eine Schnittstelle platziert werden kann. In Abbildung 2.11 ist das Zusammenspiel von den Geschäftsregeln und der Datenbank zu sehen. Hierbei wird die Datenbankschnittstelle von den Geschäftsregeln benutzt, während die Datenzugriffsklasse diese realisiert, indem sie die Datenbank selbst benutzt. Die Grenzlinie verläuft dann zwischen der Schnittstelle und dem konkreten Datenbankzugriff, da bei dieser Instanz nur abgehende Pfeile vorliegen und die anderen Zielklassen keine Kenntnis über sie haben.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.11.: Geschäftsregeln und Datenbank - Darstellung nach [38, S. 182]
Mit dieser Abgrenzungslinie können nun zwei Komponenten erstellt werden, die separat betrachtet und entwickelt werden können, wie in Abbildung 2.12 zu sehen ist. Hierbei sei nochmal hervorzuheben, dass für die Datenbankkomponente (Datenbankzugriff und Datenbank) jede beliebige Art von Datenbank realisiert werden kann, was ein vorteilhaftes Hinauszögern dieser Entscheidung im Entwicklungsprozess ermöglichte, S. 181-183]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.12.: Komponentenaufteilung - Darstellung nach [38, S. 183]
2.2.4. Anwendungsfälle
Die höchste Priorität für die SA ist die Unterstützung der Anwendungsfälle, die den Zweck des Softwaresystems erfüllen und anwendungsabhängige Geschäftsregeln realisieren. Eine gute SA macht das Verhalten des Systems auf Architekturebene deutlich, was durch das Hervorheben der Anwendungsfälle bewirkt wird. Diese werden an geeigneter Stelle innerhalb der Architektur definiert und in Beziehungen zu anderen Komponenten gesetzt, wodurch ihre Funktionalität für Außenstehende eindeutig hervorgeht. Anwendungsfälle sollten vom restlichen System entkoppelt werden, da sie sich mit hoher Wahrscheinlichkeit während der Lebensdauer der Software ändern. Daher sollte die Benutzerschnittstelle für einen Anwendungsfall von der Benutzerschnittstelle für einen anderen Anwendungsfall separiert werden. Das gleiche Prinzip gilt auch für die Datenbankanbindung und andere mit dem Anwendungsfall interagierende Komponenten. Somit werden alle Anwendungsfälle in einem vertikal aufgebauten System abwärts verlaufend getrennt gehalten. Infolgedessen hat das Hinzufügen von neuen Anwendungsfällen keine Auswirkungen auf bereits vorhandene. Die Entkopplung der Schichten (vgl. 2.2.2) und die Separierung der Anwendungsfälle ermöglichen im Entwicklungsprozess zudem eine Organisation des Entwicklerteams, das in seinen verschiedenen Bereichen bei der Implementierung keine Auswirkungen aufeinander hat. In einer objektorientierten Sprache wird ein Anwendungsfall als Objekt mit einer oder mehreren Funktionen realisiert, das bei Bedarf Referenzen auf Entitäten und anderer Datenelemente enthält. [38, S. 203-214]
2.2.5. Entitäten
Eine Entität wird als Objekt innerhalb eines Softwaresystems bezeichnet, das wichtige anwendungsunabhängige Geschäftsregeln widerspiegelt, die auf den Kerndaten des Geschäfts basieren. Entitäten besitzen Implementierungen dieser Geschäftsregeln als Funktionen und geben diese als Schnittstelle nach außen preis. In Abbildung 2.13 ist zu sehen, wie eine Entität für ein Darlehen in UML aussehen könnte. Die Entität ist die einzige Instanz, die die Geschäftstätigkeit repräsentiert.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.13.: Darlehen Entität - Darstellung nach [38, S. 202]
Sie muss von allen anderen Aspekten, wie beispielsweise Benutzerschnittstellen oder Datenbanken separiert werden, wodurch sie in jedem beliebigen System oder Bereich unabhängig von dessen Umgebungsfaktoren benutzt werden kann. In einer objektorientierten Sprache wird sie meist als Klasse oder Struktur realisiert. Anwendungsfälle (vgl. 2.2.4) führen im Vergleich zu Entitäten anwendungsabhängige Geschäftsregeln aus, bei denen sie Richtlinien enthalten, wie sie die anwendungsunabhängigen Geschäftsregeln innerhalb der Entitäten auslösen. Entitäten wissen hierbei nicht, ob sie von Anwendungsfällen verwendet werden. Sie sind also den Anwendungsfällen übergeordnet, weil sie allgemeingültiger sind und in verschiedenen Bereichen zum Einsatz kommen können. Diese Ausrichtung erfolgt nach dem Dependency Inversion Prinzip (vgl. 2.2.1).[38, S. 202f.]
2.2.6. Architektur
In Abbildung 2.14 wird das Konzept von CA visuell dargestellt. Hierbei bilden die bereits erwähnten unternehmensspezifischen Geschäftsregeln (Entitäten) den Kern und werden von den anwendungsspezifischen Geschäftsregeln (Anwendungsfalle), die diese benutzen, umhüllt. Die Wahrscheinlichkeit, dass sich die Entitäten ändern, wenn eine externe Modifikation vorkommt, ist sehr gering. Auch die Anwendungsfälle sind isoliert zu betrachten und werden bei Änderungen der Datenbank oder der Benutzerschnittstelle nicht beeinflusst. Sie werden jedoch betroffen, wenn sich betriebsbezogene Richtlinien ändern, was unvermeidlicherweise auch Quellcodeänderungen zur Folge hat. Der dritte Ring in CA beinhaltet verschiedene Adapter, die Daten von den Anwendungsfällen für externe Komponenten aufbereiten. Das könnten zum Beispiel eine Datenbank- oder die Benutzerschnittstelle sein. Die äußerste Schicht setzt sich aus externen Komponenten wie UI-Frameworks oder der Datenbank zusammen, welche detaillierte Informationen über die eingesetzte Technologie beinhalten. Diese Faktoren können lange außen vorgehalten und im Entwicklungsprozess erst spät konkret entschieden werden. Die Kreise von Abbildung 2.14 sind in dieser Anzahl und Aufteilung nicht zwingend notwendig, geben aber eine sehr gute schablonenhafte Darstellung wieder. Die äußeren Schichten bestehen aus nachrangigen Details, während jede weitere Schritt nach innen zu einer abstrakteren Schicht mit wichtigeren Richtlinien führt. Die wesentliche Regel in diesem Diagramm ist die Abhängigkeitsregel, welche besagt, dass Abhängigkeiten nur von außen nach innen weisen dürfen. Vor allem dürfen Funktionen, Klassen und Variablen von inneren Schichten nie eine Komponente aus einer weiter außenliegenden Schicht erwähnen oder deren Datenformate benutzen. Wenn ein Kontroller einen Anwendungsfall auslösen möchte und dieser seine Ausgabe an den Präsentator übermitteln soll, was in einer Anwendung häufig vorkommt, würde die Abhängigkeitsregel, dass innere Schichten keine Namen oder Referenzen von Komponenten der äußeren Schichten beinhalten dürfen, verletzt werden. Deshalb wird auf Anwendungsfallebene eine Schnittstelle nach dem DIP definiert, die vom Präsentator realisiert und vom Anwendungsfall benutzt wird. Somit kann der Kontrollfluss die Grenzlinie von innen nach außen durchschreiten, während die Abhängigkeiten dennoch richtig erhalten bleiben.[38, S. 211-217] Dieser Verlauf des Kontrollflusses wird in 2.2.7 anhand eines konkreten Beispiels genauer erläutert.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.14.: Clean Architecture - Darstellung nach [38, S. 212]
2.2.7. Beispiel
Im Beispiel, das in Abbildung 2.15 gezeigt wird, geht es um ein typisches Szenario in einem System, das eine Datenbank benutzt und eine Benutzeransicht besitzt. Die Eingabedaten werden in diesem Fall von einem nicht genauer spezifizierten Webserver entgegengenommen und an den Kontroller weitergeleitet. Dieser erstellt dann die Eingabedaten in Form eines Datenobjektes, welches er dann an die Eingabeschnittstelle, die vom Anwendungsfall implementiert wird, übermittelt. Der Anwendungsfall interpretiert die Daten und ruft anschließend die für den Ablauf der Geschäftsregeln erforderlichen Operationen auf den Entitäten auf. Um Zugriff auf die von den Entitäten benutzen Daten zu erhalten, wird die Datenbankzugriffsschnittstelle verwendet, was dem DIP nachkommt. Damit die Daten, die von den Entitäten erstellt werden, an den Präsentator gelangen, benutzt der Anwendungsfall die Ausgabeschnittstelle und liefert über sie die erzeugten Ausgabedaten. Nach Erhalt der Daten bereitet der Präsentator diese in eine darstellbare Form auf, welche von einem ViewModel repräsentiert wird. Die Komponente ViewModel kann weitere Bezeichnungen und Flags, die für die Benutzeransicht wichtig sind, beinhalten. Die Benutzeransicht muss anschließend nur noch die im ViewModel enthaltene Daten in die Webseite verschieben. Die Grenzlinien in Abbildung 2.15 werden nur von den äußeren Schichten in Richtung der inneren gekreuzt, genau so wie es von der Abhängigkeitsregel vorgeschrieben wird. Genau genommen bräuchte man die Eingabeschnittstelle in diesem Fall nicht, da der Kontroller direkt vom Anwendungsfall abhängig sein dürfte. Für eine höhere Testbarkeit des Systems ist dies allerdings eine gute Vorgehensweise.[38, S. 216f.]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.15.: CA Beispiel - Darstellung nach [38, S. 217]
2.2.8. Clean Architecture bei clientseitiger Software
Wenn eine clientseitige Applikation nach den CA-Prinzipien implementiert wird, sollte eine Aktualisierung der Benutzerschnittstelle keine Änderungen an anderen Stellen im Quellcode erfordern. Deshalb gehört nur die Logik, die für die Benutzerschnittstelle relevant ist, in Klassen, die für die Präsentation zuständig sind. Das Datenmodell treibt die Benutzeroberfläche für Veränderungen an, sollte jedoch unabhängig von den einzelnen Benutzeroberflächenkomponenten sein. Deshalb ist eine Aufteilung der Applikation in mindestens zwei Schichten erforderlich. Eine Präsentationsschicht, die für die Anzeige der Applikationsdaten verantwortlich ist und eine Datenschicht, welche sich um die Geschäfts- und Datenlogik kümmert. Die Interaktionen zwischen Daten- und Präsentationsschicht können durch das Einführen einer zusätzlichen Domainschicht vereinfacht werden, was zugleich für eine bessere Entkopplung sorgt. Abbildung 2.16 veranschaulicht die drei erwähnten Schichten. Dabei enthält die Präsentationsschicht Benutzeroberflächenkomponen- ten und dazugehörige Klassen für die Zustandshaltung. In der Datenschicht befinden sich Speicherbereiche, die verschiedene Datenquellen beinhalten können. Um Geschäftslogik und Geschäftsdaten zu entkapseln, befinden sich Anwendungsfälle und Entitäten innerhalb der Domainschicht.[6]
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.16.: Drei Schichten - Darstellung nach [6, S. 3]
2.3. Native Android-Appentwicklung
2.3.1. Android Studio
Android Studio ist die meist verwendete Entwicklungsumgebung für native AndroidEntwicklung. Als Android 1.0 im Jahre 2008 veröffentlicht wurde, gab es noch keine Entwicklungsumgebung wie Android Studio. Die Entwickler von nativen Android-Applikationen hatten lediglich eine Menge Kommandozeilenwerkzeuge und Skripte zum richtigen Zusammenführen mehrerer Dateien zur Verfügung. Eine große Anzahl an Features, wie das Hervorheben von Quellcode, Projektsetups und Quellcodeuntersuchungsmöglichkeiten, wurden erst im gleichen Jahr mit dem Release der Eclipse IDE verfügbar. Zu dieser Zeit mussten für die Android-Entwicklung jedoch immer noch verschiedene Komponenten separat installiert und zusammengeführt werden. 2013 folgte die Beta-Version der Entwicklungsumgebung Android Studio, die auf JetBrain’s IntelliJ IDEA Community Edition basiert. Android Studio machte das Setup für Projekte sehr einfach und benötigte lediglich die Installation des Java Developement Kit. Java als bisher bevorzugte Sprache zum Entwickeln von Android-Applikationen wurde in den letzten Jahren von Kotlin abgelöst.[15, S. 160f.] Heute besitzt Android Studio sehr viele Features, wie beispielsweise einen schnellen Emulator, ein Quellcodeanalysetool, Möglichkeiten zur Reduzierung der Applikationsgröße, einen intelligenten Codeeditor, ein flexibles System zum Erstellen verschiedener Applikationsvarianten und ein Untersuchungstool zum Auswerten von Prozessorbelastung, Speicherverbrauch und Netzwerkaktivitäten.[24]
2.3.2. Lebenszyklen
Aktivitätsklassen sind in der Android-Entwicklung eine zentrale Komponente der Applikation, die verschiedene, auf ihren Lebenszyklus angepasste, CallbackFunktionen besitzen. In einer separaten Manifest-Datei wird festgelegt, welche Aktivität als Einstiegspunkt der App beim Start dienen soll. Nach dem Auswählen der richtigen Aktivität stellt diese beim Aufruf ein Fenster zur Verfügung, in das die Applikation die Benutzeroberfläche zeichnen kann. Ein Entwickler sollte stets darauf achten, die verschiedenen Lebenszyklen einer Aktivität richtig zu verwalten.[27] Während ein Benutzer die Applikation in den Hintergrund verschiebt oder wieder aufruft und benutzt, können unterschiedliche Phasen des Lebenszyklus durchlaufen werden, in denen das Android-Framework verschiedene Operationen ausführt, die beim Außerachtlassen unvorhergesehene Folgen haben können. Beispielsweise können die Werte von Variablen innerhalb einer Aktivität durch das Verschieben der Applikation in den Hintergrund oder einer einfachen Bildschirmrotation verloren gehen und so zu Abstürzen führen. Die Aktivitätsklasse stellt eine Menge von Callback-Funktionen zur Verfügung, mit deren Hilfe auf das Eintreten verschiedener Phasen des Lebenszyklus reagiert werden kann. Bei einem Videoplayer kann zum Beispiel das Abspielen automatisch gestoppt und die Netzwerkverbindung getrennt werden, wenn der Benutzer zu einer anderen Applikation navigiert. Nachdem er zurückgekehrt ist, kann die Verbindung zum Netzwerk wieder aufgebaut und das Video fortgesetzt werden. Die verschiedenen Lebenszyklen und deren Callback-Funktionen sind in Abbildung 2.17 zu sehen. onCreate() muss zwingend vom Entwickler implementiert werden, um einmalige Startlogik, wie ViewModelAnbindungen oder das Deklarieren der Benutzeroberfläche, festzulegen. Innerhalb von onCreate() wird ein Bündel-Objekt als Parameter erhalten, welches einen eventuell ehemaligen Zustand der Aktivität beinhaltet. Nachdem die Ausführung der Funktion beendet wurde, befindet sich die Aktivität im Started-Zustand, onStart() wird aufgerufen und die Aktivität wird für den Benutzer sichtbar. Beim Durchlaufen dieser Funktion ist die Aktivität jedoch noch nicht interaktiv bedienbar, sondern bereitet sich lediglich auf das Eintreten in den Vordergrund vor. Anschließend wird der Resumed-Zustand erreicht, in dem die Aktivität in den Vordergrund kommt und onResume() aufgerufen wird. In diesem Zustand interagiert der Benutzer solange mit der Applikation, bis ein Event auftritt, das den Applikationsfokus entzieht. Im Falle einer Unterbrechung tritt die Aktivität in den Paused-Zustand ein und on- Pause() wird aufgerufen. Die Aktivität kann dann zurück in den Resumed-Zustand mit erneuter Ausführung von onResume() gelangen. Diese Funktion sollte daher Komponenten initialisieren, deren Freigabe in onPause() definiert sind. onPause() ist ein erster Indikator dafür, dass der Benutzer die Aktivität verlässt und diese nicht mehr im Vordergrund ist. Deshalb sollten hier Operationen angehalten werden, die nicht weiterlaufen sollen, wenn sich die Aktivität im Paused-Zustand befindet. Dazu gehört beispielsweise das Freigeben von Systemressourcen wie GPS oder andere batterieverzehrende Dienste. Zeitintensive Operationen wie das Speichern von Be- nutzerdaten oder Netzwerkanfragen sollten nicht in onPause() ausgeführt werden, da die Ausführungsdauer dieser Funktion normalerweise sehr gering ist und nicht die dafür benötigte Zeit bereitstellt. Eine passendere Callback-Funktion für solche Operationen ist die Callback-Funktion onStop(), die beim Eintreten des Stopped- Zustand ausgeführt wird. Dieser Zustand wird erreicht, wenn die Aktivität nicht mehr sichtbar für den Benutzer ist. Meistens gerät die Applikation nach dem Paused- Zustand auch in den Stopped-Zustand. Es gibt jedoch Fälle, wie das Öffnen eines Dialogfensters oder die Benutzung eines Modi, der mehrere Bildschirme gleichzeitig ermöglicht, in denen der Stopped-Zustand nicht zwingend auf den Paused-Zustand folgt, da die Aktivität lediglich den Fokus verliert, aber dennoch sichtbar bleibt. Wenn sich eine Aktivität im Stopped-Zusand befindet, wird sie mit allen Zustandsund Variableninformationen im Speicher gehalten, ist jedoch nicht mehr an den Bildschirm gebunden. Das ermöglicht einen schnelleren Abruf der Informationen, wenn die Aktivität zurück in den Resumed-Zustand kommt. Außerdem bleiben so Eingaben in Textfeldern erhalten und müssen nicht manuell gespeichert werden. Vom Stopped-Zustand aus kommt der Benutzer entweder zurück und onRestart() wird aufgerufen, oder die Aktivität wird beendet, was onDestroy() ausführt. Dies kann ein bewusstes Schließen der Applikation als Grund haben, oder auch vom System bei einer Konfigurationsänderung, wie zum Beispiel einer Bildschirmrotation, ausgelöst werden. In diesem Fall wird nach onDestroy() sofort eine neue Aktivitätsinstanz erstellt und onCreate() aufgerufen. Es bietet sich deshalb an, relevante Daten in einem ViewModel, das den Aktivitätslebenszyklus übersteht, zu verwalten. Eine weitere Möglichkeit für die Beendigung der Applikation besteht durch das Android-Betriebssystem als Folge einer Freigabe von Arbeitsspeicher. Prinzipiell kann so eine Freigabe immer geschehen, jedoch ist es am wahrscheinlichsten, wenn sich die Aktivität im Stopped-Zustand befindet.[32]
2.3.3. Layouts
Ein Layout definiert die Struktur der Benutzeroberfläche, die dem Benutzer angezeigt wird und mit der er interagieren kann. Sämtliche Elemente werden hierarchisch durch View und View-Gruppenobjekte angeordnet. Ein einzelner View zeichnet normalerweise etwas, das der Benutzer auf dem Bildschirm sehen kann, während eine View-Gruppe mehrere Views und weitere View-Gruppen beinhalten kann und deren Struktur definiert. Unter View-Objekte fallen zum Beispiel Buttons oder Textfelder. Eine View-Gruppe kann auch als Layout bezeichnet werden, das verschiedene Strukturmöglichkeiten, wie lineare oder eingeschränkte Anordnung der internen Elemente zur Verfügung stellen kann. Views und View-Gruppen können durch eine separate XML-Datei per Hand erstellt, einem Layout-Editor definiert oder per Programmcode implementiert werden. Die Deklaration in einer extra XML Datei hat den Vorteil, dass die Präsentation von der Verhaltenslogik der Benutzeroberfläche getrennt wird und erleichtert das Unterstützen von ver-schiedenen Bildschirmgrößen.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.17.: Aktivitätslebenszyklus - Darstellung nach [32]
Abbildung 2.18 zeigt eine simple XML-Datei, die als Wurzelelement ein LinearLayout mit vertikaler Anordnung besitzt, welches wiederum einen TextView und einen Button enthält. Während die Android-Applikation kompiliert wird, wird jede einzelne XML-Datei in eine View Ressource übersetzt.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.18.: Layout als XML-Datei - Darstellung nach [28]
In der onCreate() Funktion einer Aktivität kann diese dann zur Laufzeit mit set- ContentView() geladen werden, wie in Abbildung 2.19 zu sehen ist. R ist dabei die Android-Ressourcenklasse, in der die XML-Datei im Unterordner layout unter dem Namen activity_main vorzufinden ist.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2.19.: Laden eines Layouts - Darstellung nach [28]
Um auf Attribute der einzelnen Views in der XML-Datei zugreifen zu können, muss ein Identifikationsattribut vergeben werden. In Abbildung 2.18 wurden sowohl der TextView als auch der Button mit einem Identifikationsmerkmal versehen. Zur Laufzeit kann eine Referenz auf den View mit der Funktion findViewById() hergestellt werden, was in Abbildung 2.20 verdeutlicht wird. Anschließend steht das View-Objekt mit spezifischen Funktionen für die Änderung seiner Attribute zur Verfügung.
[...]
- Quote paper
- Matthias Kerat (Author), 2022, Clean Architecture angewandt auf moderne Android-Entwicklung, Munich, GRIN Verlag, https://www.grin.com/document/1273852
-
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X.