Laden...

Schichtenmodell Verständnisfrage bez. C# Implementierung

Erstellt von realProg vor 13 Jahren Letzter Beitrag vor 13 Jahren 5.838 Views
R
realProg Themenstarter:in
95 Beiträge seit 2010
vor 13 Jahren
Schichtenmodell Verständnisfrage bez. C# Implementierung

Hallo Leute,

hätte hier eine kurze Verständnisfrage, die ich mir noch nicht 100% erklären kann.

Ich will mir eine Software schreiben und dabei das Schichtenkonzept anwenden.
3. Application
2. Business

  1. Data | Serial
    (über alle Schichten erstreckt sich vertikal noch ein Model)

Schicht 1 umfasst eine Datenbank anbindung, sowie einen Datentransfer per serieller Schnittstelle.

Nun meine Frage: Verstehe ich es richtig, dass die Dataschicht den Zugriff auf die Datenbank regelt (zB mit LINQ), die Businessschicht dann mehr oder weniger die Daten einfach an die Anwendungsschicht weitergibt, bzw vorher noch verändert?
(Bspablauf: Application will user in DB eintragen, gibt den User an die Businessschicht weiter, diese gibt ihn an die Dataschicht weiter, wo der User dann tatsächlich in die DB geschrieben wird.)

Hat die Businessschicht die meiste Zeit einfach nur die Funktion, dass es die Daten zwischen Data und Anwendungsschicht weitergibt?

eine weitere Frage:
wenn ich zB in der DB die Entityklassen Auto und Eigentümer habe, muss ich dann in jeder Schicht meiner Application wiederum eine Klasse erzeugen, die nur für diese Entityklasse zuständig ist.
(Bsp:
Businessschicht:
Klassen ... OwnerService, IOwnerService, CarService, ICarService
Dataschicht:
Klassen ... OwnerData, IOwnerData, ICarData, CarData

oder wie macht man das normal?)

1.002 Beiträge seit 2007
vor 13 Jahren

Hallo realProg,

Robert Mühsig hat dazu einen schönen Blog-Eintrag verfasst: HowTo: 3-Tier / 3-Schichten Architektur.

m0rius

Mein Blog: blog.mariusschulz.com
Hochwertige Malerarbeiten in Magdeburg und Umgebung: M'Decor, Ihr Maler für Magdeburg

R
realProg Themenstarter:in
95 Beiträge seit 2010
vor 13 Jahren

Danke, aber genau diesen Beitrag, sowie das Video habe ich bereits durchstöbert.
Es erklärt mir meine Frage trotzdem nicht, weil ja in diesem Beispiel jeweils in jeder Schicht nur ein Interface und eine Klasse implementiert ist.

Was ist aber zB. wenn ich in der Datashicht mehrere Klassen habe (Owner, Car,..)? muss ich dann in jeder dieser Klassen wieder eine eigenen Verbindung zur Datenbank aufbauen?

Mein Problem ist, das ich nicht weis, wie es wie in meinem 1. Beitrag beschrieben mit mehreren Klassen funktioniert (siehe Beitrag 1)

T
415 Beiträge seit 2007
vor 13 Jahren

Jede nächst höhere Schicht kennt seine darunterliegende, aber nicht umgekehrt. Somit ist es durchaus vorgesehen / erlaubt, in allen deinen BL Klassen Zugriff auf den DAL zu haben, um so klassenspezifische Daten abzufragen.

1.564 Beiträge seit 2007
vor 13 Jahren

Hallo realProg

Hat die Businessschicht die meiste Zeit einfach nur die Funktion, dass es die Daten zwischen Data und Anwendungsschicht weitergibt?

Ganz am Anfang der Applikation hast du mit der Annahme Recht - und ich schätze dass genau das verwirrend sein kann.

In den ersten Prototypen/Sprints/Inkrementen enthält der Business Logic Layer (BLL) zwar die Business Objekte, diese sind jedoch an Anfang noch recht leer. Der Grund dafür ist, dass der BLL das Hirn des Systems darstellt und das muss sich erst entwickeln. Die GUI ist die Haut der Applikation und die Datenschicht das Herz. Die beiden sind vornherein vorhanden und ändern sich dafür über die Zeit deutlich weniger.

Nimm das Beispiel dass du hier angesprochen hast. "neuen User anlegen". Am Anfang geht's vielleicht wirklich nur darum einen String "UserName" und ggf. ein Passwort von der GUI in die DB zu transportieren. Irgendwann soll dann aber sichergestellt werden, dass das Passwort mindestens 8 Zeichen lang ist. Dann muss der Benutzer außerdem in einem parallelen System noch eingetragen werden. Außerdem soll ein Business Workflow gestartet werden, der dem neuen Benutzer eine Email schickt.

Vielleicht soll der Benutzer irgendwann einer Benutzergruppe zugeordnet werden. Diese wiederum muss validieren ob der Benutzer überhaupt bestimmte Anforderungen erfüllt und ggf. eine Mail an den Gruppen-Administrator schicken. und und und...

Nach meiner Erfahrung ändern sich in 70-80% der Zeit (nach einer gewissen Projekt-Startup Phase) die logischen Anforderungen eines Systems, nicht die GUI und auch nicht die Art der Daten. Deswegen ist der BLL anfangs recht klein und mag vielleicht etwas überflüssig wirken. Normalerweise wirst du in dem aber die Meiste Zeit verbringen 😉.

Grüße
Flo

Blog: Things about Software Architecture, .NET development and SQL Server
Twitter
Google+

Je mehr ich weiß, desto mehr weiß ich was ich noch nicht weiß.

R
realProg Themenstarter:in
95 Beiträge seit 2010
vor 13 Jahren

danke für eure raschen Antworten!

Ok das ist mir jetzt shcon mal klar, die nächste Frage, was noch offen ist:
wenn ich in meiner Datenbank nun viele verschiedene Entitätsklassen habe (Owner, Car, Condition,...) mache ich dann in jeder der Schichten (ausser Applicationlayer) für jede Entitätsklasse eine Klasse Interface und Implementation?

Businessschicht:
Klassen ... OwnerService, IOwnerService, CarService, ICarService
Dataschicht:
Klassen ... OwnerData, IOwnerData, ICarData, CarData

oder fasse ich zB die gesamten Zugriffe auf die Datenbank in eine Klasse im Datalayer zusammen?
anders habe ich ja unzählige kleine KLassen, wo immer die Referenz auf die darunterliegende Klasse verlinkt werden muss.
Wie macht man das in der Praxis?

2.298 Beiträge seit 2010
vor 13 Jahren

Du musst nicht für jede Klasse ein Interface definieren, wäre auch schwachsinn.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

1.564 Beiträge seit 2007
vor 13 Jahren

Hallo realProg

Nein.

Erstmal weiß ich nicht was deine XYZService Klassen sind.

Da die Klassen die vom DAL (Data Access Layer) an den BLL (Business Logic Layer) übergeben werden reine Datenstrukturen - ohne jegliche Logik - sind brauchst du hier wirklich keine Interfaces, außer deine Business Objekte sollen selbst die Daten als Properties halten.

Im BLL gibt's für jede Entität (mindestens!) eine Klasse. Hier ist es eher von Vorteil mehrere Implementierungen für die gleiche Datenbasis zu verwenden. (Was spricht denn gegen viele kleine Klassen? Das Gegenteil wären wenig große Klassen und das ist monolithisch.)

Die Menge der DataMapper Klassen (nach Martin Fowler) muss weder 1:1 der Menge der Tabellen, noch der Menge der Entitäten entsprechen. Aus Datenbank-Sicht ist es reine Erwägungsfrage ob die Daten einer Tabelle in einem eigenständigen Mapper oder über einen gemeinsamen Mapper (de-)serialisiert werden. Ich würde beispielsweise bei einem Bestell-System überlegen ob eine OrderPosition überhaupt alleine lebensfähig ist oder ob diese eh immer mit ihrer übergeordneten Order unterwegs ist. Wenn die Order immer dabei ist würde ich die OrderPositions ggf. im OrderDataMapper mit handeln. Dein Ansatz "Eine Klasse für die ganze Datenbank" entspricht einem DataGateway und sollte nur bei kleinen Projekten mit sehr einfachen Datenbanken verwendet werden, sonst wird auch das schnell zum Monolith.

Interfaces sind wichtig und sinnvoll, aber nur dann wenn sie wirklich Sinn machen(!). Auf jeden Fall sollten die DataMapper über Interfaces zur Verfügung gestellt werden, um Unit Tests und Mock-Objekte zu ermöglichen. Bei Daten-Objekten sind sie - wie oben schon geschrieben - nicht notwendig. Bei Entitäten habe ich mindestens ein Interface pro Basis-Entität, bei den Business-Case spezifischen Implementierungen kommt es für mich auf die Abhängigkeiten anderer Entitäten an um zu entscheiden ob ich ein Interface definiere oder nicht.

Grüße
Flo

Blog: Things about Software Architecture, .NET development and SQL Server
Twitter
Google+

Je mehr ich weiß, desto mehr weiß ich was ich noch nicht weiß.

R
realProg Themenstarter:in
95 Beiträge seit 2010
vor 13 Jahren

Hallo,

ahm, warum ist im Datalayer ein Interface nicht sinnvoll?
im Datalayer sind ja erst die Zugriffsmethoden auf die DB implementiert und die will ich ja einheiltich haben, damit ich gegebenenfalls die Datenbank später auswechseln kann, oder verstehe ich da gerade etwas komplett falsch,
aber in dem Video von Mühsig wars doch auch mit Interfaces geregelt

1.002 Beiträge seit 2007
vor 13 Jahren

Hallo realProg,

Florian Reischl wollte – soweit ich das verstanden habe – nur betonen, dass es keinen Sinn macht, pauschal für jede Entity ein Interface zu erstellen. Du musst also nicht, nur weil du die Entität Car verwendest, zwangsläufig das zugehörige ICar-Interface erstellen.

m0rius

Mein Blog: blog.mariusschulz.com
Hochwertige Malerarbeiten in Magdeburg und Umgebung: M'Decor, Ihr Maler für Magdeburg

1.564 Beiträge seit 2007
vor 13 Jahren

Hallo realProg

Missverständnis. Im DAL sind Interfaces sehr sinnvoll, hier existiert sogar fast die wichtigste Stele für Interfaces im gesamten System (meiner Meinung nach). ABER:

Die Klassen, welche sich um das Laden und Schreiben von Daten kümmern sollten unbedingt über Interfaces abstrahiert sein. Beispielsweise hat dein System ein "UserDataMapper" Interface, welches die Methoden "GetByNameAndPassword(name, password)" oder "Save(User)" besitzt. Ist jetzt natürlich nur sehr simpel angedeutet.

Auf der anderen Seite werden durch die DataMapper Klasse(n) ja wirkliche Instanzen von Daten aus der Tabelle geladen oder in diese geschrieben. Wenn du hier jetzt einfache Data Transfer Objects (DTOs) verwendest, welche keinerlei Logik beinhalten, dann würde ich hier nicht auch noch Interfaces definieren. Diese DTOs sind für genau eine einzige Aufgabe gemacht, Daten transportieren und dann muss ich die auch nicht abstrahieren. Ich überschreibe normalerweise nur die GetHashCode() und Equals() Methoden um hier anhand der Object-ID aus der DB zu vergleichen.

Grüße
Flo

Blog: Things about Software Architecture, .NET development and SQL Server
Twitter
Google+

Je mehr ich weiß, desto mehr weiß ich was ich noch nicht weiß.

R
realProg Themenstarter:in
95 Beiträge seit 2010
vor 13 Jahren

also liege ich mit folgender Denkweise falsch:

Ich dachte ich mach es so:
im Applicationlayer wird ein Button betätigt, der alle User auslesen soll.
im Businesslayer gibt es eine Klasse, die nur für die User zuständig ist (inklusive Interface) die gibt es wiederum an den Datalayer weiter. Im DAL wird dann tatsälich auf die Daten zugegriffen und die erhaltenen Werte zB in eine DataTable gespeichert und wieder bis zum Applicationlayer zurückgeliefert.

in welcher Weise hat diese Denkweise Nachteile, bzw. wo liegt der Hund begraben?

1.564 Beiträge seit 2007
vor 13 Jahren

Erstmal Frage:
Meinst du mit Applicationlayer jetzt die reine GUI, also die Anzeige, oder meinst du damit beispielsweise eine MVC (Model View Controller) Klassen-Kollaboration oder ähnliches?

Dann kann ich leichter auf die eigentliche Frage antworten 😃

Blog: Things about Software Architecture, .NET development and SQL Server
Twitter
Google+

Je mehr ich weiß, desto mehr weiß ich was ich noch nicht weiß.

R
realProg Themenstarter:in
95 Beiträge seit 2010
vor 13 Jahren

Sorry,

also ich meine die reine GUI.

1.564 Beiträge seit 2007
vor 13 Jahren

Okay, danke 😃

Also, ich lasse die GUI normalerweise nicht direkt den BLL aufrufen. Stattdessen verwende ich eine Form (oder Web-Page) als View und schalte einen Controller dazwischen. Das erhöht massiv die Testbarkeit.

Die View wird mit einem Controller initialisiert und gestartet. Der Controller kann ein Interface sein, muss aber nicht zwingend solange für die gleiche View (derzeit) nicht mehrere unterschiedliche Controller verwendet werden.

Der Controller ist entweder Teil des GUI Assemblies - falls nur eine GUI - oder wird über eine separate DLL bereitgestellt - falls mehrere GUIs. Der Controller hat entweder bereits initiale Informationen um nun die Objekte - welche in der View angezeigt werden sollen - aufzubereiten oder durchbricht initial den Business-Layer und holt sich aus dem DAL die initialen Objekte. Dies verletzt zwar einmalig die Schichten, allerdings wäre jeder andere Ansatz in vielen Fällen unnötiger Overhead (zu viel Source Code) den man sich meist sparen kann.

Der DAL ist über eine EntityFactory injected um die geladenen Daten-Objekte in die entsprechenden Entitäten zu transformieren und diese zurückzuliefern.

Beim Start der GUI ruft diese auf dem Controller eine(!) Methode auf um die Daten (als DTOs, Strings, Entitäten, ...) zu erhalten welche angezeigt werden sollen. Betätigt der User einen Knopf, so wird der Befehl meist 1:1 an den Controller weitergeleitet, welcher die Aktion (ggf. grob auswertet und) an den BLL weitergibt. Der DAL ist hier erstmal ziemlich außen vor. Ausnahme, der BLL benötigt noch Daten die initial nicht vom Controller geladen wurden. In dem Fall verwendet der BLL den DAL um die nötigen Daten nachzuladen (nennt sich dann Lazy Loading).

Wenn der Anwender nun der Meinung ist, dass alles passt drückt er Speichern, welches wieder von der View an den Controller weitergegeben wird. Der Controller ruft ggf. noch Validirungs-Funktionen auf den Entitäten auf und veranlasst zuletzt den DAL zum Speichern. Um hier alle zu speichernden Informationen zusammen zu halten verwende ich eine Unit Of Work (UoW).

Kleines Beispiel

Dialog 1 soll alle Benutzer anzeigen.*Der ShowUserController wird mit einer ShowUserListView (nicht ListView sonder UserList-View 😉) initialisiert und gestartet. *Der Controller zeigt die View an *Im FormLoad werden vom Controller über GetUsers alle User abgefragt welche angezeigt werden. Der Controller bricht nun durch den BLL und ruft auf einem UserDataMapper eine Methode GetAllActive() auf. Man könnte auch eine ActiveUsersAccessor Klasse im BLL bauen, finde ich aber meist Overkill *Der DAL ist über eine UserFactory injected. Die Methode GetAllActive selektiert die Daten aus der Datenbank, transformiert diese in UserData Objekte (=DTO) und verwendet eine Methode UserFactory.GetUser(UserData) um die Daten-Objekte in Entitäten zu transformieren. User ist bei mir ein Interface. *Wenn der Controller die Entitäten vom DAL bekommen hat kann er diese an die View zurückgeben. *Jetzt wählt der Benutzer einen User aus und drückt "Editieren". Die View ruft auf dem Controller "EditUser(user)" auf. *Der Controller schließt die aktuelle View, startet einen neuen EditUserController, übergibt dem den aktuellen User, startet den neuen Controller und beendet sich. *--------------------------------------------------------------------------------- *Der neue Controller muss diesmal nicht erst auf den DAL zugreifen, sondern hat direkt alles was er benötigt. *Die EditUserView zeigt den User an. *....

Ist jetzt nur grob skizziert, aber ich hoffe man erkennt das Prinzip. Wie du auch siehst breche ich (und damit bin ich nicht alleine) zeitweise die Trennung zwischen Application Layer (= Presentation Layer + Application Controller + Front Controller). Ich finde die anderen Optionen jedoch zu umständlich.

Hoffe ich konnte etwas helfen

Grüße
Flo

Blog: Things about Software Architecture, .NET development and SQL Server
Twitter
Google+

Je mehr ich weiß, desto mehr weiß ich was ich noch nicht weiß.