Laden...

Überzogene Abstraktion im MVVM-Pattern?

Letzter Beitrag vor einem Jahr 5 Posts 647 Views
Überzogene Abstraktion im MVVM-Pattern?

Hallo,

folgendes Zitat hat mich dazu animiert, hier mal eine architektonische Frage zum MVVM-Pattern zu stellen, da ich in meinem Team eine bisher noch nicht geklärte Diskussion zum Grad der Abstraktion führe.

Zitat von Abt

Die Austauschbarkeit von UI-Pattern erreichst Du mit einer Drei-Schichten-Architektur.

Das Model kannst Du i.d.R. wiederverwenden, aber nicht das ViewModel.
Das ViewModel ist vereinfacht ausgedrückt die UI-Logik für die View - und die sind sehr eng miteinander gekoppelt. Sowohl die Implementierung wie auch die Technik.

View und ViewModel haben zudem i.d.R. eine Abhängigkeit zu einem konkreten UI-Framework.
Die View kannst Du also problemlos innerhalb von WPF tauschen. Du kannst aber i.d.R. eben nicht das ViewModel zwischen Frameworks sharen.
Das ist auch nicht die Idee dahinter.

Wenn wir Oberflächen entwickeln, habe wir in der Regel eine Assembly, dieMyProject.UIheißt. Hier wird alles implementiert, was zum MVVM-Pattern gehört. Die meisten Klassen sindinternalgekennzeichnet und Aufrufe von Fenstern werden über Hilfsmethoden angeboten, wo das Verbinden von UI und ViewModel geschieht. Das Laden der Daten geschieht per IoC angeforderten Provider-Klassen, welche die Daten für die Bindung dem ViewModel zur Verfügung stellen oder Präsentationsobjekten.

Ich persönlich weigere mich für ViewModels als auch Präsentationsobjekten eine pauschale Abstraktion durch Interfaces einzuführen, vor allem da all diese Klassen internal sind. Ich habe für mich noch kein Szenario gehabt, in dem mir diese pauschale Abstraktion geholfen hätte. In manchen Fällen habe ich konkrete Gründe, eine Schnittstelle für spezielle Aufgaben einzuführen, aber auch diese sind in der Regel auch nur innerhalb der Assembly sichtbar.

Das meist angeführte Thema ist die Testbarkeit dieser Klassen. Aber selbst hier sehe ich keinen Grund von meiner Vorgehensweise abzuweichen. PO-Objekte tragen nur Informationen und jegliche Test-Implementierung dieser Klassen würde wieder genauso aussehen. ViewModels sind schon etwas kritischer, aber hier kann die Entkoppelung für den Test über den IoC-Container gelöst werden. Müssen also auch nicht gesondert für den Test implementiert werden. Ansonsten folge ich dem Gedanken von Abt, dass die Wiederverwendbarkeit all dieser Klassen nur sehr bedingt möglich ist, sodass sich der Overhead der Abstraktion meiner Meinung nach in keiner Weise lohnt.

Ich folge hier inzwischen einen sehr pragmatischen Ansatz, der für das Umfeld bei meinem Arbeitgeber gut funktioniert. Ich habe gemerkt, dass gewisse Komplexitäten von allen Entwicklern verstanden und umgesetzt werden müssen. Es hat sich gezeigt, dass gewisse architektionische Ansätze ad absurdum geführt werden, wenn diese nicht von allen verstanden und auch umgesetzt werden. Uns fehlen hier auch gewisse Konstrollstrukturen wie Softwarearchitekten oder Code Reviews. Auch der Wissenstransfer zur korrekten Anwendung unserer eigenen Frameworks und Strukturen ist noch ausbaufähig. Somit empfinde ich die Abstraktion eher als Over-Engineering.

Konkret haben wir in einem Projekt diese abstrakte Variante umgesetzt. Der UI-Bereich besteht nun aus mindestens vier Assemblies wobei eine davon sich um die Abstraktion der Modelle und eine andere um die Abstraktion der ViewModels und der DataProvider kümmert. Architektionisch ist das alles aus theoretischer Sicht sehr schön gelöst. Nur hat sich der Implementierungsaufwand und somit auch die Zeit erhöht. An der Umsetzung des Projektes selbst war ich nicht beteiligt, merke aber dass ich erhebliche Probleme habe, mich im Quellcode zu orientieren. Am Projekt beteiligte Entwickler verstehen den Grund der Komplexität nicht und sind davon eher genervt. Das Entwickeln geht in dem Projekt nicht mehr so flüssig von der Hand.

Ich hoffe, ich konnte meine Gedanken einigermaßen gut niederschreiben. Mich interessiert, wie Ihr das Thema seht bzw. auch angeht. Darf auch gerne allgemein gehalten sein, da es nicht unbedingt ein MVVM spezifisches Thema ist.

Grüße

Hallo teebeast,

Darf auch gerne allgemein gehalten sein

grundsätzlich ist zwischen Anwendungsentwicklung und Library-/Framework-Entwicklung zu unterscheiden, da beide Gruppen verschiedene Ziele verfolgen. Hier gehts um die Anwendungsentwicklung und da halte ich es "nicht so abstrahiert wie möglich, sondern so abstrahiert wie nötig", wobei sich das nötig mehr od. weniger von alleine ergibt um testbaren Code zu haben.

Muster (Pattern) stellen Ideale dar, welche sich im Laufe der Zeit herauskristallisiert haben. Das heißt jedoch nicht automatisch, dass jedes Muster gem. Referenzimplementierung voll und ganz umgesetzt werden soll/muss. Es sollte eher als Anleitung, grober Bauplan, Orientierung gesehen werden. Daher wenn für euer Team das funktioniert, der Code testbar ist wozu mehr Aufwand für weitere Abstraktionen betreiben?

Uns fehlen hier auch gewisse Konstrollstrukturen wie Softwarearchitekten oder Code Reviews

Das lässt sich ja ändern. V.a. mit Code Reviews kann jederzeit und leicht begonnen werden. Alleine schon durch Fragen wie "warum wurde das so gelöst" kann innerhalb des Teams eine interessante Diskussion entstehen, bei deren Ende alle der Diskussion teilhabenden mehr Verständnis für die Lösung haben, die u.U. letztlich ganz anders aussehen kann.
Code Reviews sind nicht nur Kontrolle, sondern auch Lernen und Verstehen.

Der UI-Bereich besteht nun aus mindestens vier Assemblies

Ist das nicht eine künstliche Einschränkung dass es "mind. vier Assemblies" sind? Das führt ja zwangsläufig zu

merke aber dass ich erhebliche Probleme habe, mich im Quellcode zu orientieren.

und den weiters angeführten Problemen. Zudem hat das großes Fehlerpotential, da auf einem Blick die Zusammehänge wohl kaum überschaubar sind.
Probier mal -- z.B. in einem Test-Projekt -- die "Bestandteile" von MVVM näher zusammenzurücken, z.B. in einem Unterordner im Projekt. Dann ist die Orientierung im Code leicht, das Verständnis was der Code tut ebenfalls leicht überschaubar. Änderungen und Weiterentwicklungen gehen dann zügiger voran, da eben das Verständnis vorhanden ist und nur in einem Ordner gewert werden braucht, anstatt in 2, 3, 4 Assemblies wo fast niemand genau weiß warum dort.

Sollte dann z.B. das Model woanders auch benötigt werden, also falls sich dieser Fall tatsächlich ergibt, so kann das ja verschoben werden.
Aber von vornhinein würde ich (heute) keine Projekte mehr zwangsmäßig in (thematische) Assemblies aufteilen (XYZ.View, XYZ.ViewModel, XYZ.Models, etc.), sondern das nach Anwendungsfällen (use cases), Benutzeraktionen, usw. aufteilen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Zitat von gfoidl

Aber von vornhinein würde ich (heute) keine Projekte mehr zwangsmäßig in (thematische) Assemblies aufteilen (XYZ.View, XYZ.ViewModel, XYZ.Models, etc.), sondern das nach Anwendungsfällen (use cases), Benutzeraktionen, usw. aufteilen.

Kannst Du das näher erleutern, woran Du das normalerweise orientierst?
Z.B. Feature, Modul, Dialog getrieben, etc.?

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

Da ich zitiert wurde antworte ich mal.

Für mich hört sich der Startbeitrag nach einem sehr festgefahrenen und pauschalisierten Gedankenweg an. Muss nicht sein; wirkt auf mich so. Mir fehlt der Aspekt, wie solche Konzepte gedacht sind und über einen spezifischen Fall angewandt werden - der klassische Tellerrand.
Zusätzlich zum festgefahrenen Weg, klingt das für mich altmodisch, denn

Zitat von gfoidl:

Aber von vornhinein würde ich (heute) keine Projekte mehr zwangsmäßig in (thematische) Assemblies aufteilen (XYZ.View, XYZ.ViewModel, XYZ.Models, etc.), sondern das nach Anwendungsfällen (use cases), Benutzeraktionen, usw. aufteilen.

so sehe ich das auch.

Hallo Palladin007,

so pauschal kann ich das nicht sagen, denn es hängt auch von der konkreten Anwendung* ab. Dort nehm ich dann halt das "natürlichste" wie sich das aufteilen lässt. Klar jetzt kommt die Frage: "was ist das natürlichste?" und ich würd die Antwort gleich beginnen wie diese 😉

* also jetzt nicht nur WPF mit MVVM od. sonst was, sondern obs Benutzerverwaltung, Simulationsprogramm, etc. ist

Ich würde auf jeden Fall Commands und Queries separarieren (das muss nicht unbedingt in CQ(R)S ausarten und kann auch in MVVM verwendet werden), dann hab ich schon einmal (grob) eine Aufteilung nach Use Cases.
Das Ganze dann in Verbindung mit der jeweiligen View ergibt eien Art "Feature" und dieses ist in einen eigenen Ornder / Namespace gepackt. Dort ist dann alles drin, was für dieses Feature benötigt wird und erspart die lästigen Sprünge quer durch die Solution, so ist es leichter zu verstehen da zusammengehördende Dinge auch nah beinander liegen. Größere Projekte lassen sich so auch recht elegant umsetzen, ohne dass alles irgendwie fragil erscheint und sich keiner mehr auskennt.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"