Was kann MVP?

Erweitern des MVP-Musters für eine auf Unternehmensanwendungen ausgerichtete Benutzeroberflächenarchitektur

Haozhe Ma

MVP (Model-View-Presenter) stellt einen Durchbruch in der Betrachtung von Benutzeroberflächenmustern dar und verdeutlicht, dass Benutzeroberflächendesigner in ihren Anwendungen das Prinzip der Trennung von Bereichen beibehalten sollten.

Es gibt allerdings viele verschiedene Interpretationen der MVP-Muster. Beispielsweise setzen manche stillschweigend voraus, dass das MVP-Muster explizit das Benutzeroberflächenarchitekturmuster darstellt. Das ist bei Anwendungen für Unternehmen nicht ganz zutreffend. Verglichen mit anderen Typen von Benutzeroberflächenanwendungen müssen Unternehmensanwendungen mit mehr Beteiligten, mehr Komplexität und vielen Abhängigkeiten von anderen Systemen, z. B. von Diensten und anderen Anwendungen, umgehen. Diese besonderen Charakteristika erfordern, dass die Benutzeroberflächenarchitektur von Unternehmensanwendungen mehr Wert auf Flexibilität, Wartbarkeit, Wiederverwendbarkeit, Implementierungskonsistenz und Abkopplung der Geschäftsfunktionalität von der zugrundeliegenden Technologie legt, um Abhängigkeiten bestimmten Produkten und Herstellern zu vermeiden.

Wenn nur das MVP-Muster an und für sich als Muster für die Benutzeroberflächenarchitektur von Unternehmensanwendungen übernommen wird, dann stellen sich einige Fragen. Hier sind nur einige:

Eine typische Unternehmensanwendung enthält viele Ansichten, und Ereignisse, die in einer Ansicht auftreten, können sich auf andere Ansichten auswirken. Wenn in einem Fenster beispielsweise auf eine Schaltfläche geklickt wird, könnte daraufhin ein Popup-Fenster eingeblendet und die Daten in einem anderen Fenster gleichzeitig aktualisiert werden. Wer ist für die Steuerung einer solchen "Fensterablauflogik" verantwortlich? Sollte dies durch die Darstellungsfunktion jeder Ansicht gesteuert werden?

In einer SOA-Architektur (Service-Oriented Architecture) erhält die Anwendungsbenutzeroberfläche in der Regel über Dienste Informationen. Beispielsweise müsste die Benutzeroberfläche einen generierten WCF-Dienstclientproxy aufrufen, um den WCF-Dienst für den Abruf von Daten aufzurufen. Ist es gutes Design, wenn die Darstellungsfunktion diesen Dienstclientproxy direkt aufruft? Wie lässt sich die Benutzerflächenarchitektur so gestalten, dass sich Änderungen möglichst wenig auf die Benutzeroberflächenimplementierung auswirken, wenn diese Dienste mit anderen Technologien implementiert wurden oder wenn die Dienstmodelle verändert werden?

Diesem Gedankengang folgend könnte einige Implementierungen generierte Dienstclientproxymodelle in der gesamten Anwendung verwenden. Wäre dieses Vorgehen mit Risiken verbunden? Wenn ein dediziertes Benutzeroberflächenmodell benötigt wird, dann stellt sich die Frage, welcher Teil für die Zuordnung von Clientproxymodell und Benutzeroberflächenmodell verantwortlich sein soll.

Dies sind keine neuen Fragen, und es wurden viele andere Muster eingeführt, um die Lücke zu füllen. Zum Beispiel wurde das Anwendungscontrollermuster eingeführt, das die Steuerung des Navigationsablaufs übernehmen kann.

Ich dachte, es wäre hilfreich, einige dieser disparaten Diskussionen zur MVP-Erweiterung zusammenzutragen und eine holistische Sicht auf den Entwurf der Benutzeroberflächenarchitektur darzustellen. Wenn man das Problem aus der Perspektive von Unternehmensanwendungen betrachtet, dann können Benutzeroberflächenarchitekten leichter erkennen, welche wichtigen Teile für den Benutzeroberflächenentwurf erforderlich sind, und ein konsistentes Muster definieren, an dem sich die Implementierung der Benutzeroberflächenanwendung orientieren kann.

In diesem Artikel wird der Begriff "MVP-Muster" verwendet, allerdings wurde das ursprüngliche MVP-Muster ausrangiert und gegenwärtig werden zwei Varianten des Original-MCP-Musters verwendet. Eine Variante ist das Muster "Passive View", und die andere Variante ist das Muster "Supervising Controller". Obwohl beide Muster für bestimmte Szenarios geeignet sind und beide Varianten Vor- und Nachteile haben, basiert die in Abbildung 1 dargestellte Benutzeroberflächenarchitektur auf dem Muster "Passive View" und erweitert dieses. Das bedeutet sicher nicht, dass die Benutzeroberflächenarchitektur nicht auf dem Muster "Supervising Controller" aufgebaut werden könnte; aber dies ist meine persönliche Vorliebe.

Auf dem Muster "Passive View" basierende Architektur
Abbildung 1 Auf dem Muster "Passive View" basierende Architektur

Wir wollen die Diskussion mit einer Erläuterung dessen beginnen, wodurch die Benutzeroberflächenarchitektur durch eine Erweiterung des MVP-Musters gebildet wird. Abbildung 1 zeigt zusammenfassend, welche wichtigen Komponenten für eine Benutzeroberflächenarchitektur erforderlich sind. In diesem Diagramm werden sieben Hauptteile vorgestellt: Ansichtsklasse, Presenter, Benutzeroberflächenmodell, Prozessflusscontroller, Dienstagent, Dienstclientproxymodell und Dienstclientproxy.

Ansicht

Die Ansichtsklasse entspricht im Grunde genommen der View-Rolle des Musters "Passive View". Die Ansichtsklasse übernimmt einige Aufgaben, angefangen bei der Handhabung des Anzeigelayouts der Benutzeroberfläche und präsentationsspezifischer Logik.

Die zweite Aufgabe der Ansichtsklasse besteht im Auslösen von Ereignissen und deren Weiterleitung an die Presenter-Klasse, und zur Erledigung dieser Aufgabe sind mehrere Implementierungen erforderlich. Erstens muss die Ansichtsklasse View die IView-Schnittstelle implementieren. Um sicherzustellen, dass sich dies möglichst minimal auf die Implementierung der Presenter-Logik auswirkt und die Möglichkeit für Komponententests zu bieten, sollte die Presenter-Klasse über eine IView-Schnittstelle mit der View-Klasse interagieren.

Zweitens muss die Ansichtsklasse View öffentliche Eigenschaften definieren, welche die Presenter-Klasse lesen und bearbeiten kann. Im Muster "Passive View" übergibt die View-Klasse keine Daten an die Presenter-Klasse. Stattdessen kann sich die Presenter-Klasse die Daten, die sie interessiert, in der View-Klasse auswählen. Diese Art von Implementierung verringert überdies die Vertragsbindung zwischen Ansicht und Presenter und trennt deren Aufgabenbereiche deutlicher.

Es stellt sich jedoch die Frage, welchen Datentyp die View-Eigenschaften verwenden sollten. Idealerweise sollten diese Eigenschaften in der Ansichtsklasse nur mit einfachen Typen definiert werden, z. B. Zeichenfolgen, Ganzzahlen usw. In der Praxis kann es jedoch sehr mühsam werden, wenn die Ansichtsklasse Daten auf diese Weise definiert. Es ist akzeptabel, eine Dateneigenschaft durch Verweise auf komplexe Typen, beispielsweise Definitionen von Benutzeroberflächenmodellen, verfügbar zu machen. Dadurch wird ein Gleichgewicht zwischen architektonischer Reinheit und Implementierungsaspekten hergestellt.

Drittens muss die View-Klasse auch Presenter-Vorgänge aufrufen, wenn in der Ansicht Ereignisse ausgelöst werden. Die View-Klasse kann die Presenter-Klasse direkt aufrufen, ohne Daten zu übergeben. Es gibt kein lose gekoppeltes Design zwischen View und Presenter, weil View und Presenter immer ein Paar bilden. Es wird empfohlen, einem Presenter-Vorgang genau ein View-Ereignis zuzuordnen.

Die letzte Aufgabe der Ansichtsklasse besteht in der Aktualisierung der Eigenschaftswerte. Die Presenter-Klasse aktualisiert die Ansichtseigenschaften, um auf eine Änderung hinzuweisen. Die Ansichtsklasse kann entscheiden, wie auf solche Änderungen zu reagieren ist. Sie könnte die Ansicht aktualisieren, um die Datenänderung widerzuspiegeln, oder sie könnte auch entscheiden, gar nichts zu tun.

Presenter

Die Presenter-Klasse entspricht im Grunde genommen der Presenter-Rolle des Musters "Passive View". Hier bestimmt die Presenter-Klasse allerdings nicht den Prozessfluss. Die Presenter-Klasse empfängt Anforderungen von der Ansichtsklasse und veröffentlicht Ereignisanforderungen für den Controller, damit der Controller entscheiden kann, welcher Schritt als Nächstes ausgeführt werden soll. Weil die Presenter-Klasse keine Prozessflusslogik handhabt, kann sie nicht wissen, welche Ereignisanforderungen von der Ansichtsklasse sich auf andere Ansichten auswirken. Wenn die Presenter-Klasse Anforderungen von der Ansichtsklasse empfängt, veröffentlicht sie sofort die entsprechende Ereignisse, damit der Controller auf diese Ereignisanforderungen antworten und entscheiden kann, welcher Prozessschritt als Nächstes ausgeführt werden soll. Die Presenter-Klasse führt erst dann eine Aktion aus, wenn der Controller sie dazu anweist.

Der Controller entscheidet, welche Presenter-Vorgänge ausgeführt werden sollen. Wenn ein Presenter-Vorgang vom Controller aufgerufen wird, führt die Presenter-Klasse die betreffende Aktionen aus, z. B. Daten über einen Dienstagent abrufen. Wenn die Presenter-Klasse Aktionen mit Diensten ausführen muss, geschieht dies über einen Dienstagent. Sie übergibt dem Dienstagent die erforderlichen Parameter und erhält vom Dienstagent Ergebnisse.

Wenn die Presenter-Klasse die Ansicht über Datenänderungen benachrichtigen möchte, aktualisiert sie hierzu die Eigenschaftswerte der Ansicht. Dann muss die Ansichtsklasse entscheiden, wie diese angezeigt werden soll. Wie oben erwähnt, arbeitet die Presenter-Klasse über die IView-Schnittstelle mit der Ansichtsklasse zusammen, statt direkt auf das View-Objekt zuzugreifen. Da die View-Instanz bereits bei der Initialisierung der Presenter-Klasse an die Presenter-Instanz übergeben wurde, steht der Presenter-Instanz bereits eine View-Instanz zur Interaktion zur Verfügung.

Schließlich kann der Presenter auf das Benutzeroberflächenmodell zugreifen und dieses in einem Cache zwischenspeichern, wenn später auf Daten des Benutzeroberflächenmodells zugegriffen werden muss.

Prozessflusscontroller

Der Prozessflusscontroller ähnelt dem Anwendungscontrollermuster. Sie unterschieden sich lediglich darin, dass der hier besprochene Prozessflusscontroller lediglich dafür zuständig ist, den Prozessfluss anhand der vom Presenter ausgelösten typisierten Ereignisse zu steuern. Der Prozessfluss ist nicht auf den Bildschirmnavigationsfluss beschränkt. Hierzu gehört auch, die Reihenfolge der auf Ereignisanforderungen bezogenen Presenter-Aktionen zu steuern.

Der Prozessflusscontroller abonniert Ereignisse, die vom Presenter veröffentlicht werden, und reagiert nur auf Ereignisse, die vom Presenter veröffentlicht werden. Diese Ereignisse sind typisierte Ereignisse. Anders ausgedrückt, der Prozessflusscontroller reagiert nicht auf allgemeine Ereignisse.

Weil der Prozessflusscontroller nur auf typisierte Ereignisse reagiert, wurde bereits ein Prozessfluss vorab festgelegt, wenn ein Ereignis ausgelöst wird. Die Logik des Prozessflusscontrollers wird dadurch vereinfacht. Jedes vom Presenter veröffentlichtes Ereignis enthält Daten, die vom Prozessflusscontroller beim Initiieren anderer Presenter-Vorgänge benötigt werden.

Der Prozessflusscontroller initiiert Presenter- und zugehörige View-Instanzen, wenn sie bis dahin noch nicht initiiert wurden. IoC (Inversion of Control) ist wegen Bedenken hinsichtlich Querverweisen erforderlich. Dadurch wird auch ein lose gekoppeltes Design zwischen Presenter und Prozessflusscontroller bereitgestellt.

Benutzeroberflächenmodell

Das Benutzeroberflächenmodell entspricht im Grunde genommen der Model-Rolle des Musters "Passive View". Im Muster "Passive View" ist das Modell nicht aufwändig. Es stellt einfach die Modellstrukturdefinition bereit. Wie im Presenter-Abschnitt beschrieben, ist der Presenter für die Verwaltung des Modellzustands verantwortlich.

Ich nenne dieses Benutzeroberflächenmodell lediglich deswegen UIModel statt einfach Model, damit es vom Dienstclientproxymodell, das weiter unten beschrieben wird, unterschieden werden kann.

Die Klasse UIModel definiert die Modellstruktur, die sich für die Handhabung der Benutzeroberflächenanwendungslogik eignet. Die Benutzeroberflächenmodelldefinition sieht unter Umständen genauso aus wie das Dienstclientproxymodell. In manchen Situationen – insbesondere, wenn die Benutzeroberfläche Daten aus mehreren Dienstquellen anzeigen muss – muss das Benutzeroberflächenmodell anders strukturiert werden als das Dienstclientproxymodell.

Dienstagent

Der Dienstagent fungiert als Vermittler zwischen Presenter und Dienstclientproxy. Bei dem Dienst muss es sich nicht unbedingt um einen Webdienst handeln. Er stellt alle Ressourcen dar, die Daten bereitstellen oder Geschäftslogik ausführen. Das könnte ein Webdienst sein, es könnte sich aber um auch eine einfache Datei-E/A handeln.

Der Dienstclientproxy hat in der Webdiensttechnologie eine besondere Bedeutung. Hier wird der Dienstclientproxy verwendet, um den Gateway für einen Dienst zu repräsentieren.

Die Implementierung des Dienstclientproxy hängt von der Technologie ab. Aus der Sicht des Presenters sollte er besser nicht wissen, wie die Daten übertragen oder bereitgestellt werden. Diese technischen Details können im Dienstagent verborgen werden. Die Ebene des Dienstagent schützt den Presenter daher davor, von Änderungen der Dienstimplementierungstechnologie, Dienstversionsverwaltung, Dienstmodelländerungen usw. beeinflusst zu werden.

Der Dienstagent bietet Vorgänge, mit denen der Presenter zusammenarbeiten kann. Wenn diesen Vorgängen ein komplexer Typ übergeben werden muss, muss im Benutzeroberflächenmodell ein komplexer Typ definiert werden. Dies gilt auch für den Rückgabetyp von Operationen. Diese Methodenaufrufe können dann entsprechenden Vorgängen der Dienstclientproxyklasse übergeben werden. In manchen Fällen kann ein Dienstagentvorgang mehrere Dienstclientproxymethodenaufrufe initiieren.

Weil die Dienstagentvorgänge komplexe Typen akzeptieren, die im Benutzeroberflächenmodell definiert wurden, müssen Dienstagentvorgänge beim Aufruf von Dienstclientproxyvorgängen das Benutzeroberflächenmodell dem Dienstclientproxymodell zuordnen. Wenn Dienstagentvorgänge Ergebnisse dem Presenter zurückgeben müssen, müssen sie das Dienstclientproxymodell dem Benutzeroberflächenmodell zuordnen, nachdem sie die Ergebnisse von den Dienstclientproxyvorgängen erhalten haben.

Das könnte recht mühsam sein. Es sind allerdings gute Tools verfügbar, mit dem sich Elemente der beiden Modellstrukturen einfach einander zuordnen lassen, sodass diese Aufgabe nur einmal erledigt werden muss.

Dienstclientproxy und Dienstclientproxymodell

In der Webdiensttechnologie bietet der Dienstclientproxy dem Dienstclient selbst dann lokalen Zugriff, wenn der Dienst auf einem Remotecomputer gehostet wird. In diesem Artikel wird der Dienstclientproxy als Gateway für den Dienst beschrieben. Das Dienstclientproxymodell repräsentiert die Modelldefinition des Dienstvertrags.

Der Dienstclientproxy gibt Aufrufe an Dienste weiter und gibt die Antworten der Dienste zurück. Wenn der Dienst mit ASMX (ASP.NET Web Services)- oder WCF (Windows Communication Foundation)-Technologien implementiert wurde, kann der Dienstclientproxy automatisch generiert werden.

Das Dienstclientproxymodell repräsentiert dann die Modellstrukturdefinition des Dienstvertrags.

Implementierungsbeispiel

Zur Veranschaulichung der in Abbildung 1 beschriebenen Benutzeroberflächenarchitektur wollen wir uns eine Windows Forms-Anwendung ansehen, die die Implementierung zeigt. Diese Beispielanwendung ist im Download zu diesem Artikel enthalten.

Diese Beispielanwendung muss zuerst eine Liste von Regionen laden. Wenn eine Region ausgewählt wird, werden die Kunden angezeigt, die zu dieser Region gehören. Wenn ein Kunde ausgewählt wird, wird ein Fenster mit einer Abfrage für eine Zeitspanne eingeblendet. Nachdem eine Anfangs- und eine Endzeit eingegeben wurden, wird eine Liste der Bestellungen, die der ausgewählte Kunde getätigt hat, im Datenraster im Hauptfenster angezeigt.

Ich erkläre anhand dieses Szenarios, in dem eine Liste von Regionen angezeigt wird, wie die Benutzeroberflächenarchitektur aus Abbildung 1 in der Beispielsanwendung implementiert wird. In Abbildung 2 wird die Aufrufsequenz für dieses Szenario dargestellt.


Abbildung 2 Benutzeroberflächenaufrufsequenz

Wenn das Main Screen-Formular geladen wird, wird zuerst die Presenter-Schnittstelle initiiert und dann wird die aktuelle View-Instanz dem Konstruktor der Presenter-Instanz übergeben:

private void MainView_Load(

  object sender, EventArgs e) {



  _presenter = new MainPresenter(this);

  ...

}

Die Initialisierung der MainPresenter-Instanz bewirkt, dass der MainPresenter-Konstruktor zuerst die übergebene MainView-Instanz einer privaten Variablen vom Typ IMainView zuweist. Dann wird eine Controller-Instanz initialisiert und die aktuelle Presenter-Instanz dem Controller-Konstruktor übergeben:

public MainPresenter(IMainView view) {

  _view = view;

  _controller = new Controller(this);

}

Die Initialisierung der Controller-Instanz bewirkt, dass der Konstruktor die übergebene MainPresenter-Instanz einer privaten Variablen vom Typ IMainPresenter zuweist. Dieser Konstruktor definiert auch den Ereignishandler, der auf die von der MainPresenter-Instanz veröffentlichten Ereignisse, z. B. RetrieveRegions, reagieren soll:

public Controller(IMainPresenter presenter) {

  _mainPresenter = presenter;

  ...

  _mainPresenter.RetrieveRegions += (OnRetrieveRegions);

}

Im Load-Ereignis des Hauptformulars wird der Presenter aufgerufen, um nach der Initialisierung des Presenter-Objekts Regionen abzurufen:

Private void MainView_Load(object sender, EventArgs e) {

  ...

  _presenter.OnRetrieveRegionCandidates();

}

Wenn der Presenter diesen Aufruf erhält, veröffentlicht er zuerst das Ereignis RetrieveRegions, statt die Regionen selbst abzurufen. Im RetrieveRegions-Ereignis wird in der IMainPresenter-Schnittstelle definiert und in der MainPresenter-Klasse implementiert:

public event EventHandler<RetrieveRegionsEventArgs> 

  RetrieveRegions;

  ...



public void OnRetrieveRegionCandidates() {

  if (RetrieveRegions != null) {

    RetrieveRegions(this, 

      new RetrieveRegionsEventArgs());

  }

}

Da ein Ereignishandler für RetrieveEvents registriert wurde, kann die Controller-Klasse auf das RetrieveRegions-Ereignis reagieren:

private void OnRetrieveRegions(

  object sender, RetrieveRegionsEventArgs e) {



  _mainPresenter.HandleRetrieveRegionsEvent();

}

Der Controller entscheidet, dass der Prozessfluss die Steuerung wieder an die MainPresenter-Instanz zurückgeben sollte, und fordert diese auf, mit dem Abrufen von Regionen fortzufahren. Wenn der Controller andere Presenter-Instanzen als MainPresenter initialisieren muss, kann er hierzu das Unity Framework einsetzen.

Im HandleRetrieveRegionsEvent-Vorgang der MainPresenter-Klasse wird der Dienstagent zum Abruf der Regionen aufgerufen. Der Einfachheit halber wird der Dienst in meinem Beispiel nicht implementiert. Er gibt einfach einige Pseudodaten aus, damit die Anwendung funktioniert. Nachdem der Dienstagent ein Ergebnis zurückgegeben hat, übergibt die MainPresenter-Instanz die Daten nicht an die MainView-Instanz. Stattdessen wird die RegionCandidates-Eigenschaft der MainView-Instanz aktualisiert:

public void HandleRetrieveRegionsEvent() {

  RegionAdminServiceAgent agent = 

    new RegionAdminServiceAgent();

  List<Region> regionCandidates = agent.RetriveRegions();

  _view.RegionCandidates = regionCandidates;

}

In der RegionCandidates-Eigenschaft der MainView-Instanz wird die Anzeige der Regionen gehandhabt:

public List<UIModel.Region> RegionCandidates {

  set {

    _regionCandidates = value;

    PopulateRegionCandidates();

  }

}

Dies ist die gesamte Sequenz, in der die Regionen abgerufen und in der Hauptansicht (MainView) angezeigt werden. Es sind definitiv mehr Schritte zum Abruf der Regionen erforderlich als ein einfacher Aufruf des Dienstagent. Aus der Perspektive von Unternehmensanwendungen betrachtet, wird nicht nur ein lose gekoppeltes Design eingeführt, sondern auch ein konsistentes Implementierungsmuster gefördert. Dies kann die Pflege und den Wissenstransfer für Entwicklungsteams stark vereinfachen.

Nur noch eine Anmerkung zu diesem Codebeispiel: die ganze Sequenz beginnt mit dem ersten Windows Forms-Load-Ereignis. Eine etwas anspruchsvollere Implementierung könnte mit dem Controller starten oder den Controller entscheiden lassen, welches Formular zuerst geladen werden soll.

Zusammenfassung

In diesem Artikel wurde ein Ansatz zum Entwurf einer Benutzeroberflächenarchitektur vorgestellt, der auf der Erweiterung der MVP-Muster basiert. Benutzeroberflächenanwendungen können kompliziert sein, und es gibt viele verschiedene Stilrichtungen im Benutzeroberflächenanwendungsentwurf. Die Technik, die ich in diesem Artikel vorgestellt habe, stellt eine dieser vielen verschiedenen Lösungen dar. Diese Technik ist in vielen Situationen hilfreich, Sie sollten jedoch unbedingt prüfen, ob sie Ihren Anforderungen gerecht wird, bevor Sie sie implementieren.

Es sind bereits viele Frameworks für die Benutzeroberflächenentwicklung auf dem Markt, und viele dieser Frameworks basieren auf MVP, Model-View-Controller oder Muster, die als Erweiterung dieser beiden Muster entwickelt wurden. Zuerst sollten Sie einmal herausfinden, welche Hauptteile von diesen Frameworks implementiert werden, so wie ich beispielsweise die Benutzeroberflächenarchitektur in diesem Artikel abstrahiert habe. Sich sofort auf die Implementierungsdetails zu stürzen, ohne zuerst das große Ganze zu betrachten, ist keine vernünftige architektonische Herangehensweise. Wenn das vorliegende Problem aus allgemeiner architektonischer Sicht verstanden wird, ist sichergestellt, dass die grundlegenden Probleme der Systemarchitektur gelöst werden und dass ein wiederholbarer und gut durchdachter Entwurf zur Orientierung gegeben ist.

In dem Beispiel dieses Artikels wurde die Controller-Implementierung in C# erstellt. Ein besserer Ansatz ist möglicherweise die Verwendung einer Prozessflusstechnologie wie Windows Workflow Foundation, mit der Entwurf und Implementierung möglicherweise flexibler gestaltet werden können. Dieses technische Implementierungsdetail hat jedoch keinen Einfluss auf die Prinzipien, die der in diesem Artikel beschriebenen Benutzeroberflächenarchitektur zugrunde liegen.

Eine weitere Erörterung der MVP-Muster finden Sie in der Ausgabe vom August 2006 des MSDN-Magazins.

Zhe Ma* ist technischer Architekt im Bereich Unternehmensarchitektur der Unum Group in Portland, Maine. Sie erreichen ihn unter zma@unum.com.*.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels:Don Smith