Architecture Blueprints

Werbung
Architecture Blueprints
Daniel Liebhart, Peter Welkenbach, Perry
Pakull, Mischa Kölliker, Michael Könings,
Markus Heinisch, Guido Schmutz
Ein Leitfaden zur Konstruktion von Softwaresystemen mit
Java Spring, .NET, ADF, Forms und SOA
ISBN 3-446-40952-1
Leseprobe
Weitere Informationen oder Bestellungen unter
http://www.hanser.de/3-446-40952-1 sowie im Buchhandel
1
Grundlegende Architekturkonzepte
Über dieses Kapitel
In diesem Kapitel stellen wir einige theoretische Grundlagen zur aktuellen Ansicht
guten Software-Entwurfs vor. Diese Konzepte lassen sich mit allen modernen Frameworks ähnlich gut umsetzen – wenn auch nicht immer in vollem Umfang – und können als State of the Art des modernen Applikationsdesigns angesehen werden. Design-Guidelines werden mit Design Patterns beschrieben.
Kapitel 1.1 Enterprise Application Architecture Patterns geht auf die allgemeinen
Enterprise Patterns ein. Diese beschreiben die Grundlagen aller modernen Multi-TierArchitekturen.
Im Kapitel 1.2 Grundlegende Architekturkonzepte wird hauptsächlich auf die unterschiedlichen Aspekte von prozeduralem und objektorientiertem Design eingegangen.
Das abschließende Kapitel 1.3 Zusammenfassung bietet die Informationen nochmals
in einer komprimierten Form für eilige Leser.
Nach der Lektüre dieses grundlegenden Kapitels sollten dem Leser die wesentlichen
Architektur- und Design-Konzepte bekannt sein und ihm beim Lesen der weiteren Kapitel zum besseren Verständnis dienen.
1.1
Enterprise Application Architecture Patterns
Ein Design Pattern ist ein existierendes Software-Konstrukt und gleichzeitig eine Regel, die beschreibt wie und wann dieses zu erstellen ist. Eine Beschreibung dieser
Kombination bekommt einen Namen und Beschreibungen der Problemstellung und
der Lösung.
Design Patterns gehen ursprünglich auf den Architekten (nein, kein Software Architekt, sondern ein richtiger Häuserbauer) Christopher Alexander zurück, der in seinem
Buch „The Timeless Way of Building“ sich wiederholende bauliche Maßnahmen in
Form von Patterns beschrieben hat. Folgendes gibt eine solche Pattern-Beschreibung
von ihm wieder:
Pattern Name: An architectural pattern example - Window Place:
16
Grundlegende Architekturkonzepte
Problemkontext: “Everybody loves window seats, bay windows, and big windows
with low sills and comfortable chairs drawn up to them. …A room which does not
have a place like this seldom allows you to feel comfortable or perfectly at ease. …
If the room contains no window which is a “place", a person in the room will be torn
between two forces:
•
He wants to sit down and be comfortable.
•
He is drawn toward the light.
Obviously, if the comfortable places--those places in the room where you most want
to sit--are away from the windows, there is no way of overcoming this conflict. …
Lösungskontext: Therefore: In every room where you spend any length of time during
the day, make at least one window into a “window place"."
Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides (Gamma et al, 1995)
haben dieses Konzept dann Mitte der Neunziger auf die Softwareentwicklung übertragen. Ihr Buch „Design Patterns – Elements of Reusable Object-Oriented Design“
gilt als Basis für alle weiteren Beschreibungen von Design Patterns in der Softwareentwicklung. Seitdem haben viele Autoren weitere Design Patterns für unterschiedliche Kontexte (z.B. Strukturpatterns, Refactoring-Patterns) und Programmiersprachen
publiziert.
Die Hauptaufgabe von Design Patterns besteht in der Weitergabe und der Wiederverwendung von Erfahrung. Design Patterns beschreiben Best Practices und Lösungen
von erfahrenen Designern. Unterschiedliche Technologien und Programmiersprachen
erlauben unterschiedliche Realisierung einzelner Design Patterns. Viele moderne
Frameworks realisieren Design Patterns im Hintergrund und erlauben auch unerfahrenen Programmieren Anwendungen nach modernen Design Richtlinien zu erstellen.
In der Regel kann jedes Pattern einer bestimmten Schicht (Layer) zugeordnet werden,
z.B. dem User-Interface Layer oder dem Domain Layer. Das Pattern bietet dann eine
Lösung von einem typischen Problem in diesem Layer.
Im Folgenden stellen wir einige zentrale Design Patterns kurz vor. Für tiefergehendere
Beschreibungen sei auf die Original-Literatur verwiesen, bzw. auf solche, welche die
einzelnen Patterns im Kontext konkreter Programmiersprachen darstellt (z.B. C#,
Java).
1.1.1
Domain Logic Patterns
Für die Organisation der Domänenlogik sieht Fowler (Fowler, 2003) drei verschiedene Patterns vor: Transaction Script, Domain Model und Table Module.
Zudem sieht er eine zusätzliche Möglichkeit vor, die Domänenlogik aufzutrennen
und einen Service Layer über ein darunterliegendes Domain Model oder Table Module zu legen.
Enterprise Application Architecture Patterns
1.1.1.1
17
Transaction Script
Organisiert und unterteilt die Geschäftslogik in einzelne Prozeduren so, dass jede
Prozedur eine einzelne Anfrage vom Presentation Layer abdeckt.
Viele einfache Geschäftsapplikationen können als eine Serie von Einzeltransaktionen
angesehen werden. Dies gilt in hohem Maß für klassische Client/Server-Applikationen.
Jede Interaktion zwischen Client und Server enthält ein begrenztes Maß an (nicht
allzu komplexer) Geschäftslogik. In den meisten Fällen handelt es sich hierbei um
simple Validierungen und Kalkulationen und betrifft nur wenige Entitäts-Typen (Achtung: nicht Einzelobjekte!) pro Interaktion.
Transaction Script kapselt diese Logik pro Interaktion in einzelne Operationen und
kommuniziert direkt oder nur über einen schlanken Adapter mit einer relationalen
Datenbank. Jede Transaktion hat ihr eigenes Transaction Script. In vielen Fällen entspricht ein Transaction Script einer Datenbank-Transaktion. Es gibt zwei Möglichkeiten, wie die Transaction-Script-Logik modularisiert werden kann:
•
Ein Transaction Script pro Operation einer Klasse
•
Ein Transaction Script pro Klasse (Command Pattern)
Beide haben Vor- und Nachteile. Beim generischen Command Pattern hat jede Operation den gleichen Namen (z.B. execute() ); der Klassenname sollte die Semantik der
Transaktion beschreiben. Beim anderen Ansatz beschreibt der Operationsname hingegen die Semantik, womit sich Intention Revealing Interfaces realisieren lassen. Das
Command Pattern in Kombination mit Memento ist wichtig für Kompensationen
(Compensating Transaction), wie sie vor allem in SOA vorkommen können.
Ein Service fasst semantisch nahe stehende Transaction Scripts als einzelne Service-Methoden zusammen. Diese einzelne Transaction Scripts delegieren dann
an einen Command und Memento realisierenden Helfer Service, der auch für
Kompensationen zuständig ist. Im Allgemeinen wird dies ein interner Teil des
ApplicationService selbst sein, um die Einfachheit in diesem Ansatz zu wahren.
18
Grundlegende Architekturkonzepte
JSF
User Interface
managed
bean
page
Service
Client Tier
remoting
Middle Tier
Application
Application
Service
Dto
Assembler
Dao
Validator
Factory
Application
Service
Validator
Domain
Infrastructure
Persistence
Abbildung 1-1: Transaction Script Architecture
Ein Applikations-Service realisiert in diesem Blueprint das Transaction Script Pattern.
1.1.1.2
Domain Model
Ein Objektmodell der Problemdomäne, welches Verhalten und Daten umfasst.
Das Modell setzt sich aus einem Netzwerk feingranularer Klassen zusammen. Jede
dieser Klassen repräsentiert ein wichtiges Objekt der Geschäftsdomäne. Hierbei kann
es sich um etwas so Großes wie einen Konzern oder aber um eine einzelne Zeile
einer Rechnung handeln.
Ein OO Domain Model ist einem relationalen Datenbankmodell sehr ähnlich, doch
kapselt es nicht nur Daten, sondern auch das den Daten zugeordnete Verhalten und
wird in Teilen nicht normalisiert sein, d.h., auch Listen und Maps als Attribute sind
enthalten. Außerdem unterscheidet es sich durch den Einsatz von Vererbung.
Fowler diskutiert hierzu zwei Variationen des Domain Model Patterns:
•
Simple Domain Model
•
Rich Domain Model
Das Simple Domain Modell entspricht fast 1:1 dem Datenbankmodell, wobei jede
Tabelle durch einen Domänen-Entitätstypen repräsentiert wird. Bei diesem Design
kommt man oftmals mit Active Records (siehe Kapitel 1.1.2.3) aus.
Das Rich Domain Modell unterscheidet sich erheblich vom Datenbankmodell, u.a.
durch Anwendung diverser OO-Konzepte und Design Patterns (Vererbung, Strategy
Pattern, Command Pattern etc.). Bei diesem Ansatz ist die Verwendung eines Data
19
Enterprise Application Architecture Patterns
Mappers (siehe Kapitel 1.1.2.4) unumgänglich, Active Record ist hier nicht mehr das
angemessene Design.
JSF
managed
bean
page
User Interface
Layer
Application
Service
Client Tier
remoting
Application
Layer
Middle Tier
Application
Service
DTO
Application
Service
Factory
Domain
Service
Domain
Layer
Contract
Aggregate
Factory
Repository
Domain Component
Entity
Value
Object
Infrastructure
Layer
Persistence
Abbildung 1-2: Rich Domain Modell-Architektur
Das Rich Domain Model ist das zentrale Konzept des Domain Driven Designs. Dieses
wird in Kapitel 1.2 im Detail beschrieben.
1.1.1.3
Table Module
Eine einzelne Instanz (Singleton), welche die Geschäftslogik für alle Zeilen in einer
Datenbanktabelle oder -View kapselt.
Problem des Domain Model ist der sogenannte Impedance-Mismatch: Die Abbildungsproblematik zwischen objektorientiertem Domain Model und relationalem Datenbankmodell.
Ein Table Module organisiert die Geschäftslogik in einer Klasse pro Datenbanktabelle
(oder View). Der primäre Unterschied zum Domain Model liegt hierbei in der ObjektIdentität und Objekt-Multiplizität.
Betrachten wir ein typisches Master-Detail-Szenario mit einer Liste von n Einzelpositionen, die einem Auftrag zugeordnet sind.
20
Grundlegende Architekturkonzepte
Während beim Domain Model der Auftrag ein Objekt ist, mit n Referenzen auf die n
Einzelpositions-Objekte, kommt das Table Module mit einem (Listen-)Objekt aus, das
alle Einzelpositionen kapselt. Im Domain Model hat jede Einzelposition eine eindeutige Objekt-Identität, über die es angesprochen werden kann. Im Table Module ist
nur das Listenobjekt bekannt, die Einzelpositionen sind hinsichtlich ihres Zugriffs
anonym. Zum Auffinden eines einzelnen Positionsobjektes in der Liste muss eine ID
(meist der PK der Datenbank) oder der Listenindex bekannt sein.
JSF
User Interface
page
managed
bean
Service
Client Tier
remoting
Middle Tier
Application
Application
Service
Dto
Assembler
Table Module
Application
Service
Validator
Domain
Infrastructure
Persistence
Abbildung 1-3: Table Module-Architektur
Oft ist ein Table Module genau einer Tabelle zugeordnet und kapselt die Operationen
auf diesen Daten.
1.1.1.4
Service Layer
Definiert Applikations-Grenzen mit einem Layer von Einzel-Services, die zusammen
ein Set an verfügbaren Operationen aus Sicht des Clients bereitstellen.
Cockburn (Cockburn, 1998) bezeichnet dieses Pattern auch als Application Boundary
Pattern. Service Layer koordiniert bei jeder ausgeführten Operation gleichzeitig die
Systemantwort (Applikationsantwort) sowie Transaktionen und kapselt die Geschäftslogik der Applikation nach außen ab.
Der Vorteil von Service Layer liegt in der Möglichkeit, unterschiedlichen Clients ein
einheitliches Set an Geschäftsoperationen anzubieten und die Applikationsantwort für
jede Operation einheitlich zu koordinieren.
Service Layer wird nicht benötigt, wenn nur eine einzige Art von Client (z.B. ein
Web-Frontend) vorliegt und die realisierten Use Cases keine komplexen Transaktio-
Enterprise Application Architecture Patterns
21
nen mit mehreren Ressourcen verlangen. In solch einfachen Szenarien kann der Page
Controller (Fowler, 2003) die Rolle des Service Layer übernehmen.
Die Service-Operationen ergeben sich im Wesentlichen aus den Use Cases der Applikation. Bei einfachen CRUD (Create, Read, Update, Delete) Use Cases ergibt sich
eine 1:1-Abbildung auf die Service Layer-Operationen.
Es gibt zwei Designstrategien, den Service Layer-Pattern zu implementieren:
•
Domain Façade
•
Operation Script
Domain Façade
Der Service Layer wird als dünne Façade über einem Domain Model implementiert.
Hierbei implementieren die Klassen der Façade keinerlei Geschäftslogik, diese wird
im Domain-Modell realisiert. Die Façade des Service Layers entkoppelt Domain Model und Präsentation und definiert so die Interaktionsmöglichkeiten der Applikation
mit dem Domain Layer, der ja auch von anderen Applikationen genutzt werden kann.
Operation Script
Hierbei wird der Service Layer durch eine Anzahl Klassen realisiert, die auch eine
gewisse Applikations-Geschäftslogik beinhalten, jedoch die Domänen-Geschäftslogik
an einen Domain Layer delegieren. Auf diese Weise wird ein Design umgesetzt, das
von verschiedenen Autoren (z.B. Fowler, 2003) bevorzugt wird:
Trennung der Geschäftslogik in Applikationslogik und Domänenlogik (siehe auch den
Abschnitt zu Domain Driven Design in Kapitel 1.2 und dabei speziell zu Services in
Kapitel 1.2.2.3).
Die den Clients zur Verfügung gestellten Operationen werden als Scripts implementiert, wobei diese ihrer logischen Zusammengehörigkeit nach in Klassen gruppiert
werden, welche dann den Applikations-Service bilden. Typischerweise findet man in
solchen Architekturen Klassen mit Namen wie „BookingService“, der die Scripts für
den „Booking UseCase“ gruppiert. Ein Service Layer besteht dann aus diesen Applikations-Service-Klassen. Diese sollten sich idealerweise von einem Layer Supertype
ableiten.
1.1.2
Data Source Architectural Patterns
Die Rolle der Datenzugriffs-Schicht (Integration Layer) ist es, mit den unterschiedlichen Teilen der Infrastruktur zu kommunizieren, welche die Applikation braucht,
um ihre Tätigkeit auszuführen. Der dominierende Teil dieses Problems ist dabei die
Kommunikation mit der Datenbank. Dies wiederum bedeutet für die meisten kommerziellen Systeme Kommunikation mit einer relationalen Datenbank.
Die ersten Patterns, die der Datenbankzugriffs-Schicht zugeordnet werden können,
behandeln die unterschiedliche Art und Weise, wie die Domänenlogik mit der Datenbank kommunizieren kann.
22
Grundlegende Architekturkonzepte
1.1.2.1
Table Data Gateway
Ein Objekt, welches als Gateway (siehe Kapitel 1.1.9.1) zu einer Datenbanktabelle
auftritt. Eine einzige Instanz behandelt alle Rows einer Tabelle (1 Instanz / n Rows).
«T able Data Gateway»
PersonGatew ay
+
+
+
+
+
person_t
findById(long) : Person
findByName(String) : void
update(long, String, String, Date) : void
insert(String, String, Date) : void
delete(long) : void
Abbildung 1-4: Table Data Gateway Pattern
Vermischen von SQL und Applikations-Logik kann mehrere Probleme verursachen.
Viele Entwickler sind sich nicht gewohnt, SQL zu schreiben und werden dies daher
vielleicht nicht besonders gut machen. Zudem möchte man die SQL Statements möglichst unabhängig vom Code halten, damit man diese auch nachträglich noch einfach
anpassen kann.
:View
:TableDataGateway
:Persistence
:Database
user
search(Criteria)
findByCriteria(Criteria)
findByCriteria(Criteria)
select
ResultSet
RowSet
apply domain logic(RowSet)
render
Rendered RowSet data
modified RowSet
validate(RowSet)
save (RowSet)
save(RowSet)
insert/update
Abbildung 1-5: Interaktionen mit Table Data Gateway und Table Module
:TableModule
23
Enterprise Application Architecture Patterns
Ein Table Data Gateway hält alle notwendigen SQL Befehle für eine bestimmte Tabelle oder View: Selects, Inserts, Updates und Deletes. Sämtlicher Code ruft die Methoden des Table Data Gateway auf, um mit der Datenbank zu interagieren.
1.1.2.2
Row Data Gateway
Ein Objekt, welches als Gateway (siehe Kapitel 1.1.9.1) zu einem einzelnen Tabellenrecord auftritt. Es handelt sich um eine Instanz, welche jeweils einen Tabellenrecord
betrifft (1 Instanz / 1 Row).
PersonFinder
+
+
findById(long) : Person
findByLastName(String) : Person[]
«Row Data Gateway»
PersonGatew ay
-
firstName: String
dateOfBirth: Date
lastName: String
+
+
+
insert() : void
update() : void
delete() : void
person_t
Abbildung 1-6: Row Data Gateway Pattern
Das Problem ist die Abbildung des OO Domänen-Modells auf das Datenbankmodell.
Beim Konzept des Row Data Gateway wird jede Klasse des Domänen-Modells durch
eine separate Tabelle in der Datenbank repräsentiert. D.h. im Umkehrschluss: Jede
Zeile in der Datenbanktabelle entspricht genau einer Instanz der durch die Tabelle
repräsentierten Klasse. Die Zugriffsmechanismen werden durch das Konzept des Row
Data Gateway realisiert. Es wird ein Objekt erzeugt, welches als Gateway auftritt und
eine Zeile repräsentiert.
Ein Row Data Gateway erzeugt Objekte, die genau so ausschauen wie die Records in
der Datenbank, können aber mit den regulären Mechanismen der Programmiersprache bearbeitet werden. Alle Details des Datenzugriffs sind hinter dieser Schnittstelle
verborgen.
1.1.2.3
Active Record
Ein Objekt, welches eine Zeile einer Datenbank-Tabelle oder -View repräsentiert, die
Datenbankzugriffe dazu kapselt und Domänenlogik auf den Daten hinzufügt.
Ein Objekt, das sowohl Daten wie auch Verhalten enthält. Die meisten der Daten sind
persistent und sollen in der Datenbank gespeichert werden. Active Record macht
nichts anderes als die Datenzugriffslogik im Domain Object zu platzieren.
24
Grundlegende Architekturkonzepte
«Active Record»
Person
-
firstName: String
lastName: String
dateOfBirth: Date
person_t
+ insert() : void
+ update() : void
+ delete() : void
+ lastName() : void
behavior
+ getAddress() : Address
Abbildung 1-7: Active Record Pattern
1.1.2.4
Data Mapper
Eine Schicht von Mappern, welche Daten zwischen den Objekten und der Datenbank
transferiert, ohne dabei die Objekte und die Datenbank voneinander und dem Mapper abhängig zu machen.
Person
-
firstName: String
lastName: String
dateOfBirth: Date
+
getAddress() : Address
PersonMapper
+
+
+
person_t
insert() : void
update() : void
delete() : void
Abbildung 1-8: Data Mapper Pattern
Objekte und relationale Datenbanken haben unterschiedliche Mechanismen um
Daten zu strukturieren. Viele Konzepte der Objekt-Orientierung, wie Collections und
Vererbung gibt es in der relationalen Datenbank nicht. Bei Objekt-Modellen mit
einem großen Anteil an Geschäftslogik und der Verwendung der objektorientierten
Konzepte, werden das Objektmodell und das Datenmodell relativ unterschiedlich
aussehen und stimmen nicht 1:1 überein.
Trotzdem will man aber Daten zwischen den beiden Modellen transferieren können
und der Datentransfer kann daher sehr komplex werden.
Der Data Mapper ist eine Software-Schicht, welche die Objekte im Hauptspeicher von
der Datenbank trennt. Seine Verantwortung liegt allein im Datentransfer zwischen
den Objekten und der Datenbank und damit der Isolation der beiden Schichten. Mit
dem Data Mapper brauchen die Objekte keine Kenntnis darüber, ob und welche
Datenbank vorhanden ist; sie brauchen keinen SQL Code abzusetzen und brauchen
das Datenbank-Schema nicht zu kennen.
1.1.3
Object-Relational Behavioral Patterns
Wenn Leute über O/R Mapping sprechen, konzentrieren sie sich normalerweise auf
die strukturellen Aspekte: Wie können die Tabellen mit den Objekten in Beziehung
gesetzt werden. Oft ist dies aber das kleinste Problem, viel schwieriger sind die Ar-
25
Enterprise Application Architecture Patterns
chitektur- und Verhaltens-Aspekte. Die Architektur-Ansätze wurden im Kapitel 1.1.2
behandelt, hier geht es nun um die Patterns, welche das Verhalten von ObjektRelationalen Systemen beschreiben. Dabei geht es um die Problematik, wie sich die
verschiedenen Objekte selbst von der Datenbank laden und in die Datenbank speichern.
1.1.3.1
Unit of Work
Verwaltet eine Liste von Objekten, welche von einer Geschäftstransaktion betroffen
sind. Koordiniert Änderungen und ist für Konfliktlösung verantwortlich.
:Servi ce
:Database
:UnitOfWork
:Enti ty
new(ID)
set data
register di rty(entity)
commit
save
i nsert
Abbildung 1-9: Unit of Work
Die Geschäftslogik erstellt, ändert und löscht Geschäftsobjekte (in der Abbildung eine
Entity, welche als Active Record realisiert ist). Eine UnitOfWork-Instanz zeichnet
diese Prozesse auf. Wenn es zum Commit kommt, entscheidet die Unit of Work, was
zu tun ist (d.h. insert, update oder delete) und nicht die Geschäftslogik enthaltenden
Services. In der obigen Abbildung ist eine der beiden Möglichkeiten dargestellt, wie
die Entities bei einer Unit of Work registriert werden können. Man unterscheidet
zwischen
•
Caller Registration und
•
Object Registration
In Abbildung 1-9 ist Caller Registration dargestellt, bei der das Entity Object vom
nutzenden Service bei der UnitOfWork registriert wird. Bei Object Registration würde
sich das Entity-Objekt selbst bei der UnitOfWork registrieren.
26
Grundlegende Architekturkonzepte
1.1.3.2
Identity Map
Stellt sicher, dass jedes Objekt nur einmal von der Datenbank geladen wird, indem
diese in einer Identitätsmap (im Trivialfall eine einfache Hashmap mit dem PK als
Schlüssel) vorgehalten werden. Bei der Anfrage der Applikation nach einem bestimmten Objekt wird zunächst in der Identitätstabelle gesucht.
finder
identitiy map
database
Client
find(11)
found= get(11)
null=
[found not null]:
[found is null]: found= SELECT .. FROM .. WHERE
Abbildung 1-10: Identity Map
Die Grundidee von Identity Map ist, eine Serie von Maps zu haben, welche sämtliche
Objekte aufnehmen, die von der Datenbank gelesen werden. Im einfachsten Fall hat
man eine Map pro Datenbank-Tabelle. Bevor man ein Objekt von der Datenbank
lädt, wird zuerst in der Map geprüft, ob das entsprechende Objekt nicht bereits geladen ist.
Eine Unit of Work (siehe Kapitel 1.1.3.1) hat oftmals eine Identity Map integriert.
1.1.3.3
Lazy Load
Ein Objekt, das nicht alle benötigten Daten enthält, aber weiß, wie es diese nachladen kann.
Es gibt vier grundsätzlich verschiedene Varianten von Lazy Load.
•
Lazy Initialization: Verwendet einen speziellen Marker-Wert (normalerweise null)
um zu signalisieren, dass der Wert noch nicht geladen ist. Jeder Zugriff auf den
Wert prüft zuerst gegen den Marker-Wert, und lädt denn Wert, falls notwendig.
•
Virtual Proxy: Ein Stellvertreter-Objekt, welches das selbe Interface wie das Originalobjekt implementiert. Wird zum ersten Mal eine Methode auf diesem Proxy
aufgerufen, dann wird im Hintergrund das eigentliche Objekt geladen und der
Aufruf dann an dieses delegiert.
27
Enterprise Application Architecture Patterns
a customer
the database
Client
getOrders
[orders not loaded]:
load orders
orders=
Abbildung 1-11: Lazy Load Pattern
•
Value Holder: Ein Objekt mit einer getValue Methode, welche von den Clients
verwendet wird, um an das eigentliche Objekt zu kommen. Wird zum ersten Mal
getValue aufgerufe, dann wird im Hintergrund das eigentliche Objekt geladen und
als Return-Wert von getValue zurückgegeben.
•
Ghost: Ist bereits das eigentliche Objekt, aber noch ohne geladene Daten. Wenn
zum ersten Mal eine Methode auf dem Ghost aufgerufen wird, lädt der Ghost die
gesamten Daten in seine Attribute nach.
1.1.4
Object-Relational Structural Patterns
Dieses Kapitel beschreibt die strukturellen Mapping Patterns, bei denen es darum
geht, Objekte im Hauptspeicher auf Datenbank-Tabellen zu mappen. Diese Patterns
sind normalerweise für den Table Data Gateway (siehe Kapitel 1.1.2.1) relevant, ein
Teil davon kann aber auch für den Row Data Gateway (siehe Kapitel 0) oder den
Active Record (siehe Kapitel 1.1.2.3) verwendet werden. Für den Data Mapper (siehe
Kapitel 1.1.2.4) hingegen werden höchstwahrscheinlich alle in diesem Kapitel beschrieben Pattern verwendet.
1.1.4.1
Identity Field
Hält eine Datenbank PK/ID in einem Objekt vor, um die Identität zwischen einem InMemory-Objekt und einer Datenbank-Row zu unterhalten.
Relationale Datenbanken unterscheiden Rows mittels eines Schlüssels, dem so genannten Primary Key. In-memory Objekte brauchen allerdings keinen solchen Schlüssel, da das Objekt-System die Identität eines Objektes im Hintergrund sicherstellt.
Lesen von Daten aus der Datenbank ist dabei unproblematisch, um aber Daten später
wieder in die Datenbank zurückschreiben zu können, müssen die Datenbank-Schlüssel mit den Identitäten der in-memory Objekten verbunden werden.
28
Grundlegende Architekturkonzepte
Im Grundsatz ist das Konzept des Identity Field dabei sehr einfach, man speichert einfach den Primary Key der relationalen Tabelle als Attribut im entsprechenden Objekt.
1.1.4.2
Foreign Key Mapping
Bildet eine Assoziation zwischen Objekten auf Foreign Key Referenzen zwischen Tabellen ab.
Person
-
Address
firstName: String
lastName: String 1
dateOfBirth: Date
* -
street: String
zipCode: String
city: String
address_t
person_t
column
*PK ID: NUMBER(8,2)
FIRST_NAME: VARCHAR(50)
LAST_NAME: VARCHAR(50)
1
DATE_OF_BIRTH: DATE
PK
+
PK_person_t(NUMBER)
+FK_ADR_PERS
0..*
column
*PK ID: NUMBER(8,2)
STREET: VARCHAR(50)
FK PERSON_ID: NUMBER(8,2)
CITY: VARCHAR(50)
FK
+
FK_ADR_PERS(NUMBER)
PK
+
PK_address_t(NUMBER)
Abbildung 1-12: Foreign Key Mapping
Objekte können sich direkt über Objekt-Referenzen referenzieren. Wenn diese Objekte in der Datenbank gespeichert werden, sollen diese Beziehungen natürlich auch
mitgespeichert werden. Dabei können nicht einfach die Werte der Beziehungen in
die Datenbank geschrieben werden, da diese Laufzeit-Referenzen des entsprechenden
Programms sind. Eine zusätzliche Schwierigkeit entsteht dadurch, dass ein Objekt auch
Collection von Referenzen auf andere Objekte halten kann. Solch eine Struktur missachtet die erste Normalform einer relational Datenbank.
Ein Foreign Key Mapping bildet eine Objekt Referenz auf einen Foreign Key in der
Datenbank ab.
1.1.4.3
Association Table Mapping
Speichert eine Assoziation als zusätzliche Tabelle mit Foreign Key Referenzen auf die
beiden Tabellen, welche durch die Assoziation verbunden werden.
Objekte können multi-value Attribute einfach über Collections behandeln. Relationale Datenbanken kennen dies nicht und beschränken sich auf single-value Columns.
Wenn man eine 1:n Assoziation auf die Datenbank abbilden will, dann kann dies
über Foreign Key Mapping geschehen, indem ein Foreign Key auf der 1: Seite verwendet wird. Bei einer n:m Assoziation ist dies nicht möglich, da auf keiner Seite der
Beziehung ein einzelner Wert steht.
29
Enterprise Application Architecture Patterns
Employee
+member
0..*
Proj ect
0..*
employee_proj ect_t
employee_t
column
*pfK ID: NUMBER(8,2)
column
*PK EMPLOYEE_ID: NUMBER(8,2)
*pfK PROJECT_ID: NUMBER(8,2)
+FK_EMPLOYEE
0..*
FK
+
FK_EMPLOYEE(NUMBER)
PK
+
PK_employee_t(NUMBER)
1
FK
+
FK_PROJECT(NUMBER)
PK
+
PK_employee_project_t(NUMBER, NUMBER)
proj ect_t
column
*PK ID: NUMBER(8,2)
+FK_PROJECT
0..*
1
PK
+
PK_project_t(NUMBER)
Abbildung 1-13: Association Table Mapping Pattern
Die Antwort ist die klassische Lösung, welche man im Datenbank-Umfeld bereits seit
Jahren kennt und anwendet: es wird eine zusätzliche Tabelle benutzt, um die Beziehung zu speichern. Mit dem Association Table Mapping Pattern können die multivalue Attribute auf diese Link-Tabelle abgebildet werden.
1.1.4.4
Single Table Inheritance
Repräsentiert eine Klassen-Vererbungshierarchie als eine einzelne Tabelle, welche
Columns für alle Attribute der einzelnen Klassen enthält.
Party
-
description: String
person_t
Organisation
Person
-
firstName: String
lastName: String
dateOfBirth: Date
-
name: String
-
foundationDate: Date
Company
Abbildung 1-14: Single Table Inheritance
column
DESCRIPTION:
FIRST_NAME:
LAST _NAME:
DATE_OF_BIRTH:
ORG_NAME:
FOUNDAT ION_DAT E:
30
Grundlegende Architekturkonzepte
Single Table Inheritance ist eine Möglichkeit, Attribute einer Vererbungshierarchie auf
Tabellen einer relationalen Datenbank zu mappen. Die Alternativen dazu sind Class
Table Inheritance (Kapitel 1.1.4.5) und Concrete Table Inheritance (Kapitel 1.1.4.6).
Die Stärken von Single Table Inheritance sind:
•
Es gibt nur eine Tabelle in der Datenbank
•
Es sind keine Joins notwendig, um Daten zu laden
•
Refactoring von Attributen nach oben oder unten in der Hierarchie im Domain
Model hat keine Auswirkungen auf die Datenbank
Die Schwächen von Single Table Inheritance sind:
•
Columns sind manchmal relevant und manchmal nicht, was zu Unklarheiten
führen kann, wenn die Tabellen direkt angesprochen werden
•
Die einzelne Tabelle kann zu groß werden, mit vielen Indizes und häufigem
Locking, was zu Performanceproblemen führen kann.
•
Es gibt nur einen einzigen Namensraum für Columns, es muss also sichergestellt
werden, dass nicht fälschlicherweise die gleiche Column für mehrere Attribute
verwendet wird.
1.1.4.5
Class Table Inheritance
Repräsentiert eine Klassen-Vererbungshierarchie mit einer Tabelle pro Klasse.
party_t
Party
-
column
DESCRIPTION:
descripti on: String
person_t
Person
-
firstName: String
lastName: String
dateOfBirth: Date
Organisation
-
column
FIRST_NAME:
LAST_NAME:
DATE_OF_BIRTH:
name: String
organisation_t
column
NAME:
Company
-
foundationDate: Date
company_t
column
FOUNDATION_DAT E:
Abbildung 1-15: Class Table Inheritance
31
Enterprise Application Architecture Patterns
Class Table Inheritance ist eine Möglichkeit, Attribute einer Vererbungshierarchie auf
Tabellen einer relationalen Datenbank zu mappen. Die Alternativen dazu sind Single
Table Inheritance (Kapitel 1.1.4.4) und Concrete Table Inheritance (Kapitel 1.1.4.6).
Die Stärken von Class Table Inheritance sind:
•
Alle Columns sind für jede Row relevant und daher einfacher zu verstehen
•
Die Beziehung zwischen dem Domain Model und der Datenbank sind „straightforward“.
Die Schwächen von Class Table Inheritance sind:
•
Es müssen mehrere Tabellen gelesen werden, um ein einzelnes Objekt zu laden,
was einen Join oder mehrere einzelne Queries bedeutet.
•
Refactoring von Attributen nach oben oder unten in der Hierarchie im Domain
Model hat Auswirkungen auf die Datenbank-Tabellen
•
Die Tabellen der Supertypen können zum Flaschenhals werden, da auf diese sehr
oft zugegriffen wird
•
Der hohe Normalisierungsgrad kann die Tabellenstruktur schwierig für ad-hoc
Queries machen
Dieses Pattern wird oft auch als Leaf Table Inheritance bezeichnet.
1.1.4.6
Concrete Table Inheritance
Repräsentiert eine Klassen-Vererbungshierarchie mit einer Tabelle pro konkrete Klasse
in der Hierarchie.
person_t
Party
-
description: String
Organisation
Person
-
firstName: String
lastName: String
dateOfBirth: Date
column
DESCRIPT ION:
FIRST_NAME:
LAST_NAME:
DATE_OF_BIRTH:
-
name: String
-
foundationDate: Date
Company
Abbildung 1-16: Concrete Table Inheritance
organisation_t
column
DESCRIPT ION:
NAME:
company_t
column
DESCRIPT ION:
NAME:
FOUNDATION_DATE:
32
Grundlegende Architekturkonzepte
Concrete Table Inheritance ist eine Möglichkeit, Attribute einer Vererbungshierarchie
auf Tabellen einer relationalen Datenbank zu mappen. Die Alternativen dazu sind
Single Table Inheritance (Kapitel 1.1.4.4) und Class Table Inheritance (Kapitel
1.1.4.5).
Die Stärken von Concrete Table Inheritance sind:
•
Jede Tabelle ist unabhängig und hat keine irrelevante Columns
•
Es braucht keine Join-Operationen, wenn nur Daten von einem konkreten Domain
Object notwendig sind
•
Auf jede Tabelle wird nur zugegriffen, wenn die dazugehörende Klasse bearbeitet
wird, was die Datenbank-Last verteilen kann.
Die Schwächen von Concrete Table Inheritance sind:
•
Primary Keys sind schwierig zu handhaben
•
Wenn das Objekt-Modell refactored wird und Attribute in den Domänen-Klassen
nach oben oder unten in der Hierarchie verschoben werden, dann hat dies Auswirkungen auf mehrere Tabellen.
1.1.5
Object-Relational Metadata Mapping Patterns
1.1.5.1
Query Object
Ein Objekt, welches Datenbank-Abfragen repräsentiert.
:Query
:Cirteria
:Criteria
-
operator: = ">"
field: = "numberOfDependents"
value: = 0
-
operator: = "="
field: = "lastName"
value: = "muster"
Abbildung 1-17: Query Object
Ein Query Object ist ein Interpreter (Gamma, 1995), das heißt eine Struktur von Objekten, die sich selbst als SQL Query repräsentieren können. Man kann diese Query
erstellen, indem anstelle von Tabellen und Columns mit Klassen und Attributen gearbeitet wird. Damit können diese Queries unabhängig vom Datenbank-Schema geschrieben werden und Änderungen am Datenbank-Schema können an einer zentralen
Stelle durchgeführt werden.
33
Enterprise Application Architecture Patterns
1.1.6
Presentation Patterns
Man stelle sich eine Benutzerobefläche mit einem Eingabefeld und zwei Anzeigefeldern vor. Im Eingabefeld (vom imaginären Typ TextInputField) kann eine Zahleneingabe vorgenommen werden. Dieser Wert wird als „Actual Value“ bezeichnet. Ein
geänderter Wert wird anschließend von der Applikation mit einem Wert verglichen,
der in einem der beiden Anzeigefelder (ebenfalls vom Typ TextInputField, das hier als
Read-Only konfiguriert sein soll) angezeigt wird und als „Target Value“ bezeichnet
wird.
Nach einer auf fachlichen Anforderungen basierenden Kalkulation wird im weiteren
Anzeigefeld der sog. „Calculated Value“ angezeigt. In Abhängigkeit von fachlichen
Regeln wird der kalkulierte Wert einer von 3 Kategorien (bad, good, excellent) zugewiesen und damit bestimmt ob er grün, gelb oder rot angezeigt wird.
Je nach Design Pattern erfolgen die einzelnen Schritte in unterschiedlichen Komponenten der Präsentationsschicht, wie im Folgenden dargestellt.
1.1.6.1
Forms und Controls
Hierbei handelt es sich laut Fowler um klassische Client/Server-Applikationen, wie sie
in den 90er Jahren z.B. mit Visual Basic, Delphi oder Powerbuilder entwickelt wurden und noch immer weit verbreitet sind.
Abbildung 1-18 zeigt die Bausteine für das einleitend genannte Beispiel.
+calculatedValue
Reading Form
event observer
+ actualTextValueChanged() : void
TextInputField
1
+targetValue
-
textValue: String
textColor: Color
1 event sender
+ textValueChanged() : void
+actualValue
1
Abbildung 1-18: Forms und Controls - Bausteine
Die beteiligten Bausteine sind als „Reading Form“ und drei zugeordnete TextInputFields zu identifizieren. Unter der „Reading Form“ verstehen wir die GUI-Komponente, auf der sich die drei Eingabefelder befinden (also typischerweise vom Typ Panel oder ähnliches). Die drei TextFelder werden über die Variablen „calculatedValue“, „targetValue“ und „actualValue“ angesprochen. Die Logik wird über die „Reading Form“ gesteuert.
Bei den Daten im Datenmodell dieses Design Patterns handelt es sich um klassische
Record Sets, sie sind also meist eine relationale Datenrepräsentation.
Eine typische Kommunikation zwischen den Bausteinen ist in Abbildung 1-19 dargestellt:
34
Grundlegende Architekturkonzepte
actualValue
:TextInputField
Reading Form
targetValue
:TextInputField
user
enter new value
textValueChanged
actualTextValueChanged
getTextValue
getTextValue
doCalculation
setTextValue(calculatedValue)
setTextColor
Abbildung 1-19: Forms und Controls - Kommunikation
Erkennbar ist deutlich, dass die Kommunikation vom „Reading Form“ aus erfolgt.
Fowler charakterisiert diese Architektur durch folgende Eigenschaften:
•
Eine Form beschreibt das Layout der einzelnen Controls (hier TextInputFields)
•
Die Form fungiert als Observer für die auf ihr befindlichen Controls und stellt
Callback-Handler-Methoden zur Verfügung, um auf Control-Events reagieren zu
können.
•
Einfache Datenänderungen (1:1 Updates, Typwandlung) erfolgen durch einfaches
Databinding
•
Komplexe Datenänderungen (Kalkulationen, Anzeigeoptionen, wie z.B. Farbe) erfolgen über die Event-Handler-Methoden der Form
1.1.6.2
Model View Controller (MVC)
Hierbei handelt es sich wahrscheinlich um das bekannteste aller Design Patterns.
Obwohl es in diversen Ausprägungen umgesetzt wird, welche nicht dem Original
entsprechen, werden viele der Implementierungen trotzdem als MVC bezeichnet.
Das klassische Model-View-Controller Pattern geht auf Smalltalk 80 zurück und kommuniziert wie in Abbildung 1-20 dargestellt. Controller und View haben in dieser
Version keine Abhängigkeiten untereinander. Sie kommunizieren indirekt, indem sie
auf Änderungen im Model reagieren. Beide realisieren das Observer Pattern.
Im Vergleich mit dem erstgenannten Pattern „Forms und Controls“ ist hervorzuheben,
dass das ursprüngliche MVC mit objektorientierten Modellen arbeitet. Hierbei wird
als Model-Baustein von einem objektorientierten Domain Model ausgegangen.
35
Enterprise Application Architecture Patterns
Controller
Model
«use»
View
«use»
Abbildung 1-20: Model View Controller - klassisch
«Controller of InputField»
:Reading
actual :TextInputField
«View of InputField»
«View of InputField»
actual :TextInputField
calculated :TextInputField
user
change data
perform(#actual, 50)
update
update
perform(#actual)
update
perform(#calculated)
perform(#calculatedColor)
Abbildung 1-21: MVC Aufruf-Sequenz
In Abbildung 1-21 ist die Kommunikation im klassischen MVC unter Smalltalk dargestellt. Man beachte die indirekten Callbacks. Diese sind an der Übergabe der Aspekte
der GUI-Felder erkennnbar ( #actual, #calculated) .
Viele in der Praxis eingesetzten Frameworks arbeiten jedoch wie in Abbildung 1-22
auf der nächsten Seite dargestellt.
Wie man unschwer erkennen kann, gibt es hier wesentlich mehr Abhängigkeiten der
Bausteine untereinander. Der Vorteil gegenüber der klassischen Version liegt in einer
einfacheren Umsetzung, da Bausteine direkt kommunizieren können und aufwendige
Observer-Implementierungen und CallBack-Mechanismen entfallen. Dies liegt daran,
dass zumindest bei klassischer Programmierung (ohne AOP) Entkopplungen von Bausteinen zu einem Mehraufwand in der Implementierung führen. Auch mit AOPMechanismen sind solche Konzepte oftmals unübersichtlich. Direkte Kommunikation
36
Grundlegende Architekturkonzepte
Controller
«change»
Model
View
«use»
«inform change»
Abbildung 1-22: Model View Controller in der Praxis
ist im Allgemeinen einfacher zu verstehen und zu programmieren. Deshalb ist es
wichtig, in solchen Situationen Frameworks zur Verfügung zu haben, welche die Komplexität kapseln.
In den beiden folgenden Abbildungen ist eine Umsetzung in Java Swing dargstellt:
Controller
«change»
Model
View
«use»
«inform change»
Abbildung 1-23: Model View Controller in Swing
JCheckBox
Rendering
ChangeListener
inform
inform
EventHandling
ToggleButtonModel
CheckBoxUI
Abbildung 1-24: Swing JCheckBox
37
Enterprise Application Architecture Patterns
Das entscheidende Konzept bei MVC war ein Design, das Fowler als „Separated Presentation“ bezeichnet. Dahinter verbirgt sich eine stringente Trennung von DomänenObjekten und Präsentationsobjekten mit Darstellungslogik. Domänen-Objekte sollten
keinerlei Anhängikeiten zu Präsentationsobjekten haben, so dass sie in unterschiedlichen Präsentationsobjekten wiederverwendet werden können (z.B. Daten einmal als
Tabelle und einmal als Balkengrafik dargestellt).
Diese Aufgabe des Controllers ist, die Benutzereingaben entgegenzunehmen und
über die anschließend auszuführenden Aktionen zu entscheiden.
Es ist wichtig zu verstehen, dass es nicht nur eine View und einen Controller gibt,
sondern diese mehrfach paarweise auftreten. Jedes GUI-Element besteht aus View und
Controller, welche auf einem gemeinsamen Model basieren.
1.1.6.3
Model View Presenter (MVP)
Bei Model View Presenter handelt es sich um ein Design, das zum ersten Mal bei IBM
1990 erschien. Dieses Design-Konzept versucht das Beste der beiden zuvor genannten Patterns (Forms und Controls, MVC) zu kombinieren.
Forms und Controls ist ein leicht zu verstehendes Design, welches eine gute Trennung zwischen wieder verwendbaren GUI-Komponenten und der Applikationslogik
erlaubt. Allerdings fehlt ein Separated Presentation Konzept. Dieses Design ist vorteilhaft für Client/Server-Anwendungen mit relationalem Daten-Handling.
MVC demgegenüber hat ein ausgereiftes Separated Presentation Konzept und war von
Anfang an für objektorientierte Domänen-Modelle konzipiert.
Model View Presenter versucht nun, für objektorientierte Umgebungen die Stärken
der beiden Patterns zu kombinieren.
Model View Presenter separiert nicht mehr View und Controller als distinkte, unabhängige Bausteine. Eine View in MVP definiert die Struktur der GUI-Komponenten.
Sie enthält keine Logik, wie auf User-Interaktionen zu reagieren ist. Dies ist Aufgabe
des Presenters. Ein wichtiges Konzept ist hierbei, dass jede Kommunikation (Updates)
des Presenters mit dem Model über grobgranulare Commands realisiert wird, was
auch eine gute Basis zur Realisierung von Undo/Redo darstellt.
Presenter
(Präsentationslogik)
liest / mutiert
Model
(Fachliche Daten)
kennt
View
(Anzeigecode)
TextFeld, Button, etc.
Abbildung 1-25: Model View Presenter
38
Grundlegende Architekturkonzepte
In Abbildung 1-26 ist die typische Kommunikation dieses Patterns veranschaulicht.
actual:
TextInputField
:Presenter
:Reading
:TextOutputField
User
user changes value
actual.value changed
getValue
setActualValue
value changed
getCalculatedValue
getCalculationCategory
alt color choosing
[category == bad]
setTextColor(RED)
[category == good]
setTextColor(YELLOW)
[category == excellent]
setTextColor(GREEN)
Abbildung 1-26: Model View Presenter - Ablauf
Der Presenter-Baustein realisiert das „Supervising Controller“ Pattern. Die einzelnen
GUI-Widgets delegieren User-Events an diesen. Der Presenter (Supervising Controller)
koordiniert Änderungen am Domain Model.
1.1.6.4
Presentation Model (auch Application Model)
Mit diesem Konzept kann eine weitere Flexibilität ins Design eingeführt werden.
Zwischen Präsentations-Bausteinen und Domänen-Modell wird ein weiterer Baustein
eingeführt. Dieser kapselt das „Presentation Model“. Die Idee dahinter ist die gemeinsame Vorhaltung von Präsentations-Status und -Verhalten unabhängig von den
eigentlichen GUI-Bausteinen im Präsentationslayer. D.h. hier werden Daten, der Benutzersteuerung zusammen mit Nutzdaten vorgehalten, und dazu notwendige Methoden.
Abbildung 1-27 veranschaulicht das Konzept. Das „Presentation Model“ vermittelt
zwischen View und Model und hält zusätzliche, der Darstellung auf dem GUI dienende Informationen vor.
39
Enterprise Application Architecture Patterns
Book Title
-
title = model.getTitle()
formTitle = model.getFormTitle()
title: String
formTitle: String
+model
1
«presentation model»
Book
-/
«domain model»
Book
title: String
formTitle: String = "BookTitle: " +...
1 -
title: String
Abbildung 1-27: Presentation Model - Klassen
Das allgemeine Konzept lässt sich folgendermaßen darstellen:
View
Controller
«inform change»
Presentation
Modell
«inform change»
Domain Modell
Abbildung 1-28: Presentation Model - Konzept
In Java wird dieses zum Beispiel von JGoodies wie in Abbildung 1-28 auf der nächsten Seite dargestellt umgesetzt.
40
Grundlegende Architekturkonzepte
View
Controller
«inform change»
«presentation modell»
JComponent
«inform change»
Swing Modell
Abbildung 1-29: Presentation Model in JGoodies
1.1.6.5
Page Controller
Ein Objekt, das den HTTP-Request für eine einzelne Web-Seite (JSP, JSF-Page, HTML)
oder HTTP-Form-Aktion entgegennimmt und die Response steuert.
View 1
View 2
PageController 1
PageController 2
Function A
Function A
Function B
Function B
Abbildung 1-30: Page Controller pro Web-Seite
1.1.6.6
Front Controller
Ein Objekt, welches die HTTP-Requests für alle Web-Seiten einer Applikation entgegennimmt und die Response steuert. Front Controller können an Page Controller delegieren.
Ein Problem bei komplexeren GUIs ist die Tatsache, dass oft die gleiche Funktionalität
von allen/vielen Views (Formularen, Web-Pages, …) umgesetzt werden muss. Beispiele hierfür sind typischerweise Security, Validierung, Konvertierungen und Internationalisierung. Hierdurch kommt es zu redundanten Implementierungen.
41
Enterprise Application Architecture Patterns
View 1
View 2
PageController 1
PageController 2
Function A
Function A
Function B
Function B
Abbildung 1-31: Ohne Front Controller
Sinn eines Front Controllers ist es, diese potenziellen, redundanten Funktionalitäten
durch Einführung einer Zwischenschicht an einem Ort zu konzentrieren.
View 1
View 2
FrontController
Function A
Function B
Abbildung 1-32: Mit Front Controller
Der Front Controller nimmt die Anfragen der Views entgegen und leitet sie an die
eigentliche Implementierung weiter.
1.1.7
1.1.7.1
Distribution Patterns
Remote Façade
Stellt eine grob-granualare Façade über feingranularen Objekten zur Verfügung, und
steigert dabei die Effizienz in der Netzwerk-Kommunikation.
In einem objektorientierten System mit komplexer Geschäftslogik wird man viele
feingranulare Objekte vorfinden, mit vielen Interaktionen zwischen den Objekten
und dadurch auch sehr vielen Methodenaufrufen. Das ist richtig so und auch kein
Problem, solange man sich in einem Addressraum befindet. Dies ändert sich aber
schlagartig, wenn man Aufrufe zwischen Prozessen macht. Remote Aufrufe sind viel
teurer, weil es dafür auch viel mehr zu machen gibt: Daten müssen serialisiert werden, Security muss geprüft werden, Packete müssen verschickt und durchs Netzwerk
42
Grundlegende Architekturkonzepte
«Domain Object»
Person
«Remote Facade»
PersonFacade
+
+
-
getPersonDto() : Person
setPerson(PersonDto) : void
firstName: String
familiyName: String
middleName: String
dateOfBirth: date
prefix: String
suffix: String
Abbildung 1-33: Remote Façade Pattern
geroutet werden. Inter-Prozess Aufrufe sind um Faktoren langsamer als In-Prozess
Aufrufe – selbst dann wenn sich beide Prozesse auf der gleichen Maschine befinden.
Daher muss jedes Remote Objekt eine grobgranulare Schnittstelle besitzen, welche
die Anzahl notwendiger Aufrufe auf ein Minimum reduziert. Dies hat nicht nur Auswirkungen auf die Methodenaufrufe selbst, sondern auch auf die Objekte. Statt dass
man eine Person und ihre Adressen getrennt behandelt, definiert man eine Methode,
welche Person und Adressen zusammen in einem Aufruf modifiziert, eine so genannte Remote Façade.
Eine Remote Façade ist eine grobgranulare Façade (Gamma 1994) über ein Netz von
feingranularen Objekten. Keines der feingranularen Objekte besitzt ein Remote Interface, und die Remote Façade enthält keine Geschäftslogik. Alle was die Remote Façade tut, ist das Übersetzen von grobgranularen Methoden auf die unterliegenden
feingranularen Objekte.
1.1.7.2
Data Transfer Object (DTO)
Ein Datenbehälterobjekt, das die Daten zwischen Layern und Prozessen überträgt um
– vor allem zwischen physikalischen Tiers – die Anzahl von Methodenaufrufen zu
minimieren und die Applikationsteile zu entkoppeln.
DTOs sind spezielle Ausprägungen des Value Object Patterns.
DTOs müssen serialisierbar sein und orientieren sich vom Design her an der Applikation und nicht an der Domäne. Ein Domain Model kann von unterschiedlichen Applikationen genutzt werden. Die Applikationen selbst beinhalten jedoch eine unterschiedliche Applikationslogik und unterschiedliche Anforderungen an die Präsentation
der Daten.
Nehmen wir das hypothetisches Domain Model (Abbildung 1-34) als Beispiel.
Person
-
firstName: String
lastName: String
salary: double
age: int
dob: Date
Abbildung 1-34: Ein hypothetisches Domain Model
Address
-
city: String
zip: String
street: String
Enterprise Application Architecture Patterns
43
Zwei Applikationen benötigen die Logik in diesem Domänen-Modell, die im Zusammenhang mit DTOs jedoch keine Rolle spielt.
Applikation A benötigt das folgende DTO für Personen-Daten, damit diese auf einem
GUI erfasst werden können:
PersonDto
-
firstName: String
lastName: String
age: int
Abbildung 1-35: Ein mögliches DTO für Personendaten
Applikation 2 zeigt an folgenden Daten Interesse und definiert das DTO für Personendaten anders. (Selbstverständlich könnten die DTOs auch komplexere, geschachtelte Objektstrukturen darstellen, doch wir vereinfachen hier aus Gründen der Übersichtlichkeit.)
PersonDto
-
city: String
firstName: String
lastName: String
Abbildung 1-36: Ein weiteres mögliches DTO für Personendaten
In diesen beiden einfachen Fällen wäre es kein Problem, den Code für die DTOs von
Hand zu erstellen. Wichtiger und umfangreicher ist jedoch der Code zum Erzeugen
der DTO-Objekte, zum Befüllen dieser und zum Konvertieren von Datentypen (vor
allem bei Web-Anwendungen). Auch diese Aufgabe ließe sich für zwei Applikationen
mit nur diesen beiden DTOs problemlos bewältigen.
Geht man jedoch davon aus, dass sich Anforderungen der beiden Applikationen
ändern können, dass nicht nur ein PersonDto, sondern noch viele weitere benötigt
werden, und kommen evtl. weitere Applikationen hinzu, so wird die Pflege der notwendigen DTOs schnell müssig und lästig. Es müssen Wege gefunden werden, um
die Anforderungen im DTO-Lifecycle möglichst flexibel und effizient zu lösen.
Das Grundproblem besteht darin, zwei Modelle (Domänen-Modell und ApplikationsModell) aufeinander abzubilden. Dies ist eine Anforderung, welche man für Persistenzfragen schon gelöst hat, indem man sog. Mapper einführt. Solche DTO-MappingFrameworks können eine geeignete Lösung für das beschriebene Problem darstellen.
DTO-Mapping-Frameworks arbeiten ähnlich den O/R-Mappern mit Metadaten, welche Quell- und Zielstrukturen inklusive der notwendigen Typkonvertierungen beschreiben. Eine Laufzeitumgebung nutzt diese Informationen anschließend, um DTO-Instanzen zu erzeugen, sie mit entsprechend konvertierten Daten aus einer Datenquelle zu
44
Grundlegende Architekturkonzepte
befüllen oder eine Zieldatenstruktur mit konvertierten Daten aus einem DTO heraus
zu befüllen.
Im Falle der Kopplung zweier Komponenten (oder Services) können durch die Einführung von DTOs zwei Ziele erreicht werden:
•
Record-Data, ermöglicht einen grobgranularen Operations-Aufruf (wichtig vor allem für Remote-Calls zur Verringerung der Netzlast), d.h. viele feingranulare Aufrufe werden zu einem einzigen grobgranularen reduziert. Folge: Reduzierung der
Remote-Methodenaufrufe
•
Beide Services kennen einander nicht und sind somit voneinander unabhängig.
Beide sind nur vom DTO abhängig (Einführung einer Indirektion zwischen beiden
Services).
1.1.7.3
Assembler
Nimmt im Allgemeinen mehrere feingranulare Objekte entgegen und erstellt daraus
ein grobgranulares Datentransfer-Objekt. Assembler werden deshalb oft in Kombination mit DTOs verwendet.
Person
-
firstName: String
lastName: String
PersonDto
«use»
PersonAssembler
*
«create»
-
firstName: String
lastName: String
zip: String
city: String
street: String
+
+
toXml() : String
fromXml(String) : void
1
«use»
Address
-
city: String
street: String
zip: String
Abbildung 1-37: Assembler zur Erstellung grobgranularer DTO Objekte
1.1.8
1.1.8.1
Offline Concurrency Patterns
Optimistic Offline Lock
Verhindert Konflikte zwischen nebenläufigen Geschäftstranskationen indem ein Konflikt bemerkt wird und die Transaktion zurück gerollt wird.
Das Optimistic Offline Lock Pattern prüft bevor eine Datenbank-Änderung durchgeführt wird, ob die Daten in der Datenbank noch dieselben sind, wie zum Zeitpunkt
des Lesens. Nur wenn dies der Fall ist, können die Daten geschrieben werden.
45
Enterprise Application Architecture Patterns
Die meist eingesetzte Implementierung des Optimistic Offline Lock verwendet eine
Versionsnummer, welche zusätzlich zu den Daten geführt wird und die bei jeder Änderung eines Datensatzes erhöht wird.
database
Client1
Client2
getPerson(22)
Business Transaction
person 22=
getPerson(22)
person 22=
edit person
edit person
update person 22
ok
update person 22
failure: wrong person version
Abbildung 1-38: Optimistic Offline Lock
1.1.8.2
Pessimistic Offline Lock
Verhindert Konflikte zwischen nebenläufigen Geschäftstransaktionen indem nur eine
Geschäftstransaktion zur gleichen Zeit Zugriff auf die Daten hat.
Das Pessimistic Offline Lock Pattern verhindert Konflikte, indem diese gar nicht erst
zugelassen werden. Es zwingt die Geschäftstranskationen dazu, einen Lock anzufordern, bevor die Daten überhaupt verwendet werden können, d.h. bereits beim Lesen
der Daten. D.h. wenn man den Lock hat, kann man auch sicher sein, dass nachher
das Speichern der Änderungen auch möglich sein wird.
Die Verwendung des Pessimistic Offline Lock ist angebracht, wenn die Chance eines
Konfliktes zwischen nebenläufigen Sessions hoch ist oder aber die Kosten eines Konfliktes zu hoch sind. Es gilt zu beachten, dass das Pessimistic Offline Lock Pattern eine
Ergänzung zum Optimistic Offline Lock Pattern ist und nur dann verwendet werden
soll, wenn es wirklich notwendig ist. In jedem anderen Fall ist Optimistic Offline Lock
die bessere Lösung.
46
Grundlegende Architekturkonzepte
database
Client2
Client1
business transaction
getPerson(22)
person 22=
getPerson(22)
error: person locked
edit person
update person 22
ok
Abbildung 1-39: Pessimistic Offline Lock
1.1.9
Base Patterns
1.1.9.1
Gateway
Ein Objekt, welches den Zugriff auf externe Systeme oder Ressourcen kapselt.
Company System
Customer
«interface»
Order Gateway
Order System
«realize»
Employee
Order System specific API
Order System
Abbildung 1-40: Gateway als Adapter zu externem System
Beim Zugriff auf externe Systeme oder Ressourcen soll die Abhängigkeit von dessen
API gemindert bzw. verhindert werden. Es wird eine Indirektion in Form eines Gate-
47
Enterprise Application Architecture Patterns
ways eingeführt, welcher diese Abhängigkeit auflöst. Der API-abhängige Code wird
im Gateway-Code gekapselt (dieser wirkt also als Adapter).
1.1.9.2
Mapper
Ein Objekt, welches eine Kommunikation zwischen zwei unabhängigen Objekten
aufsetzt.
1.1.9.3
Layer Supertype
Ein Typ, der als Supertyp für alle Typen im gleichen Layer auftritt.
Man findet bei Fowler (Fowler, 2003) ein Beispiel, bei dem ein Domain Supertype
das Identitäts-Handling realisiert, welches für alle Entitätstypen in der Domäne notwendig ist.
class DomainSuperType {
private Long id;
public DomainSuperType( Long id ) { this.id = id;
public Long getId() { … }
public void setId( Long Id ) { … }
...
1.1.9.4
}
Registry
Ein allen Applikationsteilen bekanntes Objekt, welches von anderen Objekten genutzt werden kann, um andere Objekte und Services zu finden.
«Registry»
PersonRegistry
+
+
+
getPerson(long) : Person
addPerson(Person) : void
findPersonByName(String) : Person[]
Abbildung 1-41: Registry Pattern
Wenn man Objekte finden will, dann startet man üblicherweise bei einem anderen
Objekt, welches eine Assoziation auf das Zielobjekt hat und verwendet die Assoziation um zu navigieren. Will man z.B. alle Bestellungen eines bestimmten Kunden,
dann kann beim Kunden gestartet werden und über die Bestellungs-Assoziation zu
den Bestellungen navigiert werden. Oft hat man aber die Referenz auf das Objekt, auf
dem man starten will noch nicht zur Verfügung. Man weiß die Kunden-ID und möchte über diese die Referenz auf den Kunden erhalten. In diesem Fall braucht man eine
Art Lookup-Methode – ein Finder – und genau dies beschreibt das Registry Pattern.
48
1.1.9.5
Grundlegende Architekturkonzepte
Separated Interface
Definiert ein Interface in einem eigenen Package, unabhängig von der Implementation.
Domain Layer
CompanyDao
«interface»
DataAccessTechnology
«use»
«realize»
Integration Layer
DataAccessTechnologyImpl
Abbildung 1-42: Separated Interface
Beim Entwickeln eines Systems kann die Qualität des Designs erhöht werden, indem
die Koppelung zwischen den Komponenten reduziert wird. Ein guter Weg dafür ist es,
die Klassen in Packages zu gruppieren und die Abhängigkeiten dazwischen zu kontrollieren. Es können dann Regeln aufgestellt werden, wie Klassen von einem Package
Klassen von einem anderen aufrufen können - zum Beispiel darf eine Klasse aus dem
Domain Layer nie eine Klasse aus dem Presentation Layer aufrufen.
Allerdings wird man oft Methoden aufrufen müssen, die der allgemeinen Dependency Struktur widersprechen. Ist dies der Fall, dann kann das Separated Interface Pattern
verwendet werden, bei dem die Interfaces in einem Package definiert, die Implementierung jedoch in einem anderen Package liegt. Dadurch ist der Client zwar abhängig
vom Interface, braucht aber keine Ahnung über die Implementierung zu haben.
Dieses Pattern wird von Robert C. Martin als Dependency Inversion Principle wie
folgt beschrieben (Martin, 2003):
•
„High-level modules should not depend on low-level modules. Both should depend on abstractions.”
•
„Abstractions should not depend on details. Details should depend on abstractions.“
1.1.9.6
Service Stub
Entfernt direkte Abhängigkeiten von problematischen Services während dem Testen.
49
Domain Driven Design
Unternehmenssyteme sind oft von externen Services abhängig, wie in Abbildung 1-43
zum Beispiel das OrderSystem von einem CreditCardService. Um solche Systeme
einfacher entwickeln und testen zu können, ist es oft hilfreich, einen Service Stub
anstelle des realen Services zu verwenden. Dieser Stub läuft lokal ab, ist schnell und
kann auch sehr gut für die Simulation von Extrem- bzw. Fehlerverhalten verwendet
werden, was mit dem richtigen Service nur schwer möglich ist.
«interface»
OrderSystem
CreditCardService
+
approvePayment(double) : void
«realize»
«realize»
«WSDL»
VisaCreditCardServ ice
CreditCardServ iceStub
+
+
approvePayment(double) : void
approvePayment(double) : void
Abbildung 1-43: Service Stub Pattern
Einen Service Stub immer dann verwenden, wenn die Abhängigkeit von einem bestimmten Service das Entwickeln und Testen behindert.
Viele Entwickler benutzen auch den Begriff Mock Objekt, wenn sie von einem Service Stub sprechen.
1.1.9.7
Mediator
Zwei Objekte oder Systeme sollen miteinander kommunizieren können, wobei jedes
der Systeme seine eigene Schnittstelle besitzt.
Um die Systeme voneinander zu entkoppeln, wird eine Indirektion in Form eines Mediators eingeführt. Nur dieser kennt beide Systeme und deren Schnittstellen und dient
als Kommunikations-Koordinator zwischen den beiden Systemen.
Dieses Design Pattern findet vor allem in der SOA vielfältige Anwendung.
1.2
Domain Driven Design
Domain Driven Design ist ein Kommunikationsmittel! Die Grundidee basiert auf
klassischem OO-Design und versucht, die Sprache der jeweiligen Fachdomäne objektorientiert abzubilden, um den Bruch zwischen fachlicher und technischer Beschreibung/Umsetzung möglichst gering zu halten.
Herunterladen