Laden...

MVVM bei konsequenter Umsetzung von DDD

Erstellt von SuperVisor vor 9 Jahren Letzter Beitrag vor 8 Jahren 7.988 Views
S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren
MVVM bei konsequenter Umsetzung von DDD

Hi zusammen

Lese mich gerade ein wenig in DDD ein. Da ich eine WPF-Applikation mit dem MVVM-Pattern erstellen möchten, steh ich vor einem kleinen Problem.

Gemäss Definition von DDD muss ein Objekt/Model immer einen gültigen Zustand haben. Dazu dürfen die Eigenschaften nicht (oder nur kontrolliert) verändert werden. Aus diesem Grund werden die Setter der Eigenschaften meist als "Private" oder "Internal" definiert. Alle Änderungen am Objekt selber werden durch Methoden durchgeführt.

In meinem ViewModel möchte ich nun das Model verwenden. Dazu übergebe ich ein solches dem Konstruktor. Da die Setter im Model als "Private" markiert sind, kann ich diese im ViewModel logischerweise nicht ändern.

Nun, wie wird das praktischerweise umgesetzt? Soll ich die Setter auch entgegen der Definition von DDD auf "Public" setzen oder soll ich für jede Änderung eine eigene Set-Methode erstellen? Ich denke, dass der letzte Ansatz der schlechteste ist, da so der Code extrem unübersichtlich und kaum wartbar werden kann. Ausserdem schreib ich mir da einen Wolf. Ist es in einem solchen Fall "erlaubt" gegen die Definition zu verstossen?

Unterhalb ein stark vereinfachtes Beispiel (kein INPC, etc.)


 public class PersonViewModel
    {
        private readonly PersonModel _model;

        public PersonViewModel(PersonModel model)
        {
            _model = model;
        }

        public string FirstName
        {
            get { return _model.FirstName; }
            set { _model.FirstName = value; } //Setter in Model ist Private
        }

        public string LastName
        {
            get { return _model.LastName; }
            set { _model.LastName = value; } //Setter in Model ist Private
        }

        public int Age
        {
            get { return _model.Age; }
            set { _model.SetAge(value); } //Methode in Model
        }
    }

    public class PersonModel
    {
        public PersonModel(Guid id, string firstName, string lastName)
        {
            Id = id;
            FirstName = firstName;
            LastName = lastName;
        }

        public Guid Id { get; private set; }
        public string FirstName { get; private set; }
        public string LastName { get; private set; }
        public int Age { get; private set; }

        public void SetAge(int newAge)
        {
            if (newAge > 0 & newAge < 150)
            {
                Age = newAge;
            }
        }
    }


5.658 Beiträge seit 2006
vor 9 Jahren

Hi SuperVisor,

wenn die Anforderungen sind, daß bestimmte Eigenschaften des Models durch die View (bzw. durch das ViewModel) geändert werden müssen, dann mußt du diese Funktionalität eben implementieren. Entweder braucht das Model für die Eigenschaften öffentliche Setter oder eine öffentliche Methode. In der Methode oder im Setter kannst du ja dann immer sicherstellen, daß das Model immer in einem gültigen Zustand ist. Das hat aber nichts mit DDD im Speziellen zu tun.

Christian

Weeks of programming can save you hours of planning

P
1.090 Beiträge seit 2011
vor 9 Jahren

Hi zusammen

Gemäss Definition von DDD muss ein Objekt/Model immer einen gültigen Zustand haben. Dazu dürfen die Eigenschaften nicht (oder nur kontrolliert) verändert werden.

Genau das macht doch das VM. Das Prüfen des alter sollte aus dem Model heraus wandern und ins VM wandern (Besser vieleicht noch eine eigene Klasse, kann man dann auch an andere stelle wieder verwenden). Und nur wenn das alter gültig ist wird es im Model geändert.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

Hi

@Palin: Verstehe deinen Ansatz. Nur möchte ich die Logik des Objektes/Models zusammen haben. Möchte ich dieses Objekt/Model noch in einem anderen Kontext nutzen (z.B. ohne GUI), dann müsste ich diese Logik noch einmal implementieren. So kann ich mir auch die zusätzliche Klasse ersparen...

@MrSparkle: Ok, verstehe. Wie meinst du das mit

Hat aber nichts mit DDD im Spezielln zu tun

Diese Aussage verstehe ich nicht ganz.

Gruss
Roger

P
1.090 Beiträge seit 2011
vor 9 Jahren

Schau dir mal den Artikel und die Diskussion an.

[Artikel] Drei-Schichten-Architektur

Die Logic solltest du nur einmal Implementierne, aber wenn du Logic und Daten trennst machst du dir das Leben wirklich einfacher.

@MrSparkle
Wenn so weiter geht, hast du den 2 meisten zitierten Post hier im Forum erstet.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

Die verschiedenen Schichten sind mir bekannt. So wie ich das sehe und DDD auch sagt ist eine gewisse Logik/Businesslogik im Model notwendig um die Konsistenz des Objektes zu garantieren.

Nehmen wir das Beispiel in meinem Ausgangspost. Dort habe ich im Model eine Methode "SetAge". In dieser wird geprüft ob das Alter, welches gesetzt werden soll gültig ist (<0 und >150). Würde ich diese Logik im ViewModel implementieren, so kann das Objekt bzw. das Age-Property in einer Konsolenapplikation einen ungültigen Wert erhalten.

Habe ich da evtl. grundsätzlich etwas falsch verstanden?

Aber so langsam driften wir vom usprünglichen Thema ab....

5.658 Beiträge seit 2006
vor 9 Jahren

Also ehrlich gesagt verstehe ich dein Problem überhaupt nicht. Es besteht doch kein Widerspruch zwischen der Anforderung, daß ein Model nur einen gültigen Zustand haben darf, und der Anforderung, daß dieser Zustand von außen geändert werden kann.

Die Validierung selbst gehört natürlich in die BL, wobei sie auch für die GUI aufrufbar sein sollte, um bspw. sinnvolle Fehlerhinweise anzeigen zu können.

Christian

Weeks of programming can save you hours of planning

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

Genau, so she ich das auch und zwar in beiden Punkten!

Palin hat nur geschrieben, dass ich Logik und Daten trennen sollte, und das macht für mich in diesem Fall keinen Sinn....

P
1.090 Beiträge seit 2011
vor 9 Jahren

Bitte einfach mal den ganzen Beitrag durch lesen, auch die Disskusion.
Inruhe drüber nachdenken und Verstehen.

Wenn du dann immer noch der Meinung bist, das es allgemein (ausnahmen kann es immer geben) gut ist Logic und Daten zusammen in eine Klasse zu Packen. Können wir gerne drüber Diskutieren.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

742 Beiträge seit 2005
vor 9 Jahren

Die Anforderung in DDD wurde formuliert, um erstens Domain-Aktionen besser zu formulieren und zweitens deine Domain Objekt konsistent zu halten. Auch wenn viele CRUD-Anwendungen das anderst wirken lassen, aber in einer Anwendung gibt es normalerweise kein "Save User". Stattdessen gibt es CreateUser, ChangePassword, UpdateProfile usw.

Das heißt wenn du deine Domänee definierst, könntest du mit diesen Commands anfangen (ich lass jetzt viel weg wie z.B. Properties, damit ich nicht so viel schreiben muss 😉).


class ChangeUsernameCommand
{
   string Username
}

class User
{
   string username;

   string UserName { get { return username; } }

   void ChangeUsername(ChangeUsernameCommand command)
   {
       CheckNotNull(command);
       CheckNotEmpty(ommand.UserName));

       username = command.Username; 
   }
}


und für diesen Command kannst du jetzt ein VM bauen. Damit verletzt du die Bedingung nicht mehr und hast deine Aktionen viel besser modelliert.

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

@Palin: wir sprechen vermutlich von verschiedenen Dingen. Wenn ich bezogen auf mein Beispiel im ersten Post von Daten spreche, dann meine ich damit die Daten innerhalb des Models. Wenn ich dich nun richtig verstanden habe, sprichst du von Daten die z.B. aus der DB kommen. Korrekt?

@malignate: Besten Dank für den Input!

F
10.010 Beiträge seit 2004
vor 9 Jahren

Dazu dürfen die Eigenschaften nicht (oder nur kontrolliert) verändert werden. Aus diesem Grund werden die Setter der Eigenschaften meist als "Private" oder "Internal" definiert. Alle Änderungen am Objekt selber werden durch Methoden durchgeführt.

Es gibt sprachen die ermöglichen keine Properties ( z.b. Java ) und damit auch nicht das man beim Setzen einer Eigenschaft eingreifen kann.
Diese benötigen natürlich dann explizite Funktionen zum setzen.

Gemäss Definition von DDD muss ein Objekt/Model immer einen gültigen Zustand haben

Ja und?
Wie ich hier schon oft schreiben musste, auch ein Model kann sich Validieren und dann je nach Anforderung Hard oder Weich reagieren.
Das hat nichts mit einer übergeordneten Business Schicht zu tun.

Also, Setter die man Sinnvoll ändern kann, kann man auch öffentlich machen.
In diesen Settern dann die Validierung durchführen und bei der Anforderung das das Objekt immer gültig sein muss entweder die Angaben nicht übernehmen ( selten sinnvoll ) oder Exception werfen.
Ansonsten IDataErrorInfo implementieren.

Das kann man sowohl im Model als auch im ViewModel.

Pattern sind nicht zur Bestrafung gedacht, deshalb muss man bei ihrer Benutzung YAGNI und KISS nicht aussen vor lassen.

742 Beiträge seit 2005
vor 9 Jahren

Ich bin kein DDD Experte, das heißt die folgenden Aussagen sind meine Meinung und mein grobes Verständnis von DDD.

Die geforderte Eigenschaft wurde im Prinzip für Business Objekte formuliert und DTO's und ViewModels bleiben komplett außen vor. Deshalb bringt es nichts, sich darüber Gedanken zu machen, wie man das in der UI umsetzen kann. Und im Domain Objekt sehe ich eigentlich keinen Platz für weiche Validierung. Entweder wird ein Benutzername geändert oder eben nicht, aber nicht zu 50%.

Außerdem sind hier Setter für die Modellierung eigentlich eher schwierig, weil sie wenig Wissen über die Domäne transportieren. Das Design geht dann schnell richtung Anemic Domain Model. Das kann man natürlich wollen, sollte aber nicht als Unfall passieren.

P
1.090 Beiträge seit 2011
vor 9 Jahren

@SuperVisor
Ich meine schon die Daten die in den Model sind. In der Diskussion wurde aber auch an mehreren Stellen wie so es Sinnvoll ist Logic und Daten zu Trennen (SRP usw.).

Klassischer weise wird bei MVVM die Validierung im VM gemacht und nich im Model. Ansätze in denen sie im Model gemacht werden funktionieren auch. Können aber recht unschön sein.

Schauen wir uns das mal bei dem User an. Beim Registrieren ist es ja meistens so, das es ein Username noch nicht geben darf. Wenn ich hier die Validierung im Model machen möcht muss das Model alle Usernamen kennen oder brauch zugriff auf die Datebank (kann natürlich hinter einem Inteface gekapselt werden). Ist beides aber nicht wirklich schon. Wenn du das gleiche Model dann beim LogIn verwenden wills wird es Lustig. Da muss es ja den Usernamen giben. Das obejekt hat also 2 gültige zustände die sich gegenseitig wiedersprechen.

Ich kenne zwar jetzt das Buch zu DDD nicht, aber ich kann mir nicht vorstellen, das dass der Autoren im Sinn hatte.

Kann es sein das du das DomainModel mit dem Model abbilden willst?
Wenn ich es jetzt richtig verstanden habe enstpricht das DomainModel eher dem VM.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

4.939 Beiträge seit 2008
vor 9 Jahren

Ich habe hier auch den Eindruck, daß ihr beiden (SuperVisor und Palin) von verschiedenen Sachen sprecht, s. Domain Driven Design.
Wie dort beschrieben, umfaßt also die BL die Funktionalität sowie auch die Daten, d.h. in der BL findet u.a. jedliche Validierung statt.

@Palin: Wenn du von Validierung im ViewModel bei MVVM sprichst, so soll das ja bezwecken, daß die View entsprechende Fehlermeldungen (oder Fehlerzustände etc.) anzeigen kann - dies heißt aber nicht, daß die BL keinerlei Validerung mehr durchführen sollte (denn dann könnte man ja daran vorbei programmieren).
Und für dich scheint ein Model nur die Daten zu sein, allgemein wird aber unter Modell die gesamte BL verstanden (d.h. inkl. Funktionalität - ausgenommen eben nur bei einem anämischen Domänenmodell).

P
1.090 Beiträge seit 2011
vor 9 Jahren

Und für dich scheint ein Model nur die Daten zu sein, allgemein wird aber unter Modell die gesamte BL verstanden (d.h. inkl. Funktionalität - ausgenommen eben nur bei einem anämischen Domänenmodell).

Wie ich schon angedeutet habe gibt es da auch andere Lösungen. Aber das Model als reine Datenklasse zu sehen ist nicht meine idee sondern wird auch in Building Enterprise Applications with Windows Presentation Foundation and the Model View ViewModel Pattern (English Edition) beschrieben.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

Hi Palin

Klassischer weise wird bei MVVM die Validierung im VM gemacht und nich im Model.

Das kann ich so nicht ganz unterschreiben. Es gibt aus meiner Sicht Validierungen, die auf dem VM gemacht werden müssen und solche im Model. Nehmen wir das Beispiel mit der Registrierung eines Users. Du hast recht, wenn du schreibst, dass es im Model keinen Sinn macht zu überprüfen, ob ein Benutzer bereits in der DB existiert oder nicht. Eine solche Validierung gehört für mich in einen Service. Dieser Service ist im VM verfügbar. Das Model muss "nur" solche Validierungen durchführen, welche für die Konsistenz des Models notwendig sind. Das kann z.B. die Überprüfung sein, ob ein UserName mindestens 3 Zeichen hat und ein Sonderzeichen enthält o.ä. Das VM soll z.B. nur überprüfen ob das Passwort1 mit dem Passwort2 übereinstimmt.

Ein Service stellt für mich immer "Zusatzdienstleistungen" zur Verfügung, welche im Model nicht angeboten werden sollten (SOC) oder können.

Ich hab hierzu ein kleines Beispiel gemacht. Das VM ist sehr minimalistisch gehalten. Daher fehlen sicher einige Punkte wie INPC o.ä. Dies soll aber kein lauffähiges/korrektes Beispiel sein, sondern nur die Idee der Service und des Models zeigen.

 public class UserModel
    {
        public static readonly UserModel Undefined = new UserModel(Guid.Empty, string.Empty, string.Empty);

        public UserModel(Guid id, string userName, string password)

        {
            Id = id;
            UserName = userName;
            PasswordHash = password;
        }

        public Guid Id { get; private set; }
        public string UserName { get; internal set; }
        public string PasswordHash { get; internal set; }

        #region Informational

        public bool IsValid()
        {
            //Do some validation of the UserModel here
            return true;
        }

        #endregion

        #region Behavior

        public void SetUserName(string userName)
        {
            if (string.IsNullOrWhiteSpace(userName))
                throw new ArgumentException("username");

            if (userName.Length > 3)
                throw new Exception("Username should be more than 3 characters.");

            UserName = userName;
        }

        public void SetPassword(string passwordHash)
        {
            PasswordHash = passwordHash;
        }

        #endregion
    }

    public class RegistrationService
    {
        public bool RegisterUser(UserModel userModel)
        {
            if (userModel.IsValid())
            {
                //Do some validation
                return true; //return true if registration is successfully
            }
            return false;
        }
    }


    public class RegistrationViewModel
    {
        private readonly RegistrationService _registrationService;
        private readonly UserModel _userModel;

        public RegistrationViewModel(RegistrationService registrationService)
        {
            _userModel = UserModel.Undefined;
            _registrationService = registrationService;
        }

        public string UserName { get; set; }
        public string Password1 { get; set; }
        public string Password2 { get; set; }

        public void RegisterUserCommand()
        {
            _registrationService.RegisterUser(_userModel);
        }

        private bool Password1IsEqualPasswor2()
        {
            if (Password1.Equals(Password2))
            {
                return true;
            }
            return false;
        }
    }
P
1.090 Beiträge seit 2011
vor 9 Jahren

Ist doch schon mal schön das wir uns einig sind das manche Validierungen besser ins VM gehören. Und wie gesagt es gibt auch Ansätze, in denen Teile der Validierung im Model gemacht werden.

Ich persönlich bin da kein großer Fan von und halte es für besser die beiden Belange von einander zu trennen.

Um es jetzt mal an deinem Beispiel zu demonstrieren.
Was ist wenn sich die Länge des Benutzernamen ändert z.B. auf 10 Zeichen, kann ich mich dann nicht mehr im Forum anmelden? Ist auch schon wider nicht schön. Jetzt muss dein Model wieder 2 Zustände unterstützen. Schon angemeldete Benutzer und neu Anmeldungen.

p.s.
Bei


   if (userName.Length < 3)
                throw new Exception("Username should be more than 3 characters.");

Passt die Fehlermedung nicht zu Prüfung. Mehr als 2 Buchstaben sind gültig. Aber es sollen mehr als 3 sein.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

Doch, die bestehenden Benutzer können sich weiterhin anmelden. Die Methode wird nur aufgerufen, wenn der Benutzername NEU gesetzt wird.

Ja, da ist mir ein Tippfehler unterlaufen....

4.939 Beiträge seit 2008
vor 9 Jahren

Immer noch nicht richtig 😉
Aber solchen Code würde man ja auch mit Unit-Tests absichern...

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

Korrekt, also das mit dem UnitTest! :-)

742 Beiträge seit 2005
vor 9 Jahren

Die Validierung gehört ins ViewModel und ins DomainModel (dazu gehört das eigentlich Model, Repositories, Commands usw.)

Warum?

  1. Das DomainModel wird von vielen Stellen verwendet, z.B. mehrere UIs, Import Prozesse, Automatische Tasks usw. Die UI dafür verantwortlich zu machen, dass das DomainModel richtig verwendet wird, ist ein bisschen fahrlässig. Insbesondere auch dann wenn mehrere Entwickler im Spiel sind.

  2. Die Lösungen sind höchst unterschiedlich. In der UI könnte man z.B. während des Eingebens überprüfen, ob der Name noch frei ist. Ist man dagegen in einem Szenario, wo man noch eine gute Konsistenz erreichen kann (z.B. kleine Anwendung mit Locking in der DB oder Server) kann man auch mit Constraints überprüfen, ob der Benutzername angelegt werden kann.

P
1.090 Beiträge seit 2011
vor 9 Jahren

Doch, die bestehenden Benutzer können sich weiterhin anmelden. Die Methode wird nur aufgerufen, wenn der Benutzername NEU gesetzt wird.

Also in deinem Beispiel wird in der Methode SetUserName die Validierung gemacht. Das kling für mich so als ob es bei der Registrirung und beim Einloggen (NEU Anmelden) aufgerufen wird. Ansonsten müstest du dir schon wieder die Benutzernamen Merken bzw. für einen Computer könnte es nicht unterschiedliche Benutzer geben.

Ganz erlich bei deinem Ansatz (Daten und Logic im Model (MVVM) zu kombinieren) sehe ich keinen einzigen vorteil, sondern eigendlich nur Nachteile. Erläutere doch mal welchen Vorteil du siehst, b.z.w. in DDD beschrieben wird. (Geht DDD überhaut auf das MVVM Pattern ein?)

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

Hi Palin

Also in deinem Beispiel wird in der Methode SetUserName die Validierung gemacht. Das kling für mich so als ob es bei der Registrirung und beim Einloggen (NEU Anmelden) aufgerufen wird.

OK, über die Namensgebung kann diskutiert werden. Man könnte die Methode z.B. "SetNewUserName" oder "ChangeUserName" nennen. Aber das sollte eigentlich nicht das Problem sein. Gehen wir nochmals zurück zu deinem Beispiel mit der Registrierung. Eine User-Registrierung bedeutet für mich, dass sich der User für ein bestimmtes System zuerst registrieren muss um bestimmte Dienste/Funktionen nutzen zu können (z.B. ein Shop). Dafür gibt er einen Benutzernamen. zwei Passwörter und evtl. weitere Daten an. Während oder nach einem klick auf "Registrieren" werden die Daten validiert und entsprechende weitere Prozesse angestossen (Versand Mailbestätigung, etc.). Das alles hat aber nichts mit einem Login zu tun. Ein Login wird gemacht, wenn bereits eine Registrierung eines Benutzers stattgefunden hat.

Mein Beispiel nutzt das User-Model in einem "RegistrationViewModel". Für den Login gibt es ein weiteres VM ("LoginViewModel"). Für das "RegistrationViewModel" würde ich die Methode "SetUserName" nutzen, für das "LoginViewModel" jedoch nicht, da ich dort den "UserName" nicht neu setzen will, sondern nur "UserName" und "Password" mit den Daten in der Datenbank validieren möchte. Das würde dann wiederum in einem "LoginService" mit entsprechenden Methoden geschehen. Ich habe so die Registrierung und den Login komplett voneinander getrennt.

Aber vielleicht kannst du mir/uns mal anhand eines kleinen Beispiels aufzeigen, wie du diese Anforderungen lösen würdest.

F
10.010 Beiträge seit 2004
vor 9 Jahren

OK, über die Namensgebung kann diskutiert werden.

Eher nicht. Wenn es durch die Namensgebung zu solchen Verwechselungen oder Missverständnissen führen kann, ist der Name Suboptimal und sollte angepasst werden

P
1.090 Beiträge seit 2011
vor 9 Jahren

In deinem Beispiel hast du doch nur eine Methode zum setzen des Namens, bei Login könnte ich also nicht den Namen setzen.

Wie ich es grundlegend umsetzen würde habe ich ja schon gesagt. Bei mir wäre das VM für die Validierung zuständig. Es gab also 2 VM (Login/Registration) und 1 Model User. Die 2 VM wüsten welche Methoden sie zur validierung aufrufen müsten. Die Methoden selber würde ich wahrscheinlich in den BL packen, so das ich sie bei bedarf auch von anderen Klassen nutzen kann. (Sollte den Prinzipien SRP, SOC und IOC gerecht werden und kein anderes Verletzen.)

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

4.939 Beiträge seit 2008
vor 9 Jahren

@Palin: Ich verstehe noch nicht so recht, was du bei dir unter Model verstehst? Sind das für dich nur DTOs/POCOs und was ist dann die BL? Wie ich oben schon schrieb, sind die einzelnen Model-Klassen (für mich) Teil der BL, d.h. sie beinhalten auch Logikmethoden (z.B. Validierung).

Bei meinem aktuellen Projekt verwende ich auch DTOs, um die Daten zwischen den verschiedenen Schichten zu transportieren (aber für mich ist trotzdem das Model die B(usiness) L(ogic)) - Model im Sinne von MVVM.

P
1.090 Beiträge seit 2011
vor 9 Jahren

Das Model entspricht im meinen Ansatz einem Poco (grundlegend einer reinen Datenhaltungsklasse, hab ich aber oben schon erwähnt und auch auf meine Quelle verwiesen).

Und ja man kann auch Validierungen im Model machen (hab ich jetzt auch ein paar mal erwähnt).
Es gibt auch MVVM Ansätze die das Laden der Daten in das Model verlagern. Ich persönlich sehe in beiden Ansetzen eher Nachteile (Auf die Validierung bin ich hier ja jetzt auch schon ein paar mal eingegangen). Fals es Vorteile gibt Setter im Model als private zu deklarieren, sollte sie mir doch jemand Erläutern können.

Ganz ehrlich das DDD Buch von Eric Evans hab ich jetzt nicht gelesen. Ich kann mir aber schwer vorstellen, das es nicht die selben Prinzipien wie z.B. die GoF zugrunde legt. Meiner Vermutung ist immer noch, das das DomainModel nicht dem Model im MVVM Pattern entspricht. Die Lösung von SuperVisor die Setter private zu machen und fürs setzen der Eigenschaften (Propertys) extra Methoden anzubieten, ist einfach sehr Kompliziert.

Mein Ansatz an dem Punkt ist relative einfach. Ich kann nicht alles wissen/verstehen und ich werde auch nie alles richtig machen (Auch wenn ich nach beiden strebe). Wenn es kompliziert wird, mache ich wahrscheinlich was Falsch und muss nochmal in Ruhe drüber nachdenken ob es nicht eine bessere Lösung gibt.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

In deinem Beispiel hast du doch nur eine Methode zum setzen des Namens, bei Login könnte ich also nicht den Namen setzen.

Für den Login muss aus meiner Sicht der Benutzername im Model auch nicht gesetzt werden. Ich würde den Benutzernamen und das Passwort direkt aus dem VM abgreifen und dem entsprechenden LoginService weitergeben.

Die Quelle, welche Palin erwähnt hat, verwendet mehr oder weniger POCO's. Es gibt im Beispielcode aber auch Methoden innerhalb der POCO-Klassen. Also nicht mehr reine POCO's. BusinessLogik wird zudem in einen weiteren Layer ausgelagert. (Hoffe habe das Beispiel richtig gelesen zu haben. Hatt nur kurz Zeit). Ich bin daher nicht so ganz überzeugt vom Buch (bezüglich diesem Aspekt), da verschiedene Ansätze verfolgt werden.

Wir setzen diese Art der Aufteilung (separate BL) bei einer unserer Applikationen auch ein. Das funktioniert soweit auch ganz gut. Wir konnten nur Schwierigkeiten festestellen, sobald mehrere Models mit SubModels und relativ viel BusinessLogik im Spiel war. Hätten wir dort den DDD-Ansatz konsequenter verfolgt, wäre es vermutlich einfacher gegangen.

Es gibt auch MVVM Ansätze die das Laden der Daten in das Model verlagern. Ich persönlich sehe in beiden Ansetzen eher Nachteile..

Das sehe ich auch so. Das Model hat nichts mit dessen Persistierung zu tun! So wird das auch in DDD gemacht.

Die Lösung von SuperVisor die Setter private zu machen und fürs setzen der Eigenschaften (Propertys) extra Methoden anzubieten, ist einfach sehr Kompliziert.

Das war genau der Grund, weshalb ich diesen Thread gestartet habe. Ich sehe es zwar nicht als kompliziert, sondern eher aus Aufwändig an. Besonders bei Models mit vielen Eigenschaften. In DDD wird "propagiert" dass alle Veränderungen eines Models kontrolliert zu erfolgen haben, damit keine ungültigen Zustände zustande kommen können. Durch das setzen von "Private-Setter" kann dies unterbunden werden. Man kann natürlich Logik in den Setter implementieren, ist aus meiner Sicht aber nicht wirklich "schön".

Meine Quelle wäre:Microsoft .NET - Architecting Applications for the Enterprise (2nd Edition)

F
10.010 Beiträge seit 2004
vor 9 Jahren

In DDD wird "propagiert" dass alle Veränderungen eines Models kontrolliert zu erfolgen haben, damit keine ungültigen Zustände zustande kommen können. Durch das setzen von "Private-Setter" kann dies unterbunden werden. Man kann natürlich Logik in den Setter implementieren, ist aus meiner Sicht aber nicht wirklich "schön".

Aber genau dafür wurden in C# doch Properties eingeführt, damit man das implementieren kann.
Wozu sollte man sonst Properties haben?

Bei dem wo die Validierung und vorallem wie zu geschehen hat laufen wir auch langsam wieder in Richtung Glaubenskrieg.
Es gibt Leute die glauben ActiveRecord ist das mass der Dinge, andere halten DDD dafür, andere meinen das XYZ das bessere ist.

Auf jeden Fall ist es wichtig zu verstehen was ein Pattern bewirken will und das dann mit den richtigen Mitteln umzusetzen.

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

Wieso das genau eingeführt wurde, kann ich dir auch nicht sagen. Aber du hast recht, es ist alles eine Glaubensfrage. Ich denke ein gesunder Mix oder eine gute Portion Menschenverstand ist überall angebracht.

Für mich habe ich nun definiert, dass reine Informationsproperties einen Public-Setter bekommen. So habe ich die Freiheit, diese Daten rasch zu ändern. Sobald irgendwelche Logik (z.B. Statuswechsel, Prüfungen, etc.) berührt wird, werde ich das über Methoden lösen.

Auf jeden Fall ist es wichtig zu verstehen was ein Pattern bewirken will und das dann mit den richtigen Mitteln umzusetzen.

Kann ich so unterschreiben!

742 Beiträge seit 2005
vor 9 Jahren

Ich verstehe die ganze Diskussion gerade nciht mehr. Wo ist der Zusammenhang zwischen DDD (v.a. Business Logik und Domain Language, d.h. Business Layer) und MVVM (View Layer) ???

P
1.090 Beiträge seit 2011
vor 9 Jahren

Für den Login muss aus meiner Sicht der Benutzername im Model auch nicht gesetzt werden. Ich würde den Benutzernamen und das Passwort direkt aus dem VM abgreifen und dem entsprechenden LoginService weitergeben.

Naja man Log sich ja in ein Programm ein damit das Programm weiß wer man ist (Rechteverwaltung usw). Deine User Klasse kannst du dafür nicht verwenden, wenn du alte und neue User unterstützen willst (Unterschiedliche Zeichenlängen beim Namen).

Auch beim Laden von dem vorhandenen Usern, z.B. für den Administrator. Wirst du wohl auch eine andere Klasse brauchen oder andere Methoden.

Leute die deine Klassen benutzen wollen werden wohl immer TryCatch Bocke verwenden müssen um auf die Ausnahmen zu reagieren.

Also ich kann von dem Ansatz nur abraten.

Wenn du nicht darauf bestehst, dass das DomineModel und das Model (MVVM), das gleiche sind wird vieles einfacher.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 9 Jahren

Wie bereits geschrieben, ist der Methodenname vermutlich falsch gewählt. Ausserdem ist es ja auch kein Problem zwei Methoden zu implementieren ("ChangeUserName" und "SetUserName"). Verstehe nicht, weshalb dies nun zu einem so grossen Thema mutiert.

Nein, es müssen nicht überall TryCatch-Blöcke verwendet werden. Ich könnte z.B. eine eigene Validation-Exception-Klasse erstellen und diese an einem zentralen Ort abfangen und auf die Exception entsprechend reagieren.

P
1.090 Beiträge seit 2011
vor 9 Jahren

Wie bereits geschrieben, ist der Methodenname vermutlich falsch gewählt. Ausserdem ist es ja auch kein Problem zwei Methoden zu implementieren ("ChangeUserName" und "SetUserName"). Verstehe nicht, weshalb dies nun zu einem so grossen Thema mutiert.

Hättest du direkt die 2 Methode Implementiert, währen wir schon ein Stück weiter. 😉

Wichtig für mich ist das du Verstehst, das ein Objekt je nachdem im welchen Kontext (Login/Registrieren) es verwendet wird, durch aus verschiedene gültige Werte in einem Propertie haben kann. Und das mit dem User war jetzt ein einfaches Beispiel, wenn es etwas komplexer wird hast du Ruck Zuck n-Methoden nur um ein Property je nach Kontext zu Sätzen. Was schon mal eine Rechtgroße Klasse ist.

Dazu kommt noch, das die Leute die die Klasse verwenden wissen müssen welcher der Methoden aufgerufen werden muss. Change- oder SetUserName? Change kommt zu erst und es Funktioniert ja auch erst mal überall (z.B. laden der Daten aus der DB). Dann kommt die Anforderungsänderungen das der Name mindestens 6 Zeichen lang sein muss und dann wirst du dir, wohl erst mal jede stelle Anschauen müssen wo ChangeUserName aufgerufen wird, um festzustellen in welchen Kontext es verwendet wird.

Nein, es müssen nicht überall TryCatch-Blöcke verwendet werden. Ich könnte z.B. eine eigene Validation-Exception-Klasse erstellen und diese an einem zentralen Ort abfangen und auf die Exception entsprechend reagieren.

So ein Zentraler Ort kling natürlich erst mal gut.
Aber du weist doch erst mal gar nicht was in dem Teil des Programmes gemacht wurde. Muss es da jetzt eine Benutzeranzeige geben oder reicht ein einfacher Logeintrag. Gibt es noch Ressourcen die Freigegeben werden müssen u.s.w. Wie willst du den da entsprechend reagieren.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

F
183 Beiträge seit 2009
vor 8 Jahren

Also ich beschäftige mich auch seit einigen Tagen mit dem Thema DDD und habe den Thread hier gerade mal verfolgt. Übrigens ich finde das Thema sehr interessant, aber ich glaube hier geht einiges durcheinander.

Ich muss gestehen, ich sehe bei mir noch viele Fragezeichen, aber das Hauptproblem (auch hier im Thread) ist doch, das so ein "Amenic Data Model", also reine DTO's als Model und die Logik steckt sonstwo (aber woanders) zumindest im Term von DDD ein Anti-Pattern ist.

Die Frage ist doch vielmehr wie man MVVM und DDD gemeinsam nutzen kann. In DDD ist das Model halt der Kern der Software, aber ich glaube zum Thema Validierungen gibt es doch zwei unterschiedliche Arten der Validierung.

Das eine ist, die Validierung innerhalb des Fachmodells. Innerhalb des Fachmodels müssen Validierungen erlaubt sein. Das ist ja auch gerade das Konzept der Factories und der Aggregates. Die Factories sollen ja gerade Zustände erzeugen, die "gültig" sind, das heisst, das erzeugte Objekt erfüllt alle Invarianten und ist somit "valid". Natürlich sollten auch alle Operationen ein Objekt nicht in einen invaliden Zustand überführen, ich denke das erklärt sich von selber.

Die andere Sache ist die UI - und die hat meiner Meinung nach mit dem Fachmodell gar nix zu tun. Bei dem was wir in WPF typischerweise unter "Validierung" verstehen, ist die Überprüfung von User-Eingaben, ob diese richtig oder falsch sind. Das hat meiner Meinung nach nichts mit dem Fachmodell zu tun, weil die UI nix mit dem Fachmodell zu tun hat. Es geht lediglich darum sicherzustellen, dass die Eingaben, die getätigt werden, sinnvoll sind, damit in der Folge dann über Commands oder ähnliches Operationen auf dem Fachmodel (z.B. über einen ApplicationController) mit validen Daten ausgeführt können.

Mit anderen Worten: Das eine ist UI, das andere ist Fachlichkeit.

Ich weiss der Thread ist schon einige Zeit zum erliegen gekommen, aber vielleicht gibt es ja doch noch einige, die das Thema weiterdiskutieren wollen, und vielleicht helft ihr mir mit euren Antworten auch noch ein bisschen, tiefer in das Thema einzusteigen.

vg

742 Beiträge seit 2005
vor 8 Jahren

Ich stimme dir in allen Punkten zu, fluxy, aber ich sehe nicht, wo du einen neuen Punkt in die Diskussion einbringst. Ich habe das ja oben auch so formuliert ... 😉

F
183 Beiträge seit 2009
vor 8 Jahren

Nagut, dann muss ich halt noch etwas konkreter werden. Ehrlich gesagt finde ich das Beispiel etwas ungünstig gewählt (wurde ja auch schon gesagt), das Problem ist das dies was wir da sehen ja ein Daten-Viewmodel ist, und nicht etwa ein Viewmodel, welches direkt an eine View gebunden werden könnte.

Ich werde einfach mal eine Frage stellen und die Antwort offen lassen (auch weil das gerade ein Punkt ist an dem ich selber scheitere, aber vielleicht habt ihr ja eine Lösung).

Bleiben wir doch bei dem Person beispiel. Das Beispiel alleine reicht nur nicht, weil DDD ohne eine Fachdomäne ist irgendwie quatsch. Okay sagen wir, wir entwerfen eine Software für einen kleinen Handwerksbetrieb. Dieser Handwerksbetrieb möchte Löhne abrechnen. In einem ersten Schritt soll eine Software entwickelt werden, in der die Unternehmensstruktur abgebildet werden kann, und jedem Mitarbeiter ein Monatsgehalt, und die anzahl der Wochenstunden zugeordnet werden kann.

Wir setzen uns nun mit der Fachdomäne zusammen und eiigen uns auf auf folgendes kleines Modell, ihr findet es als Anhang. Es ist ganz einfach: Jeder Mitarbeiter hat einen Vertrag (contract), und jeder Mitarbeiter ist Chef von 0..* weiteren mitarbeitern (auf die bildung von Teams wurde erstmal verzichtet). Gut also implementieren wir das Fachmodell, jede Person bekommt also die Möglichkeit, Personen als Mitarbeiter zu seiner Mitarbeiterliste hinzuzufügen, und für seine Mitarbeiter das Gehalt sowie die Wochenarbeitsstunden zu erfassen. Nun gut, soweit so schön.

Jetzt sind wir zurück in der eigenen Firma und man möchte das Projekt wirklich nach DDD umsetzen, also kein Anemic Data model. Der Kunde möchte eine schöne UI, also beschliesst man eine schöne WPF Anwendung mit dem MVVM Pattern umzusetzen. Anforderung ist also

  • Wir wollen kein Anemic Data Model, Fachdaten und Fachlogik werden also nicht getrennt
  • Wir wollen eine UI mit WPF aufbauen
  • Und es soll das MVVM-Pattern eingesetzt werden.

Okay der erste Schluss ist, unsere Viewmodels kennen unser Model nicht. Das Laden von Daten ist relativ einfach. Wir wollen diese Logik nicht im Viewmodel haben, also schreiben wir uns einen ApplicationController. Nach Eric Evans ist das eine Schicht, die keine Logik ausführt, sondern nur Delegiert. Also delegieren wir an ein Repository, das läd für uns alle Personen. Das Viewmodel bekommt Personen-Objekte als Viewmodel (die allerdings keine Cohesion zu einem Person-Objekt haben). Alle Daten die wir erfassen setzen nur Properties auf dem Viewmodel.

Der User kann bekommt sich selbst als Person auswählen und bekommt ein Grid mit Personen angezeigt, welche seine Mitarbeiter sind. Es gibt einen Button um Mitarbeiter zum Grid hinzuzufügen, dafür nutzen wir ein Command. Während des Hinzufügens soll aber nicht nur ein neues Viewmodel erzeugt werden, sondern die Person auf Fachlogikebene auch hinzugefügt und gespeichert werden.

Wie machen wir das nun?
Wie kommen wir an das Personenobjekt, ohne (z.B. über eine Factory uns ein neues zu erstellen)?
Macht ihr doch mal weiter, aber bitte verschmutzt den Thread hier nicht. Ich habe mir soooo viel Mühe gegeben -;)

Vielleicht bekommen wir ja doch noch eine Lösung hin...

vg fluxy

742 Beiträge seit 2005
vor 8 Jahren

Ich würde mal vorraussetzen, dass wir ein einfaches Response-Request Modell haben. Entweder über ene API oder direkt per Methoden Aufrufe. Mit Events und evtl. CQRS könnte das wieder anderst aussehen. Außerdem sehe ich nicht, warum das ViewModel nicht das Model kennen soll, das ist halt eine weitere Kapselung, die man sich gut überlegen sollte.

Es gibt dann einfach mehrere Fälle wie deine API aussehen kann, z.B.


var teamMember = userFactory.Create("Max");
teamMember.Save();

var teamLeader = userRepository.Find(id);
teamLeader.AddMember(teamMember);
teamLeader.Save();

Und das kann dann noch gekapselt werden, durch ein Application Layer, Services, API...spielt keine große Rolle.

Dein Szenario würde ich so implementieren.

  1. CreateUserViewModel befüllen und Validieren
  2. Mit dem CreateUserViewModel Anfrage erstellen
  3. User(Model) zurück erhalten (je nach Kapselung) und daraus UserViewModel erzeugen
  4. UserViewModel zur Liste hinzufügen.

Dazwischen hängen vll. noch irgendwelche DTO's, aber das ist ja egal