Lose Koppelung durch Messaging und Domain Events – Teil 2

Teil 2 – Ein paar Gedanken zu objektorientierte Prinzipien und Implementierung des EventAggregator

Vorheriger Teil – Teil 1 – Aggregate mittels Domain Events entkoppeln und synchronisieren

Einleitung und ein paar Überlegungen

Im ersten Teil der Blogreihe habe ich bereits über Gründe, warum die Entkoppelung von Komponenten so wichtig ist, gesprochen. Anhand einer einfachen Anforderung habe ich gezeigt, wie schnell Komponenten ungünstigerweise miteinander verdrahtet sind. Mit wachsenden Anforderungen steigt die Komplexität einer Anwendung. Ein klar erklärtes Ziel von Domain-Driven Design ist aber die Reduzierung von Komplexität. Ein wirkliches Problem sind Abhängigkeiten. Je komplexer Anforderungen, desto mehr Abhängigkeiten scheint es zu geben. Doch welche Strategie ist die richtige, um Komplexität zu beherrschen. Leider gibt Eric Evans Standardwerk „Domain-Driven Design Tackling Complexity in the Heart of Software“ [DDD] nicht immer ausreichend Antworten dazu. Auch sind die Interpretationen, wie man Domain-Driven Design richtig anwendet, teilweise extrem verschieden. Vielleicht gibt es auch nicht nur den einen richtigen Weg?!

Aus meiner Sicht basiert die Implementierung von Domain-Driven Design auf objektorientierten Prinzipien. Ohne diese ist es nicht möglich, Domänenmodelle zu entwerfen und in lauffähigen Code zu übersetzen. In der Definition von Objektorientierung nach Alan Kay ist alles ein Objekt, wobei Objekte die Instanz einer Klasse sind. Klassen bestehen dabei aus Struktur und Verhalten. Der Zugriff auf die Daten eines Objektes erfolgt über Schnittstellen (damit sind Methoden gemeint), um inkonsistente Objektzustände durch Beeinflussung von außen zu vermeiden. Dahinter steckt im Übrigen das Prinzip der Kapselung (auch Geheimnisprinzip genannt).

Ein typisches Beispiel für die Verletzung des Geheimnisprinzips ist häufig die Verwendung von öffentlichen Settern, die ungewollte Manipulation von Daten eines Objektes zulassen. Im schlimmsten Fall ist es Clientcode möglich, die Identität eines Objekts zu ändern, siehe Abbildung 1.


Abbildung 1 Verletzung des Geheimnisprinzips – Client Code kann ungewollte Manipulierungen am Objekt durchführen

Das Problem, das in Abbildung 1 besteht, ist zum Beispiel folgendes: Client Code bezieht über einen Repository– Aufruf eine Produktinstanz aus der Datenbank. Durch einen Programmierfehler ändert der Client Code die ID des Produkts und gibt die Instanz an das Repository, um es zu speichern. Da es sich nun um eine neue Identität handelt, wird das Objekt in der Datenbank als neue Zeile eingefügt. Klar ist dieses Szenario hypothetisch; es könnte sicher weitere Prüfungen im darunterliegenden Code geben, die dieses Fehlverhalten verhindern etc. Jedoch sollte so ein Problem durch die konsequente Anwendung des Geheimnisprinzips von vornherein vermieden werden. Die Daten dürfen nur kontrolliert über Methoden änderbar sein. Daten die nach der Objektinitialisierung invariant sein müssen, dürfen gar nicht mehr änderbar sein. Diese sind als readonly Felder anzulegen, siehe Abbildung 2. In der Realität existiert Code wie in Abbildung 1 leider viel zu häufig.


Abbildung 2 Korrekte Implementierung – Manipulation der ID ist nicht mehr möglich

Objekte kommunizieren nach Alan Kay immer über Nachrichten miteinander. All diese genannten Designprinzipien als auch die SOLID Prinzipien sind meiner Auffassung nach nötig, um Domain-Driven Design sauber zu implementieren. Ich werde mich in einer der nächsten Blogausgaben nochmals näher damit beschäftigen bzw. darauf eingehen, vor allem warum Domänenobjekte (Entities und Aggregate im Sinne von DDD) immer aus Daten und Verhalten bestehen. Das Erzeugen eines Entitymodells mit Hilfe eines O/R Mappers erzeugt keine Domänenobjekte, sondern lediglich um Datenzugriffsobjekte (DAO, vgl. auch [IDDD] S. 440-441); auch wenn es fälscherlicherweise häufig so gesehen wird, dass es sich bei Datenbanktabellen, die in Klassen übersetzt wurden, um Domänenobjekte handelt. Das ist aber nicht so, da die Klassen nur aus Struktur bestehen und damit nur Datenhalter sind. Domänenwissen ist aber auch Verhalten! Übrigens: Gerade weil O/R Mapper noch CRUD Funktionalität erzeugen, handelt es sich eher um das Table Module Konzept [Fowler], was sich grundsätzlich vom DDD Ansatz unterscheidet.

In der letzten Zeit hat sich in der DDD Community vor allem der Einsatz von Domain Events etabliert. Domain Events spielen eine wichtige Rolle, wenn es darum geht, Komponenten voneinander loszulösen. Nötig sind sie auch, wenn man CQRS anwenden möchte. Auf CQRS werde ich in einem weiteren Beitrag näher eingehen.

Die EventAggregator Implementierung

Nun aber zurück zum Thema Entkoppelung von Aggregaten und der versprochenen Implementierung des EventAggregator. Der EventAggregator soll als zentraler Messagebus innerhalb einer Anwendung agieren. Das Prinzip wie die losgelösten Aggregate miteinander kommunizieren sollen, habe ich in der vorherigen Ausgabe erläutert und schematisch dargestellt.

Der Anspruch dabei ist, dass keine zusätzlichen Systemkomponenten wie beispielsweise eine MSMQ o.ä. benötigt werden, da verteilte Szenarien nicht im Fokus stehen. Vaughn Vernon [IDDD] verwendet in seinen Beispielen eine statische EventPublisher Klasse, um Domain Events zu veröffentlichen. Ich möchte den EventAggregator allerdings gerne aus dem IoC-Container auflösen und per Konstruktorinjektion in betreffende Service- und Repository– Klassen injizieren. Deswegen zunächst die Schnittstellen-Definition. Der EventAggregator soll die beiden Methoden Publish und Subscribe bereitstellen, siehe Abbildung 3. Bei der Schnittstelle IDomainEvent handelt es sich um ein Markerinterface.


Abbildung 3 Die Schnittstellendefinition des EventAggregator

Um eine häufige Frage an dieser Stelle zu beantworten: Dependency Injection ergänzt den DDD Ansatz hervorragend. Alle Komponenten wie Services, Repositories oder Datenbankadapter sollten in einem Container registriert und daraus auflösbar sein. Domänenobjekte entspringen aber niemals dem IoC Container. Für die Instanzierung von Entitäten und Aggregaten sind Repositories zuständig. Benötigt also ein Aggregat eine Instanz des EventAggregator, so reicht das Repository seine injizierte Instanz der Klasse an den Konstruktor des Aggregates weiter.

Bei der Implementierung des EventAggregator habe ich mich stark an dieser Implementierung von Udi Dahan orientiert. Seine Implementierung der DomainEvents-Klasse ist statisch. Ich möchte aber soweit es geht alle Instanzen aus dem IoC-Container beziehen. Einen Grund, warum die Klasse statisch sein sollte, habe ich nicht direkt erkannt, sofern es im gesamten Anwendungskontext möglich ist, die Instanz des EventAggregator als Singleton im IoC-Container zu halten. Wird der Container allerdings immer Per-Call neu erstellt, so sollte bzw. muss der EventAggregator statisch sein.

In meinem Beispeil aus Teil 1 ging es darum, ein Domain Event zu veröffentlichen, wenn die Adresse eines Kunden geändert wurde. So muss zunächst eine Implementierung des KundenAdresseGeändert-Ereignisse erstellt werden, siehe Abbildung 4.


Abbildung 4 CustomerAddressChanged Domain Event Implementierung

Domain Event Objekte sollten nach ihrer Instanzierung unveränderbar sein. Deshalb sollten auch hier wieder private Setter verwendet werden. Domain Events dürfen im Übrigen nicht dazu dienen, ganze Objekte (also z. B. die Customer-Instanz) zu verschicken, sondern nur Identifier oder einfache Datentypen. Benötigt der Empfänger (der Observer) eine Instanz, so muss er sich diese selbst aus seinem Repository besorgen. Das ist wichtig, weil damit vermieden wird, das der Empfänger möglicherweise eine veraltet Instanz erhält. Außerdem müssen die Klassen in verschiedenen Zusammenhängen nicht immer gleich aussehen.

Wurde in der Customer- Klasse nun die Adresse erfolgreich geändert, so wird quasi nur noch ein Domain Event veröffentlicht. Im Sinne des Observer Musters ist die Customer- Klasse das Subjekt. Das Subjekt weiß nicht, wer sich alles dafür interessiert. Die Abhängigkeiten zu der MailSender Komponente und dem Sachbearbeiter (DeskOfficer-Klasse) werden wir mit der Verwendung der Domain Events und dem EventAggregator endlich los. Die Implementierung der Customer- Klasse sieht exemplarisch nun aus wie in Abbildung 5.


Abbildung 5 Customer Klasse besitzt keine Abhängigkeiten mehr zu anderen Aggregaten, nur noch zum EventAggregator

Eine exemplarische Implementierung auf der Empfänger-Seite könnte beispielsweise so aussehen, Abbildung 6.


Abbildung 6 Implementierung des Subscriber Aggregates

Auch der Subscriber, in diesem Fall das DeskOfficer Aggregat, benötigt keine Abhängigkeit zum Versenden. Hier muss ebenfalls die EventAggregator Instanz verfügbar sein. Im Konstruktor wird der Nachrichtentyp registriert. Das erfolgt durch den Aufruf der Methode Subscribe<T> des EventAggregator. Als Parameter muss ein Delegat mit gegeben werden. Damit reagiert die DeskOfficer Instanz auf alle Nachrichten vom Typ CustomerAddressChanged, die auf dem EventAggregator veröffentlicht werden. Das Wissen, was mit der Nachricht zu machen ist, steckt in der OnCustomerAddressChanged-Methode des DeskOfficer Objektes. Das Geheimnisprinzip bleibt gewahrt. Kein anderes Objekt entscheidet, wie sich der DeskOfficer zu verhalten hat, wenn das Fachereignis „Kunde Adresse wurde geändert“ eingetreten ist.

Schauen wir uns zum Schluss noch die Implementierung des EventAggregator selbst an. Ich habe mich in diesem Beispiel auf die Verarbeitung der registrierten Delegate beschränkt, siehe Abbildung 7. Deswegen habe ich dieses Bespiel mit „einfache“ Implementierung betitelt. In der nächsten Ausgabe werde ich den EventAggregator so erweitern, dass er auch in der Lage ist, MessageHandler Implementierungen aus einem Unity-Container aufzulösen.


Abbildung 7 Einfache EventAggregator Implementierung

Ich hoffe, die ersten beiden Teile konnte die Vorteile von Messaging und der Entkoppelung von Komponenten nachvollziehbar aufzeigen. Über Feedbacks und Kommentare freue ich mich natürlich. Was haltet ihr von diesem Vorgehen?

Verwendete Literatur:

[IDDD] Vaughn Vernon, Implementing Domain-Driven Design, 2013, Addison-Wesley

[DDD] Eric Evans, Domain-Driven Design, 2003, Addison-Wesley

[Gamma] Erich Gamma, Design Patterns, 1994, Addison-Wesley

[Martin], Robert C. Martin, Clean Code, 2008, Prentice Hall

[Fowler], Martin Fowler, Patterns of Enterprise Application Architecture, 2003

Lose Koppelung durch Messaging und Domain Events – Teil 1

Teil 1 – Aggregate mittels Domain Events entkoppeln und synchronisieren

Ausgangssituation

Viele Prinzipien objektorientieren Software-Designs zielen auf Abstraktion und Entkoppelung hin. Ziel ist dabei unter anderem, dass Komponenten möglichst wenig Abhängigkeiten besitzen und somit leichter verständlich sowie einfacher weiter zu entwickeln sind. In der Realität sieht man oft das Gegenteil. Im Lebenszyklus eines Softwaresystems mehren sich die Abhängigkeiten zwischen Softwarekomponenten. Das macht den Code schwer wartbar, da Änderungen in einer Komponente dann zwangsläufig auch Änderungen in anderen Komponenten erfordern. Dies gilt es aber unbedingt zu vermeiden.

Bei der Anwendung von Domain-Driven Design (DDD) Prinzipien kommt schnell bei der Implementierung von Aggregaten im Entwicklerteam die berechtigte Frage auf, wie wird ein Aggregat von der Aktualisierung eines anderen benachrichtigt? Stellen wir uns vor, wir haben folgende fachliche Anforderung zu erfüllen:

„Ändert sich die Adresse eines Kunden, so muss der zuständige Sachbearbeiter per Email benachrichtigt werden.“

Gehen wir davon aus, dass wir Customer als auch DeskOfficer als Aggregate Root identifiziert haben.

Bei einer solch einfach anmutenden Anforderung werden in der Realität in der Regel jede Menge Design-Fehler begangen.

Übliche Implementierungen erzeugen zu viele Abhängigkeiten

In der folgenden Abbildung 1 sehen wir eine typische Implementierung, wie sie in der Realität häufig zu finden ist.


Abbildung 1 Typische Implementierung bricht das Single Responsibility Prinzip

Das erste Problem bei der Implementierung in Abbildung 1 besteht darin, dass das Aggregat Customer eine Abhängigkeit zu einer anderen Komponente, nämlich der MailSender Klasse, besitzt. Des Weiteren wird hier das Single Responsibility Principle (SRP) [Martin] verletzt. Die Methode „ChangeAddress“ tut nun etwas, was sie eigentlich nicht vorgibt zu tun. Sie ändert nämlich nicht nur die Adresse, sondern versendet auch eine Mail. Für Client-Code ist das aber nicht erkennbar.

Nun, man könnte die Methode umbenennen in ChangeAddressAndSendAddressChangeMessage o.ä. Diese würde das Problem nicht wirklich lösen. Käme eine weitere Anforderung hinzu, dass der zuständige Sachbearbeiter bei der Adressänderung auch direkt per „Notification“ in seiner Anwendung benachrichtigt werden soll, so würde der Methodenname wieder angepasst werden müssen (z.B. ChangeAddressAndSendAddressChangeMessageAndNotifyDeskOfficer). Methodenname als auch der Code der Methode würden im länger und unleserlicher werden.

In diesem Fall würden sogar noch eine Abhängigkeit zum Aggregat DeskOfficer hinzukommen, um auf diesem die Methode Notify aufzurufen, siehe Abbildung 2. Das Problem von Abhängigkeiten wäre aber ohnehin nicht gelöst. Single Responsibility wird damit auf keinen Fall erreicht.


Abbildung 2 Noch mehr Abhängigkeiten

In Abbildung 3 ist zu sehen, dass das Customer-Aggregat von den anderen beiden Aggregaten direkt abhängig ist. Zwar schreibt Eric Evans [DDD], dass ein anderes Aggregate außerhalb der Aggregatsgrenze referenziert werden kann, falls es nötig ist. Dies führt aber meiner Meinung nach zu großen, unübersichtlichen Objektgraphen und unnötigen Abhängigkeiten. Vaughn Vernon [IDDD] schlägt vor, dass Aggregate ausschließlich durch „Identity References“ –also IDs- auf einander verweisen sollten. D. h. beispielsweise, dass unser Customer Aggregat nur die ID zum verantwortlichen Sachbearbeiter (ResponsibleDeskOfficer) hält, aber keine Objektinstanz. Damit können zudem auch mögliche Performance-Probleme, die bei der Objektinstanzierung auftreten könnten, gelöst werden. Gibt es Änderungen am Customer-Objekt, die auch für den verantwortlichen Sachbearbeiter interessant sind, so muss die betreffende Instanz durch ein Domain Event (Fachereignis) benachrichtigt werden. Um zwei Aggregate, die nichts voneinander wissen, zu synchronisieren, werden demnach Domain Events benötigt. Mit dieser Technik erreicht man die gewünschte Entkoppelung der Komponenten.


Abbildung 3 Das Customer Aggregat muss andere Aggregate referenzieren, um seine Aufgaben zu erfüllen.

Bevor ich weiter auf Domain Events eingehe, hier noch ein weiterer ungünstiger -aber oft verwendeter- Lösungsansatz, bei dem die Aufrufe in eine darüberliegende Dienstklasse, z. B. einem CustomerModifyDomainService verlagert werden, siehe Abbildung 4. Das ist aus meiner Sicht nicht wirklich besser, da das Problem der Abhängigkeiten und Methoden-Benennung nur eine andere Ebene verlagert werden würde.


Abbildung 4 Auch keine Lösung, nur eine Verlagerung des Problems…

Domain Events

Schauen wir durch die DDD-Brille, so können wir aus der o.g. Anforderung ein Domain Event, sprich ein fachliches Ereignis, herauslesen. Im Grunde kann man auf einer abstrakten Ebene sagen: Wenn das Ereignis „Kundenadresse wurde geändert“ eingetreten ist, so muss in der Folge eine Email versendet werden und der Sachbearbeiter direkt im System benachrichtigt werden. Das fachliche Ereignis löst also weitere Aktionen aus. In einem Fachmodell sollte nun ein Ereignis „CustomerAddressChanged“ existieren. Zu beachten ist, dass Domain Events immer in der Vergangenheit formuliert werden. Diese Ereignis tritt immer dann ein, wenn die Adresse eines Kunde verändert wurde. Den Befehl (Command), der das Ereignis auslöst, besitzt das Aggregat Customer in Form der Methode ChangeAddress. Wie andere Aggregate auf dieses Ereignis reagieren müssen, liegt nicht im Verantwortungsbereich des Customer-Aggregates (Seperation of Concerns).

Reden Fachexperten über ihre Fachdomäne, so formulieren sie häufige Ereignisse in der Form „Wenn dies passiert ist, dann muss danach das und das passieren/ informiert werden…“ o.ä.

Domain Events sind Teil der Ubquitious Language [DDD] und müssen sich deshalb auch im Domänenmodell bzw. im Code wiederfinden. Im Gespräch mit den Fachexperten muss herausgefunden werden, wer sich für welches Ereignis interessiert und wie derjenige darauf reagieren muss.

Domain Events sind im Grunde nichts anderes Nachrichten, die zwischen Komponenten eines Systems ausgetauscht werden. Dies entspricht dem Observer Muster [Gamma]. Ein Subjekt veröffentlicht eine Nachricht und ein oder mehrere Beobachter (Observer) abonnieren diesen Nachrichtentyp. Dieses Muster ist auch als Publish-/ Subscribe Muster bekannt. Der Vorteil ist, dass die Komponenten sich nun nicht mehr kennen bzw. miteinander verdrahtet sein müssen.

Für unseren Fall ergibt sich folgende Struktur, Abbildung 5. Die Kommunikation zwischen den Komponenten erfolgt nur noch in Form von Nachrichten. Die Aggregate Root
Customer referenziert nun keine Instanz der Aggregate Root DeskOfficer mehr, sondern hat nur noch eine Identity Reference [IDDD], sprich eine ID, siehe auch Abbildung 6. Ändert sich die Kundenadresse, erfolgt dies durch den Aufruf des ChangeAddress– Commands (Methode) auf einer Customer-Instanz. Dieser Command löst das Domain Event CustomerAddressChanged aus, welches auf einem Message Bus (EventAggregator –Klasse) veröffentlich wird. Alle Aggregate, die diesen Nachrichtentyp abonniert haben, reagieren darauf und können ihre Aufgabe erfüllen. So kann die betreffende DeskOfficer Instanz eine Notification erstellen etc. Für das Versenden der Email würde ich einen spezifischen MessageHandler erstellen, der verantwortlich ist, die richtige Email zu erstellen. Dieser Handler kann dann die MailSender-Komponente referenzieren, die den eigentlichen Versand übernimmt oder die Mail-Nachricht besser zunächst in einer Queue ablegt etc.

 
 


Abbildung 5 Entkoppelte Aggregate


Abbildung 6 Adresse ändern und Domain Event veröffentlichen

In diesem ersten Teil meiner neuen Blogreihe zum Thema „Lose Koppelung durch Messaging“ habe ich eine grundsätzliche Idee geliefert, wie Komponenten entkoppelt werden können. Im nächsten Teil zeige ich, wie die benötigten technischen Komponenten wie EventAggregator und MessageHandler in C# realisiert werden und wie wenig Coding-Aufwand dafür eigentlich notwendig ist.

Als weiterführende Literatur zur Thematik möchte ich vor allem Vaughn Vernons „Implementing Domain-Driven Design“ empfehlen.

 
 

Verwendete Literatur:

[IDDD] Vaughn Vernon, Implementing Domain-Driven Design, 2013, Addison-Wesley

[DDD] Eric Evans, Domain-Driven Design, 2003, Addison-Wesley

[Gamma] Erich Gamma, Design Patterns, 1994, Addison-Wesley

[Martin], Robert C. Martin, Clean Code, 2008, Prentice Hall

Validation Application Block und WCF

Begleitend zu meiner DDD Kolumne in der Fachzeitschrift Visual Studio One, bei welcher es in den letzten beiden Ausgaben um das Thema Validierungen ging, möchte ich in diesem Artikel den Einsatz des Validation Application Block der Enterprise Library 5.0 näher betrachten. Die Enterprise Library ist dafür bekannt, dass sie Best-Practise-Konzepte in Form wiederverwendbarer Komponenten bereistellt. Die aktuelle, seit Mai 2011 verfügbare Version 5.0, ist im Download Center auf der Microsoft Webseite erhältlich.

Zum Download

Seit November 2011 ist zudem noch ein Integrationpack für Windows Azure verfügbar.

Neben dem Validation Application Block (VAB) stellt die Library auch andere nützliche Bibliotheken, wie den Unity DI-Container, eine Datenzugriffskomponente sowie Cache-, Logging-, Security- und Exception-Komponenten. Die letztengenannten sind einsetzbar, um typische schichtenübergreifende Aufgaben zu erledigen. Durch den Einsatz kann vor allem Entwicklungszeit gespart werden. In diesem Artikel möcnte ich mich den Vorteilen des Einsatzes in einer Serviceschicht unter Verwendung von WCF widmen.

Nach der Installation der Microsoft Enterprise Library 5.0 (EntLib5) auf der lokalen Maschine, sollten die für das Projekt benötigten Bibliotheken in einen Projektordner kopiert werden, damit die Solution überall lauffähig ist, ohne das auf jeder Entwicklermaschine eine Installation der EntLib5 notwendig wird.

Dieses Vorgehen ist generell zu bevorzugen. Drittanbieterbibliotheken, die in einem Projekt verwendet werden, sollten nie vom Installationsort bezogen werden, sondern immer in einem separaten Projektordner (z.B. in einem Ordner namens „Shared Binaries“. ) abgelegt werden und in die Quellcodeverwaltung eingecheckt werden, so dass die Solution auf allen Entwicklermaschinen lauffähig ist. Konzepte wie Continuous Integration erzwingen ein solches Vorgehen. Nutzt man Binärdateien, die nur lokal verfügbar sind, schlägt der Team-Build fehl.

Der nun zu betrachtende Fall geht davon aus, dass ein Dienst in Form einer öffentlichen API bereitgestellt wird, der Daten liefert als auch verarbeitet. Dafür stellt dieser die notwendigen Operationen bereit. Die Visualisierung der Daten könnte nunmehr durch verschiedene UI Komponenten unterschiedlichster Technologien erfolgen. Für mich bedeuted dies ebenfalls, dass der Service in der Pflicht ist, eingehende Requests zu validieren und bei Nichterfüllung fachlicher Anforderungen, die Anfrage zurückzuweisen. Es wäre fatal, sich darauf zu verlassen, dass der Aufrufer des Dienstes alle Validierungen korrekt durchgeführt hat.

Eine bewährte Methode besteht darin, die DataContracts so auszustatten, dass zumindest formelle Prüfungen schon beim Empfang der Nachrichten stattfinden. Hier kommt nun der Validation Application Block zum Einsatz.

Im konkreten Beispiel möchten wir einen kleinen Webshop entwickeln. Jegliche Geschäftslogik soll auf einem Application-Server laufen, der nach außen von einem WCF-Service repräsentiert wird. Für die Darstellung im Web dient ein ASP.NET MVC Projekt, welches die WCF-Dienste konsumiert. Das Projekt befindet sich der Einfachheit halber in der selben Solution. Dem WCF-Dienste Projekt „Shopping.Services“ müssen die Verweise auf die beiden Bibliotheken „Microsoft.Practices.EnterpriseLibrary.Validation“ und „Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF“ hinzugefügt werden [ Siehe Abb. 1].Abb. 1

Der CustomerService stellt für das Erstellen neuer Kunden eine Operation CreateCustomer bereit, welche eine Nachricht vom Typ CreateCustomerRequest entgegennimmt. Diese Serviceschnittstelle sieht wie folgt aus [Listing 1]:

Listing 1

namespace Shopping.Service
{
    using System.Collections.Generic;
    using System.ServiceModel;
    using Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF;
    using Shopping.Service.DataContracts;
    using Shopping.Service.Messages;

    [ServiceContract]
    [ValidationBehavior]
    public interface ICustomerService
    {
        /// <summary>
        /// Liefert eine Auflistung aller Kunden.
        /// </summary>
        /// <returns></returns>
        [OperationContract]
        IEnumerable<CustomerInformation> GetAllCustomers();

        /// <summary>
        /// Erstellt einen neuen Kunden.
        /// </summary>
        /// <param name="request">Die Anfrage.</param>
        [OperationContract]
        [FaultContract(typeof(ValidationFault))]
        void CreateCustomer(CreateCustomerRequest request);

        /// <summary>
        /// Löscht einen Kunden aus dem System, sofern möglich.
        /// </summary>
        /// <param name="request">Die Anfrage.</param>
        [OperationContract]
        void DeleteCustomer(DeleteCustomerRequest request);
    }
}

Der Schnittstelle muss das Attribute ValidationBehavior hinzugefügt werden. Damit wird quasi ein WCF-Behavior angeschalten, welches sich in den Nachrichtenkanal einklinkt und die eingehende Nachricht überprüft. Die Operation selbst benötigt das FaultContract -Attribut vom Typ ValidationFault, damit im Fehlerfall eine sinnvolle Fehlermeldung an den Aufrufer gegeben werden kann.

Die Nachricht CreateCustomerRequest sieht wie in Listing 2 dargestellt aus. Die Nachricht besitzt einen Member vom Typ CustomerCreate. CustomerCreate [Listing 3] ist der spezifische DataContract, welcher exakt auf den UseCase „Kunde anlegen“ zugeschnitten ist. Die Klasse besitzt nur die Felder, die bei der Aktion „Kunde anlegen“ verwendet werden dürfen. Dies ist wichtig, da unnötige Member in der Regel Verwirrung stiften. Ich habe schon oft gesehen, dass ein einziger DataContract für mehrere Operationen verwendet wird. Z.B. gleichzeitig für das Erstellen, Bearbeiten und um Details anzuzeigen. Davon ist allerdings abzuraten. In einem solchen Fall ist unklar, welche Eigenschaften im speziellen Fall überhaupt benötigt werden. Daher ist es aus meiner Sicht unerlässlich, für jede Operation einen zugeschnittenen DataContract bereitzustellen.

In Listing 2 ist zu sehen, dass der Member NewCustomer mit dem Attribut ObjectValidator versehen wurde. Dieses ist dafür verantwortlich, dass die Validierung des DataContract erfolgt. Lässt man dieses Attribut weg, dann wird die Validierung des NewCustomer nicht durchgeführt.

Listing 2

namespace Shopping.Service.Messages
{
    using System.ServiceModel;
    using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
    using Shopping.Service.DataContracts;

    [MessageContract]
    public sealed class CreateCustomerRequest
    {
        [MessageBodyMember]
        [ObjectValidator()]
        public CustomerCreate NewCustomer { get; set; }
    }
}

Listing 3

namespace Shopping.Service.DataContracts
{
    using System.Runtime.Serialization;

    using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

    [DataContract]
    public sealed class CustomerCreate : ICustomerCreate
    {
        [DataMember]
        [NotNullValidator]
        [StringLengthValidator(2, 256)]
        public string FirstName { get; set; }

        [DataMember]
        [NotNullValidator]
        [StringLengthValidator(2, 256)]
        public string LastName { get; set; }

        [DataMember]
        public string StreetAddress { get; set; }

        [DataMember]
        public string PostalCode { get; set; }

        [DataMember]
        [NotNullValidator]
        [StringLengthValidator(3, 256)]
        public string City { get; set; }

        [DataMember]
        public string Country { get; set; }
    }
}

Die Member der Klasse CustomerCreate, welche zwingend einen Wert erfordern, wurden mit dem Attribute NotNullValidator versehen. Dieser stellt sicher, dass der Member einen Wert enthält und nicht NULL ist. Dieses Kennzeichnung legt fest, dass es sich um ein Pflichtfeld handelt.

Zusätzlich habe ich noch den StringLengthValidator hinzugefügt. Dieser legt den Gültigkeitsbereich der Zeichenfolge fest. Hier kann man sozusagen schon am Service Feldlängen prüfen, so dass nicht erst ein Versuch unternommen wird, in die Datenbank -sofern eine relationale Datenbank als physischer Speicherort benutzt wird- zu schreiben, obwohl die dort zulässige Länge überschritten wurde.

Mit diesen wenigen Schritten ist es möglich, die Prüfung eingehender Daten am WCF-Service vorzunehmen.

Neben den beiden hier verwendenten Validierungsattributen gibt es natürlich noch eine Menge weiterer. Beispielsweise einen RangeValidator, der die Gültigkeit numerischer Werte prüft als auch einen RegexValidator, der z.B. bei Überprüfung von Email-Adressen gute Dienste leisten kann.

Auch das Erstellen eigener Validatoren ist denkbar einfach.

Nehmen wir an, der Member Country ist optional. Wird jedoch ein Wert angegeben, so sind nur bestimmte Länder gültig oder man möchte das Land gegen eine bestehende Liste prüfen, um sicher zu stellen, dass nur realistische Werte angegeben werden. Für diesen Fall implementieren wir ein Custom-Attribut [Listing 4]. Dieses Attribut ruft die Implementierung CountryValueValidator auf, welche die Überprüfung des eingehenden Wertes vornimmt. Kommt kein Wert mit, so wird auch keine Prüfung vorgenommen, da es sich ja um eine optional Angabe handelt. Falls doch, wird überprüft, ob der Wert in der Auflistung vorhanden ist. In realistischen Szenarien sollte dies jedoch keine hart codierte Liste sein. Um das Vorgehen zu veranschaulichen, sollte es aber in Ordnung gehen. [Listing 5]

Listing 4

namespace Shopping.Service.Validators
{
    using System;
    using Microsoft.Practices.EnterpriseLibrary.Validation;
    using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

    public sealed class CountryValueValidatorAttribute : ValueValidatorAttribute
    {
        protected override Validator DoCreateValidator(Type targetType)
        {
            return new CountryValueValidator(string.Empty, string.Empty, false);
        }
    }
}

Listing 5

namespace Shopping.Service.Validators
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.Practices.EnterpriseLibrary.Validation;
    using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

    public sealed class CountryValueValidator : ValueValidator
    {
        private readonly List<string> countries = new List<string>() { "Deutschland", "Schweiz", "Österreich" };

        public CountryValueValidator(string messageTemplate, string tag, bool negated)
            : base(messageTemplate, tag, negated)
        {
        }

        public override void DoValidate(object objectToValidate, object currentTarget, string key, ValidationResults validationResults)
        {
            if (objectToValidate == null)
            {
                return;
            }

            if (objectToValidate is string && !string.IsNullOrEmpty(objectToValidate.ToString()))
            {
                if (!this.countries.Any(c => c == objectToValidate.ToString()))
                {
                    var errorMessage = string.Format("Das Land '{0}' ist nicht zulässig.", objectToValidate);
                    validationResults.AddResult(new ValidationResult(errorMessage, objectToValidate, "Country", string.Empty, this));
                }
            }
        }

        ...    }
}

Gibt man nun über einen Client einen ungültigen Wert im Feld Land ein, so kann die Anfrage nicht verarbeitet werden. Die Operation liefert eine FaultException vom Typ ValidationFault. Laut unserem Beispiel [Listing 5] sind nur Deutschland, Schweiz und Österrich gültig. Der Anwender hat allerding Frankreich eingegeben. Der Service ist in der Lage eine sinnvolle Meldung zu liefern, welche mit wenig Aufwand zum Beispiel in einem Controller (MVC-Framework) verarbeitbar ist und als Fehlermeldung ausgegeben werden kann [Abb. 2]. Die aufrufende Controller-Methode muss im Grunde nur auf die FaultException reagieren und die Details auslesen, diese dann als ModelErrors hinzufügen. Schon weiß das MVC-Framework damit etws anzufangen und kann die Meldung in der View darstellen [Listing 6]. Das Auslesen der Werte sollte korrekterweise nicht in jeder einzelnen Methode stattfinden, sondern an eine zentrale Stelle ausgelagert und wiederverwendet werden.

Abb. 2

Lsting 6

[HttpPost]
        public ActionResult Create(CustomerCreateModel customerToCreate)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    using (var client = new CustomerService.CustomerServiceClient())
                    {
                        client.CreateCustomer(customerToCreate.Customer);
                    }
                    return this.Redirect("/");
                }
                catch (FaultException<ValidationFault> validationFault)
                {
                    foreach (var detail in validationFault.Detail.Details)
                    {
                        ModelState.AddModelError(detail.Key, detail.Message);
                    }
                    return this.View(customerToCreate);
                }
            }

            return this.View(customerToCreate);
        }

Die Ausgabe 2/2012 der Visual Studio One erscheint am 23.2.2012. Bis dahin wird auch eine Beispiel-Lösung zum Thema Validierungen zur Verfügung stehen. Den Link zum Download gebe ich in den nächsten Tagen hier bekannt.

Class oder Struct – Part I

In der letzten Zeit wurde ich einige Mal gefragt, wann man eigentlich eine Klasse und wann besser ein Struct einsetzen sollte. Und, wozu ist denn ein Struct überhaupt gut?

Grund genug, genauer darüber nachzudenken und seit langem wieder Beiträge in meinem Blog zu posten. Um es vorweg zu nehmen: Die Entscheidung, wann welcher Typ eingesetzt werden sollte, ist nicht immer wirklich leicht. Aber genau diese Thematik möchte ich nun diskutieren. Um die Problematik besprechen zu können, muss ich etwas weiter ausholen. Aus diesem Grunde habe ich mich auch dafür entschieden, den Beitrag in zwei Teile zu splitten. Im ersten Teil soll zunächst der Unterschied zwischen Referenz- und Wertetypen näher beleuchtet werden.

Das .net-Framework stellt uns scheinbar zwei sehr ähnliche Typen zur Verfügung, um Strukturen und Verhalten zu implementieren. Dies ist zunächst verwirrend. Bei näherer Betrachtung ist zu erkennen, dass diese beiden gar nicht so ähnlich sind, wie zunächst vermutet. Um den Unterschied zwischen Klasse und Struct zu verstehen, ist es notwendig, das unterschiedliche Verhalten von Referenz- und Wertetypen zu kennen. Die Frage „Klasse oder Struct“ könnte also genauso gut „Referenz- oder Wertetyp“ lauten, denn Klassen sind Referenztypen, während Structs Wertetypen sind.

C# kennt grundsätzlich zwei Kategorien von Typen. Auf der einen Seite sind dies Wertetypen (value type) und auf der andere Seite Referenztypen (reference type). Während Wertetypen ihre Werte direkt enthalten, verweisen Referenztypen auf ihre Werte. Wertetypen in C# sind unter anderem alle einfachen Typen, wie zum Beispiel int, long, decimal usw. Der Zugriff auf Wertetypen erfolgt durch direktes Lesen aus dem Speicher. Legt man eine Kopie an wird ein neuer Eintrag im Speicher erzeugt. Anders bei Referenztypen. Erzeugt man da eine Kopie, wird keine Kopie des Objekts im Speicher gemacht, sondern nur der Verweis weitergereicht. Klassen sind Referenztypen. Als typisches Beispiel für Referenztypen möchte ich hier die String-Klasse des .net-Framework nennen.

Von Wertetypen wird grundsätzlich erwartert, dass sie immutable (unveränderlich) sind, nachdem sie initialisiert wurden. Dies ist auch ein grundlegendes DDD Prinzip, welches von Eric Evans in Domain Driven Design beschrieben wird und eine wichtige Eigenschaft von „ValueObjects“ im Sinne von DDD ist. Veränderbare Wertetypen stellen grundsätzlich ein Problem dar, da der Aufrufer immer eine Kopie, welche implizit erstellt wird, erhält und nicht den Originalwert, was logischerweise auch nicht beeinflusst werden kann.

Um dieses Verhalten besser darzustellen, hier eine Veranschaulichung in Form von Beispielen.

Schlechtes Design eines Struct:

    public struct Rectangle
    {
        public int Width { get; set; }

        public int Length { get; set; }
    }

Die Eigenschaften des Typs Rectangle besitzen öffentliche Setter, die nach der Objektinstanzierung bedient werden müssen.

Der Aufrufer könnte den Typ wie folgt nutzen wollen. Da es sich bei Rectangle um einen Wertetyp handelt, wird nur eine Kopie des Objektes an die Modify-Methode weitergereicht und verändert.

    class Program
    {
        static void Main(string[] args)
        {
            var rectangle = new Rectangle();

            ModifyRectangle(rectangle);

            Console.WriteLine("Width: {0}", rectangle.Width);
            Console.WriteLine("Length: {0}", rectangle.Length);

            Console.ReadLine();
        }

        private static void ModifyRectangle(Rectangle rectangle)
        {
            rectangle.Width = 100;
            rectangle.Length = 200;
        }
    }

Die Ausführung der Anwendung führt zu einem vom Aufrufer sicherlich nicht erwarteten Ergebnis. Von solch einer Struct-Implementierung ist also unbedingt abzuraten. 

Eine korrekte Struct Implementierung sieht so aus:

    public struct Rectangle
    {
        private readonly int width;

        private readonly int length;

        public Rectangle(int width, int length)
        {
            this.width = width;
            this.length = length;
        }

        public int Width 
        { 
            get
            {
                return this.width;
            } 
        }

        public int Length 
        { 
            get
            {
                return this.length;
            } 
        }
    }

Die Eigenschaften besitzen nur Getter. Die Werte müssen vom Aufrufer bei der Objektinstanzierung an den Konstruktor übergeben werden. Die Werte sind unveränderlich.

Der Aufrufer müsste den Typ wie folgt verwenden. Das Setzen sowie Änderungen der Eigenschaften sind von aussen nicht mehr möglich.

        static void Main(string[] args)
        {
            var rectangle = new Rectangle(100, 200);

            Console.WriteLine("Width: {0}", rectangle.Width);
            Console.WriteLine("Length: {0}", rectangle.Length);

            Console.ReadLine();
        }

Im Ergebnis erhält der Aufrufer auch die erwarteten Werte.

Ich hoffe, in dieser Blogausgabe, ein erste, leicht verständliche Einführung in der Betrachtung der Unterscheidung von Klasse und Struct gegeben zu haben. Im nächsten Teil soll es vor allem darum gehen, Beispiele für die sinnvolle Verwendung von Structs zu geben und Entscheidungskriterien für den Einsatz aufzuzeigen.

An Meinungen, Anregungen und Erfahrungen bin ich natürlich sehr interessiert. Also zögert nicht, den Artikel zu kommentieren. Danke.

DDD Kolumne

Seit der Ausgabe 4/2011 erscheint in Visual Studio One meine Kolumne „Domain Driven Design als Denkansatz“. In der ersten Ausgabe 2012 erscheint dann der 4. Teil, bei welchem es sich inhaltlich vor allem Validierungen handelt.