Laden...

Die ideale Architektur für Businessapplikationen?

Letzter Beitrag vor 15 Jahren 58 Posts 48.476 Views
Die ideale Architektur für Businessapplikationen?

Hallo,

obwohl es ja einige Ansätze gibt, wie man Businessanwendungen aufbauen sollte (3-Tier, MVC, ...), ist es in der Praxis ja doch immer noch eine andere Sache, eine wirklich skalierbare, durchdachte Architektur aufzusetzen.

Deshalb würde ich hier gerne mal diskutieren, wie man so etwas idealerweise aufbauen würde. Mir ist klar, dass es nicht DIE ideale Architektur gibt, aber ich denke, es lassen sich doch etliche Best Practices finden, die man verwenden kann.

Um mal den Anfang ganz unten zu machen: In der Regel hat man ganz unten ja so etwas wie eine Datenzugriffsschicht, sei es auf eine Datenbank, einen Webservice, eine XML-Datei oder sonstwas ... in der Regel wird hier mit Entities gearbeitet, wobei üblicherweise ein Entitiy einem Datensatz einer Tabelle entspricht. Im Prinzip sollte man jedoch hier bereits mehrere Schichten einziehen, nämlich:

Nach oben: Ein einheitliches Interface, das NUR mit Entities arbeitet, und abstrahiert, aus welcher Datenquelle die Entities kommen.
Nach unten: Je nach Datenquelle einen Provider, der halt auf XML, SQL oder sonstwas zugreift, und ein Entitiy zurückliefert.
Dazwischen: Einen Microkernel, um der oberen Schicht verschiedene Provider unterschieben zu können, ohne dass sie davon etwas mitbekommt.

Bereits hier sollte mit Transaktionen gearbeitet werden, zudem bietet sich in der unteren Schicht evtl der Einsatz eines O/R-Mappers an. Auch Locking sollte hier in den grundlegenden Funktionen verfügbar sein, sprich Datensatz sperren, Datensatz entsperren, usw ...

Die Entities sind zunächst auch noch sehr eng am DB-Modell, und daher nicht applikationsbezogen modelliert.

Eine Schicht darüber werden die Entities zu Businessobjekten zusammengesetzt, die dann konkret und applikationsbezogen sind. Beispielsweise könnten hier aus einem Rechnungs-Entitiy und mehreren Rechnungspositionen-Entities ein Rechnungs-BO gemacht werden, das eher aus der OOP-Sicht als aus der DB-Sicht auf eine Rechnung schaut.

Das bietet den Vorteil, dass die Entities sich ändern können, ohne dass sich die Applikationen notgedrungen mit ändern müssen. BOs sind dennoch reine Datencontainer, damit sie problemlos serialisierbar und versionierbar sind, Funktionalität würde ich hiervon in einen BO-Service entkoppeln, der nur die Workflows zur Verfügung stellt.

Das heißt auch, dass sich die Workflows ändern können, ohne dass man jedes Mal an die BOs dran muss.

Darüber liegt schließlich eine Applikations-Service-Schicht, die quasi die Applikation ohne UI darstellt, und erst darauf setzt die UI auf (im Endeffekt meine ich hier das MVVM-Pattern).

Parallel dazu gibt es einige funktionale, applikationsübergreifende Kerne, die allgemeine Sachen wie zB Drucken, Reporting, ... zur Verfügung stellen.

Und nun zu meinen Fragen:

  • Was haltet Ihr von dieser Aufteilung?
  • Wo würdet Ihr RPC-Grenzen ziehen? Wo würdet Ihr Stack-Grenzen ziehen?
  • Wie würdet Ihr die Kommunikation über RPC aufbauen, insbesondere Events, die Rückmeldungen liefern?
  • Was fehlt bei dem ganzen Ansatz?
  • Wo habe ich etwas grundlegendes vergessen?

Wichtige Aspekte bei der ganzen Geschichte wären mir Multiuser, Multithreading, Skalierbarkeit, Verwendbarkeit des ganzen Grundgerüsts im Web und auf dem Desktop, und generell Flexibilität.

Viele Grüße,

Golo

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de

Hallo Golo,

wenn man das immer nur so pauschal sagen könnte 😉)

Also ich habe schon einige Ansätze gesehen und auch diverse Richtungen verfolgt. Von einer Mini-Applikation, die quasi nur eine 2-Schichtenarchitektur beinhaltet hatte (Programm inkl. GUI plus DB) bis hin zu einer Serverlandschaft und extrem verteilte Anwendung. Dort war das GUI so weit weg, dass Daten über einen extra Provider zum Rechenzentrum geschickt wurden, diese dann zunächst vereinheitlicht und danach verarbeitet wurden, um dann wieder weiter weg über einen Provider zu so genannten Umsystemen zu schicken.

Das wohl krasseste Szenario das ich kenne ist wohl folgender Aufbau (im groben könnte man das mit einem SOA Ansatz vergleichen):

  • GUI: .NET Anwendung, Daten via XML in Journalen zu
  • Datenprovider: Ein spezialisierter Provider, der es schafft riesige Datenmengen über eine riesige Bandbreite zu diversesten Empfänger zu verteilen (sozusagen ein Router)
  • Middleware/Businesslogik: Verarbeiter und Analysierer der Daten, hier eingesetzt BizTalk und MS-SQl Server Cluster über
  • Datendateien zu Umsystemen

Grundsätzlich geht es doch immer darum: Was ist der Zweck der Software, was für Mittel stehen zur Verfügung und was ist am Effizientesten. Ich würde das wohl damit vergleichen, was für ein Warenwirtschaftssystem soll in einem Unternehmen eingeführt werden. Ein SAP System für einen Einzelunternehmen ist wohl Quatsch, genauso wie Excel für ein 10.000 Mann Unternehmen wohl nicht ausreichend sein wird.

Also da es hier sehr starke Schwankungen der Anforderungen gibt, gerade was Businessapplikationen angeht, kann man das überhaupt nicht festlegen. Wichtig für einen Software-Entwickler finde ich, ist es so viele verschiedene Ansätze zu kennen, und nicht so verbohrt zu sein, immer das Gleiche in jedem Umfeld einsetzen zu wollen.

Grüße
Norman-Timo

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

Warum nur muss ich bei deinem Ansatz die ganze Zeit an Enterprise Java denken?

Shift to the left, shift to the right!
Pop up, push down, byte, byte, byte!

YARRRRRR!

Hallo @All,

Eine gute Grundstruktur zu haben finde ich nicht verkehrt.
Aber mann muss eben alles Anpassen können und flexiebel sein. 🙂

Meine Strucktur besteht aus:

  • O/R Mapper (als Generische DAL, mit austauschbarer Datenquelle)
  • Business-Objekte
  • UI

Wobei die Business-Objekte im zweifelsfall aus weiteren Schichten bestehen können und ich noch an einer Remoting-Schicht für den O/R Mapper arbeite. 😉

Gruß
Juy Juka

Grunsätzlich glaube ich, dass Architektur weniger von Patterns, sondern eher von der Vermeidung von Anti-Patterns getrieben ist. Für Business-Anwendungen mag das weniger gelten, weil "Businessanwendung" einfach einen bestimmten Typ von Anwendung definiert, der durch Nutzerfrontend, zentraler Datenverarbeitung und -haltung definiert ist. In diesem engen Rahmen lassen sich gut Patterns definieren.

Aber die sehen bei Services schon anders aus, ebenso wie z.B. bei Steuerungen.

Hallo svenson,

Dass Software-Architektur nicht von Patterns getrieben wird, ist hoffentlich nicht korrekt und wenn doch sehr schade. Ich glaube es nicht (dass es nicht durch Patterns getrieben wird), hab aber weder Beweise dafür noch dagegen.

Aber für die Software-Architektur gibt es ein zusätzliches Set von Patterns (oder sogar mehrer), die jedoch noch unbekannter sind als die Designe-Patterns.
z.B.: Layer-Architektur-Pattern, Pipes-and-Filter-Architektur-Pattern, Broker-Architektur-Pattern.

Gruß
Juy Juka

Was ich sagen wollte: Pattern sind Muster. Muster eigenen sich für Software von der Stange, für die Wiederholung. Stell dir vor, du musst auf einmal eine Anwendung bauen, die eine Million Transaktionen pro Sekunde verarbeiten muss. Dann kann du dein klassisches Schichten-Modell sofort in die Tonne treten und du fängst an, zuerst zu denken: Was geht auf keinen Fall: Datenbanken für Transaktionssicherung, klassische Middleware, etc.. Dann beginnt die Suche nach den Fallstricken, den No-Gos. Deine Architektur beginnt sich nicht an erfolgreichen Modellen auszurichten, sondern an den Mißerfolgen, die du gesammelt hast. Auf einmal suchst du danach, welche Muster du unbedingt vermeiden musst. Du musst deine Erfahrung in inverser Weise ins Spiel bringen.

Ansonsten lautet die Antwort auf die Frage, welche Architektur in bekanntem Umfeld die beste ist: Die alte und die bekannte. Die die andere schon 100 mal beschritten haben. Hier lauert keine Gefahr. Oder anders: Die Frage ist unspannend.

M.E. sind Musterarchitekturen - bzw. das was man als solche bezeichnet - nur Zeiterscheinungen, die den Stand der Technik bzw. der Anwendungen wiederspiegeln. "Ideal" ist ein Wort für ein Snapshot. Gestern war es das 3-Schicht-Modell, morgen die Zelle, übermorgen der Service-Ansatz. Das Pattern "Krawatte bis zum Gürtel" mag heute gültig sein. Morgen vielleicht nicht mehr. Aber das Pattern "Eine Krawatte über die man stolpert ist Scheisse" hat universelle Gültigkeit. 🙂

Hallo Golo,

eine interessante Diskussion.

Im Prinzip sollte man jedoch hier bereits mehrere Schichten einziehen, nämlich:

Nach oben: Ein einheitliches Interface, das NUR mit Entities arbeitet, und abstrahiert, aus welcher Datenquelle die Entities kommen.
Nach unten: Je nach Datenquelle einen Provider, der halt auf XML, SQL oder sonstwas zugreift, und ein Entitiy zurückliefert.
Dazwischen: Einen Microkernel, um der oberen Schicht verschiedene Provider unterschieben zu können, ohne dass sie davon etwas mitbekommt.

Es gibt doch mittlerweile für jeden die passende Datenzugriffstechnologie. Das .NET Framework 3.5 SP1 liefert bereits drei Stück:*ADO.NET (Der schnelle Klassiker) *LINQ2SQL (Der schlanke OR-Mapper für treue MSSQL-Entwickler) *ADO.NET Entity Framework (Der Mega-OR-Mapper mit vielen Stellschrauben, eigenem DB unabhängigen SQL-Dialekt und noch LINQ obendrauf)

Deiner obigen Aussage entsprechend, ist das ADO.NET Entity Framework genau das was Du suchst: Komfortabler und Tool gestützter OR-Mapper mit völliger Abstraktion von der eigentlichen Datenquelle und auch deren API bzw. SQL-Dialekt. Volle Flexibilität, da Abfragen via LINQ (stark typisiert) oder via ESQL abgesetzt werden können. Aber da erzähle ich Dir bestimmt nichts Neues.

Ich wollte eigentlich damit sagen, dass ich das Thema Datenzugriff nicht zu hoch bewerten würde. Datenzugriff ist immer wieder das Lieblingsthema der Entwickler. Statt über Sinn und Unsinn von Mapping-Konzepten zu philosophieren, wäre es Sinnvoller, der eigentlichen Geschäftslogik einen viel größeren Stellenwert beim Entwurf einer Software-Architektur einzuräumen.

Bereits hier sollte mit Transaktionen gearbeitet werden, zudem bietet sich in der unteren Schicht evtl der Einsatz eines O/R-Mappers an. Auch Locking sollte hier in den grundlegenden Funktionen verfügbar sein, sprich Datensatz sperren, Datensatz entsperren, usw ...

Transaktionen müssen ja erst aufgespannt werden. Verschiedene Komponenten können an einer Transaktion beteiligt sein. Die Datenzugriffsschicht kann das nicht leisten, da es vom Geschäftsprozess abhängig ist, welche Operationen zusammen in einer Transaktion ausgeführt werden müssen. Deshalb haben Transaktion in der Datenzugriffsschicht eigentlich nix verloren. Aber auch das ist bereits mit System.Transactions gelöst. ADO.NET, LINQ2SQL und Entity Framework unterstützen System.Transactions-Transaktionen.
Da bleibt dann eigentlich nur die Frage, wie man seine Transaktionsklammern setzt. Etweder deklarativ im Code mit TransactionScope oder, im Falle einer verteilten Anwendung auf WCF-Basis, mit TransactionFlow-Attributen.

Du hattest noch Locking erwähnt. Beim Locking ist es selbst innerhalb einer abgeschlossenen Business Anwendung schwer, eine einheitliche Implementierung zu finden. Es gibt verschiedene Ressourcen, die unterschiedlich oft (5 pro Trag oder 200 pro Sekunde) und unterschiedlich lange (10 Sekunden oder 2 Stunden) gesperrt werden müssen. Auch der Zeitpunkt der Sperrung ist selten bei allen Ressourcen gleich. Manche müssen beim Anzeigen und andere vielleicht erst bei Beginn einer Änderung gesperrt werden. Ich würde das Locking deshalb nicht automatisch durchführen lassen, da man sonst die geforderte Flexibilität nie erreichen kann. Wenn die Infrastruktur automatisch sperrt, tut sie das immer auf die selbe Weise. In verteilten Anwendungen wird das noch wesentlich komplexer, da Clients Sperren anfordern (Im Falle eines Windows.Forms-Cients weiss z.B. auch nur diese, wann der Benutzer den Knopf "Bearbeiten" auf der Symbolleiste anklickt), aber auch serverseitige Geschäftslogik, die automatische Änderungen durchführt. Stapelverarbeitung sollte man beim Locking-Konzept auch nicht vergessen. Wie soll sich das System verhlaten, wenn z.B. ein Server-Job 1000 Buchungen ausführen soll, von denen zwei gerade durch Sperren von Clients betroffen sind, die abhängige Ressourcen gerade in Ihrer GUI bearbeiten? Letzten Stand nehmen, Erneut versuchen, Überspringen?
Ich habe gute Erfahrungen damit gemacht, einen Satz von Locking-Funktionen über eine Locking-Komponente zur Verfügung zu stellen und dem Entwickler die Verantwortung für setzen, prüfen, behandeln und freigeben von Sperren zu übertragen. Wenn eine schlanke Locking-Komponente/Dienst (Ich weiss nie, wie ich da sagen soll) zur Verfügung steht, die über ein API einfach verwendet werden kann, tut sich keiner im Team schwer mit dem Locking.

Eine Schicht darüber werden die Entities zu Businessobjekten zusammengesetzt, die dann konkret und applikationsbezogen sind. Beispielsweise könnten hier aus einem Rechnungs-Entitiy und mehreren Rechnungspositionen-Entities ein Rechnungs-BO gemacht werden, das eher aus der OOP-Sicht als aus der DB-Sicht auf eine Rechnung schaut.
Das bietet den Vorteil, dass die Entities sich ändern können, ohne dass sich die Applikationen notgedrungen mit ändern müssen. BOs sind dennoch reine Datencontainer, damit sie problemlos serialisierbar und versionierbar sind, Funktionalität würde ich hiervon in einen BO-Service entkoppeln, der nur die Workflows zur Verfügung stellt.

Meistens ergeben sich eigene Datenstrukturen zur Bearbeitung und Anzeige von ganzen Datensätzen (Weitestgehed DB-Struktur=BO-Struktur) und welche die bestimmte Sichten auf Teilaspekte einer Entität oder mehrerer Entitäten in Abhängigkeit voneinander (z.B. Trefferlisten von Suchanfragen oder Datensammlungen für Berichte).

Das heißt auch, dass sich die Workflows ändern können, ohne dass man jedes Mal an die BOs dran muss. Das ist auch meine Meinung.

Darüber liegt schließlich eine Applikations-Service-Schicht, die quasi die Applikation ohne UI darstellt, und erst darauf setzt die UI auf ...

Diese Schicht ist der eigentliche Kern jeder Business-Anwendung. Dort spielt die Musik. Dort werden Transaktionen aufgespannt, Daten validiert, die eigentlichen Berechnungen gemacht, usw.. Trotzdem taucht diese Schicht in Architekturkonzepten oft eher als Black Box auf. Das ist dann Komponente xy, die macht das. Trotzdem geben sich viele lieber der Jagt nach dem heiligen Gral des Datenzugriffs hin, statt ihre Geschäftslogik sauber in Komponenten zu packen und deren Abhängigkeiten klar festzulegen. Das ist zugegeben auch nicht ganz einfach, da sich Geschäftslogik gerne in kleine Ritzen verdrückt. Ein bisschen wandert in SQL-Code, ein ordentlicher Happen versteckt sich in der Benutzeroberfläche und manches bleibt unauffällig in JavaScripts kleben. Deshalb sollte bei jedem Programmteil sorgfälig überlegt werden, welchee Teile der Geschäftslogik bewusst im GUI-Code, was im Code der Geschäftslogik-Komponenten/Diensten und was in der Datenbank implementiert wird.

  • Was haltet Ihr von dieser Aufteilung?

Ich habe ja schon einiges direkt kommentiert. Du beschreibst eigentlich eine klassische 3-Tier Architektur. Damit habe ich gute Erfahrungen gemacht.

  • Wo würdet Ihr RPC-Grenzen ziehen? Wo würdet Ihr Stack-Grenzen ziehen?

Ich würde die Anwendung in einzelne Komponenten/Dienste aufteilen, die möglichst unabhängig voneinander sind. Abhängigkeiten lassen sich natürlich nie vermeiden. Deshalb müssen sie klar geregelt werden. Mein Ansatz ist dabei, die Komponenten/Dienste in Kategorieen (nicht mit den Schichten verwechseln) einzuteilen. Diese Kategorieen könnten für eine ERP-Anwendung z.B. so aussehen:*Infrastruktur-Dienste (Authentifizierung, Authorisierung, Sitzungsverwaltung, Server-Jobs, ...) *Basis-Dienste (Mandanten-Verwaltung, Verwaltung von Grunddaten wie Einheiten, Währungen, Zahlungsbedingungen, Textbausteinen ...) *Stammdaten-Dienste (Artikelstamm, Geschäftspartner, Preise, Lagerverwaltung) *Prozess-Dienste (Angebotswesen, Einkauf, Verkauf) *Kontroll-Dienste (Disposition, Auswertungen/Reporting)

Alle Komponenten/Dienste werden genau einer dieser Kategorieen zugewiesen. Komponenten/Dienste dürfen nur auf andere Komponenten/Dienste zugreifen, wenn diese in einer niedrigeren Kategorie sind. Von Komponenten des Artikelstamms darf z.B. auf die Mandanten-Verwaltung oder die Server-Jobs zugegriffen werden, aber nicht auf Geschäftspartner, Einkauf oder Disposition. Zugegriffe müssen über RPC erfolgen. Greift eine Komponente/Dienst auf eine andere Komponente/Dienst zu, wird die abgewickelt, als würde z.B. ein Windows.Forms-GUI-Client auf die Komponente/Dienst zugreifen. Die Identität bzw die Sitzungsinformationen werden dabei mitgeschleift. Verkettete Dienstaufrufe verwendenalso normalerweise immer die Sitzung des ersten RPC-Aufrufers (Meistens ein GUI-Client). Dienste dürfen nür über definieirte Schnittstellen aufgerufen werden.
Komponenten/Dienste, die in der selben Assembly liegen, dürfen sich direkt aufrufen und müssen nicht über RPC gehen. Das ist wichtig, damit man Strukturierungsmöglichkeiten innerhalb der Geschäftslogik-Komponenten hat. Es ist auch von Vorteil, die eigentliche Implementierung der Geschäftslogik von der Kommunikations-Infrastruktur zu trennen. Mit Remoting geht das sehr gut. Die Dienst-Implementierung kümmert sich um Kommunikation und Aspekte wie Authentifizierung und delegiert den Aufruf anschließend an die eigentliche Logik-Implementierung. Diese muss dann auch nicht von MarshalByRef ableiten und kann bequem in einer separten Assembly untergebracht werden. Ich habe ein Architekturbeispiel gemacht, welches diese Konzept sehr schön zeigt: .NET Applikationsserver
Mit WCF kann man es genauso machen.
GUI-Clients jeder Art (also auch ASP.NET Seiten auf Webservern etc.) greifen immer über RCP zu. Bei Web ist das sogar besonders wichtig, da man das Web-Front-End so besser absichern kann.

  • Wie würdet Ihr die Kommunikation über RPC aufbauen, insbesondere Events, die Rückmeldungen liefern?

So statuslos wie möglich! Je weniger statusbehaftet, desto skalierbarer. Wenn Du Serverfarmen unterstützen willst/musst, solltest Du die Sitzungen zwischen den Servern replizieren oder in der DB halten. Welche Metode die bessere ist, hängt vom Einzelfall ab. Eine Sitzung sollte so wenig Daten wie möglich enthalten. Bei mir ist das z.B. nur folgendes:*Sitzungsschlüssel (Guid) *Benutzeridentität (IIdentity) *Kultureinstellungen des Benutzers (CultureInfo x 2; 1 x UI; 1 x Env.) *Zeitstempel (Datetime)

Events würde ich vermeiden. Zur Beanchrichtigung für freigegebene Sperren, wenn ein Client darauf wartet und der Benutzer die schnellsmögliche Rückmeldung braucht, sind Events allerings fein. Ansonsten würde ich immer versuchen es ohne Events zu lösen.
Asynchrone Verarbeitung ist das Zauberwort für Automatisierbarkeit. Man sollte dem Server Aufträge (Jobs) in Warteschlangen stellen können, die er im Hintergrund abarbeitet. Asynchrone Verarbeiten ermöglicht auch den Einsatz einer Workflow-Engine wie z.B. WF.

  • Was fehlt bei dem ganzen Ansatz?

Der bzw. die GUI-Client(s)! 80% des Aufwandes einer Geschäftsanwendung entfallen auf die Benutzeroberfläche. Der temporäre Status sollte beim GUI-Client liegen. Im Falle eines Web-Clients ist das der ASP.NET Sitzungsstatus bzw. Javascript im Browser. Besonders wenn Du verschiedene GUI-Clients anbieten willst/musst, solltest Du das in Deinem Entwurf berücksichtigen. Wenn Du z.B. einen GUI-Client für Windows Mobile oder für Handys (evtl. in Java geschrieben) haben willst, musst Du Dir bereits beim Entwurf Gedanken machen, wie solch ein Client mit der Geschäftslogik redet. Das Compact Framework kann z.B. kein Remoting und Java natürlich auch nicht. Die Frage, ob Du Deine Geschäftskomponenten/Dienste über unterschiedliche Endpunkte (z.B. binär TCP und HTTP SOAP) anbieten musst, hängt nicht zuletzt von der GUI-Client-Frage ab.

  • Wo habe ich etwas grundlegendes vergessen?

Bei den folgenden Überlegungen gehe ich davon aus, dass Du eine Software schreibst und an Kunden im kompilierten Zustand auslieferst. Bei einer Inhouse-Anwendung treffen einige der Überlegungen nicht zu.
Wie kann die Anwendung später vom sachverständigen Endanwender erweitert und angepasst werden? Wie sieht es mit Add-Ins aus? Geschäftsprozesse ändern sich oft schneller, als ein Software-Anbieter reagieren kann. Deshalb sollte eine moderne Business-Anwendung Erweiterungsmöglichkeiten haben. Wenn nun bei jeder Lagerbuchung plötzlich eine Webservice einer Drittanwendung aufgerufen werden soll, muss das machbar sein, ohne den Lagerverwaltungsdienst anfassen zu müssen. Deshalb muss es Punkte in der Anwendung geben, die es einem Customizer oder Entwickler erlauben sich einzuhängen. Office ist ein gutes Beispiel. In Outlook oder Work kann ich Add-Ins und Makros schreiben. Das sollte auch für die Geschäftskomponenten/Dienste Deiner Anwendung möglich sein. Auch deshalb ist es ratsam Implementierung von Dienst und eigentlicher Geschäftslgik zu trennen. Die Dienst-Implementierung kann bei Bedarf konfigurierte Add-Ins oder sogar VB.NET/C#-Code laden und diesen an festgelegten Punkten die Kontrolle übertragen.
Deployment und Updates fallen mir da noch ein. Für mich ist das Teil der Architektur. Auch PlugIns etc. müssen auf Client- und ggf. auch auf die Server-Maschine(n) verteilt werden.
Wie werden Schnittstellen versioniert? Werden Client- und Server-Komponenten getrennt versioniert, oder eine Version für alle Assemblies des Anwendung? Ist ein Versionierung überhaupt nötig?
Wie wird die Datenbank bzw. deren Struktur versioniert? Wie können Anwender die DB erweitern, ohne die Updatefähigkeit zu verlieren?

Hallo Rainbird

Sehr schöne detailierte Antwort.

Eine Frage dazu

Infrastruktur-Dienste (Authentifizierung, Authorisierung, Sitzungsverwaltung, Server-Jobs, ...)

Basis-Dienste (Mandanten-Verwaltung, Verwaltung von Grunddaten wie Einheiten, Währungen, Zahlungsbedingungen, Textbausteinen ...)

Stammdaten-Dienste (Artikelstamm, Geschäftspartner, Preise, Lagerverwaltung)

Prozess-Dienste (Angebotswesen, Einkauf, Verkauf)

Kontroll-Dienste (Disposition, Auswertungen/Reporting)

Welche Dienste würden in diesen Falle direkt auf die Datenbank zugreifen? So wie ich es sehe wären es ja die ersten Drei, die direkt zugreifen, oder?

Ist jetzt nur rein aus Verständnis, weil ich es bei meinen Anwendungsserver zurzeit strikt einhalte, das nur ein einziger Dienst auf die DB zugreifen darf.

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

Nicht mit den Schichten verwechseln

Welche Dienste würden in diesen Falle direkt auf die Datenbank zugreifen? So wie ich es sehe wären es ja die ersten Drei, die direkt zugreifen, oder?

Die Kategorieen haben nichts mit den Schichten zu tun! Es ist nicht so, dass die einzelnen Kategorieen bestimmten Schichten zugeordnet werden. Alle Dienste, dieser Kategorieen liegen in der Mittelschicht (Geschäftslogik).

Jeder Dienst greift direkt auf die Datenbank. Natürlich tut er das nicht unbedingt über ein direktes SqlCommand, sondern - wenn Abstraktion vom RDBMS für das Projekt wichtig ist - über eine Datenzugriffsschicht (Das könnte eine einfache Hilfsklasse sein, oder ein komplexes ADO.NET Entity Model mit Mappings und allem drum und dran).
Da verschiedene Dienste auch verschiedene Datenquellen haben können (Je größer das Projekt desto höher die Wahrscheinlichkeit, dass die Daten an verschiedenen Quellen liegen), würde ich den Datenzugriff nicht über einen zentralen Dienst schleusen, sondern die Datenzugriffsschicht direkt (also ohne RPC) innerhalb jedes Dienstes konsumieren.
Ich fasse außerdem Dienstschnittstellen und Datenstrukturen (in WCF würde man sagen DataContracts) pro Programmmodul in einer Contract-Assembly zusammen. Diese würde auch das ADO.NET Entity Model, die DataSets oder die Linq2SQL Klassen enthalten (Je nach eingesetzter Datenzugriffs-Technologie). Für die Architektur spielt das aber keine Rolle.

Die Kategorieen haben nichts mit den Schichten zu tun! Es ist nicht so, dass die einzelnen Kategorieen bestimmten Schichten zugeordnet werden. Alle Dienste, dieser Kategorieen liegen in der Mittelschicht (Geschäftslogik).

Ah OK. Dann ergibt das für mich jetzt alles wieder mehr Sinn 🙂
Ich habe das irgendwie anders interpretiert.

Ich fasse außerdem Dienstschnittstellen und Datenstrukturen (in WCF würde man sagen DataContracts) pro Programmmodul in einer Contract-Assembly zusammen

So mach ich das auch.

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

Hallo Rainbird,
in deiner Ausführung hast du Artikelstamm und Lagerverwaltung auf gleicher Ebene. Braucht aber nicht die Lagerverwaltung zugriff auf den Artikelstamm?

Artikelstamm und Lager

Hallo Sebi,

das kann man so und so implementieren. Wenn in den Bestandsdatensätzen und damit auch für Buchungen nur die ArticleID benötigt wird, muss die Lagerverwaltung auch nicht auf den Artikelstamm zugreifen. Allerdings muss die GUI der Lagerverwaltung den Artikelstamm-Dienst konsumieren. Der Benutzer möchte natürlich detailierte Daten sehen.

Die Geschäftsdienste sind zwar autonom, aber sie werden von höheren Kategorieen aus und von Clients konsumiert und damit kombiniert. In diesem Modell muss man sich von der Vorstellung lösen, dass GUI-Client und Geschäftsdienste symmetrisch sind.

Die Anwendung besteht aus einer Sammlung von Diensten. Aus Hosting Sicht leben diese alle nebeneinander. Um die Abhängigkeiten im Griff zu haben, teilt man die Dieste in eine gedachte Hierarchie von Kategorien ein. GUI-Clients stehen ganz oben und dürfen theoretisch sämtliche Dienste Kreuz und Quer konsumieren. Die Dieste sind sozusagen das API der Business-Anwendung. Welche Dienste ein GUI-Client-Modul konsumieren darf ist eher eine Frage des Deployments und der Lizenzierung (Im Falle einer kommerziellen Anwendung)

Es wäre aber auch nicht falsch, fuer die Lagerverwaltung eine eigene Kategorie einzuschieben. Die Kategorieen sind ja nur gedacht und verursachen an sich keinen Aufwand.

Hallo,
das heißt dann arbeiten die Dienste mit den weiter oben erwähnten Entities welche dann in ner höheren Ebene die Zugriff auf die entsprechenden Dienste hat zu Business Objekten zusammengebaut werden?

Kontrakte

Hallo Sebi,

was meinst Du genau mit Business Objekten? Der Begriff ist ein wenig schwammig.

Nicht in Schichten denken! Geschäftslogik, Dienste, Dienstschnittstellen und Datenstrukturen liegen alle zusammen in der Mittelschicht. Die Datenstrukturen werden von Geschäftslogik und Diensten gemeinsam genutzt. Im WCF-Jargon würde man Datenstrukturen DataContracts bzw. MessageContracts nennen. Vom ADO.NET Entity Framework erzeugte Objekte sind übrigens automatisch DataContracts.
Es könnten aber auch beliebige serialisierbare Objekte sein (z.B. DataSets/DataTables). Das spielt aber nicht wirklich eine Rolle.

Hi Leute,

Bereits hier sollte mit Transaktionen gearbeitet werden, zudem bietet sich in der unteren Schicht evtl der Einsatz eines O/R-Mappers an. Auch Locking sollte hier in den grundlegenden Funktionen verfügbar sein, sprich Datensatz sperren, Datensatz entsperren, usw ...

Transaktionen müssen ja erst aufgespannt werden. Verschiedene Komponenten können an einer Transaktion beteiligt sein. Die Datenzugriffsschicht kann das nicht leisten, da es vom Geschäftsprozess abhängig ist, welche Operationen zusammen in einer Transaktion ausgeführt werden müssen. Deshalb haben Transaktion in der Datenzugriffsschicht eigentlich nix verloren. Aber auch das ist bereits mit System.Transactions gelöst. ADO.NET, LINQ2SQL und Entity Framework unterstützen System.Transactions-Transaktionen.

korrigiert mich, wenn ich falsch liege...

Ich würde sagen, irgendwie habt ihr beide recht und ihr widerlegt euch ja auch nicht gegenseitig.
Wie es schon Golo sagt, wäre auch meine Intention, in den Datenzugriffslogiken Transaktionen aufzuspannen. Denn die Aufgabe von diesen ist es ja, auch aus (möglichen) mehreren Datenquellen, die Entities zusammen zu setzten und wieder abzuspeichern. Da dies auch über mehrere Abfragen geschehen kann, müssen klar auch schon hier Transaktionen zum Einsatz kommen.

Rainbird du hast zwar Recht, dass Geschäftsprozesse Transaktionen über mehrer Entities aufspannen müssen, aber es spricht ja nichts dagegen besetehende Transaktionen nochmals zu verpacken.

Ansonsten finde ich beide Ausführungen sehr interessant!

Gruß Timo

Transaktionen

Hallo Omit,

natürlich kann man Transaktionen schachteln. Wenn die Datenzugriffsschicht allerdings generell alles in Transaktionen packt, hat man z.B. ein Problem, wenn eine bestimmte Operation nicht in einer Transaktion ablaufen soll. Wenn die Datenzugriffsschicht die Transaktionen nicht automatisch macht und man sie manuell aufrufen muss, kann man auch gleich alles in der Geschäftslogik mit System.Transactions machen.

Bei langlaufenden oder anwendungsübergreifenden Geschäftsprozessen müssen statt atomarer Transaktionen eh Sagas (http://www.serviceoriented.org/long_running_transactions.html) zum Einsatz kommen. Spätestens bei solchen Sachen (die bei jeder größeren Business-Anwendung früher oder später auftreten) ist es nicht mehr sinnvoll, die Transaktionslogik auf Geschäfts- und Datenzugriffsschicht zu versteuen.

Trotzdem kann es natürlich Projekte geben, bei denen es Sinn macht, Transaktionen auch direkt in der Datenzugriffsschicht aufzuspannen (Pauschalaussagen sind nämlich generell mit vorsicht zu genießen). Nach meiner Erfahrung ist es aber - zumindest für Business-Applikationen - nicht optimal.

WF-Workflows eignen sich übrigens auch sehr gut, um Transaktionen in Geschäftsprozessen einzubringen. Mit den Workflow Services (WF über WCF) können so auch gut externe Anwendungen angekoppelt werden.

Mit Long Running Transactions hab ich mich noch nicht beschäftigt. Deshalb habe ich eine Frage:

Long Running Transactions (LRT, also known as Sagas) are transactions that may take minutes, days or weeks before the outcome of the transaction is known.

Verstehe ich das richtig, dann sind alle Ressourcen solang gesperrt, oder wie realisiert man so eine Transaktion? Oder muss man dann alles einzeln wieder zurück aufdröseln?

Ach danke für http://www.serviceoriented.org/ , kannte ich noch nicht und scheint wirklich sehr gut zu sein. Nach meiner Klausur, werde ich mich da durchackern freu, kann mich kaum zurück halten(Timo NACH der Klausur *g)!

Gruß TimoW

Hallo Omit,

Verstehe ich das richtig, dann sind alle Ressourcen solang gesperrt, oder wie realisiert man so eine Transaktion?

typischerweise würde man dann nicht pessimistisch sperren, sondern ein ==> optimistisches Sperrverfahren verwenden.

herbivore

OK danke, war mir jetzt ganz entfallen. Aber klar. Muss mich damit nochmal auseinandersetzten!

Gruß Timo

Hallo
wo würdet ihr in einer solchen SOA Umgebung die Prüfung einordnen die das löschen eines Mandanten verbietet wenn noch offene Aufträge existieren?

Dienstekategorieen

Hallo daniel,

ausgehend davon, dass die Dienste der Applikation in Kategorieen eingeteilt wurden und in einer gedachten Hierarchie angeordnet sind, kannst Du die Prüfung nicht direkt im Mandantenverwaltungsdienst machen, da der Mandantenverwaltungsdienst vermutlich in einer niedrigeren Kategorie ist als der Dienst zur Auftragsabwicklung.
Also muss man auf höherer Ebene einen Dienst einziehen, der solche Workflows kapselt (ich nenne den jetzt einfach mal Workflow-Dienst). Den Löschbefehl willst Du zwar über den Mandantenverwaltungsdienst geben, aber die Prüfung darf nicht direkt in diesem Dienst gemacht werden. Der Workflow-Dienst muss also irgendwie mitbekommen, dass jemand einen Mandanten löschen will. Damit das geht, muss der Mandantenverwaltungsdienst die Möglichkeit bieten, dass man per Konfiguration andere Dienste als Empfänger von Nachrichten bei bestimmten Ereignissen konfigurieren kann. Es funktioniert dann vom Prinzip her, wie bei einem FormClosing-Ereignis in Windows.Forms. Bei diesem Ereignis kann man mittels e.Cancel=true dem FOrmular mitteilen, dass das Schließen abgebrochen wird. Genauso könnte der Workflowdienst per Abo vom Mandantenverwaltungsdienst benachrichtigt werden, dass ein Mandant gelöscht werden soll. Der Workflow-Dienst führt die Prüfung durch (konsumiert dazu den Dienst zur Auftragsabwicklung) und sendet dem Mandantenverwaltungsdienst eine Nachricht zurück, ob Abgebrochen werden soll, oder nicht. Wichtig dabei ist, dass die Kommunikation entkoppelt abläuft. Der Mandantenverwaltungsdienst darf weder die Schnittstelle noch die Implementierung des Workflow-Dienstes kennen.

Man sollte sich bei so einer Situation auch Gedanken machen, ob das alles synchron oder asynchron ablaufen soll. Je größer die Anwendung, desto eher würde ich alles asynchron machen.

So ein lose gekoppeltes Ereignissystem ist auch bei vielen anderen Dingen nützlich, da man die Abgebildeten Geschäftsprozesse so ändern und beeinflussen kann, ohne laufende fertige Dienste anzufassen. Das macht unterm Strich eine schnellere Reaktionszeit auf Änderungen am Geschäftsprozess.

Hallo,
das heißt der Client arbeitet nur mit den Workflows welche dann wiederrum mit den Diensten kommunizieren oder die Workflows "überwachen" praktisch die Arbeit der Dienste?

Keine Schichten

Hallo daniel,

Der Workflowdienst überwacht den Mandantenverwaltungsdienst in diesem Fall. Wenn der Client einen Mandanten löschen will, dann tut er das über den Mandantenverwaltungsdienst. Der Mandantenverwaltungsdienst stellt seinerseits einen Publish-Subscribe-Mechanismus zur Verfügung, damit andere Dienste/Komponenten "Ereignisse" abonnieren und sich in die Verarbeitung einschalten können. Der Workflowdienst wird nicht direkt vom Client aufgerufen, sondern vom Mandantenverwaltungsdienst benachrichtigt, dass ein Mandant gelöscht werden soll. Der Mandantenverwaltungsdienst kennt den Workflowdienst aber nicht! Genauso wie ein Button nicht das Form kennt, welches sein Click-Ereignis behandelt.

Wenn der Client den Mandnatenlöschbefehl direkt über den Workflowdienst laufen würde, müsste dieser alle Operationen des Mandantenverwaltungsdienstes kapseln. Das ist ein Schichtendenken, das an dieser Stelle nur Nachteile bringt. Die API der Applikation wäre dadurch nicht mehr intuitiv. Wenn ich einen Mandanten löschen will, liegt es nahe, dies über den gleichnahmigen Dienst zu tun.

Hallo,
danke für die Erläuterungen.

Wie würden Workflows der Workflow Foundation in das Konzept passen?

Wf

Hallo daniel,

die würden sehr gut passen. Im .NET Framework 3.5 gint es die sog. Workflow Services. Das ist eine Technologie, die es realtiv einfach ermöglicht, WF-Workflows direkt als WCF-Dienste zu hosten. Für den Aufrufer sehen die Workflows aus, wie ganz gewöhnliche WCF-Dienste. Für Diese, die längerandauernde Abläufe abbilden, sind WF-Workflows ideal. Besonders langlaufende Transaktionen (Sagas) lassen sich mit WF grafisch entwerfen. Die wichtigsten Activities bringt WF schon mit.
Man sollte den Aufwand WF-Workflows zu erstellen und diue Einarbeitungszeit allerdings nicht unterschätzen. WF ist komplex und aufwändig! Auch über das Deployment muss man sich Gedanken machen. Für gewöhnlich möchte man Workflows kurzfristig anpassen können, ohne eine komplett neue Programmversion ausrollen zu müssen. Geänderte Workflows müssen ihren Weg auf den Applikationsserver finden.
Wenn die Infrastruktur für das Hosting von WF-Workflows in den eigenen Anwendungen mal steht, hat man sich mit Sicherheit jede Menge Vorteile eingekauft. Man sollte es - wie mit allem - auch mit den Workflows nicht übertreiben. Einsetzen, wo sinnvoll, ansonsten lassen.

Hallo,
zumal die neue Version mit den alten wohl nicht ganz kompatibel ist.

Heißt es in diesem fall dann dass der Client mit den Workflows abeitet? Oder auch hier nur indirekt über Events der Services?

Workflows

Hallo daniel,

Workflows sind ein weites Feld und sehr vielseitig einsetzbar. Es ging ursprünglich ja um den konkreten Fall, dass vor der Löschung eines Mandanten geprüft werden muss, ob noch Aufträge unter diesem Mandanten existieren. Die Prüfung darf die Mandantenverwaltung allerdings nicht direkt durchführen, da dies zu einer zirkulären Abhängigkeit zwischen Mandanten- und Auftragsverwaltung führen würde. In diesem konkreten Fall könnte man das sehr elegant lösen, in dem ein als Workflow implementierter Dienst die Mandantenverwaltung überwacht (bzw. von dieser informiert wird).

Das heißt nicht, dass das immer der beste Weg ist. Mit WF können sowohl langlaufende (also z.B. über Stunden, Tage, Wochen oder sogar Jahre) als auch kurzlaufende Workflows abgewickelt werden. Man könnte einen WF-Workflow durchaus innerhalb eines Windows.Forms-Clients einsetzen, um zu steuern wann welche Knöpfe auf Menü- und Symbolleisten ausgegraut werden und wann nicht. Man kann aber auch ein großes Geschäft - welches über Monate läuft - inklusive Beschaffung, Terminierung, Auslieferung, Zahlung, Reklamation etc. über einen komplexen Workflow steuern.

Wenn Menschen in einen Workflow direkt eingebunden sind (um z.B. Eingaben zu machen oder etwas zu genehmigen bzw. abzulehnen) ist es sinnvoll, dass Clients direkt mit einzelnen Workflow-Instanzen kommunizieren. Mittels Hosting in Workflow Services und SOAP-Kommunikation lassen sich so vor allem auch fremde Applikationen in Workflows einbinden (man denke z.B. an InfoPath).

Es kommt auf die konkreten Geschäftsprozesse an, die abgebildet werden sollen, ob und in welcher Form Workflows vorteilhaft sein. Eine pauschale Aussage gibt es da nicht. Wichtig ist, dass man die Geschäftsprozesse , die man als Software umsetzen möchte selbst sehr gut kennt auch versteht. Mangelndes Wissen und Verständnis über die Problemdomäne ist bei Software-Entwicklen generell ein Problem. Es scheint of viel interessanter zu sein, an der perfekten OR-Mapping-Implementierung zu feilen, statt sich mit den Problemen der Fachabteilungen rumzuärgern. Die Software-Architektur muss aufgrund der abzubildenden Geschäftsprozesse festgelegt werden.

Was für Geschäftsprozesse willst/musst Du denn umsetzen?

Hallo,
bei mir geht es in die Richtung die ich angeschnitten habe mit Mandant, Angebot,Auftrag, Bestellung, Lager usw.

Das verstehen der Prozesse sehe ich auch als enorm wichtig.
Überlegt hatte ich zwei Richtungen

  1. ein Workflow geht vom Erstellen eines Angebots über die Komplettierung bis zum "Ausgabe"
    und (oder)
  2. ein Workflow geht vom Angebot über den Auftrag zur Lieferung. Wobei hier in regelmäßigen Abständen alle die bei Angebot hängen rausfliegen.

Allgemein habe ich mich für eine Servcie-orientierte Architektur entschieden weil verschiedene Services noch an anderen Stellen benötigt werden z.b. Kontaktdaten für die Telefonanlage und Exchange, kleinere Anwendungen in der Produktion sowie Lager.
Wobei ich hier noch nicht ganz sicher bin wie ich die Services am besten hoste. Erste überlegungen gehen wohl richtung Windows Service da ich hier zentrale dinge wie logging und caching habe. Alternativ könnte jeder Service sein eigenes Caching haben.

Haupt-Client seitig ist es eine modularen Anwendung die das Composite WPF von p&p nutzt.

Angebote & Lieferung/Rechnung

Hallo daniel,

ich würde den Lead/Anfrage/Angebots-Workflow vom Auftrags-Workflow trennen. In Auftragserfassung/Lieferung und Rechnungswesen ändert sich der Ablauf eher selten. Bei Vetriebs-Ablufen, wie Angeboten, ist damit zu rechnen, dass sich häufiger etwas am Ablauf ändert oder neue Geschäftsregeln zusätzlich installiert werden.

Was meist Du mit Caching? Was sollen die Dienste denn genau cachen?

Hallo,
dabei dachte ich beispielsweise daran, dass irgendwelche Textbausteine oder ähnliches.

Nochmal rekapituliert:
Workflows Services, welche die Geschäftsprozesse abbilden abonnieren Events der Services um sich dort "einzuschalten".
Hab ich das soweit richtig verstanden?

Verständnisfrage

Workflows Services, welche die Geschäftsprozesse abbilden abonnieren Events der Services um sich dort "einzuschalten".
Hab ich das soweit richtig verstanden?

Grundsätzlich ja.

Allerdings sind mit Events nicht unbedingt C#-Events im Sinne von Button.Click zu verstehen. Eine allgemeinere Bezeichnung wäre: Publish/Subscribe-Mechanismus.

Mit Caching wäre ich vorsichtig. Der SQL Server bringt ein sehr gutes Caching bereits automatisch mit. In den meisten Fällen ist es besser, solchen Kleinkram wie Textbausteine dann vom RDBMS abzurufen, wenn man es braucht. Es im Hauptspeicher des Applikationsservers herumgemmeln zu lassen, obwohl mehr als 99% der Laufzeit des Systems niemand auf Textbausteine zugreift, bringt denke ich, wenig. Im Gegenteil. Es vergeudet den kostbaren Arbeitsspeicher des Servers. Am Ende kostet der Abgleich zwischen Cache und DB mehr als das Caching brint, denn Textbausteine können ja auch geändert werden. Der Cache sollte dann ungültig werden und neu aufgefüllt werden bzw. aktualisiert werden.

Hallo,
ja so einen Publish/Subscribe Menachnismus war gemeint.

Wie ist sowas denkbar?
Jeder Service veröffentlicht direkt oder die Services veröffentlichen ihre Events im Host und die Publisher fragen im Host nach den Events?
Oder wie kann sowas aussehen?

/edit:
Vielleicht etwas blöd formuliert oben. Konkret meine ich ob das Events Publishing/Subscribing auch über WCF oder über den Host laufen sollte.

Beispiel von Dienst-Ereignissen

Wie ist sowas denkbar?

Es gibt verschiedene Möglichkeiten, so einen Mechanismus zu implementieren. Ich will eine davon kurz vorstellen. Ich nenne das Konzept mal Pre-Action/Action/Post-Action. Dabei können für jede Dienst-Operationen beliebig viele Pre- und Post-Aktionen registriert werden. Die Aktionen werden in EventAdapter-Klassen implementiert. Diese Adapter werden über eine XML-Konfigurationsdatei als Pre- oder Post-Aktionen bestimmter Dienst-Operationen konfiguriert und mittels Microkernel geladen.
Damit das Ganze etwas besser nachvollziehbar ist, gehe ich als Basis von meinem N-Tier-Architekturbeispiel aus. Dort gibt es einen Dienst zur Verwaltung des Artikelstammes. Angenommen beim Anlegen eines neuen Artikels soll ein Workflow angestossen werden, der über die Veröffentlichung des neuen Artikels im firmeneigenen Webshop entscheidet. Die Implementierung der Geschäftslogik (ProductManager) soll dazu nicht angerührt werden. Damit der Workflow getriggert werden kann, muss die Dienst-Operation zum Speichern von Artikeln (SaveProducts-Methode) der Infrastruktur mitteilen, dass vor bzw. nach der Ausführung der eigentlichen Geschäftslogik-Implementierung ggf. registrierte Ereignisempfänger aufgerufen werden sollen. Das könnte in der Praxis so aussehen:


/// <summary>
/// Speichert Produkt-Datensätze innerhlb einer Transaktion.
/// </summary>
/// <param name="products">Produktdatensätze</param>
/// <param name="transaction">Transaktion (Optional)</param>
/// <returns>Tabelle mit gespeicherten Produktdatensätzen</returns>
public ArticleMasterDataSet.ProductsDataTable SaveProducts(ArticleMasterDataSet.ProductsDataTable products, Transaction transaction)
{
    // Ländereinstellungen vom Aufrufer annehmen
    ApplicationServer.AssumeCultureFromSession();

    // Wenn der Aufrufer in der Rolle "Product Writer" ist ...
    if (ApplicationServer.IsInRole("Product Writer"))
    {
        // Variable für Rückgabe-Tabelle
        ArticleMasterDataSet.ProductsDataTable result = null;
        
        // Ggf. an Transaktion des Aufrufers teilnehmen
        Transaction.Current = transaction;

        // Automatische Transaktion erzwingen
        using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
        {
            // Parameterliste erzeugen
            Dictionary<string,object> parameters=new Dictionary<string,object>();
            parameters.Add("products",products);
            parameters.Add("transaction",Transaction.Current);

            // Kontextinformationen dieses Dienst-Operations-Aufrufs zusammenstellen
            ServiceOperationContext context = new ServiceOperationContext(typeof(IArticleMasterService), "SaveProducts", parameters);

            // Ggf. registrierte Pre-Actions ausführen ...
            ApplicationServer.ExecuteRegisteredServiceEventPreActions(context);

            // Aufruf an Geschäftslogik delegieren
            result = new ProductManager().SaveProducts(products, Transaction.Current);

            // Ggf. registrierte Post-Actions ausführen ...
            ApplicationServer.ExecuteRegisteredServiceEventPostActions(context);

            // Transaktion(steil) erfolgreich abschließen
            scope.Complete();
        }
        // Tabelle zurückgeben
        return result;
    }
    else
        // Ausnahme werfen
        throw new SecurityException(ApplicationServer.LocalizedAccessDeniedMessage);
}

Die Infrastruktur (in meinem Beispiel im Host implementiert) verwendet folgende XML-Konfigurationsdatei, um Abonnenten für Dienst-Ereignisse zu konfigurieren:


<?xml version="1.0" encoding="utf-8" ?>
<serviceevents xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
               xmlns="urn:Rainbird.AppServer.EventSubscriptions.2008">
  <!-- Dienst -->
  <service name="Rainbird.Examples.NTier.ArticleMaster.Contracts.IArticleMasterService">

    <!-- Dienst-Operation, die als Ereignis veröfentlicht werden soll -->
    <event name="SaveProducts">
      
      <!-- Post-Action Abonnement des Dienst-Ereignisses -->
      <subscription mode="post-action" 
                    assembly="Rainbird.Examples.NTier.WebshopPublishWorkflow.Adapter.dll"
                    class="SaveProductsPostActionEventAdapter" />      
    </event>
    
  </service>
</serviceevents>

Die Schnittstelle für Ereignis-Adapter sieht so aus:


using System;
using System.Collections.Generic;
using System.Text;

namespace Rainbird.AppServer.API
{
    /// <summary>
    /// Schnittstelle für Adapter-Implementierungen zur Behandlug von Dienst-Ereignissen.
    /// </summary>
    public interface IServiceEventAdapter
    {
        /// <summary>
        /// Implementierung dieser Methode wird ausgeführt, wenn ein abonniertes Dienst-Ereignis gefeuert wurde (vor Ausführung der Geschäftslogik).
        /// </summary>
        /// <param name="operationContext">Operations-Kontext</param>
        /// <returns>Schalter für erfolgreiche Ausführung</returns>
        bool PreAction(ServiceOperationContext operationContext);

        /// <summary>
        /// Implementierung dieser Methode wird ausgeführt, wenn ein abonniertes Dienst-Ereignis gefeuert wurde (Nach Ausführung der Geschäftslogik).
        /// </summary>
        /// <param name="operationContext">Operations-Kontext</param>
        /// <returns>Schalter für erfolgreiche Ausführung</returns>
        bool PostAction(ServiceOperationContext operationContext);
    }
}

Wenn Interesse besteht, kann ich eine, um Dienst-Ereignisse erweitere, Version meines Beispiels hier hochladen.

Der Ansatz hat den Vorteil, dass er relativ einfach anzuwenden ist, die Abonnenten via Microkernel entkoppelt sind und so ein Business-System erweitert werden kann, ohne dass es neu kompiliert werden muss. Außerdem lässt sich das Konzept auch nachträglich gut in Systeme einbauen, die von Grund auf synchron aufgebaut sind.
Nachteilig ist, dass die Ausführung der Pre- und Post-Aktionen synchron erfolgt. Die Ausführungszeit von, auf diese Art Ereignis aktivierten, Dienst-Operationen verlängert sich mit jedem zusätzlichen Abonnement. Natürlich kann man in der Ereignis-Adapter-Implementierung asynchron weiterarbeiten, aber diese Verantwotung obliegt dem Entwickler des jeweiligen Adapters.

Besser wäre es, die Mittelschicht grundsätzlich asychron aufzubauen (z.B. über ein Bus-System). Dafür gibt es auch fertige Produkte, wie z.B. den BizTalk Server. Der ist allerdings wieder so allgemein und zudem komplex, dass er für viele Entwickler eher abschreckend ist.

Jeder Service veröffentlicht direkt oder die Services veröffentlichen ihre Events im Host und die Publisher fragen im Host nach den Events?

In meinem kleinen Beispiel ist der Publish-Subscribe-Mechanismus genauso wie andere Infrastrukturdienste direkt im Host implementiert. Man könnte ihn aber auch als separaten Dienst/Komponente aufsetzen. Das lässt sich pauschal nicht sagen.

Hallo Rainbird,

Pre-Action/Action/Post-Action

Das ganze nennt sich auch einfach Aspektorientierung (definiert aber noch ein Paar weitere "Action's").

Gruß
Juy Juka

Aspekte

Das ganze nennt sich auch einfach Aspektorientierung (definiert aber noch ein Paar weitere "Action's").

Das ist so nicht ganz richtig. Es geht ja nicht darum technische bzw. Infrastruktur-Aspekte von der Geschäftslogik zu trennen, sondern die Geschäftslogik selbst zu strukturieren. Vor allem soll die Geschäftslogik ohne Neukompilierung nachträglich erweitert werden können. Die Erweiterung/Änderung einer Geschäftsprozess-Implementierung ist kein Aspekt. Aspekte wären z.B. Logging, Sicherheit, Transaktionen, etc..

Okay, man kann die Erweiterungsfähigkeit und was an Infrastruktur dazu benötigt wird auch als Aspekt betrachten. Die Pre- und Post-Aktionen könnte man auch sehr elegant mit AOP-Frameworks wie PostSharp technisch umsetzen. Das ist aber nur "Kosmetik". Ich wollte bei .NET Bordmitteln bleiben.

Wie Workflows bzw. sonstige Erweiterungen der Geschäftslogik aktiviert/getriggert werden ist im Detail gar nicht so wichtig. Ich hatte ja Eingangs erwähnt, dass ich nur Eine von Vielen Möglichkeiten vorstelle, wie man sowas machen kann. Mir kommt es auch gar nicht so auf die Konkrete Publish/Subscribe-Implementierung an, sondern darauf, dass Dienste einer Lösung nach bestimmten Regeln miteinander kommunizieren.

Wenn man die "Actions" via Aspekt aufruft, könnte der Code z.B. so aussehen:


/// <summary>
/// Speichert Produkt-Datensätze innerhlb einer Transaktion.
/// </summary>
/// <param name="products">Produktdatensätze</param>
/// <param name="transaction">Transaktion (Optional)</param>
/// <returns>Tabelle mit gespeicherten Produktdatensätzen</returns>
[PublishAsServiceEvent]
[Transaction(TransactionParameterName="transaction",TransactionRequired=true)]
public ArticleMasterDataSet.ProductsDataTable SaveProducts(ArticleMasterDataSet.ProductsDataTable products, Transaction transaction)
{
    // Ländereinstellungen vom Aufrufer annehmen
    ApplicationServer.AssumeCultureFromSession();

    // Wenn der Aufrufer in der Rolle "Product Writer" ist ...
    if (ApplicationServer.IsInRole("Product Writer"))
    {
        // Aufruf an Geschäftslogik delegieren
        return new ProductManager().SaveProducts(products, Transaction.Current);
    }
    else
        // Ausnahme werfen
        throw new SecurityException(ApplicationServer.LocalizedAccessDeniedMessage);
} 

Leute die sich nicht gerne wiederholen und sehr viel Wert auf schlanken Code legen, haben an einer Implementierung im AOP-Stil mit Sicherheit mehr Freude, als an meinem Imperativen Prozeduralen Ansatz. Trotzdem mag ich persönlich mich nicht so recht mit AOP anfreunden.

Hallo,
der Ansatz sieht interesant aus. Ähnliches habe ich mir auch vorgestellt allerdings ohne die "Konfiguration" über die XML Datei.

Asyncron kann das ganze ja aber nur dann ablaufen wenn die eigentliche Aktion
(

// Aufruf an Geschäftslogik delegieren  
result = new ProductManager().SaveProducts(products, Transaction.Current);  

)
nicht von Pre-Events abhängig ist wie z.B. das verhindern der Mandanten Löschung (was ich weiter oben mal als Beispiel erwähnte)

Oder wie meinst du das mit dem Bus-System bzw asyncronen?

Erweiterbarkeit ohne Abhängigkeit

Ähnliches habe ich mir auch vorgestellt allerdings ohne die "Konfiguration" über die XML Datei.

Wie sollte es aber ohne einen Microkernel mit Konfiguration funktionieren? Die Anwendung soll ja erweiterbar sein, ohne neu kompilieren zu müssen.

Asyncron kann das ganze ja aber nur dann ablaufen wenn die eigentliche Aktion
...
nicht von Pre-Events abhängig ist wie z.B. das verhindern der Mandanten Löschung

Die eigentliche Aktion weiss nichts von irgendwelchen Pre- oder Post-Aktionen. Sie ist auch nicht abhängig von diesen. Trotzdem kann eine Pre- oder Post-Aktion dafür sorgen, dass die Mandantenlöschung abgebrochen wird, wenn noch Aufträge vorhanden sind.
Wenn Du Dir nochmal meinen Beispiel-Code weiter oben anschaust (den ohne Aspektorientierung), wird Dir auffallen, dass Pre-Aktionen, die eigentliche Aktion und auch die Post-Aktionen alle innerhalb des selben TransactionScopes ausgeführt werden. Eine Pre- oder Post-Aktion muss deshalb nur eine Ausnahme werfen, um die Mandantenlöschung zu verhindern. Wenn innerhalb eines TransactionScopes eine Ausnahme geworfen wird, führt das automatisch zu einem Rollback der Umbegungstransaktion. Natürlich wird damit auch das ggf. schon abgesetzte "DELETE FROM Mandators WHERE MandatorID=@mandatorID" Statement rückgängig gemacht.
Es würde sogar gehen, wenn man keine Datenbank, sondern z.B. nur eine CSV-Datei hätte. Man müsste dann eben einen kompensierenden Ressourcen-Manager schreiben, der Transaktionsverhalten für CSV-Dateien implementiert.

Oder wie meinst du das mit dem Bus-System bzw asyncronen?

Wenn man die Abgängigkeiten zwischen Diensten und auch ggf. zwischen ganzen Anwendungen (Stichwort EAI) komplett eliminieren möchte, sollte man über den Einsatz eines Enterprise Service Bus nachdenken. Das Prinzip dabei ist einfach: Statt dass alle Dienste und Anwendungen direkt miteinander reden, reden sie mit einer zentralen Infrastruktur, dem Bus. Damit sit kein Dienst mehr vom anderen abhängig, sondern alle sind nur noch vom Bus abhängig. Folgende Skizze zeigt, wie ein Enterprise Service Bus gedacht ist: http://www.tecchannel.de/_misc/img/detailoriginal.cfm?pk=351812&fk=456248&id=il-76296967492666493

Allerdings ist es alles andere als trivial, so eine Bus-Infrastruktur zu schreiben. Deshalb lohnt sich ein Blick auf fertige Produkte. Im Microsoft-Umfeld wäre das der Microsoft BizTalk Server. Der BizTalk Server funktioniert folgendermaßen: MSDN: The BizTalk Server 2006 Messaging Engine

Solche fertigen Infrastruktur-Produkte für SOA sind allerdings nicht billig. Die Einsteiger-Version des BizTalk Servers liegt bei ca. 8000 EUR. Mit Produkten anderer Hersteller sieht es ähnlich aus. Die Frage ist, ob man es für die Lohnkosten von 8000 EUR selber so hinbekommt, einen Enterprise Service Bus zu implementieren.

@Rainbird: Kennst du Berichte über die Verbreitung solcher Servicebusse? Wenn man einen Software entwickelt, die man an die Frau/Mann(Firma) bringen will, kann man ja nur schlecht sagen: "Ja bevor Sie die Software einsetzten können, müssen Sie sich erstmal einen Service Bus für 10-30t € kaufen".
Sind die ESB alle so standardisiert, dass man nicht für jeden ESB, eine eigene "Anbindung" schreiben muss?

Solche fertigen Infrastruktur-Produkte für SOA sind allerdings nicht billig. Die Einsteiger-Version des BizTalk Servers liegt bei ca. 8000 EUR. Mit Produkten anderer Hersteller sieht es ähnlich aus. Die Frage ist, ob man es für die Lohnkosten von 8000 EUR selber so hinbekommt, einen Enterprise Service Bus zu implementieren.

Wenn den so hinbekommen würdest, dürften die Lohnkosten auch "etwas" höher sein! *g

Gruß Timo

p.S. Ich stehe jetzt nicht unmittelbar davor eine Software zu entwickeln, die einen ESB einsetzt. Ich frage nur auch gerne mal prophylaktisch.

Witzige Vorstellung, das die verscheidenen ESB zueinander Kompatible sein sollten.

Natürlich sind sie es nicht.
Du musst dich für einen entscheiden, oder sehr viel Zeit investieren um
eine generische anbindung zu schaffen.

Hallo zusammen,

ich kann auch nur bestätigen: Wenn EAI, dann BizTalk. Das ist meiner Meinung nach das wohl ausgereifteste Produkt in dieser Branche. Vor allem die Skalierbarkeit und die Modulare Anbindung an Fremdsysteme ist erstaunlich.

Das selbst nachimplementieren zu wollen halte ich für unaufwändbar.

Ich arbeite nun seit etwa 3 Monaten intensiv mit BizTalk und bin einfach nur begeistert von diesem Produkt, und kann jedem nur empfehlen, der es sich leisten kann. (Die Nutzungsgrad des Systems ist enorm bei entsprechenden Anwendungsfällen natürlich)

Grüße
Norman-Timo

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

Witzig? Ich dacht, dass wäre ein Grundgedanke von SOA. Aber danke für die Info!

Gruß Timo

Da hast du dann aber das Kleingedruckte überlesen.

SOA meint nur, das Du Dienste angeboten bekommst, die Du
mit einem Standartverfahren ansprechen kannst.

Nicht das alle die gleichen Dienste anbieten.

Probleme der heutigen Software

@Rainbird: Kennst du Berichte über die Verbreitung solcher Servicebusse? Wenn man einen Software entwickelt, die man an die Frau/Mann(Firma) bringen will, kann man ja nur schlecht sagen: "Ja bevor Sie die Software einsetzten können, müssen Sie sich erstmal einen Service Bus für 10-30t € kaufen".

Da ist das Problem. Es ist auf Dauer nicht haltbar, dass Business-Software als abgeschlossene Produkte verkauft wird. Okay, für eine fünf Mann Firma geht das. Für den Mittelstand geht es schon nicht mehr. Mehr als die Hälfte aller Software-Projekte scheitern. Das solte aber nicht sein.

Statt eine "Software" zu kaufen, sollten die Firmen Dienste/Komponenten kaufen und diese Orchestrieren. Mit WCF, WPF und WF sind die nötigen Basis-Technologieen für solche Software eigentlich schon vorhanden. Aber es gibt wenige bis gar keine Beispiele für Software, die wirklich so entwickelt wurde. Bisher hat sich das noch niemand getraut. Die aktuellen ERP-Systeme auf dem Markt sind alle samt alt und gewachsen. Die haben noch ganz andere Probleme. An autonome, Policy-unterstützte und über standardisierte Protokolle ansprechbare Dienste/Komponenten ist da gar nicht zu denken.

Hallo Rainbird,

Du hast das schön auf den Punkt gebracht. So eine Software-Landschaft wäre spitze. Dann wäre z.B. ein neuer Webshop schnell zusammen getragen: Kaufe Artigkelveraltung X, kombiniere mit Bestellsystem Y und übergib ergebnis an Backorder-System Z (kennt dafür jemand eine Deutsch übersetzung?). Und bei allen Komponenten höchstens Konfigurationseinstellungen, damit die Schnittstellen zu einander passen.

Gruß
Juy Juka

Und bei allen Komponenten höchstens Konfigurationseinstellungen, damit die Schnittstellen zu einander passen.

Um ein klein wenig XSLT fürs Umwandeln von Nachrichten eines Format von Komponente A in das Format von Komponente B, wirst Du wohl nicht herumkommen. Etwas weiter gefasst, kann man das allerdings als "Mapping-Konfiguration" durchgehen lassen.

Hätte ein freies Projekt in diesem Bereich eine Chance sind durchzusetzen, was meint ihr?

Hallo Rainbird,

Hätte ein freies Projekt in diesem Bereich eine Chance sind durchzusetzen, was meint ihr?

Nur wenn es wirklich extrem Flexiebel wäre und vor allem sehr viele bereits Konfigurierte Anbindungen an bestehende Fremdsysteme mit liefert, die man "nur noch" auswählen muss (am Bessten gleich mit einem Asisstenten).

Und ich würde sogar so weit gehen, dass es verschiedene Möglichkeiten für die Anbindung unterstützen sollte, nicht nur (z.B.) SOAP oder XML im allgemeinen, sondern auch z.B. direkte Programmierung in verschiedenen Programmier-Sprachen (mit .NET + Interpo liese sich ja gleich eine Menge abdecken).

Aber das ganze wird dann auch gleich wieder so kompliziert zu Konfigurieren und Fehleranfällig, dass man auch gleich den Quellcode für das Verbinden von zwei konkreten Systemen schreiben kann (ich tuhe 50% meiner Arbeitszeit nichts anderes).

Es währe also eine sehr schwierige Gradwanderung zwischen Flexibilität, Robustheit und Einfachheit.

Gruß
Juy Juka

Aber das ganze wird dann auch gleich wieder so kompliziert zu Konfigurieren und Fehleranfällig, dass man auch gleich den Quellcode für das Verbinden von zwei konkreten Systemen schreiben kann (ich tuhe 50% meiner Arbeitszeit nichts anderes).

Nein nein. Man verbindet gar nichts direkt miteinander. Es ist ein Bus-System. Es gibt generische Schnittstellen für Adapter. Diese Adapter ermöglichen die Kommunikation mit dem Bus. Das heißt Senden und Empfangen von Nachrichten. Man integriert nicht die einzelnen Anwendungen/Dienste miteinander, sondern man integriert alle Aanwendungen/Dienste mit dem Bus (via Adapter). Die Rechnung ist einfach. Ich muss nur einmal eine Anbindung an den Bus schreiben, egal mit wie vielen anderen Anwendungen/Diensten meine eben integrierte Anwendung reden muss. Bei Anwendungen/Diensten, die bestimmte Standards wie SOAP oder REST unterstützten, kommt ein generischer Adapter zum Einsatz. Im Großen und Ganzen bleibt nur der Import von Nachrichtenschemas der angebundenen Anwendungen/Dienste und das Umwandeln und Routen von Nachrichten zwischen den Anwendungen/Diensten über den Bus.

Konfiguration, Deployment und Test- sowie Debugging-Möglichkeiten müssten einfach sein. Aspekte wie Sicherheit müssten per Konfiguration festgelegt werden können (Policies).

An welcher stelle würdet ihr die Prüfung ob sich ein objekt veränder hat halten?

Auf Client Seite, oder auf Server seitig der das original sowie das geänderte Objekt kennt und dies dann "weiß" ?

Stichwort INotifyPropertyChanged.
Daher Server.