9 kam im September 2017 (endlich!) das lange angekündigte und lang ersehnte Modulsystem Jigsaw. Jigsaw ist eine grundlegende Strukturänderung von Java-Plattform und -Sprache, mit deren Auswirkungen man sich möglichst früh beschäftigen sollte. Komponentenbasierte Software ist nichts Neues. Mit Java 9 steht nach über 20 Jahren Java- Entwicklung nun direkt ein natives Sprachmittel zur Verfügung, um Komponenten zu definieren und in der Architektur zu verankern. Was bisher nur mit Tools wie Maven oder Ivy bzw. durch statische Code-Analyse möglich war, ist nun direkt als Sprachfeature verfügbar. Wir betrachten verschiedene bekannte Modularity-Design-Patterns und zeigen, wie man diese mit Jigsaw umsetzen kann, darunter Patterns zu Architektur- und Komponentenschnitt, Patterns zu Abhängigkeiten, Patterns zu Test, Patterns zu Erweiterbarkeit und zur Evolution / Migration. Welche Patterns werden gut unterstützt, welche erfordern zusätzliche Klimmzüge oder gar eigene Erweiterungen? Welche Features kann Jigsaw in Java 9 noch nicht (Beispiel: Modul- Versionierung)? Abstract – Java Forum Stuttgart 2018 https://www.java-forum-stuttgart.de/de/Slot+3+2018.html#A3
Accso - Accelerated Solutions GmbH Cheftechnologe Martin Lehmann ist Diplom-Informatiker und arbeitet als Cheftechnologe und Softwarearchitekt bei der Accso - Accelerated Solutions GmbH. Seit Ende der 90er-Jahre wirkt er als Software-entwickler und -architekt in der Softwareentwicklung in diversen Projekten der Individual-entwicklung für Kunden verschiedener Branchen. Seit den Zeiten von Java 1.0 beschäftigt er sich mit Java als Programmiersprache und als Ökosystem. [email protected] @mrtnlhmnn www.xing.com/profile/Martin_Lehmann3 Kristine Schaal, Accso - Accelerated Solutions GmbH Softwarearchitektin Dr. Kristine Schaal ist als Softwarearchitektin bei der Accso - Accelerated Solutions GmbH tätig. Sie arbeitet seit 20 Jahren in der Softwareentwicklung und ist in Projekten der Individual- entwicklung für Kunden verschiedener Branchen unterwegs, technisch überwiegend im Java- Umfeld. [email protected] @krschaal www.xing.com/profile/Kristine_Schaal
seit Java 9: JSR 376 und seine Ziele Strong Encapsulation Eine Komponente kann ihr öffentliches API definieren und den Zugriff auf Implementierungsgeheimnisse verhindern. Reliable Configuration Ablösung des Classpath (fehleranfällig, wenn Klassen mehrfach enthalten sind Reihenfolgeprobleme). Scalable Java SE Platform Die Java-Plattform ist nun modularisiert. Man kann individuell angepasste, „schlanke“ Plattformen bauen. JSR 376 für das „Java Platform Module System“ (JPMS) Project Jigsaw seit Java 9-Release vom 21. September 2017 enthalten. Aktuell: Releases 9.0.4 und 10.0.1. JEP 261, 200, 201, 220, 260, 282 mit Modulsystem, modularisiertem JDK und der Kapselung interner APIs
Dependencies, Resolution, Encapsulation, Non-Interference: In allen Phasen (Compile, Laufzeit). Exports müssen von außen überschreibbar sein. Resource Encapsulation Services: Binding, Selective Binding Java Platform Modularization Interoperabilität mit anderen Modulsystemen (OSGi explizit genannt) Refactoring: Module aufspaltbar / zusammenlegbar, ohne Auswirkungen auf Clients Whitebox-Tests müssen möglich bleiben Schrittweise Migration, Weiterbenutzung von existierenden Tools wie Maven Linking, um optimierte Plattform bauen zu können Keine Verschlechterung der Performance Versionierung ist explizit ausgeklammert (Modul hat nur einen Versionsstring!) Wichtige Anforderungen aus der JPMS-Spec
Java Application Architecture Modularity Patterns with Examples Using OSGi Series: Robert C. Martin Series Paperback: 384 pages Publisher: Prentice Hall; 1 edition March 25, 2012 ISBN-10: 0321247132 ISBN-13: 978-0321247131 http://www.kirkk.com/modularity/ https://mreinhold.org/blog/jigsaw-complete
Manage Relationships Design module relationships Module Reuse Emphasize reusability at the module level Cohesive Modules Module behavior should serve a singular purpose Dependency Patterns Acyclic Relationships Module relationships must be acyclic Levelize Modules Module relationships should be levelized Physical Layers Module relationships should not violate the conceptual layers Container Independence Modules should be independent of the runtime container Independent Deployment Modules should be independently deployable units Usability Patterns Published Interface Make a module’s published interface well known External Configuration Modules should be externally configurable Default Implementation Provide modules with a default implementation Module Facade Facade as a coarse-grained entry point to … fine-grained underlying implementation Extensibility Patterns Abstract Modules Depend upon the abstract elements of a module Implementation Factory Use factories to create a module’s implementation classes Separate Abstractions Place abstractions and the classes that implement them in separate modules Utility Patterns Collocate Exceptions Exceptions should be close to the classes that throw them Levelized Build Execute the build in accordance with module levelization Test Module Each module should have a … test module that validates it’s behavior and illustrates it’s usage Pattern Catalog http://www.kirkk.com/ modularity/pattern-catalog/
Manage Relationships Pattern Komponenten = wieder- verwendbare „Software- Bausteine“ Wohldefinierte Schnittstellen Eingehende Schnittstellen Ausgehende Schnittstellen Der Grad der Abhängigkeiten bestimmt die Komplexität einer Anwendung maßgeblich. Vorteile Komponenten brechen den Monolithen auf. Nachvollziehbare Aus- wirkungen von Änderungen Es wird nur geladen, was wirklich benötigt wird. Es wird nur geladen, was wirklich modelliert wurde. Nachteile
Menge von Java-Packages & Resources Wird in ein „Modular JAR“ kompiliert. Dieses liegt im neuen Module-Path. Module-Namen müssen eindeutig sein (erlaubt sind . und _ , aber nicht -) Im Module-Deskriptor stehen: • Name • Abhängigkeiten • Zugriffsschutz Keine weiteren Syntax- oder Sprachkonstrukte! Ein Module als „First-Class-Citizen“ in Java module moda { requires modb; exports pkg1; opens pkg2; } moda/module-info.java
Menge von Java-Packages & Resources • „read“-Abhängigkeits- beziehungen zu anderen Modulen • „exports“: Welche Packages des Modules werden exportiert? (Default: nichts!) moda Ein Module als „First-Class-Citizen“ in Java modb read exports pkga pkgb inter nal pkg b
Manage Relationships … mit Jigsaw Module-Deskriptor Module-Name Abhängigkeiten Zugriffsschutz Keine weiteren Syntax- oder Sprachkonstrukte! Pattern Komponenten = wieder- verwendbare „Software- Bausteine“ Wohldefinierte Schnittstellen Eingehende Schnittstellen Ausgehende Schnittstellen Der Grad der Abhängigkeiten bestimmt die Komplexität einer Anwendung maßgeblich.
Manage Relationships … mit Jigsaw Komponente als Module Beziehungen Keine Scopes (wie in Maven) Statische Bindung über Namen Dynamische Bindung zur Startzeit Nutzung Statische Aufrufe Dynamisch per Reflection Pattern Komponenten = wieder- verwendbare „Software- Bausteine“ Wohldefinierte Schnittstellen Eingehende Schnittstellen Ausgehende Schnittstellen Der Grad der Abhängigkeiten bestimmt die Komplexität einer Anwendung maßgeblich.
kann ein anderes Module nur benutzen, wenn es eine „read“-Beziehung zu ihm hat (aka: „requires“). Manage Relationships Explizite Abhängigkeiten modb module moda { // read auf modb requires modb; } read modc Das Module moda hat eine read-Beziehung zu Module modb, jedoch keine zu modc moda kann nur auf modb zugreifen. moda
Manage Relationships Optionale Abhängigkeiten? Transitive Abhängigkeiten? Versionierung? Gruppierungen? Hierarchien? Remote oder lokal? Checks zu Compile-Time? Checks zu Start/Runtime? Vertiefte Konzepte … mit Jigsaw Ja! Ja! Nein (nur informell)! Nicht einfach*! Nicht einfach*! Nur lokal, keine Verteilung! Ja! Ja! *) Zumindest nicht wie in Maven mit group-id. Nicht-triviale Lösungen über Aggregrator-Modules und ModuleLayer.
mit DepVis https://github.com/accso/ java9-jigsaw-depvis Basiert auf GraphViz http://www.graphviz.org/ mit Java-API https://github.com/kohsuke/graphviz-api
Acyclic Relationships Pattern azyklische Komponenten- Abhängigkeiten! Unterscheide: Zyklen zur Compile-Zeit Zyklen zur Laufzeit – z.B. durch Aufruf per Reflection Auflösung von Zyklen Auslagern von Funktionalität Callback Nachteile von Zyklen Die Komponenten eines Zyklus bilden de facto eine einzige „große Komponente“. Hohe Koppelung zwischen den betroffenen Komponenten Eine Änderung an einer Komponente hat i.d.R. Aus- wirkungen auf alle anderen Komponenten des Zyklus.
Acyclic Relationships … mit Jigsaw Statische Zyklen mit requires in Modulen nicht erlaubt. Sie führen zu Compilerfehlern * Dynamische Zyklen zur Laufzeit (per Reflection) möglich Pattern azyklische Komponenten- Abhängigkeiten Unterscheide: Zyklen zur Compile-Zeit Zyklen zur Laufzeit – z.B. durch Aufruf per Reflection Auflösung von Zyklen Auslagern von Funktionalität Callback * Vorsicht: Compiler-Bug in 9.0.4 findet nicht alle Zyklen!
Published Interface Pattern Komponente definiert ihre öffentliche Schnittstelle. Nutzer verwenden die Komponente nur über diese Schnittstelle. Von außen ist kein Zugriff auf die Implementierung möglich.
Published Interface Pattern Komponente definiert ihre öffentliche Schnittstelle. Nutzer verwenden die Komponente nur über diese Schnittstelle. Von außen ist kein Zugriff auf die Implementierung möglich. Vorteile Fördert explizite Definition von Komponenten-Grenzen Dokumentation Änderung der gekapselten Implementierung hat keine Auswirkung auf Nutzer.
Published Interface Pattern Komponente definiert ihre öffentliche Schnittstelle. Nutzer verwenden die Komponente nur über diese Schnittstelle. Von außen ist kein Zugriff auf die Implementierung möglich. Nachteile Höhere Komplexität durch Indirektion zum Erzeugen der Implementierung Instabile Schnittstellen Abwärtskompatibilitätsfalle Law of Leaky Abstraction: „All non-trivial abstractions, to some degree, are leaky.” (Joel Spolsky)
Published Interface Pattern Komponente definiert ihre öffentliche Schnittstelle. Nutzer verwenden die Komponente nur über diese Schnittstelle. Von außen ist kein Zugriff auf die Implementierung möglich. Abgrenzungen Manche Komponenten haben keine echte Schnittstelle, z.B. Datentypen-Komponenten. Reflektiver Zugriff durch Frameworks auch auf gekapselte Implementierung sinnvoll Vollständige Implementierung austauschen? Pattern „Separate Abstractions“
Published Interface … mit Jigsaw exports erlaubt Zugriff Auf Package-Ebene Keine Wildcards Zugriff per Reflection nur mit exports oder opens Gerichteter Export möglich mit exports to bzw. opens to Pattern Komponente definiert ihre öffentliche Schnittstelle. Nutzer verwenden die Komponente nur über diese Schnittstelle. Von außen ist kein Zugriff auf die Implementierung möglich.
Published Interface … mit Jigsaw Checks zu Compile-Zeit und zur Laufzeit von 1. neu: Readability (read) 2. neu: Accessibility (exports) 3. Sichtbarkeitsmodifier public, protected, <package>, private Gilt alles auch für System- Module (wie java.base) Pattern Komponente definiert ihre öffentliche Schnittstelle. Nutzer verwenden die Komponente nur über diese Schnittstelle. Von außen ist kein Zugriff auf die Implementierung möglich.
MyInterface { public MyData myMethod (String param) throws MyException; } Interfaces Factories zum Erzeugen der Implementierung Datentypen Exceptions Was gehört zur Schnittstelle einer Komponente?
exports von Packages module modb { exports pkg.myapi; } read moda pkg. myimpl modb exports pkg. myapi Nur exportierte Packages eines Modules sind von außen zugreifbar. Per Default nichts exportiert. Export nur auf Package-Ebene möglich! Keine Wildcards möglich! Gerichteter Export an ein / mehrere Module mit exports to
exports von Packages module modb { exports pkg.myapi; } read moda pkg. myimpl modb exports Varianten zur Umsetzung des Patterns: API- und Implementierungsklassen in verschiedene Packages legen Oder Implementierung zur API dazulegen – als „package-sichtbar“. pkg. myapi
myimpl pkg. myapi My Impl Instanzen von MyImpl behalten ihren Typ auch über Module- grenze hinaus, obwohl dessen Package nicht exportiert ist. public class Factory { public MyInterface create() { return new MyImpl(); } … public class Caller { … MyInterface myInt = new MyFactory().create(); … MyIn ter face My Fact ory Call er moda modb pkga read Published Interface Typensichtbarkeit
Separate Abstractions Pattern Trenne Implementierung von API in verschiedene Komponenten API-Komponente hat keine Abhängigkeit zu der Implementierung. Auswahl/Konfiguration einer Implementierung erfolgt außerhalb API-Komponente. Factories nicht enthalten. Vorteile Implementierung kann komplett ausgetauscht werden, erlaubt späte Wahl (zur Laufzeit). Beispiel: Datenbank-Treiber Weitere Indirektionen Mehr Aufwand Höhere Komplexität, Zusammenspiel schwerer nachvollziehbar Nachteile
blue.impl api DI- Framework client Zugriff per Reflection mit „opens“ – z.B. für Spring-Fwk Separate Abstractions 2. „DI-Module“ opens to opens to opens to
Split-Package-Problem green.impl red.impl blue.impl api read read exports read Vorsicht: Modules müssen disjunkte Packages haben. Ein „Split“ eines Package auf mehrere Modules ist nicht erlaubt! Gilt auch, wenn das Package nicht exportiert ist!! Selbst dann, wenn es keine Klassen-Duplikate in den Modules gibt!!!
Implementierung in Module java.sql im MySQL-Driver Dynamische Bindung beim Start über ServiceLoader, also keine statische Auflösung über Module-Namen wie bei requires! module com.mysql.jdbc { requires java.sql; … // Implementiert Schnittstelle provides java.sql.Driver with com.mysql.jdbc.Driver; } Separate Abstractions 3. Dynamische Bindung module java.sql { … // Definiert und exportiert // Schnittstelle uses java.sql.Driver; exports java.sql; }
Separate Abstractions … mit Jigsaw Trennung in verschiedene Jigsaw-Module möglich Split-Package beachten! uses-provides für dynamische Bindung zur Startzeit per ServiceLoader Eigene Auswahl der Implementierung möglich Pattern Trenne Implementierung von API in verschiedene Komponenten API-Komponente hat keine Abhängigkeit zu der Implementierung. Auswahl/Konfiguration einer Implementierung erfolgt außerhalb API-Komponente. Factories nicht enthalten.
Module Facade Pattern Vgl. GOF-Patterns „Facade“, „Adapter“, „Decorator“ Eine Komponente dient als zentrale Facade zu anderen Komponenten. Abhängigkeit idealerweise nur zur Facade nötig, nicht zu den „dahinter“ liegenden Komponenten Vorteile Kapselt dahinterliegende Details an zentraler Stelle Kapselt Schnittstellen Höhere Lesbarkeit Einfachere Nutzung Erzwingt Reihenfolgen erlaubt automatische Vor- / Nach-Bearbeitung
Transitive Abhängigkeiten module modfacade { requires modx; requires transitive mody; } In eigenen „Facade-Modules“ kann man andere Module kapseln. Jigsaw ermöglicht auch, Abhängigkeiten transitiv weiterzugeben.
Module Facade … mit Jigsaw requires transitive in module-info reicht Abhängigkeit automatisch weiter Ermöglicht leere Aggregator- Modules Ermöglicht nicht die Kapselung „dahinter“ liegender APIs: Im Gegenteil werden diese ja durchgereicht! Pattern Vgl. GOF-Patterns „Facade“, „Adapter“, „Decorator“ Eine Komponente dient als zentrale Facade zu anderen Komponenten. Abhängigkeit idealerweise nur zur Facade nötig, nicht zu den „dahinter“ liegenden Komponenten
Test Module Pattern Zu jeder Komponente eine korrespondierende Testkomponente erstellen. Blackbox-Test testet nur die öffentlichen Schnittstelle. Die Testkomponente hängt nicht von weiteren Komponenten ab (Mocking). Separate Integrationstest- komponenten erstellen Vorteile Komponenten sind unabhängig voneinander testbar, Fehler leichter lokalisierbar Dokumentation der Nutzung der Schnittstelle Hoher Aufwand durch Mocks Nachteile
black test moda. test. black Blackbox-Tests werden in separates Jigsaw-Module ausgelagert. Dieses Testmodule benötigt keinen Zugriff auf interne Klassen. Test Module Blackbox-Tests pkga internal exports moda pkga
pkga internal …Test exports moda pkga Test Module Whitebox-Tests Whitebox-Test testet interne Klassen von moda aus pkgainternal. Verschiedene Varianten denkbar, z.B. Test-Module & „exports to“ Besser: Testcode getrennt ablegen und für Compile der Tests und für Testdurchführung in das Module „reinpatchen“.
Test Module … mit Jigsaw Blackbox als eigenes Test- Module gegen öffentliche API, Abhängigkeiten per Mocks Whitebox: Testcode auslagern und „reinpatchen“ Integrationstests gegen öffentliche API ohne Mocks Pattern Zu jeder Komponente eine korrespondierende Testkomponente erstellen. Blackbox-Test testet nur die öffentlichen Schnittstelle. Die Testkomponente hängt nicht von weiteren Komponenten ab (Mocking). Separate Integrationstest- komponenten erstellen
Levelize Modules Module relationships should be levelized Physical Layers Module relationships should not violate the conceptual layers Levelized Build Execute the build in accordance with module levelization
gerichteter Graph … mit Jigsaw Jigsaw erstellt einen Abhängigkeitsgraph: Modellierung der Abhängig- keiten mit requires Keine zyklischen Abhängigkeiten Dieser Graph sollte auch die Schichten und Säulen abbilden. Eine Unterstützung durch Jigsaw gibt es dafür nicht.
gerichteter Graph … im Build … mit Jigsaw Den Module-Graph sollte auch Build/Deployment/Test berücksichtigen. Die Graph-Abbildung unterstützt bereits Maven / Gradle (Modellierung von Abhängig- keiten; keine Zyklen erlaubt). Nur Teil-Build/DeploymentTest notwendig bei Änderungen von Modulen der „unteren“ Stufen.
Laufzeitgruppierung Jigsaw erlaubt Laufzeit-Grupp- ierung mit „Module-Layer“. Layer funktioniert ähnlich wie ein Container. Per Default: Alles im Boot-Layer Keine gute Unterstützung für Schichten/Säulen (Calls nur per Reflection über Layergrenzen) … mit Jigsaw
Laufzeitgruppierung Umgeht aber andere Einschränkungen Kein Split-Package bei Geschwister-Layern Module in verschiedenen Versionen nutzbar, wenn mit CL / Layer getrennt Komplexes Zusammenspiel von Configuration, Classloader, Layer … mit Jigsaw
mit Java Release-Build b181 und 9.0.1, 9.0.4 und JDK 10 Bash-Skripte für Compile, Run, Test (in Windows z.B. mit Babun nutzbar) Projektstrukturen für Eclipse 4.7.3 Oxygen.3 Unsere 32 Beispiele zeigen alles Wissenswerte zu Jigsaw zu Requires, Exports, Exports-To, Requires-Static, Requires-Transitive, Blackbox-Test, Whitebox-Test, Uses, Provides, Maven-Integration, Opens, Open Module, Manifest-Optionen, versteckte Main-Klasse, Automatic Modules, Reflection, Resolved Modules (Configuration), Naming von Modulen, Unnamed Module (Classpath), Layer, Javadoc, Annotations, jlink etc. pp. usw. usf. Unsere Jigsaw-Beispiele stehen auf Github