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

5 Kommentare

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:

public interface PartnerService {
 
	public Partner getPartner(long id);
 
}
 
public class Partner {
 
	private long id;
	private String name;
 
	// getters and setters omitted for readability
 
}

Und zum anderen der InkassoService inkl. DTO:

public interface InkassoService {
 
	public void doBooking(BookingInfo bookingInfo);
 
}
 
public class BookingInfo {
 
	private long partnerId;
	private BigDecimal amount;
	private String subject;
 
	// getters and setters omitted for readability
 
}

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:

public class PartnerServiceImpl implements PartnerService {
 
	@Override
	public Partner getPartner(long id) {
		Partner partner = null;
		// TODO do something to get partner
		return partner;
	}
 
}

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

public class InkassoServiceImpl implements InkassoService {
 
	private PartnerService partnerService;
 
	public InkassoServiceImpl(PartnerService partnerService) {
		this.partnerService = partnerService;
	}
 
	@Override
	public void doBooking(BookingInfo bookingInfo) {
		// TODO validate bookingInfo
		Partner partner = partnerService.getPartner(bookingInfo.getPartnerId());
		// TODO use partner to do the booking
	}
 
}

Abhängigkeitsstruktur Schnittstelle und Implementierung

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, siehe dazu diesen Javamagazin-Artikel, der die Vorteile der Java-basierten Konfiguration gegenüber der Konfiguration mit XML darstellt. Für die Partner-Fachkomponente sieht diese Konfiguration so aus:

@Configuration
public class PartnerConfig {
 
	@Bean
	public PartnerService partnerService() {
		return new PartnerServiceImpl();
	}
 
}

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:

@Configuration
@Import(PartnerConfig.class)
public class InkassoConfig {
 
	@Autowired
	private PartnerConfig partnerConfig;
 
	@Bean
	public InkassoService inkassoService() {
		return new InkassoServiceImpl(partnerConfig.partnerService());
	}
 
}

Vollständige Abhängigkeitsstruktur inkl. Konfiguration

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.

Tobias Flohre

Tobias Flohre arbeitet als Senior-Softwareentwickler/Architekt bei der codecentric AG. Seine Schwerpunkte sind Java-Enterprise-Anwendungen und Architekturen mit JavaEE/Spring. Er ist Autor diverser Artikel und schreibt regelmäßig Blogbeiträge zu den Themen Architektur und Spring. Zurzeit beschäftigt er sich mit Integrations- und Batch-Themen im Großunternehmen sowie mit modernen Webarchitekturen.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

  • Christian Grünberg

    Die Idee finde ich auch gut. Unglücklich finde ich dabei, dass du für die Java-Config ein eigenes Projekt machen musst – ich weiß es geht nicht anders

    • Jörg Schömer

      Hallo Tobias,

      schöner Artikel.

      Grüße,
      Jörg

      @Grünberg: so unglücklich finde ich das eigene Projekt nicht, somit kann man viel einfacher unterschiedliche Konfigurationen bauen.

  • Mirko Novakovic

    5. Januar 2012 von Mirko Novakovic

    Tobias, finde gerade Deinen letzten Satz sehr gut: „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.“

    Immer mehr Konferenzbeiträge und Artikel befassen sich mit Frameworks, neuen Sprachen etc. – aber nicht mehr mit der „klassischen Entwicklung“. Das beschriebene Design finde ich wichtig und gerade in großen Umgebungen richtig.

    Mirko
    Ich würde es gut finden, wenn Du noch etwas über die Test Strategien schreiben würdest. Ich würde auch die Tests trennen und einen abstrakten Schnittstellentest anbieten (Contract Testing) und einen für die Implementierungsdetails, der vom Schnittstellentest erben muss – so definierst Du quasi einen „Contract“ für die Implementierungen. Habe hier gute Erfahrungen gemacht insbesondere auch Vor- und Nachbedingungen, sowie Invarianten festzulegen und im Test zu „dokumentieren“.

    • Tobias Flohre

      6. Januar 2012 von Tobias Flohre

      Hi Mirko,

      ja, zu Tests möchte ich auch noch etwas schreiben, aber erst einmal kommen jetzt die Ressourcen…

      Tobias

    • Michael Simons

      Mirko: Das habe ich in den letzten 2 bis 3 Jahren oft gedacht, angesagt ist nur noch der, der das nächste Framework, die nächste VM basierte Sprache vorstellt. Dazu auch die Frage, die mich auf der letzten W-JAX beschäftigte: Old and tired?. Der Artikel und der Grundgedanke dahinter gefällt mir daher auch sehr gut.

      Viele Grüße aus Aachen,
      Michael

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.