Ist eine 3 Schichten Architektur mit eigener DAL immer empfehlenswert?
Wenn es darum geht eine “normale” Webapplikation zu bauen, dann kommt man meist auf eine 3 Schichten Architektur heraus. Der meistgelesenste Blogeintrag dreht sich um diese Architektur, allerdings kommen bei mir immer mal wieder Zweifel auf, ob man nicht zuviel Aufwand in der DAL betreibt – immerhin gibt es tolle OR Mapper. Doch auch damit bin ich bereits auf die Nase gefallen und hab mich von dem Gebrauch dieser Linq2Sql oder Entity Framework generierten Model-Klassen distanziert. Ayende Rahien hat aber mal wieder meine Zweifel geweckt…
3-Schichten Architektur
Ayende selbst ist bekennender Fan von NHibernate (immerhin hat er es mitentwickelt) und mag auch das Repository Pattern wohl auch nicht mehr so.
Allerdings sind die Grundgedanken vollkommen nachvollziehbar. Die Grafiken stammen aus einem von Ayendes Posts. Der normale Aufbau wäre bei einer 3-Schichten Architektur so:
Der Ablauf sobald ein User z.B. auf einen bestimmten Eintrag auf einer Website klickt wäre so:
Ihn stört es, dass man durch Repositories noch eine weitere Abstraktionsebene aufbaut. In den meisten meiner Projekte hatten wir es auch so umgesetzt und in dem “Data" Layer noch einen ORM, wie z.B. Linq2Sql oder das Entity Framework benutzt.
Zu viel Abstraktion?
Die Frage die ich mir stelle: Lohnt sich der Aufwand (und die Kosten für den Kunden) den Data-Layer zu kapseln, wenn man ohnehin ein OR Mapper benutzt?
Kann man denn realistisch und mit vertretbaren Aufwänden überhaupt eine gescheites IUserRepository bauen? Ayende hat ein paar Punkte aufgezählt, warum die Kapselung eines Data Access Layers unnötig auch nicht wirklich umsetzbar ist. In einem weiteren Post geht er genau ein und zeigt anhand des Membership Providers (den ich persönlich für grausam erachte), dass es vergebene Mühe ist.
Durch die Kapselung wird es leichter mit Unit-Tests die Businesslogik zu testen!
Durch ein Projekt wo wir in der Businesslogik direkt Linq2Sql und das Entity Framework eingesetzt haben, war es äußerst mühsam bis unmöglich Unit-Tests zu schreiben. Daher war es für die Testbarkeit der Businessschicht enorm wichtig, dass man die DAL auch mocken kann. Allerdings sind die beiden OR/M ja nicht die einzigen .NET OR/M…
NHibernate
Ayende ging auch in einem Post auf das Mocking von NHibernate ein und wie man auch Unit-Tests abbilden kann.
Die große Frage (und warum ich diesen Post schreibe)
Mach ich mir das Leben selber kompliziert indem ich versuche die DAL zu kapseln? Nehm ich vielleicht einfach nur die falschen OR/Mapper? Ist NHibernate so toll und kann mir jemand ein tolles Beispielprojekt samt Unit-Tests zeigen?
Ist eine 3-Schichten Architektur am Ende immer erstrebenswert? (Achtung: Ich geh hier von einem 0815 Webprojekt aus und zählen den OR/Mapper nicht als direkte Schicht, sondern ich würde direkt in der Business-Schicht NHibernate arbeiten. Dass es im Embedded Bereich andere Spielregel gibt ist mir klar
)




Thomas
4. August 2010
Ich arbeite auch exakt so, wie in seinem Worst-Case-Szenario oben abgebildet, d.h. mit IRepository.
Dabei treten natürlich oft diese Fälle auf, in denen Daten vom Service aus dem Repository geholt und einfach nur durchgeschleift werden (nicht testens-wert btw.
). Auf der einen Seite gut, auf der anderen Seite natürlich stupider, dummer Code.
Auf der anderen Seite ist es doch so: die dahinterliegende Datenbank ändert sich in einem von 100 Projekten, wenn man mal von Software ausgeht, die dies als Feature beinhaltet (z.B. ne Foren-Software, die mit MSSQL oder MySQL arbeiten kann). Und selbst wenn sie sich änderte, würde man ja mit nHibernate oder Entity Framework keine Probleme bekommen.
Und ob ich nun im Service auf den ORM zugreife oder aufs Repository, ist eigentlich relativ egal.
Was für mich als Frage noch bleibt: wo mappt man auf seine Domain Objects/POCOs? Ich will definitiv nicht die vom ORM generierten Objekte (nehmen wir mal LinqToSql) applikationsweit verwenden.
Thomas
4. August 2010
Nachtrag: “von Software absieht” … wo ist der Edit-Button?
Jürgen Gutsch
4. August 2010
Hallo Robert,
sehr gute Frage
Meine Antwort: It Depends!
Für 0815 Projekte rentiert sich das definitiv nicht. Hier ist der OR-Mapper (welcher auch imemr) definitiv dein DAL. Wieso einen eigenen spezialisierten Layer wenn die Arbeit vom OR-Mapper erledigt wird. In den meisten Fälle wird eh nur die Abfrage durch den BL und den DAL an den OR-Mapper durchgeschoben. Da gibt es weder viel zu testen noch irgend eine Logik.
Erst wenn die Austauschbarkeit des OR-Mapper, des Datenbanksystems, etc. gefordert wird, macht ein DAL erst wirklich Sinn.
Viele Grüße
Jürgen
Timur Zanagar
4. August 2010
Hallo Robert,
Ich kann deine Bedenken sehr gut verstehen. Die ganze Abstraktion ist schön und gut und hat mitunter das Ziel die entsprechende Schicht auszutauschen. Aber wie oft mussten wir eine Schicht austauschen, da der gekappselte OR Mapper ausgetauscht wurde?
Ist es eher nicht der Sinn von einer DAL die auszutauschen damit man mehrere Datenbanken bedienen kann? Nimmt mir hier der OR Mapper nicht die Arbeit ab?
Wenn ich bedenke das ich heute nochmal ein altes aktuelles Projekt angefasst habe und dort zwei DAL’s gibt, eine für MSSQL und eine für PostgreSQL, dann bin ich froh diese Abstraktion durchgeführt zu haben. Diese DALs sind nicht mit irgendeinem OR Mapper geschrieben sondern pures ADO.NET. Wenn nun noch eine Datenbank dazukommen sollte, könnte ich doch dann ein OR Mapper verwenden?
Jegliche Abstraktion ist nur so gut wie Sie verwendet wird. Ohne eine Verwendung ist eine Abstraktion schlicht weg Leergeld.
Jürgen Gutsch
4. August 2010
@Thomas
> wo mappt man auf seine Domain Objects/POCOs?
Das erledigt sich mit NHibernate da du deine eigenen Objekte nutzen kannst: http://aspnetzone.de/blogs/juergengutsch/archive/2009/09/28/zweiter-start-mit-nhibernate.aspx
Thomas
4. August 2010
@Jürgen: nHibernate schaue ich mir erst mit Version 3 an, auf LINQ werd ich nicht verzichten
Jürgen Gutsch
4. August 2010
@Thomas, du kannst auch mit NHibernate LINQ verwenden
Thomas
4. August 2010
@Jürgen: Link? Laut Albert wird es erst mit 3.0 nativ implementiert. Keine Lust jetzt wieder irgendwie drumrumzufrickeln.
Grund: Repository + LINQ to SQL funktionieren einwandfrei. Wenn sich meine Datenbank ändert lösche ich alle Objekte im L2Sql-Designer und ziehe sie neu rein, speichere ab und passe nur noch das Mapping im Repository an. Nirgendwo, an keiner Stelle, irgendwelche komischen Strings und SQL-Abfragen.
Das ist ein Level an Bequemlichkeit, das mir ne andere Lösung erstmal bieten soll. Und zusätzlich ist es schön abstrakt und lässt sich ohne jeden Umstand mocken
.
Robert Mühsig
4. August 2010
Und wie testet man dann NHibernate Kram in der Business Schicht? Mit einer InMemory DB wie in Ayendes Post beschrieben?
Robert Mühsig
4. August 2010
@Jürgen: Irgendwie hab ich ein ungutes Gefühl wenn ich in meinem Model irgendwelchen OR/M Kram mit reinpacken muss, wie bei deinem Beispiel:
public class Task: Entity
{ … }
Jürgen Gutsch
4. August 2010
@Thomas
Link: siehe oben, ganz am Ende des Blogbeitrages
Verwechselst du gerade LINQ mit LINQ to SQL?
Wenn NHibernate Liste zurückkgibt, die irgendwie IEnumerabel implementieren kannst du definitiv LINQ nutzen. LINQ to NHibernate ist eine andere Geschichte.
Klar ist LINQ to SQL bequemer, bis es einen entsprechenden Designer für NHibernate gibt, wird es noch etwas gehen. Aber mir ging es hier eigentlich um den “Code First”-Ansatz und die Möglichkeit so mit NHibernate die eigenen DTOs zu verwenden und sich das Mapping zu sparen, das mit LINQ to SQL nötig ist.
Jürgen Gutsch
4. August 2010
@Robert, das ist von mir um Gemeinsamkeiten der DTOs zu kapseln, nicht vom OR-Mapper
Jürgen Gutsch
4. August 2010
@Robert, Tests: ja, zum Beispiel oder du Mockst die NHibernate Objekte. It Depends: Je nach dem was du halt testen willst.
Robert Mühsig
4. August 2010
@Jürgen: Was meinst du mit “Gemeinsamkeiten der DTOs zu kapseln”? Hast du noch ein extra Domain Model?
Jürgen Gutsch
4. August 2010
@Robert, kannst du in dem Demoprojekt sehen, dass ich in meinem Blogbeitrag (link oben) angehängt habe. Hat nichts mit NHibernate zu tun:
public class Entity: IEntity where T: IEntity
{
protected Entity()
{
Initialize();
}
protected virtual void Initialize()
{
}
public virtual Guid Id { get; set; }
public virtual bool Equals(T obj)
{
return Id == obj.Id;
}
public override bool Equals(object obj)
{
return Equals((T)obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
(Hm… Die Klasse muss eigentlich abstract sein)
Thomas
4. August 2010
@Jürgen: Ja, Missverständnis. LINQ stand als Synonym für Linq to Xyz. Ich will natürlich die Datenbankabfragen mit LINQ direkt machen und keine dreckigen Strings mehr benutzen
.
Und das ominöse Mapping (ORM=>eigene Objekte) kommt bei mir nur einmal in einer Map-Methode je Repository vor, damit habe ich kein Problem, zumal es typsicher ist und der Compiler gleich anspringt, anders als bei ner Config per XML o.ä.
Jürgen Gutsch
4. August 2010
@Thomas: klar
Du hattest nur die Frage gestellt, wo in Fall ohne Repository das Mapping statt finden soll.
Daniel
4. August 2010
Zum Thema Mapping auf POCOs: Das kann man auch automatisieren. Macht z.B. http://automapper.codeplex.com/
Ken
4. August 2010
Betrachten wir das doch mal nüchtern.
Vorteile eines DAL: Austauschbarkeit, Testbarkeit, Mapping auf POCOs.
Nachteile: Verstecken der Features des O/R-Mappers bzw. aufwändiger Nachbau dieser, höherer Aufwand weniger Nutzen!?
Mal ein kleiner Auszug aus meinem Projektleben. Wir haben vor einem Jahr auf einen DAL verzichtet und sind damit ordentlich an die Wand gefahren. O/R-Mapper war das Entity Framework inkl. einer Lazy-Loading “Erweiterung”. Wir haben die generierten Objekte auch in der gesamten Anwendung genutzt. Großes Problem dabei ist, dass die Datenstruktur der Datenbank die Datenstruktur der Anwendung bestimmt. Doof.
Ein weiterer Nachteil davon: die ganze Anwendung ließ sich nicht Unit-Testen.
Was müsste denn ein O/R-Mapper bieten, damit man ihn (vielleicht) ohne DAL nutzen könnte? In meinen Augen sind es 2 Punkte: Das Mapping auf POCOs und die Testbarkeit. Ausgetauscht wird (fast) nie…somit ist das vernachlässigbar.
Was bei dem Projekt außerdem noch auffiel: Es wurde eine Vielzahl von Queries mehrfach geschrieben. Es war eben einfach, diese in der Businesslogik zu definieren. Jeder für sich..immer wieder. Ob der Grund dafür die Faulheit der Enwtickler war, diese Geschichten nicht in eigene Methoden auszulagern oder sowas dann eben einfach passiert will ich nicht beurteilen. Jedenfalls macht es die Wartbarkeit des Codes bei DB-Änderungen nicht einfacher. Schließlich muss ich an mehreren Punkten die Queries anpassen. Und das will ich nicht.
Fazit: Ich bin mir unschlüssig. Bisher fand ich das Repository-Pattern super und wir haben es bei dem Redesign des Backends auch eingesetzt und fahren damit echt gut. Vielleicht ist doch die Projektgröße entscheidend!? Aber wer kann diese schon zu Beginn eines Projektes bewerten??
Robert Mühsig
4. August 2010
Also was ich so aus den Kommentaren raushöre: Ich hab wohl den falschen OR-Mapper genommen und sollte mal ein Blick auf NHibernate werfen.
@Jürgen: Irgendwelche IRepositories hast du also nicht mehr, sondern machst in deiner Business Schicht die NHibernate Session auf und arbeitest damit?
Jürgen Gutsch
4. August 2010
@Robert, ich glaube nicht, dass du den falschen OR-Mapper genutzt hast. Wenn du OR-Mapper nutzt, mit denen du nicht die eigenen DTOs nutzen kannst, musst du halt im BL mappen (eher unschön, irgendwo hatte ich aber mal was zu Code First mit LINQ to SQL gelesen, da sollte es auch gehen)
Doch, in der Regel nutzt ich das Repository-Pattern und gehe nur dort auf die OR-Mapper (egal welche). In dem Blogbeitrag habe ich nur ein einfaches (nicht repräsentatives!!) Projekt erstellt. In der Regel habe ich eine 4-Schicht Architektur (http://aspnetzone.de/blogs/juergengutsch/archive/2009/12/30/linq-und-linq-to-sql.aspx
Die vierte Schicht ist Vertikal und enthält die Interfaces und DTOs)
Und Ja: Würde ich in einer 0815-Anwendung einen OR-Mapper verwenden, würde ich den im BL direkt auf den OR-Mapper gehen.
Alex
4. August 2010
Ich bin ganz bei Jürgen.
Hier noch eine Einstiegsserie zu (Fluent)NHibernate von Gabriel Schenker:
http://dotnetslackers.com/articles/ado_net/Your-very-first-NHibernate-application-Part-1.aspx
http://dotnetslackers.com/articles/ado_net/Your-very-first-NHibernate-application-Part-2.aspx
http://dotnetslackers.com/articles/ado_net/Your-very-first-NHibernate-application-Part-3.aspx
Peter bucher
4. August 2010
@jürgen
mit ienumerable hast du aber kein defered execution, da brauchts iqueryable.
Im nomalfall braucht es keine repository schicht, wenn ein ormapper genutzt wird.
Die würde ich nur einbauen, wenn abstraktion nötig ist (bspw zus. Zugriff auf xml),
oder um die entwickler eimzuschränken.
Das wäre dann iqueryable vs methoden mit argumenten, und nur die nutzen nhibernate direkt.
Gruss peter
Kristof
4. August 2010
Ein eigener DAL hat ganz klar den Vorteil, dass man auch auf Unerwartete reagieren kann, ohne irgendwelche Mapper oder Frameworks über den Haufen werfen zu müssen, auch wenn das bedeutet etwas mehr zu coden.
Wir haben diese Fleißarbeit “ApexSQL Code” überlassen. Ein Template, eine View oder Tabelle und hinten kommt der passende Code heraus. Datenbänkänderungen reduzieren sich da auf Copy-Paste. Mit einem selbst erdachten Pattern in Form von Templates sind wir so flexibel wie es nur geht, was man von Linq und Co. oft nicht behaupten kann.
Den eigenen Code hat man zudem auch unter Kontrolle. In komplexen Projekten auf Blackboxes zu vertrauen, kann sich als fatal erweisen, weil die EINE Anforderung eben dann nicht geht und Workarounds entwickelt werden müssen.
Wir leben ja fast alle von der Erstellung von Individuallösungen für unsere Kunden oder der eigenen Firma. Warum soll das nicht auch für die eingesetzten Werkzeuge gelten?
Christian
5. August 2010
Ihr verwirrt mich gerade ein bischen:
- manche schreiben dass ein DAL nie schlecht ist. Auch wenn er nichts anderes macht als die Abfragen dem ORM zu übergeben und das Result dann einfach zurück in die Service Schicht zurück zu geben (evtl. davor noch ein Mapping von ORM Objekten zu den normalen Business Objects)
- andere schreiben dass man auf den DAL komplett verzichten kann und den ORM direkt aus der Business Schicht ansprechen kann
Ja wie denn nun?
Und wie geht ihr mit XML, Textdateien, Images usw. um die z.B. auf dem Filesystem liegen? Auch in der Business Schicht?
Thomas
5. August 2010
Hier gibt es einfach nicht “die” Wahrheit. Mir scheint dass alle die mit nem mächtigen ORM arbeiten, und hier sehe ich bisher nur nHibernate, Ayendes Ausführungen gegenüber ein wenig offener sind.
Ich z.B. bin bisher mit dem Repository-Pattern, und der Art und Weise wie du sie nochmal zusammenfasst, noch an keine Grenze gestoßen und sehe (derzeit) dass die Vorteile für mich überwiegen:
Es ist testbar, wenn ich will; das Mapping ist kein großer Aufwand; und überhaupt beruhigt mich diese Abstraktion noch ein wenig mehr in Hinblick auf die unbekannte Zukunft.
Ich hatte neulich ein schönes Beispiel, als ich eine etwas ältere Komponente erweitert habe. Als ich die entwickelt habe, habe ich bereits auf Repositories gesetzt (sie nur nicht so genannt), aber noch mit ADO.NET und Stored Procedures gearbeitet. Dadurch, dass es abstrahiert war, war es überhaupt kein Problem für die neuen Features zum Datenbankzugriff nun Linq to Sql zu verwenden – davon hat die übrige Anwendung nichts mitbekommen.
Aber das ist meine eigene Sicht der Dinge – es bleibt wie schon gesagt bei “it depends”, du musst es wohl selbst ausprobieren und deine eigene Entscheidung treffen
.