Verwenden von Geschäftsobjekten in Ansichtsmodellen

11

Was wird bei der Verwendung wiederverwendbarer Geschäftsobjekte als bewährte Methode beim Erstellen von Ansichtsmodellen angesehen?

Wir verwenden ein Objekt, das wir aufrufen Builder, um unsere Ansichtsmodelle zu erstellen. Ein Builder für jede logische Ansichtseinheit (Bestellungen, Benutzer usw.), wobei jede Einheit eine Reihe verschiedener Ansichtsmodelle enthalten kann (Bestellungen enthalten Zusammenfassung, Auftragspositionen usw.).

Ein Builder kann Daten durch ein oder mehrere Standardgeschäftsobjekte ziehen, um ein Ansichtsmodell zu erstellen.

Was ist die bessere Vorgehensweise bei der Verwendung von Geschäftsobjekten / -modellen in Ansichtsmodellen?

Ansatz 1

Verwendung von Geschäftsobjekten im Ansichtsmodell zulassen?

//Business object in some library
public class Order
{
    public int OrderNum;
    public int NumOrderLines;
    //...
}

//Order builder in website
public class OrderBuilder
{
    public OrderSummary BuildSummaryForOrder(int OrderNum)
    {
        Some.Business.Logic.Order obOrder = Some.Business.Logic.GetOrder(OrderNum);
        //Any exception handling, additional logic, or whatever

        OrderSummary obModel = new OrderSummary();
        obModel.Order = obOrder;

        return obModel;
    }
}

//View model
public class OrderSummary
{
    public Some.Business.Logic.Order Order;
    //Other methods for additional logic based on the order
    //and other properties
}

Ansatz 2

Nehmen Sie nur die erforderlichen Daten aus den Geschäftsobjekten

//Business object in some library
public class Order
{
    public int OrderNum;
    public int NumOrderLines;
    //...
}

//Order builder in website
public class OrderBuilder
{
    public OrderSummary BuildSummaryForOrder(int OrderNum)
    {
        Some.Business.Logic.Order obOrder = Some.Business.Logic.GetOrder(OrderNum);
        //Any exception handling, additional logic, or whatever

        OrderSummary obModel = new OrderSummary()
        {
            OrderNum = obOrder.OrderNum,
            NumOrderLnes = obOrder.NumOrderLines,
        }

        return obModel;
    }
}

//View model
public class OrderSummary
{
    public int OrderNum;
    public int NumOrderLines
    //Other methods for additional logic based on the order
    //and other properties
}

Ich kann die Vor- und Nachteile beider erkennen, aber ich frage mich, ob es einen akzeptierten Ansatz gibt. In Ansatz 1 gibt es keine Duplizierung von Code um die Modelle herum, aber es entsteht eine Abhängigkeit von der Geschäftslogik. In Ansatz 2 verwenden Sie nur die für die Ansicht erforderlichen Daten, duplizieren jedoch den Code um die Modelle.

Andy Hunt
quelle

Antworten:

12

Option 1 schafft eine enge Kopplung zwischen dem Domänenmodell und der Ansicht. Dies widerspricht genau den Problemansichtsmodellen, die gelöst werden sollen.

Eine Ansicht modelliert "Grund zur Änderung", wenn sich die Ansicht selbst ändert. Indem Sie ein Domänenmodellobjekt in das Ansichtsmodell einfügen, führen Sie einen weiteren Grund für die Änderung ein (z. B. die geänderte Domäne). Dies ist ein klarer Hinweis auf einen Verstoß gegen das Prinzip der Einzelverantwortung. Wenn zwei oder mehr Gründe für eine Änderung vorliegen, werden Ansichtsmodelle angezeigt, die viel Wartung erfordern - wahrscheinlich mehr als die wahrgenommenen Wartungskosten für die Duplizierung über Domänen- / Ansichtsmodelle hinweg.

Ich würde immer Ansatz 2 befürworten. Es ist häufig der Fall, dass Ansichtsmodelle sehr ähnlich aussehen, sogar identisch mit Domänenmodellobjekten, aber die Unterscheidung, wie ich erwähnte, sind ihre unterschiedlichen Gründe für Änderungen.

MattDavey
quelle
Habe ich Recht, wenn ich denke, dass mit "Grund zur Änderung" eine Änderung im Sinne der Wartung gemeint ist, nicht eine Änderung im Sinne der Aktualisierung (z. B. UI-Ereignis)?
Andy Hunt
@AndyBursh Ja, das ist richtig - siehe diesen Artikel , insbesondere die Zeile "Robert C. Martin definiert eine Verantwortung als Grund für eine Änderung und kommt zu dem Schluss, dass eine Klasse oder ein Modul einen und nur einen Grund für eine Änderung haben sollte."
MattDavey
Ich mag Ihre Antwort, aber einige Gedanken ... Das Ansichtsmodell ändert sich nicht unbedingt, nur weil sich das Modell ändert. Nur wenn Sie eine bestimmte Eigenschaft binden oder verwenden, die sich geändert hat, ist dies ein Problem, da sich Ihr Verweis auf das gesamte Objekt bezieht. Ein Verweis auf das Domänenobjekt erleichtert das Vornehmen und erneutes Speichern. Ihre Speichermethoden hängen auch vom Domänenobjekt ab, sodass Sie das Ansichtsmodell zurückkonvertieren oder Ihre Geschäftsmethode einrichten müssen, um Ansichtsmodelle zu akzeptieren, die ebenfalls nicht gut sind. Ich denke immer noch, dass # 2 am sinnvollsten ist, aber nur um zwei Cent.
KingOfHypocrites
Wenn Sie keine Domänenobjekte in einer VM haben können, wie würden Sie dann etwas Komplizierteres wie ein Array von Aufträgen darstellen?
Jeff
Bedeutet dies also, dass beispielsweise das Formatieren eines Zeitstempels für die Benutzeranzeige zur Ansichtsebene und nicht zur Domänenebene gehören sollte und Objekte auf Domänenebene nur einen rohen, unformatierten Zeitstempel an die Ansichtsobjekte zurückgeben sollten, und letztere sollten dies tun die Formatierungslogik enthalten?
The_Sympathizer
2

Option 1 ist vorzuziehen, da Code-Duplikationen vermieden werden. Das ist es.

Wenn sich das Domänenmodell erheblich ändert, ist es fast sicher, dass sich die Ansicht trotzdem ändern muss. Mit Option 2 müssen Sie dann das Ansichtsmodell UND den Builder sowie die Ansicht selbst ändern. So etwas ist ein absolutes Gift für die Wartbarkeit. YAGNI.

Der Zweck eines separaten Ansichtsmodells besteht darin, den Status, der nur für die Ansicht (z. B. welche Registerkarte derzeit ausgewählt ist) vom Geschäftsmodell getrennt ist, getrennt zu halten. Die Geschäftsdaten selbst sollten jedoch wiederverwendet und nicht dupliziert werden.

Michael Borgwardt
quelle
YAGNI - der heimliche Attentäter bei der Lösung der meisten Software-Design-Probleme.
Martin Blore
6
Es tut mir leid, aber dies ist ein schrecklicher Rat für alle außer den trivialsten Anwendungen. Ansichtsmodelle haben keinen Status. Sie sind Datenübertragungsobjekte. Welche Registerkarte ausgewählt ist, ist Teil der STRUKTUR der Ansicht und hat NICHTS mit den DATEN im Ansichtsmodell zu tun. Wartung ist kein Albtraum, wenn Sie Ihr Programm richtig strukturieren und so etwas wie Automapper verwenden, um Ihre Ansichtsmodelle zu hydratisieren.
Luzifer Sam
"Wenn sich das Domänenmodell erheblich ändert, ist es fast sicher, dass sich die Ansicht trotzdem ändern muss." - Einverstanden. Aber was ist, wenn Sie eine kleine Änderung an der Domain haben? Bei Option 1 erfordert jede kleine Änderung an der Domäne (auch nur das Umbenennen einer Eigenschaft) eine entsprechende Änderung der Ansicht. Dies ist auch ein absolutes Gift für die Wartbarkeit.
MattDavey
@MattDavey: Wenn Sie eine Eigenschaft umbenennen, müssen Sie bei einem separaten Ansichtsmodell auch die Ansicht ändern (oder was auch immer zwischen Domäne und Ansichtsmodell zugeordnet ist) und haben jetzt zwei verschiedene Namen für dasselbe Objekt, was mit Sicherheit Verwirrung stiftet.
Michael Borgwardt
@ Lucifer Sam: Offensichtlich haben wir sehr unterschiedliche Konzepte, was ein Ansichtsmodell ist. Ihre klingt für mich sehr, sehr seltsam, als würden Sie Mainframe-Apps für dumme Terminals beschreiben, aber sicherlich keine modernen Web- oder Fat-Client-Apps.
Michael Borgwardt
2

Prinzipien und Mantras sind manchmal wertvoll, um das Design zu leiten ... aber hier ist meine praktische Antwort:

Stellen Sie sich vor, Ihre Ansichtsmodelle werden in JSON oder XML serialisiert. Wenn Sie versuchen, Ihre Domain-Modelle zu serialisieren, kommt es zu einem schrecklichen Textgewirr, bei dem höchstwahrscheinlich Probleme mit Zirkelverweisen und anderen Problemen auftreten.

Der Zweck eines Ansichtsmodells besteht nicht darin, Domänenmodelle zu gruppieren, damit die Ansicht sie verwenden kann. Stattdessen sollte das Ansichtsmodell ein völlig flaches Modell der Ansicht sein ... das, was Sie tatsächlich auf dem Bildschirm betrachten. Ihre Ansichtslogik sollte sich nur mit der Strukturierung der im Ansichtsmodell vorhandenen Daten befassen.

Idealerweise sollte Ihr Ansichtsmodell fast ausschließlich aus vorformatierten Zeichenfolgen bestehen. Denken Sie darüber nach ... Sie möchten nicht einmal eine DateTime oder Dezimalstelle in Ihrem Ansichtsmodell, weil Sie dann die Formatierungslogik in C #, Javascript, Objective-C usw. nicht mehr ausführen können.

Luzifer Sam
quelle
2
Ich hatte noch nie Probleme mit der Serialisierung von Domain-Modellen. Und alles in Strings in einem Modell konvertieren? Ernsthaft?
Michael Borgwardt
3
@ MichaelBorgwardt Ja, das sollte ein Ansichtsmodell sein. Sie möchten Ihre Domain-Modelle nicht serialisieren und überall hin senden. Die gesamte Geschäftslogik sollte an einem Ort sicher zu Hause bleiben. Die Ansichten sollten jedoch flexibel sein und auf jedem Gerät gerendert werden können, weshalb Sie STRUKTUR, DATEN und STIL vollständig trennen möchten.
Luzifer Sam
Entschuldigung, aber DAS ist ein schrecklicher Rat für jede Anwendung, Punkt. Dies führt zu überentwickelten Anwendungen mit doppeltem Code, die genau das Gegenteil von flexibel sind.
Michael Borgwardt
1
@MichaelBorgwardt Es hört sich so an, als wären Sie es gewohnt, mit anämischen Domänenmodellen zu arbeiten, bei denen die Entitäten kaum mehr als Eigenschaftstaschen mit wenig oder gar keinem Verhalten sind. In diesem Fall wäre ein DTO / View-Modell grundsätzlich ein Duplikat. Wenn Sie jedoch ein umfangreiches Domänenmodell mit komplexen Beziehungen haben, wird eine Schicht von DTOs / Ansichtsmodellen erforderlich, die den Domänenentitäten nicht so ähnlich sind.
MattDavey
@MattDavey: Es hört sich so an, als wären die Domain-Modelle, mit denen Sie arbeiten, nicht nur reiche, sondern wahre Kleptokraten. Ich mag auch keine anämischen Modelle, aber sie sind immer noch Modelle, und ihr Verhalten sollte sich auf die Darstellung der Domäne beschränken. Prinzip der Einzelverantwortung und all das ...
Michael Borgwardt