HowTo: 3-Tier / 3-Schichten Architektur

Eine 3-schichtige Architektur ist eigentlich ein “Klassiker” in der Softwareentwicklung. Da allerdings das Thema sehr weitläufig ist und Anfänger (und unbelehrbare Entwickler) aus Mangel an Zeit, Lust oder Erfahrung zurückschrecken gibt es genügend Beispiele wo einfach darauf verzichtet wurde.

Um den Grundgedanken zu vermitteln und um zu zeigen, dass es eigentlich sehr einfach ist, sowas am Projektanfang zu implementieren, schreibe ich diesen Artikel

Was für “Schichten” und warum 3?
Fast jede Software greift auf Daten zurück – sei es XML, ein Webservice, eine Datenbank, eine Textdatei oder ein X-beliebiges anderes System.
Diese Daten werden irgendwie verarbeitet – sei es eine mathematische Funktion, eine Validierung oder eine bestimmte Filter und Suchfunktion.
Damit das Ergebnis auch irgendwo angezeigt wird (bzw. die Eingaben entgegen genommen werden) gibt es in den meisten Fällen auch ein Frontend, sei es eine Consolen-Applikation, eine Website oder irgend etwas anderes.

Jetzt wären wir bei den 3-Schichten angekommen:
 image

“Nur 3? Ich hab mehr!”
Natürlich kann man unzählige Schichten noch dazwischen schieben. Ein Beispiel ist z.B. die Software Factory von Microsoft. Da gibt es noch etliche Mappings zwischen den Data-Access-Schichten bis hin zu den Service-Schichten. Siehe auch den Wiki-Artikel zu den Schichtenmodellen.

“Ich frage nur Daten ab – ich brauch nur 2 Schichten.”
Über SQL etc. kann man natürlich auch Filtern, Sortieren etc. – da könnte man auch die “Business” Schicht in Frage stellen. Aus meiner Erfahrung sollte man das allerdings lieber nicht machen – später können noch irgendwelche Anforderung dazukommen, die nix im Data Access Layer zu suchen haben. Bis es soweit ist, könnte die Business-Schicht die Daten einfach nur “durchreichen”.

Beispielapplikation:
image

Data-Access Layer: “ThreeTier.Data”
Business Layer: “ThreeTier.Service”
Presentation Layer: “ThreeTier.ConsoleApp”
+ Unit-Tests: “ThreeTier.Tests”

Hier haben wir eine recht einfache Beispielapplikation – das ganze noch mit ein paar kleinen Unit-Tests bestückt (Einführung in Unit-Tests hier).

Die Architektur sieht man z.B. auch recht gut in Rob Conerys Storefront Projekt.

Schichten im Detail: ThreeTier.Data
image 
Hier definiere ich erst mal meine Objekte, welche ich im System nutze – simple POCOs.
Zugegeben, man kann sich darüber streiten ob man sein “Model” tatsächlich mit in dem Data-Projekt haben möchte. Da allerdings alles meistens mit irgendwelchen Daten zusammenhängt, passt das schon.
Wir haben hier nur die User Klasse:

image 
Im Ordern “DataAccess” liegt unsere Schnittstellen (Einführung zu Schnittstellen) zu den Datenquellen.
In diesem Fall haben wir nur die Schnittstelle “IUserRepository” (Repository zu dt. sowas wie Lager, Speicherort etc.) – dort definieren wir, welche Operationen ich generell auf eine X-beliebige Datenquelle ich ausführen möchte:
image
Das “DemoUserRepository” ist die konkrete Implementierung dieser Schnittstelle. Da ich keine DB oder ähnliches wollte, werden hier statische Daten zurückgegeben.

Welchen Vorteil bringt mir jetzt das Interface?
Das Interface könnte man hier in Frage stellen, allerdings erlaubt es später recht einfach die Datenquelle zu wechseln – weil alles auf der Schnittstelle beruht.
Da man im Regelfall mit einer DB etc. arbeitet möchte man z.B. in Unit-Tests nicht unbedingt die Datenbank fluten, sondern kann sich hier statische Testdaten zurückgegeben lassen. Einfach durch die Schnittstelle.
So könnte man auch leichter von einem “Showcase” zu einer echten Implementierung umschwenken.

Da ich in meinem Beispiel aber nur statische Daten zurückgebe, habe ich im Unit-Tests genau diese getestet.

Schichten im Detail: ThreeTier.Service

image
Im Service haben wir nach dem gleichen Prinzip auch eine Schnittstelle für unseren “UserService” erstellt.

image

In unserem UserService gibt es einmal eine Login-Methode und eine Methode, welche (ganz im Sinne von Social Networking) die Freunde von einem User zurück gibt. Hierbei habe ich zudem auch nur statische Daten genommen. Das ganze basiert allerdings auf dem “UserRepository”.

Schichten im Detail: ThreeTier.ConsoleApp
image

Mal wieder ein Konsolenprogramm – zwar ist das keine schöne Oberfläche, aber für das Beispiel soll es genügen:

        static void Main(string[] args)
        {
            Console.WriteLine("Great Social Community System - Please Login...");
            Console.Write("Name: ");
            string loginname = Console.ReadLine();
            Console.Write("PW: ");
            string password = Console.ReadLine();

            IUserService srv = new DemoUserService();

            if (srv.Login(loginname, password))
            {
                Console.WriteLine("Hello: " + loginname);
                Console.WriteLine("Your demo friend collection in the system: ");
                List<User> friends = srv.GetFriendsFromUser(loginname).ToList();

                foreach (User friend in friends)
                {
                    Console.WriteLine(" + " + friend.Login + " - Id: " + friend.Id);
                }
            }
            else
            {
                Console.WriteLine("Login failed");
            }

            Console.ReadLine();
        }

Ausgabe:

image 

Extras: Unit-Tests

Um ein gutes Beispiel zu geben, habe ich sogar 6 Unit-Tests geschrieben. Das Frontend hab ich allerdings nicht getestet ;)
image

Code-Coverage: 97% (Data + Service)
image

Resultat:

Durch die 3-Schichtige Architektur ist es später leichter Möglich neue Features einzubauen und die Applikation zu Warten. Im Team macht sich das auch recht gut, da man dadurch eine bessere Teamaufteilung machen kann.

[ Download Source Code ]

Video:

32 Kommentare bisher »

  1. Rainer Schuster sagt

    am 10. Juli 2008 @ 06:44

    Sehr schönes Beispiel,

    bei steigender Komplexität ließe sich hier dann zusätzlich ein DI Framework wie in Spring.NET vorhanden, oder von MS Unity einbinden um eine lose Kopplung zu erhalten. Das weißt du ja aber schon. Wäre eine schöne Ergänzung, als nächsten Artikel …

  2. Robert Mühsig sagt

    am 10. Juli 2008 @ 06:47

    Angedacht habe ich es bereits – daher auch diese Interfaces. Allerdings ist es so, dass ich so ein Beispiel auch mal unseren Azubis zeigen möchte – wenn ich da jetzt noch Dependency Injection mit aufgenommen hätte, würde sofort wieder der Eindruck entstehen "Das ist mir jetzt zu hoch".
    Bei dem ReadYou Projekt werde ich aber sowas mit beachten :)

  3. Raik Dittrich sagt

    am 10. Juli 2008 @ 09:28

    Interessanter Artikel :) Das Thema ist zwar uralt, aber trotzdem immer wieder aktuell, da alle neuen Programmierer immer wieder in dieselben Fallen laufen ("3-Schichten? brauch ich nicht, krieg ich schneller und einfacher hin…").
    Was ich aber trotzdem überlegenswert finde, ist die Behandlung von Datenmodell und Datenzugriff in derselben Schicht. Es besteht die Gefahr der zu starken Verzahnung des Modells mit der konkreten Datenzugriffstechnik, ein sehr alter Fehler. Hier ist die bewußte Trennung in Logik (Datenmodell) und Adapter (Datenzugriff) sehr sehr wichtig. Hier finde ich die Überlegungen von Ralf Westphal  sehr interessant, die in dieselbe Richtung gehen (Stichwort Softwarezellen). Können wir ja mal bilateral bequatschen :)

    Grüße, Raik

  4. Robert Mühsig sagt

    am 10. Juli 2008 @ 09:36

    Die Verzahnung hier mit dem Datenmodell und dem Datenzugriff war mir dann beim Schreiben ebenfalls aufgefallen – für den Einstieg habe ich das allerdings erstmal so hingenommen ;)

  5. Thomas sagt

    am 10. Juli 2008 @ 19:42

    Ich packe grundsätzlich alle Geschäftsobjekte in einen eigenen, sozusagen vertikalen, Layer:

    Blabla.BL – Geschäftslogik
    Blabla.DA – Datenzugriff
    Blabla.BO – Geschäftsobjekte
    Blabla.UI – Web-Anwendunge oder sonstwas

    Ich nutze immer BO für Business Object, die Begrifflichkeit ist eigentlich wurscht – man kann’s auch Entity, Data Value Object oder was auch immer nennen – ich habe zumindest noch keine schlüssige No-Go-Erklärung für eine der Bezeichnungen gefunden.

    Letztendlich kann ich die Objekte so auch in allen Schichten nutzen, z.B. auch zum Binden von Daten an einen Repeater in ASP.NET – der die Daten aus dem BL erhält, welches den DAL anzapft. Damit kann in allen Schichten mit einheitlichen Objekten gearbeitet werden, ohne mit DataSets oder sonstwas rummachen zu müssen und vor allem ohne sich zu krämen, dass man im UI jetzt etwas ausm DAL nutzt …

    Funktioniert natürlich auch nur solange sinnvoll, wiem an keinen OR-Mapper einsetzt … die perfekte Lösung habe ich jedenfalls auch noch nicht.

    Dein Code liefert unterm Strich trotzdem ganz gute Ansätze, deine Azubis können sich glücklich schätzen ;-) .

  6. Rob Revona sagt

    am 12. Juli 2008 @ 12:20

    Danke für das Beispiel.
    Wo und wie wird denn das Logging implementiert, d.h. in welcher Schicht ?

  7. Joseph Fischer sagt

    am 14. Juli 2008 @ 08:45

    Super Beispiel!

    Ist möglicherweise der bewussten Einfacheit des Beispiels geschuldet, aber müssten die Schnittstellen zu den jeweiligen Services zur Umsetzung der losen Kopplung nicht jeweils in einer getrennten Assembly angeboten werden?

  8. Robert Mühsig sagt

    am 14. Juli 2008 @ 09:04

    Zum Logging: Das ist eine gute Frage. Logging sollte in jeder Schicht erfolgen bzw. habe ich noch keine andere gute Methode gefunden. Dort wo geloggt werden soll, muss auch der entsprechende Loggingbefehl aufgerufen werden. Allerdings kann man über Log4Net das recht hübsch machen :)

    Zur losen Kopplung und den Schnittstellen: Das könnte man sicherlich machen. In der "Service Factory" wird solch eine Trennung durch die Assemblys allerdings auch gemacht.
    Vom Aufbau her, ist dies sicherlich ein guter Gedanke :)

  9. HowTo: Fluent Interfaces - schöne APIs mit C# 3.0 | Code-Inside Blog sagt

    am 5. August 2008 @ 19:50

    [...] Aufbau ist ähnlich empfunden wie bei diesem Blogpost – allerdings etwas vereinfacht (und sollte so nicht verwendet werden (Schnittstellen [...]

  10. ntz sagt

    am 21. September 2008 @ 16:33

    Die Aufteilung in verschiedene Schichten ist ein guter Anfang. Was das Logging angeht ist das ein altes Problem.
    Man nennt Logging/Security/Caching etc., ja nicht umsonst cross cutting concerns.
    Jahrelang hab ich mich darüber geärgert, dass aufrufe die eigenltich nichts mit der funktionalität der jeweiligen Methode zu tun hatten, den code unnötig verkomplizierten.
    Klar AOP ist ein schönes Konzept, aber mit OO programmieren eben nicht einfach so umsetzbar. und die vorhanden franeworks waren bisher leider einfach viel zu umständlich.
    Zum Glück habe bin ich vor einem Jahr auf PostSharp aufmerksam geworden.
    Am Anfang war ich skeptisch ob es sich auch in größeren projekten nutzten lässt, aber dieser Zweifel wurde durch den Einsatz in einem ebensolchen ausgeräumt.
    Dabei haben wir in allen großen Cross Cutting Bereichen auf konsequent auf PostSharp gesetzt.
    Security/Caching/Logging ist auf allen layern über PostSharp Attribute implementiert worden.
    zu meiner Freude sind kaum mehr stellen im code zu finden, die nichts mit der eigenltichen aufgabe der jeweiligen methode zu tun haben.

    Allen die ähnliche Probleme haben, würde ich PostSharp empfehlen.
    Speziell, PostSharp.Laos, da es wahrscheinlich die bedürnisse der meißten abdecken wird.

  11. Code-Inside Casts: Jetzt auch noch Videos… | Code-Inside Blog sagt

    am 17. Oktober 2008 @ 23:38

    [...] – wesentlich besser als Seitenweise Text. Daher habe ich jetzt mal ein Screencast zum Thema “3 Schichten Architektur” [...]

  12. HaPe77 sagt

    am 23. Oktober 2008 @ 08:39

    Interessanter Artikel. Danke. Frage an die Profis hier:

    Welches Buch könnt ihr einem wir mir empfehlen, der zwar schon ein ca. 2 Jahre mit .Net programmiert, aber eben sich noch immer nicht an das 3-Schichten-Model gewagt hat, weil alles immer schnell gehen muss.

    Wo wird das gut und halbwegs einfach bzw. für Einsteiger verständlich erklärt?

  13. Robert Mühsig sagt

    am 23. Oktober 2008 @ 09:40

    Ein direktes Buch kann ich dir dazu nicht empfehlen, aber für die Zeitsache: Schau dir mal mein YouTube Video dazu an – kaum 10 Minuten. Wichtig bei einer 3 Schichten Architektur ist einfach nur dass man sich klar macht, was welche Schicht macht.
    Wenn man das beherzigt, sollte man es einfach mal ausprobieren ;)

  14. Savage sagt

    am 7. November 2008 @ 15:06

    Danke für dein Beispiel. – Angenommen man würde die Consolenapplikation mit einer Websiteapplikation austauschen. In welchem Layer würdest du das Sessionhandling/Membership etc. platzieren? Die gängigen Methoden wie Forms-Authentification wären dann nämlich im Presentationlayer? Würde ich bei einer Useraktion (klick auf Button) abfragen wollen, ob sich ein User z.B. in einer bestimmten Role befindet, dann würde das auch in der Web-Applikation passieren, das sollte jedoch im Businesslayer sein, oder?

  15. Robert Mühsig sagt

    am 8. November 2008 @ 14:36

    Das Membership-System so wie es ist, passt nicht ganz in die 3-Schichten Architektur rein. Man hat z.B. Probleme in dem Business Layer auf Userdaten zuzugreifen oder Profilinformationen abzugreifen wenn man das Profilsystem von ASP.NET nutzt.
    Ich hatte vor einer ganzen Weile dazu mal meine Gedanken gebloggt: http://code-inside.de/blog/2008/06/23/howto-membership-in-klassenbibliotheken-dlls/
    Wichtig ist hierbei allerdings der Hinweis in den Kommentaren.
    Ich würde so vorgehen (weil ich das Membership System nicht wirklich mag und das mir nur Probleme gemacht hat ;) ) : Bau ein UserRepository / UserService also ein komplett eigenes Usersystem (ich werde in Zukunft auch mein System mal zur Verfügung stellen).
    Dieses Usersystem kannst du in einer Webapplikation oder Desktopapplikation einbinden wie du lustig bist. In einer Webapplikation würde ich allerdings einen eigenen Membership Provider schreiben, damit man die Standardcontrols und eingebauten Funktionalitäten trotzdem noch nutzen kann. Dieser eigene Membership Provider ist nur ein Adapter zwischen ASP.NET und deinem Usersystem.
    So ist man später sehr flexibel und kann trotzdem im ASP.NET Umfeld das gewohnte verwenden.

  16. Savage sagt

    am 16. November 2008 @ 20:24

    Danke für deine Anregung, werde das so umsetzen. Aber mit einem komme ich noch nicht klar. Die ConsoleApp kennt den Data-Layer (zumindest das User Entity) und das sollte nach meinem Verständnis nach nicht sein, oder täusche ich mich da?
    Der Vorschlag von Thomas einen "vertikalen" Layer mit Entities einzuziehen klingt ja gut, aber wie er selbst schon schreibt, kommt es zu Problemen, wenn man später einen OR-Mapper einsetzen will. Wie könnte man die "Daten-Objekte" besser verwalten?
    Mein Problem ist, dass ich eine größere Web-App schreiben will und von Anfang an gleich eine saubere Architektur haben will und ich mich einfach nicht entscheiden kann.

  17. Robert Mühsig sagt

    am 16. November 2008 @ 21:17

    Ich hatte bei dem Beispiel mein "User Entry" (also das Model der Applikation) mit in den Data-Layer implementiert, was allerdings wie Thomas schon richtig sagte, nicht ganz ok ist.
    Wenn man einen O/R Mapper einsetzt (und keine POCOs), dann hat das natürlich Vorteile, aber man ist an den jeweiligen O/R Mapper meistens gebunden.
    Wenn man sich viel Mühe machen möchte: Die generierten Klassen des O/R Mappers nochmal über ein eigenes Mapping auf das eigens definierte Model mappen. Das macht etwas mehr arbeit, aber man kann von Linq2Sql zu ADO.NET EF oder NHibernate etc. ändern, ohne das es das Model interessiert.
    Dazu empfehle ich Rob Conerys "Storefront" Videos. So mache ich es selber momentan auch in einer Applikation.

  18. jogi sagt

    am 28. November 2008 @ 08:49

    danke für den tollen Artikel.
    ich versuche mich gerade in das 3-schichten-modell einzuarbeiten.
    Dabei steh ich vor folgendem Problem:
    Im UI ist ein Datagridview, das per Dataset befüllt werden soll.
    Welche Schicht liefert nun das Dataset?
    Kommt vom DAL schon ein Dataset an die BAL oder liefert der DAL einen DataTable und die BAL liefert ein Dataset an das UI.Oder macht man das völlig anderst ?

  19. Robert Mühsig sagt

    am 28. November 2008 @ 10:22

    Jede Schicht redet nur mit der drunter liegenden. Ich persönlich mag die DataSets nicht so – besser wäre es wenn die BAL irgendwelche Business Objekte weiterreicht (z.B. gibt "GetUsers()" eine List<User> zurück)
    Das DataGridView sollte man auch an solche Objektesourcen hängen können – das ist meiner Meinung nach eine bessere Variante als DataSets hin und her zu schieben.

    Also:
    DAL —> DataSets/oder bereits hier schon eigene Business Objekte –> BAL —> Business Objekte —> UI
      

  20. HowToCode: ErrorCodes, Exceptions, den User informieren, wenn etwas schief läuft - wie gehts? | Code-Inside Blog sagt

    am 6. Februar 2009 @ 01:46

    [...] ich später auch nochmal genauer beschreiben – spielt aber jetzt keine Rolle) und nutzen eine klassische 3-Schichten Architektur mitsamt ASP.NET MVC als [...]

  21. awaiK sagt

    am 8. Februar 2009 @ 20:00

    Hi,
    danke für dieses Beispiel.
    Ich beschäftige mich momentan mit Architekturfragen, wie 3-Schichten Modell usw. Leider habe ich bis jetzt keine Literatur darüber gefunden, die mir konkrete Anwendungsbeispiele liefert, nicht nur abstraktes gerede nach dem Motto: "3-Schichten Modell ist gut, weil blablabla".

    Kennt jemand weitere Quellen zu diesem Thema? Würde mich über eine Antwort freuen.

  22. HowToCode: Wie bildet man einen Workflow im Code am besten ab? | Code-Inside Blog sagt

    am 12. Februar 2009 @ 12:56

    [...] Unser Szenario ist wie beim letzten Mal eine Webanwendung, welche eine 3-Schichten Architektur implementiert. Ein User soll sich anmelden können, dabei wird geprüft, ob er [...]

  23. Mr. Unknow sagt

    am 23. Februar 2009 @ 15:46

    Ein wirklicher toller Artikel, überhaupt scheint das ne Spitzen-Seite hier zu sein. Vielen Dank!

  24. Midiman sagt

    am 27. Februar 2009 @ 22:14

    vielen Dank!
    Ich suche jetzt ein paar Hinweise, wie:
    1.) Users aus einer Datenbank gelesen wird. Hierzu möchte ich auf einer Klasse vom EF aufsetzen (Linq2SQL-Klasse, z.B. Customers)
    2.) Die Schichten erweitert werden müssen, damit Änderungen in Frontend oder BL zur Entity weitergereicht werden.

  25. awaiK sagt

    am 5. März 2009 @ 19:31

    Hi, mir ist aufgefallen das bei diesem Beispiel von dem Presentation Layer (ThreeTier.ConsoleApp) eine Referenz zum Data Access Layer (ThreeTier.Data) exzitiert. Das ist nach meinem Verständnis nicht richtig, da nur von der Business Schicht eine Referenz auf die Datenzugriffsschicht zeigen sollte.

  26. welt-held.de » Blog Archive » 3 Schichten Modell sagt

    am 23. März 2009 @ 10:46

    [...] ein C# Projekt schön nach dem 3-Schichten-Modell aufzubauen. Ich orientiere mich dazu an einem HowTo von Robert Mühsig (netter Blog [...]

  27. sniff sagt

    am 1. Mai 2009 @ 13:23

    Danke für den Artikel.

    Ich habe noch eine Frage angenommen ich möchte eine Suchfunktion, also eine Art Filter. In welcher Schicht passiert das Filtern der Daten? Sql bietet diese Funktionalität ja, kann ich alle Funktionen die Sql bietet in die Daten Schicht packen oder sollte man immer alle Daten holen und die Business Schicht dünnt die Liste dann aus?
    Also z.B sowas GetUserByName("H"); gibt alle User die mit H anfangen in einer Liste zurück. Ansonsten wäre in der Datenschicht ja immer nur alle User holen, User löschen User einfügen, User ändern? Es wäre ja viel effizienter garnicht erst alle User zu holen und zu mappen. 

  28. Robert Mühsig sagt

    am 1. Mai 2009 @ 19:48

    Die Filterung sollte prinzipell im Data Layer geschehen, da es dort von der Performance her am besten passt. Dazu kannst du dir das Video von Rob Conery ansehen:
    http://blog.wekeroad.com/mvc-storefront/mvcstore-part-4/
    (oder auch Part 5 oder 3)

    So machen wir es in einem Projekt:
    Die UI (bei uns ein ASP.NET MVC Controller) sammelt die "QueryParameter" und gibt diese weiter zum Service Layer, dieser ruft dann das Repository + die Filtertechnik die Rob Conery aufzeigt auf und gibt es dann entsprechend zurück.

  29. Jan sagt

    am 28. Mai 2009 @ 11:38

    Hi,
    bin eben auf die Seite gestoßen. So einen Artikel mit Beispiel habe ich schon lange gesucht. Toll. Mehr bitte …;)

    Viele Grüße
    Jan

  30. Pascal sagt

    am 31. Juli 2009 @ 11:47

    Danke für diesen Artikel und das Video. Einfach super. Hat mich sehr weitergebracht.

  31. Christian Kiefer sagt

    am 13. November 2009 @ 10:36

    Danke für Deinen Artikel und Video,

    Als Einstieg in das Thema 3 Schichten-Architektur ist dies ideal!

  32. Roberto's Blog : DI / IoC Container LightCore Teil 2: Registrierung über Xml Modul sagt

    am 21. Februar 2010 @ 19:11

    [...] Modul erklärtWas eine drei Schichten Architektur (Tier 3 layer) ist, erklärt Robert Mühsig in seinem Post.Das folgende Beispiel wird eine Solution mit mehreren Projekten beinhalten, die eine Anwendung mit [...]

Komentar RSS · TrackBack URI

Hinterlasse einen Kommentar

Name: (erforderlich)

eMail: (erforderlich)

Website:

Kommentar: