Beliebte Suchanfragen
|
//

Eine Fachkomponentenarchitektur mit Spring 3.0/3.1 – Part One: Struktur

29.12.2011 | 7 Minuten Lesezeit

Die Aufgabe

Wir haben eine große Firma vor uns mit mehreren hundert Entwicklern, die in vielen verschiedenen IT-Fachabteilungen arbeiten und dort jeweils für einen bestimmten Teilaspekt der Gesamtfachlichkeit verantwortlich sind. Jede IT-Fachabteilung soll Komponenten produzieren, die von anderen IT-Fachabteilungen verwendet werden können. Dabei sollen die Fachkomponenten in verschiedenen Kontexten und Umgebungen einsetzbar sein, beispielsweise im Online-Webumfeld oder im Batch-Umfeld. Die Nutzbarkeit soll so wenig wie möglich eingeschränkt sein, um auf zukünftige Anforderungen reagieren zu können. Ein Lock-In auf Technologien soll so weit wie möglich vermieden werden. Ein wichtiger weiterer Aspekt ist die gute Testbarkeit, die gegeben sein muss.
Wie könnte so eine technische Fachkomponentenarchitektur aussehen?

Die Lösung

Grundsätzlich besteht eine Fachkomponente aus einer öffentlichen Schnittstelle, die den Kontrakt beschreibt, den die Komponente anbietet, und einer verborgenen Implementierung.
Technisch gesehen ist der öffentliche Teil eine Sammlung von Interfaces, DTO-Klassen und Exceptions, während der verborgene Teil die Implementierungen der Interfaces enthält. Natürlich kann die Logik hier noch in beliebige weitere Sub-Komponenten aufgeteilt werden.
Um das Beispiel überschaubar zu halten, haben wir hier zwei Fachkomponenten, die jeweils nur einen Service anbieten. Das ist zum einen der PartnerService inkl. DTO:

1public interface PartnerService {
2 
3    public Partner getPartner(long id);
4 
5}
6 
7public class Partner {
8 
9    private long id;
10    private String name;
11 
12    // getters and setters omitted for readability
13 
14}

Und zum anderen der InkassoService inkl. DTO:

1public interface InkassoService {
2 
3    public void doBooking(BookingInfo bookingInfo);
4 
5}
6 
7public class BookingInfo {
8 
9    private long partnerId;
10    private BigDecimal amount;
11    private String subject;
12 
13    // getters and setters omitted for readability
14 
15}

Dies ist jeweils der öffentliche Teil der Fachkomponente. Der verborgene Teil, also die Implementierung der Services, besteht der Einfachheit halber jeweils nur aus einer Klasse:

1public class PartnerServiceImpl implements PartnerService {
2 
3    @Override
4    public Partner getPartner(long id) {
5        Partner partner = null;
6        // TODO do something to get partner
7        return partner;
8    }
9 
10}

Die Implementierung des InkassoServices hat eine Abhängigkeit zum PartnerService, die über den Konstruktor injiziert wird.

1public class InkassoServiceImpl implements InkassoService {
2 
3    private PartnerService partnerService;
4 
5    public InkassoServiceImpl(PartnerService partnerService) {
6        this.partnerService = partnerService;
7    }
8 
9    @Override
10    public void doBooking(BookingInfo bookingInfo) {
11        // TODO validate bookingInfo
12        Partner partner = partnerService.getPartner(bookingInfo.getPartnerId());
13        // TODO use partner to do the booking
14    }
15 
16}

Abhängigkeitsstruktur Schnittstelle und Implementierung


Für das Build- und Dependency-Management wird Maven verwendet.
Wir trennen Schnittstelle und Implementierung einer Fachkomponente in zwei eigene Projekte. Dabei hängt das Impl-Projekt immer vom eigenen Schnittstellen-Projekt ab. Ein Impl-Projekt kann aber noch von beliebig vielen weiteren Schnittstellen-Projekten abhängen. Im obigen Beispiel hängt das Impl-Projekt von Inkasso vom Schnittstellen-Projekt von Partner ab. Wichtig ist dabei, dass Impl-Projekte so nie von anderen Impl-Projekten abhängen, auch nicht transitiv, und es nicht passieren kann, dass ein Entwickler einer Fachkomponente aus Versehen Implementierungsdetails anderer Komponenten verwendet. Jede Fachkomponente definiert sich ausschließlich über die Schnittstelle, jegliche Implementierungsdetails können jederzeit ausgetauscht werden. Die Businesslogik kann einfach über Unit-Tests getestet werden.

Bisher haben wir also zwei Projekte mit POJOs, die die Businesslogik bzw. die Schnittstelle enthalten. Was jetzt noch fehlt, ist die Konfiguration, die die Komponenten per Dependency Injection verknüpft. Hierfür schlage ich die Java-basierte Konfiguration von Spring vor. Für die Partner-Fachkomponente sieht diese Konfiguration so aus:

1@Configuration
2public class PartnerConfig {
3 
4    @Bean
5    public PartnerService partnerService() {
6        return new PartnerServiceImpl();
7    }
8 
9}

Diese Konfiguration kommt in ein eigenes Projekt, das eine Abhängigkeit zum eigenen Impl-Projekt hat. Hier wird Konfiguration und Infrastruktur klar von der Businesslogik getrennt, so gibt es beispielsweise auch keine Abhängigkeit zu Spring im Schnittstellen- und Impl-Projekt. Die Konfiguration der Inkasso-Fachkomponente hat nun zusätzlich eine Abhängigkeit zum Konfigurationsprojekt der Partner-Fachkomponente:

1@Configuration
2@Import(PartnerConfig.class)
3public class InkassoConfig {
4 
5    @Autowired
6    private PartnerConfig partnerConfig;
7 
8    @Bean
9    public InkassoService inkassoService() {
10        return new InkassoServiceImpl(partnerConfig.partnerService());
11    }
12 
13}

Vollständige Abhängigkeitsstruktur inkl. Konfiguration


Die PartnerConfig wird in die InkassoConfig importiert, und bei der Erzeugung des InkassoService wird die PartnerConfig genutzt, um den PartnerService zu injizieren.
Auch wenn im Javamagazin-Artikel schon einige Vorteile dieser Art der Konfiguration genannt werden, möchte ich hier noch einmal auf die wichtigsten Features insbesondere in einem verteilten Entwicklungsumfeld verweisen:

  1. Navigation in Spring-Konfigurationen (auch über JAR-Grenzen hinweg)
  2. Die Konfiguration ist sehr einfach nachvollziehbar, da ich mit den Standard-IDE-Mechanismen sehr einfach durch die Konfiguration navigieren kann. Im obigen Beispiel für Inkasso bin ich mit einem Klick in der Definition des PartnerService, auch wenn diese in einem eingebundenen jar liegt und nicht als Source im Workspace. Das geht mit XML nicht.

  3. Auffinden von Konfigurationsdateien in fremden JARs
  4. Ist die Konfigurationsdatei eine Java-Klasse, so kann sie per „Open Type“ gefunden werden, ist sie eine xml-Datei, so kann sie per „Open Resource“ nicht gefunden werden.

  5. Herausfinden, in welchen Konfigurationen eine bestimmte Klasse oder ein bestimmtes Interface verwendet wird
  6. In Java wiederum kein Problem, auch in JARs im Classpath. Bei xml zumindest über JARs im Classpath nicht möglich.

Die explizite Konfiguration mit JavaConfig fördert die Verständlichkeit und Nachvollziehbarkeit, Schlüsselfeatures für Fehlervermeidung, Fehlerbehebung und Wartbarkeit.

Verwendung einer Fachkomponente

Wir haben jetzt also die Konfiguration für eine Fachkomponente in Form einer Spring-Java-Konfiguration vorliegen. Um die Komponente konkret zu verwenden, benötigen wir natürlich einen instanziierten ApplicationContext, in dem die Konfiguration eingebunden wird.
Was haben wir für Möglichkeiten? Einfach ist es, wenn die Anwendung, die die Fachkomponente verwenden möchte, selbst eine Spring-Anwendung ist, dann kann die Konfiguration einfach dort eingebunden werden. Um beispielsweise die Inkasso-Fachkomponente einzubinden, muss nur die InkassoConfig-Klasse in den bereits bestehenden ApplicationContext eingebunden werden. Alle abhängigen Konfigurationen werden automatisch durch die InkassoConfig importiert.
Ist das nicht der Fall, brauchen wir eine Infrastruktureinheit, die den ApplicationContext verwaltet und die Services nach außen anbietet. Das kann beispielsweise eine Web-Anwendung sein, die die Services als Rest-Webservices anbietet. Das kann eine EJB sein, an die der ApplicationContext gehangen wird. Es kann eine Anwendung sein, die auf eine Queue horcht und Anfragen von dort verarbeitet. Und zuletzt kann es natürlich auch ein statischer Service-Locator sein, der den ApplicationContext intern hält.

Fazit

Die beschriebene Fachkomponentenarchitektur teilt die für eine Fachkomponente notwendigen Bestandteile in drei Projekte auf:
– ein Schnittstellen-Projekt
– ein Implementierungs-Projekt
– ein Konfigurations-Projekt
Durch die erlaubten Abhängigkeiten zwischen den Projekten erreichen wir einerseits eine Trennung von öffentlicher Schnittstelle und interner Implementierung und andererseits eine Trennung von Businesslogik und Infrastrukturcode. Die Verwendung von expliziter, Java-basierter Konfiguration von Spring ermöglicht eine leichte Handhabung in jeder Entwicklungsumgebung sowie eine leichte Nachvollziehbarkeit und Verständlichkeit, wodurch eine einfache Wartbarkeit gegeben wird. Durch die konsequente Anwendung von Dependency Injection erreichen wir eine leichte Testbarkeit. Durch die Tatsache, dass Impl-Projekte keine anderen Impl-Projekte referenzieren dürfen, wird die Anwendung von Dependency Injection forciert. Last, but not least: die Fachkomponente benötigt keine bestimmte Laufzeitumgebung und kann so in verschiedensten technischen und fachlichen Kontexten verwendet werden.

Ausblick

Natürlich bleiben noch einige Fragen offen, beispielsweise der Umgang mit Properties, der Umgang mit Ressourcen und der Umgang mit umgebungsabhängigen Konfigurationen. Hier bietet Spring 3.1 mit der Environment Abstraction ganz neue Möglichkeiten, die ich in den folgenden Blog-Einträgen behandele:
Eine Fachkomponentenarchitektur mit Spring 3.0/3.1 – Part Two: Ressourcen
Eine Fachkomponentenarchitektur mit Spring 3.0/3.1 – Teil 3: Properties

Zum Abschluss noch ein Wort zum Thema explizite vs. implizite Konfiguration

Definition explizite Konfiguration: Dependency Injection zwischen Komponenten wird explizit durch XML-Snippets oder Java-Code konfiguriert.
Definition implizite Konfiguration: Dependency Injection zwischen Komponenten läuft entweder über Konventionen oder Classpath-Scanning und Autowiring mit Hilfe von Annotationen.

Was bedeutet explizite / implizite Konfiguration?

Convention over Configuration ist in aller Munde, und über all dem XML-Bashing der letzten Jahre ist die explizite Konfiguration von Anwendungen ziemlich uncool geworden. Trotzdem stelle ich in diesem Blog-Eintrag ein Konzept vor, in dem explizite Konfiguration eine zentrale Rolle spielt. Warum?

  1. Die Voraussetzungen
  2. Wir haben hier Hunderte Stakeholder, von anderen IT-Fachabteilungen über zentrale Architekturabteilungen bis hin zum Betrieb. Die Konfiguration der Anwendung MUSS verständlich und nachvollziehbar sein. Und eine explizite Konfiguration ist nun einmal leichter nachvollziehbar als das automatische Scannen und Instanziieren von Komponenten, die im Classpath liegen. Und mal ehrlich, wieviel Zeit kostet es uns, für eine erstellte Komponente eine Konfiguration zu erstellen? Zwei Minuten?

  3. Explizite Konfiguration ist nicht gleichbedeutend mit XML
  4. Genau genommen wird in meinem Konzept gar kein XML verwendet, da die Spring-JavaConfig erhebliche Vorteile gegenüber XML hat. Ganz ehrlich: müsste ich explizite Konfiguration mit XML machen, dann würde ich es nicht machen.

  5. Das hier ist Enterprise, Coolness ist nicht wichtig
  6. Ich stelle das Konzept nicht vor, weil ich denke, dass es cool und hip ist, sondern weil ich denke, dass es funktioniert. Und das ist immer noch das Wichtigste bei der Softwareentwicklung.

|

Beitrag teilen

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.