Wo passt die „Geschäftslogikschicht“ in eine MVC-Anwendung?

86

Erstens, bevor jemand betrogen schreit, fiel es mir schwer, es in einem einfachen Titel zusammenzufassen. Ein anderer Titel könnte gewesen sein "Was ist der Unterschied zwischen einem Domänenmodell und einem MVC-Modell?" oder "Was ist ein Modell?"

Konzeptionell verstehe ich unter einem Modell die Daten, die von den Ansichten und dem Controller verwendet werden. Darüber hinaus scheint es sehr unterschiedliche Meinungen darüber zu geben, was das Modell ausmacht. Was ist ein Domain-Modell im Vergleich zu einem App-Modell, einem Ansichtsmodell, einem Servicemodell usw.

In einer kürzlich gestellten Frage zum Repository-Muster wurde mir beispielsweise direkt mitgeteilt, dass das Repository Teil des Modells ist. Ich habe jedoch andere Meinungen gelesen, dass das Modell vom Persistenzmodell und der Geschäftslogikschicht getrennt werden sollte. Soll das Repository-Muster die konkrete Persistenzmethode nicht vom Modell entkoppeln? Andere Leute sagen, dass es einen Unterschied zwischen dem Domänenmodell und dem MVC-Modell gibt.

Nehmen wir ein einfaches Beispiel. Der AccountController, der im MVC-Standardprojekt enthalten ist. Ich habe mehrere Meinungen gelesen, dass der enthaltene Kontocode von schlechtem Design ist, gegen SRP verstößt usw. usw. Wenn man ein "richtiges" Mitgliedschaftsmodell für eine MVC-Anwendung entwerfen würde, welches wäre das?

Wie würden Sie die ASP.NET-Dienste (Mitgliedschaftsanbieter, Rollenanbieter usw.) vom Modell trennen? Oder würdest du überhaupt?

So wie ich es sehe, sollte das Modell "rein" sein, vielleicht mit Validierungslogik. Es sollte jedoch von den Geschäftsregeln (außer der Validierung) getrennt sein. Angenommen, Sie haben eine Geschäftsregel, nach der jemand per E-Mail benachrichtigt werden muss, wenn ein neues Konto erstellt wird. Das gehört aus meiner Sicht nicht wirklich zum Modell. Wo gehört es hin?

Möchte jemand Licht in dieses Thema bringen?

Erik Funkenbusch
quelle
1
Deshalb sollten Sie vier verschiedene Fragen stellen.
John Farrell
3
Das Schlüsselwort lautet "fast". Es ist wirklich die gleiche Frage, mit vielleicht Unterfragen, die verwendet werden, um die Hauptfrage zu veranschaulichen.
Erik Funkenbusch
3
Model View Controller. Ist Reposirory / BL View? Ist es Controller? Was bleibt übrig :)? Es ist MVC, nicht MSVC, nicht MRVC, nicht MBLVC. Es gibt nur drei Schichten. Das Repository ist also Teil des Modells, BL ist Teil des Modells. Sie können eine zusätzliche Trennung vornehmen, diese erfolgt jedoch innerhalb der Modellebene.
LukLed
3
@LukeLed, @bslm - Nicht wirklich. MVC sagt nicht, dass es keine anderen Ebenen geben kann, mit denen der Controller oder das Modell interagiert.
John Farrell
3
@LukLed - Nicht einverstanden - MVC ist lediglich ein Präsentationsschichtmuster. Es hat keinen Einfluss darauf, wie Sie Ihre anderen Ebenen wie BLL und DAL strukturieren.
Cory House

Antworten:

69

Die Art und Weise, wie ich es getan habe - und ich sage nicht, dass es richtig oder falsch ist - ist, meine Ansicht und dann ein Modell zu haben, das für meine Ansicht gilt. Dieses Modell hat nur das, was für meine Ansicht relevant ist - einschließlich Datenanmerkungen und Validierungsregeln. Der Controller enthält nur Logik zum Erstellen des Modells. Ich habe eine Serviceschicht, die die gesamte Geschäftslogik enthält. Meine Controller rufen meine Serviceschicht auf. Darüber hinaus ist meine Repository-Schicht.

Meine Domain-Objekte sind separat untergebracht (eigentlich in einem eigenen Projekt). Sie haben ihre eigenen Datenanmerkungen und Validierungsregeln. Mein Repository überprüft die Objekte in meiner Domäne, bevor sie in der Datenbank gespeichert werden. Da jedes Objekt in meiner Domäne von einer Basisklasse erbt, in die eine Validierung integriert ist, ist mein Repository generisch und validiert alles (und erfordert, dass es von der Basisklasse erbt).

Sie könnten denken, dass zwei Modellsätze eine Duplizierung von Code sind, und dies ist bis zu einem gewissen Grad der Fall. Es gibt jedoch durchaus vernünftige Fälle, in denen das Domänenobjekt für die Ansicht nicht geeignet ist.

Ein typisches Beispiel ist die Arbeit mit Kreditkarten. Ich muss bei der Verarbeitung einer Zahlung einen Lebenslauf verlangen, kann den Lebenslauf jedoch nicht speichern (dies ist eine Geldstrafe von 50.000 US-Dollar). Ich möchte aber auch, dass Sie Ihre Kreditkarte bearbeiten können - Änderung von Adresse, Name oder Ablaufdatum. Aber Sie werden mir beim Bearbeiten weder die Nummer noch den Lebenslauf geben, und ich werde Ihre Kreditkartennummer auf keinen Fall in Klartext auf der Seite setzen. In meiner Domain sind diese Werte zum Speichern einer neuen Kreditkarte erforderlich, da Sie sie mir geben, aber mein Bearbeitungsmodell enthält nicht einmal die Kartennummer oder den Lebenslauf.

Ein weiterer Vorteil für so viele Ebenen besteht darin, dass Sie bei korrekter Architektur eine Strukturkarte oder einen anderen IoC-Container verwenden und Teile austauschen können, ohne Ihre Anwendung zu beeinträchtigen.

Meiner Meinung nach sollte Controller-Code nur Code sein, der auf die Ansicht abzielt. Zeigen Sie dies, verbergen Sie das usw. Die Service-Schicht sollte die Geschäftslogik für Ihre App enthalten. Ich mag es, alles an einem Ort zu haben, damit es einfach ist, eine Geschäftsregel zu ändern oder zu optimieren. Die Repository-Schicht sollte relativ dumm sein - ohne Geschäftslogik und nur Ihre Daten abfragen und Ihre Domänenobjekte zurückgeben. Durch die Trennung der Ansichtsmodelle vom Domänenmodell haben Sie viel mehr Flexibilität bei benutzerdefinierten Validierungsregeln. Dies bedeutet auch, dass Sie nicht alle Daten in ausgeblendeten Feldern in Ihre Ansicht kopieren und zwischen Client und Server hin und her verschieben müssen (oder sie im Backend neu erstellen müssen).

<% if (!String.IsNullOrEmpty(Model.SomeObject.SomeProperty) && 
    Model.SomeObject.SomeInt == 3 && ...) { %>

Während alles ausgebreitet und überlagert zu sein scheint, hat es den Zweck, auf diese Weise aufgebaut zu werden. Ist es perfekt nicht wirklich. Aber ich ziehe es einigen früheren Entwürfen vor, Repositorys vom Controller aufzurufen und Geschäftslogik in Controller, Repository und Modell zu mischen.

Josh
quelle
Fast ein Spiegel dessen, was ich in unserer Enterprise-MVC-Anwendung habe. Eine N-Tier-Architektur. Die MVC-App interagiert nur mit Geschäftsobjekten und -diensten in den N-Tier-Bereichen.
Ed DeGagne
Meistens das gleiche hier. Separate Projekte für Definitionen, Modelle, Ansichtsmodelle, DAL usw. Der einzige Unterschied besteht darin, dass meine DAL Logik zum Reduzieren von Daten für das Web enthält, um die Verteilung komplexer Daten für Berichte oder benutzerdefinierte Kundenansichten zu optimieren. Ich scheue mich jetzt davor zurück, Dinge im Anwendungscache für Nachschlagetabellen usw. zu behalten, wenn Webfarmen und Azure-Clouds im Spiel sind.
Robert Achmann
1
@ Josh, wäre es hilfreich, wenn Sie einen Screenshot Ihres Beispielprojekts zeigen könnten?
Shaijut
@ Josh was ist, wenn dein Projekt keine Datenbank hat. Es interagiert mit Service-Referenzen. Alle Domänenklassen und Methoden stammen aus diesen Referenzen. Ist dieses Szenario für eine Schichtstruktur geeignet?
user6395764
17

Ich habe mich zu oft gefragt, wie genau die MVC-Elemente in eine herkömmliche Webanwendungsstruktur passen, in der Sie Ansichten (Seiten), Controller, Dienste und Datenobjekte (Modell) haben. Wie Sie sagten, gibt es viele Versionen davon.

Ich glaube, die Verwirrung besteht aufgrund der oben genannten, weithin akzeptierten Architektur, die das (angebliche) -anti-Muster des "anämischen Domänenmodells" verwendet. Ich werde nicht auf viele Details zur "Anti-Patternness" des anämischen Datenmodells eingehen (Sie können sich meine Bemühungen ansehen, um die Dinge hier zu erklären (Java-basiert, aber für jede Sprache relevant)). Kurz gesagt bedeutet dies, dass unser Modell nur Daten enthält und die Geschäftslogik in Services / Managern platziert wird.

Nehmen wir jedoch an, wir haben eine domänengesteuerte Architektur und unsere Domänenobjekte sind so, wie sie erwartet werden - sie haben sowohl Status- als auch Geschäftslogik. Und in dieser domänengetriebenen Perspektive kommen die Dinge zusammen:

  • Die Ansicht ist die Benutzeroberfläche
  • Der Controller sammelt die Eingaben der Benutzeroberfläche, ruft Methoden für das Modell auf und sendet eine Antwort an die Benutzeroberfläche zurück
  • Das Modell sind unsere Geschäftskomponenten - sie enthalten die Daten, haben aber auch Geschäftslogik.

Ich denke, das beantwortet Ihre Hauptfragen. Die Dinge werden kompliziert, wenn wir weitere Ebenen hinzufügen, wie z. B. die Repository-Ebene. Es wird häufig vorgeschlagen, dass es von der im Modell platzierten Geschäftslogik aufgerufen wird (und daher hat jedes Domänenobjekt einen Verweis auf ein Repository). In meinem Artikel, den ich verlinkt habe, argumentiere ich, dass dies keine bewährte Methode ist. Und dass es in der Tat keine schlechte Sache ist, eine Serviceschicht zu haben. Übrigens schließt domänengesteuertes Design die Serviceschicht nicht aus, sondern soll "dünn" sein und nur Domänenobjekte koordinieren (also keine Geschäftslogik).

Für das Paradigma des anämischen Datenmodells, das weit verbreitet ist (zum Guten oder zum Schlechten), wäre das Modell sowohl die Serviceschicht als auch Ihre Datenobjekte.

Bozho
quelle
Hervorragender Punkt! Eine Bemerkung: Es gibt das gleiche Chaos mit den Diensten. Zumindest Dienste können Anwendungsdienste und Domänendienste sein. Der Anwendungsdienst ist nur ein Thin Wrapper, der Informationen aus Repositorys usw. sammelt. Der Domänendienst bietet Geschäftslogik, die eine Kombination von Domänenmodellen oder nur Dinge verwendet, die nicht immer in das Domänenmodell passen.
Artru
Was ist, wenn Ihr Projekt keine Datenbank hat? Es interagiert mit Service-Referenzen. Alle Domänenklassen und Methoden stammen aus diesen Referenzen. Ist dieses Szenario für eine Schichtstruktur geeignet?
user6395764
3

Meiner Meinung nach,

Modell -

Sollte keine Geschäftslogik enthalten, sollte sie steckbar sein (WCF-ähnliches Szenario). Es wird zum Binden an die Ansicht verwendet, daher sollte es Eigenschaften haben.

Geschäftslogik -

Es sollte auf "Domain Services Layer" platziert werden, es ist insgesamt eine separate Ebene. Außerdem wird hier eine weitere Ebene "Anwendungsdienste" hinzugefügt.

App Services kommuniziert mit der Domain Services-Schicht, um die Geschäftslogik anzuwenden und zuletzt das Modell zurückzugeben.

Der Controller wird also den Anwendungsdienst nach dem Modell fragen und der Ablauf wird wie folgt ablaufen:

    Controller->Application Services(using domain services)->Model
py2020
quelle
2

Das MVC-Muster und das Asp.net-Framework unterscheiden nicht, wie das Modell aussehen soll.

Zu den eigenen Beispielen von MS gehören Persistenzklassen im Modell. Ihre Frage zur Mitgliedschaft im Modell. Das hängt davon ab. Gehören Klassen in Ihrem Modell etwas? Gibt es einen Zusammenhang zwischen der Anmeldung und den angezeigten Daten? Gibt es eine Filterung des Datenteils eines Berechtigungssystems, das bearbeitet werden kann? Hat wer zuletzt einen Objektteil Ihrer Domain aktualisiert oder bearbeitet, wie es jemand anderes sehen muss, oder etwas für die Backend-Unterstützung?

Das E-Mail-Beispiel hängt auch davon ab. Kennen Sie sich mit Domain-Eventing oder insbesondere mit Eventing aus? Haben Sie einen separaten Dienst zum Versenden von E-Mails? Ist das Senden einer E-Mail Teil Ihrer Domain oder handelt es sich um ein Problem auf Anwendungsebene außerhalb des Bereichs Ihres Systems? Muss die Benutzeroberfläche wissen, ob eine E-Mail erfolgreich gesendet wurde oder nicht? Müssen E-Mails, die nicht gesendet werden können, erneut versucht werden? Muss der Inhalt der gesendeten E-Mail für Support- oder Kundendienstanforderungen gespeichert werden?

Diese Art von Fragen ist zu weit gefasst und subjektiv, aber ich beantworte sie, damit Sie und jeder, der Sie gewählt hat, dies verstehen können.

Ihre Anforderungen / Zeitpläne / Ressourcen fließen alle in die Architektur Ihres Systems ein. Auch das Einnahmemodell kann sich auswirken. Sie müssen auch das Muster berücksichtigen, nach dem Sie fotografieren. DDD unterscheidet sich stark von Persistence-as-Model-Anwendungen, und alle dazwischen liegenden Slops gelten auch für bestimmte Apps. Schießen Sie zum Testen der App? All dies wirkt sich aus.

John Farrell
quelle