Onkel Bobs saubere Architektur - Eine Entitäts- / Modellklasse für jede Schicht?


44

HINTERGRUND :

Ich versuche, Onkel Bobs saubere Architektur in meiner Android-App zu verwenden. Ich habe viele Open-Source-Projekte studiert, die versuchen, den richtigen Weg zu zeigen, und fand eine interessante Implementierung basierend auf RxAndroid.

WAS ICH ANGEMELDET HABE:

In jeder Ebene (Präsentation, Domäne und Daten) gibt es eine Modellklasse für dieselbe Entität (sprechende UML). Außerdem gibt es Mapper-Klassen, die sich um die Transformation des Objekts kümmern, wenn die Daten die Grenzen überschreiten (von Layer zu Layer).

FRAGE:

Müssen Modellklassen in jeder Ebene vorhanden sein, wenn ich weiß, dass sie alle dieselben Attribute haben, wenn alle CRUD-Operationen benötigt werden? Oder ist es eine Regel oder eine bewährte Methode, wenn Sie die saubere Architektur verwenden?

Antworten:


52

Meiner Meinung nach ist das absolut nicht so gemeint. Und es ist eine Verletzung von DRY.

Die Idee ist, dass das Objekt Entität / Domäne in der Mitte so modelliert wird, dass die Domäne so gut und bequem wie möglich dargestellt wird. Es steht im Zentrum von allem und alles kann davon abhängen, da sich die Domain selbst die meiste Zeit nicht ändert.

Wenn Ihre externe Datenbank diese Objekte direkt speichern kann, ist das Zuordnen zu einem anderen Format zum Trennen von Ebenen nicht nur sinnlos, sondern das Erstellen von Duplikaten des Modells, und das ist nicht beabsichtigt.

Zunächst wurde die saubere Architektur unter Berücksichtigung einer anderen typischen Umgebung / eines anderen typischen Szenarios erstellt. Business Server-Anwendungen mit riesigen äußeren Schichten, die ihre eigenen Arten von speziellen Objekten benötigen. Zum Beispiel Datenbanken, die SQLRowObjekte produzieren und SQLTransactionsim Gegenzug Elemente aktualisieren müssen. Wenn Sie diese in der Mitte verwenden würden, würden Sie die Abhängigkeitsrichtung verletzen, da Ihr Kern von der Datenbank abhängen würde.

Mit einfachen ORMs, die Entitätsobjekte laden und speichern, ist dies nicht der Fall. Sie machen die Zuordnung zwischen ihrer internen SQLRowund Ihrer Domain. Selbst wenn Sie eine @EntitiyAnmerkung des ORM in Ihr Domänenobjekt einfügen müssen, würde ich argumentieren, dass dies keine "Erwähnung" der äußeren Schicht bewirkt. Da Anmerkungen nur Metadaten sind, werden sie von keinem Code angezeigt, der nicht speziell danach sucht. Und was noch wichtiger ist, es muss nichts geändert werden, wenn Sie sie entfernen oder durch die Anmerkung einer anderen Datenbank ersetzen.

Wenn Sie dagegen Ihre Domain ändern und all diese Mapper erstellt haben, müssen Sie eine Menge ändern.


Änderung: Oben ist ein wenig vereinfacht und könnte sogar falsch sein. Weil es in einer sauberen Architektur einen Teil gibt, bei dem Sie eine Darstellung pro Ebene erstellen möchten. Dies muss jedoch im Zusammenhang mit der Anwendung gesehen werden.

Und zwar hier https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

Wichtig ist, dass isolierte, einfache Datenstrukturen über die Grenzen hinweg übertragen werden. Wir möchten keine Entities- oder Database-Zeilen betrügen und weitergeben . Wir möchten nicht, dass die Datenstrukturen Abhängigkeiten aufweisen, die gegen die Abhängigkeitsregel verstoßen.

Das Übergeben von Objekten von der Mitte zu den äußeren Ebenen verstößt nicht gegen die Abhängigkeitsregel, wird jedoch erwähnt. Dies hat aber einen Grund im Rahmen der vorgesehenen Anwendung. Das Weitergeben von Entitäten würde die Anwendungslogik nach außen verschieben. Äußere Ebenen müssten wissen, wie die inneren Objekte zu interpretieren sind, sie müssten effektiv das tun, was innere Ebenen wie die "Anwendungsfall" -Ebene tun sollen.

Außerdem werden Ebenen entkoppelt, sodass Änderungen am Kern nicht unbedingt Änderungen an den äußeren Ebenen erforderlich machen (siehe den Kommentar von SteveCallender). In diesem Zusammenhang ist leicht zu erkennen, wie Objekte genau den Zweck darstellen sollen, für den sie verwendet werden. Außerdem sollten diese Ebenen in Bezug auf Objekte, die speziell für die Zwecke dieser Kommunikation hergestellt wurden, miteinander kommunizieren. Dies kann sogar bedeuten, dass es 3 Darstellungen gibt, 1 in jeder Schicht, 1 für den Transport zwischen Schichten.

Und es gibt https://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Architecture.html mit den folgenden Adressen:

Andere Leute haben befürchtet, dass das Nettoergebnis meines Ratschlags viel doppelter Code und viel rotes Kopieren von Daten von einer Datenstruktur in eine andere über die Ebenen des Systems hinweg sein könnte. Das will ich natürlich auch nicht; und nichts, was ich angedeutet habe, würde unvermeidlich zur Wiederholung von Datenstrukturen und zu übermäßigem Kopieren von Feldern führen.

Diese IMO impliziert, dass einfaches 1: 1-Kopieren von Objekten ein Geruch in der Architektur ist, weil Sie nicht die richtigen Ebenen und / oder Abstraktionen verwenden.

Er erklärt später, wie er sich all das "Kopieren" vorstellt

Sie trennen die Benutzeroberfläche von den Geschäftsregeln, indem Sie einfache Datenstrukturen zwischen den beiden übergeben. Sie lassen Ihre Controller nichts über die Geschäftsregeln wissen. Stattdessen entpacken die Controller das HttpRequest-Objekt in eine einfache Vanille-Datenstruktur und übergeben diese Datenstruktur an ein Interaktionsobjekt, das den Anwendungsfall durch Aufrufen von Geschäftsobjekten implementiert. Der Interaktor sammelt dann die Antwortdaten in einer anderen Vanille-Datenstruktur und leitet sie an die Benutzeroberfläche zurück. Die Ansichten kennen die Geschäftsobjekte nicht. Sie schauen nur in diese Datenstruktur und präsentieren die Antwort.

In dieser Anwendung gibt es einen großen Unterschied zwischen den Darstellungen. Die Daten, die fließen, sind nicht nur die Entitäten. Und dies rechtfertigt und fordert verschiedene Klassen.

Wird jedoch auf eine einfache Android-Anwendung wie einen Fotobetrachter angewendet, bei der die PhotoEntität über 0 Geschäftsregeln verfügt und der "Anwendungsfall", der sich mit ihnen befasst, nahezu nicht vorhanden ist und sich eher mit Caching und Download beschäftigt (dieser Prozess sollte IMO sein) deutlicher dargestellt), beginnt der Punkt, separate Darstellungen eines Fotos zu machen, zu verschwinden. Ich habe sogar das Gefühl, dass das Foto selbst das Datenübertragungsobjekt ist, während die eigentliche Business-Logik-Core-Ebene fehlt.

Es gibt einen Unterschied zwischen "Trennen Sie die Benutzeroberfläche von den Geschäftsregeln, indem Sie einfache Datenstrukturen zwischen den beiden übergeben" und "Wenn Sie ein Foto anzeigen möchten, benennen Sie es unterwegs dreimal um" .

Abgesehen davon ist der Punkt, an dem ich sehe, dass diese Demoanwendungen die saubere Architektur nicht darstellen, dass sie der Trennung von Ebenen zuliebe eine große Betonung beimessen, aber effektiv verbergen, was die Anwendung tut. Das steht im Gegensatz zu dem, was in https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html gesagt wird - nämlich dass

Die Architektur einer Softwareanwendung schreit nach den Anwendungsfällen der Anwendung

Ich sehe keine Betonung auf die Trennung von Schichten in der reinen Architektur. Es geht um die Abhängigkeitsrichtung und darum, den Kern der Anwendung - Entitäten und Anwendungsfälle - in idealerweise einfachem Java ohne Abhängigkeiten nach außen darzustellen. Es geht nicht so sehr um Abhängigkeiten zu diesem Kern.

Wenn Ihre Anwendung also tatsächlich einen Kern hat, der Geschäftsregeln und Anwendungsfälle darstellt, und / oder verschiedene Personen auf verschiedenen Ebenen arbeiten, trennen Sie diese bitte auf die vorgesehene Weise. Wenn Sie andererseits nur eine einfache App schreiben, übertreiben Sie es nicht. 2 Schichten mit fließenden Grenzen können mehr als genug sein. Ebenen können auch später hinzugefügt werden.


1
@RamiJemli Im Idealfall sind die Entitäten in allen Anwendungen gleich. Das ist der Unterschied zwischen "unternehmensweiten Geschäftsregeln" und "Anwendungsgeschäftsregeln" (manchmal Business vs. Anwendungslogik). Der Kern ist eine sehr abstrakte Darstellung Ihrer Entitäten, die allgemein genug ist, dass Sie sie überall verwenden können. Stellen Sie sich eine Bank vor, die viele Anwendungen hat, eine für den Kundensupport, eine auf Geldautomaten, eine als Web-Benutzeroberfläche für die Kunden. Alle diese können dasselbe verwenden, BankAccountaber mit anwendungsspezifischen Regeln, was Sie mit diesem Konto tun können.

4
Ich denke, ein wichtiger Punkt in der sauberen Architektur ist, dass durch die Verwendung der Schnittstellenadapter-Ebene zum Konvertieren (oder, wie Sie sagen, Zuordnen) zwischen der Darstellung der Entität auf den verschiedenen Ebenen die Abhängigkeit von der Entität verringert wird. Sollte es zu einer Änderung in den Ebenen Usecase oder Entity kommen (dies ist hoffentlich unwahrscheinlich, wird jedoch aufgrund geänderter Anforderungen von diesen Ebenen erwartet), sind die Auswirkungen der Änderung in der Adapterebene enthalten. Wenn Sie in Ihrer gesamten Architektur dieselbe Darstellung der Entität verwenden würden, wären die Auswirkungen dieser Änderung viel größer.
SteveCallender

1
@RamiJemli Es ist gut, Frameworks zu verwenden, die das Leben einfacher machen. Der Punkt ist, dass Sie vorsichtig sein sollten, wenn sich Ihre Architektur auf sie stützt und Sie beginnen, sie in den Mittelpunkt von allem zu stellen. Hier ist sogar ein Artikel über RxJava blog.8thlight.com/uncle-bob/2015/08/06/let-the-magic-die.html - es heißt nicht, dass Sie es nicht verwenden sollten. Es ist eher so: Ich habe es gesehen, es wird sich in einem Jahr ändern und wenn deine Bewerbung noch da ist, bleibst du dabei. Machen Sie ein Detail daraus und machen Sie die wichtigsten Dinge in einfachem altem Java, während Sie einfach alte SOLID-Prinzipien anwenden.
Zapl

1
@zapl Hältst du das auch für eine Webserviceschicht? Mit anderen Worten, würden Sie @SerializedNameGson-Anmerkungen in ein Domain-Modell einfügen? Oder würden Sie ein neues Objekt erstellen, das für die Zuordnung der Webantwort zum Domänenmodell verantwortlich ist?
Tir38

2
@ tir38 Die Trennung selbst bietet nicht den Vorteil, sondern die Kosten für zukünftige Änderungen, die damit einhergehen. => Abhängig von der Anwendung. 1) Es kostet Sie Zeit, die hinzugefügte Stufe zu erstellen und zu warten, die sich zwischen verschiedenen Darstellungen wandelt. ZB das Hinzufügen eines Feldes zur Domain und das Vergessen, es woanders hinzuzufügen, ist keine Seltenheit. Kann mit dem einfachen Ansatz nicht passieren. 2) Die spätere Umstellung auf ein komplexeres Setup ist kostenpflichtig, falls sich herausstellt, dass Sie es benötigen. Das Hinzufügen von Ebenen ist nicht einfach. Daher ist es in großen Anwendungen einfacher, mehr Ebenen zu rechtfertigen, die nicht sofort benötigt werden
zapl

7

Du hast es tatsächlich richtig gemacht. Und es gibt keine Verletzung von DRY, weil Sie SRP akzeptieren.

Zum Beispiel: Sie haben eine Geschäftsmethode createX (String name), dann haben Sie möglicherweise eine Methode createX (String name) in der DAO-Ebene, die innerhalb der Geschäftsmethode aufgerufen wird. Sie haben möglicherweise die gleiche Unterschrift und es gibt möglicherweise nur eine Delegation, aber sie haben unterschiedliche Zwecke. Sie können auch ein createX (String name) im UseCase haben. Auch dann ist es nicht redundant. Damit meine ich: Gleiche Signaturen bedeuten nicht gleiche Semantik. Wählen Sie andere Namen, damit die Semantik klar ist. Wenn Sie sich selbst benennen, hat dies keinerlei Einfluss auf die SRP.

Der UseCase ist für die anwendungsspezifische Logik verantwortlich, das Geschäftsobjekt ist für die anwendungsunabhängige Logik verantwortlich und das DAO ist für das Speichern verantwortlich.

Aufgrund der unterschiedlichen Semantik können alle Schichten ein eigenes Darstellungs- und Kommunikationsmodell haben. Oft sehen Sie "Entitäten" als "Geschäftsobjekte" und oft sehen Sie nicht die Notwendigkeit, sie zu trennen. Bei "großen" Projekten sollte jedoch versucht werden, die Ebenen ordnungsgemäß zu trennen. Je größer das Projekt, desto größer ist die Möglichkeit, dass Sie die unterschiedliche Semantik benötigen, die in verschiedenen Ebenen und Klassen dargestellt wird.

Sie können sich verschiedene Aspekte derselben Semantik vorstellen. Ein User-Objekt muss auf dem Bildschirm angezeigt werden, es hat einige innere Konsistenzregeln und es muss irgendwo gespeichert werden. Jeder Aspekt sollte in einer anderen Klasse (SRP) dargestellt werden. Das Erstellen der Mapper kann schmerzhaft sein. In den meisten Projekten, die ich unter diesen Aspekten bearbeitet habe, wird dies zu einer Klasse zusammengefasst. Dies ist eindeutig eine Verletzung von SRP, aber niemand kümmert sich wirklich darum.

Ich nenne die Anwendung von Clean Architecture und SOLID "nicht sozialverträglich". Ich würde damit arbeiten, wenn ich darf. Momentan darf ich das nicht. Ich warte auf den Moment, in dem wir darüber nachdenken müssen, SOLID ernst zu nehmen.


Ich denke, keine Methode in der Datenschicht sollte die gleiche Signatur wie eine Methode in der Domänenschicht haben. In der Domänenebene verwenden Sie geschäftsbezogene Namenskonventionen wie signUp oder login. In der Datenebene verwenden Sie save (wenn DAO-Muster) oder add (wenn Repository, weil dieses Muster Collection als Metapher verwendet). Schließlich spreche ich nicht über Entitäten (Daten) und Modell (Domäne), sondern unterstreiche die Nutzlosigkeit von UserModel und seinem Mapper (Präsentationsebene). Sie können die Benutzerklasse der Domäne innerhalb der Präsentation aufrufen, was nicht gegen die Abhängigkeitsregel verstößt.
Rami Jemli

Ich stimme Rami zu, der Mapper ist unnötig, da Sie das Mapping direkt in der Interaktor-Implementierung durchführen können.
Christopher Perry

5

Nein, Sie müssen nicht in jeder Ebene Modellklassen erstellen.

Entity ( DATA_LAYER) - ist eine vollständige oder teilweise Darstellung des Datenbankobjekts.DATA_LAYER

Mapper ( DOMAIN_LAYER) - ist eigentlich eine Klasse, die Entity in ModelClass konvertiert, für die es verwendet wirdDOMAIN_LAYER

Schauen Sie sich das an: https://github.com/lifedemons/photoviewer


1
Natürlich bin ich nicht gegen Entitäten in der Datenebene, aber in Ihrem Beispiel hat die PhotoModel-Klasse in der Präsentationsebene dieselben Attribute wie die Photo-Klasse in der Domänenebene. Es ist technisch die gleiche Klasse. ist das nötig

Ich denke, dass in Ihrem Beispiel etwas nicht stimmt, da der Domain-Layer nicht von anderen Layern abhängen sollte, da in Ihrem Beispiel die Mapper von den Entitäten in Ihrem Daten-Layer abhängen, welche IMO, es sollte umgekehrt sein
navid_gh 25.12.18
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.