Code-Inside wird noch (Bloggen, Twittern, in Social Bookmarking…) webzwonulliger – neustes Experiment: Screencasts. Videos können sehr eindrucksvoll zeigen, wie einfach und schnell etwas zu bewerkstelligen ist – wesentlich besser als Seitenweise Text. Daher habe ich jetzt mal ein Screencast zum Thema “3 Schichten Architektur” erstellt.
Ich habe mich dabei (trotz der bescheidenen Qualität und der (scheinbar nur für neue Mitglieder?) 10 Minuten Videolängenbegrenzung) bewusst für YouTube entschieden:
Gigantische Nutzerzahlen
Keine Hostingkosten
Einfache Verlinkung
Es kennt einfach jeder und die Usability ist im Vergleich zur Konkurrenz einfach unschlagbar
So ganz 100% bin ich auch nicht mit dem Ergebnis zufrieden, allerdings ist so ein Screencast doch etwas ganz anderes als ein Blogpost. Dazu kommt dass der Mensch im Normalfall seine Stimme eigentlich nicht wirklich selber hören mag… aber beurteilt selbst und ich würde mich über Feedback freuen:
Mein kleines Projekt “ReadYou” kommt in winzigen Schritten vorwärts – allerdings mehr auch nicht. Grund hierfür ist meist, dass wenn man sich in ein Thema mal versucht einzulesen, man sofort auf zwei neue interessante Sachen stößt. So z.B. die Mockinggeschichte um meine UnitTests besser zu gestalten.
Architekturumgestaltung
Meine Grundsätzliche Architektur war so aufgebaut wie in diesem Blogpost beschrieben.
Durch das gute Feedback zu dem 3-Schichten Architektur Blogpost habe ich es nun grob so eingeteilt:
Mein Model liegt nun nicht mehr im Data rum, sondern ist theoretisch auf allen Schichten erreichbar und daher in einer eigenen DLL zu finden. Das Model besteht aus einfachen POCOs – die nichts anderes machen außer Daten halten.
Bei Data ist natürlich genau dasselbe vorzufinden: Die Trennung zwischen den Interfaces und der eigentlichen Implementierung die ich vorgenommen habe.
Response & Request bei den Services
Als ich mein Interface für den BookService vorbereitet habe, habe ich überlegt, was ich alles für “Aufrufe” brauche:
Allerdings ist das nicht wirklich schön – mein Service hätte äußerst viele Methoden um Bücher ranzuholen. Einmal anhand des Autors, einmal nach Kategorie etc. Komplexer wären noch Sachen wie: “GetBooksByCategoryAndUser” – heiei… für jeden Fall eine Methode zu schreiben, erscheint mir nicht sinnvoll.
Meine Lösung: Request & Response
Wie ein “WebService” geben meine Services auch Responses zurück und verlangen ein Request Objekt:
Diese Objekte habe ich in ReadYou.Service.Model hinterlegt – da sie nur im Service vorkommen, ich allerdings diese Definitionen und die Logik in seperaten DLLs trennen wollte:
Dabei leitet der “GetBooksRequest” von “BaseRequest” ab und beim Response genauso.
Durch dieses Objekt kann ich später genau mein “GetBooksRequest” definieren:
public class GetBooksRequest : BaseRequest
{
public Tag Tag { get; set; }
public Category Category { get; set; }
public User User { get; set; }
public Author Author { get; set; }
}
Jetzt könnte ich mir sowas wie “Gib mir alle Bücher vom Autor XYZ, des Users ABC mit der Kategory “Krimi”" – das erlaubt mir einige Freiheiten und sollte erweiterbar sein, falls mir wieder was neues einfällt.
Wie sieht das im Projekt aus?
Alles ist aufgeteilt – dabei fehlen allerdings noch UnitTests für den “Data” Teil und für die WebApp (die hier noch nicht zu sehen ist). Unter “Common” befinden sich nur ein paar Extensions die mir das leben erleichtern
Mit dieser Ordnung bin ich gerade recht zufrieden – alles ist soweit getrennt und alles was Logik hat, hat auch ein Interface. Späße wie Dependency Injection und co. steht auch nichts im Wege. Durch die Response / Request Sache bin ich später recht flexibel – auch wenn es etwas mehr Schreibaufwand ist.
Das wichtigste ist allerdings:
Wie seht ihr das? Ist die Idee mit den Response/Request Objekten vielleicht doch nicht so toll? Gebt einfach euer Feedback ab – den Code (sobald ich noch etwas weiter bin), werde ich auf Codeplex zur Verfügung stellen. Momentan ist diese Version noch nicht hochgeladen.
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:
“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”.
Schichten im Detail: ThreeTier.Data 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:
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: 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
Im Service haben wir nach dem gleichen Prinzip auch eine Schnittstelle für unseren “UserService” erstellt.
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
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:
Extras: Unit-Tests
Um ein gutes Beispiel zu geben, habe ich sogar 6 Unit-Tests geschrieben. Das Frontend hab ich allerdings nicht getestet
Code-Coverage: 97% (Data + Service)
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.
Rob Conery (Entwickler von SubSonic und nun bei ASP.NET Team beschäftigt) versucht sich nun genau an diesen Dingen und dokumentiert dies – mittlerweile sind so bereits 10 Teile zusammengekommen:
Ich finde diese Videos sehr interessant gemacht – vor allem sieht man, warum welche Architekturentscheidung so oder so getroffen wurde – Rob erklärt ziemlich präzise jeden einzelnen Schritt und alles im TDD Umfeld.
Ich empfehle auch die Kommentare zu lesen, da dort bereits einige Architekturentscheidungen durch diskutiert wurden. Diese Diskussionen sind doch sehr lehrreich – insgesamt finde ich die momentane Architektur, welche Rob gewählt hat, sehr interessant.
Entwufsmuster sind immer ein Thema in der Softwarearchitektur, allerdings gibt es zwar das wunderbare Standardwerk von der Gang of Four, leider ist das dort etwas arg trocken beschrieben und besonders als Nachmittagsliteratur schwer verdaulich.
Wer trotzdem gerne ein Buch über Entwufsmuster haben möchte, welche diese zudem sehr detailiert und mit (Java)-Beispielcode erlernen will, dem empfehle ich das “Entwurfmuster von Kopf bis Fuß” Buch.
Besonders hervorzuheben sind dabei folgende Punkte:
Der Schreibstil ist angenehm locker und auch viele Zeichnungen / Konzeptionen etc. kommen zum Einsatz, dadurch erhöht sich wirklich der Spaß am lesen.
Die wesentlichen Entwurftmuster werden gut und sehr verständlich Schritt für Schritt erklärt. Dabei werden auch Risiken oder “Nebenwirkungen” genannt.
Um mal viele Entwurfsmuster in einem Demoprojekt zu sehen, gibts zum Ende des Buches ein kleines Beispiel mit Enten
Der Beispielcode ist in Java geschrieben, allerdings kann man das auch gut auf .NET / C# mappen, sodass es da zu keinen Schwierigkeiten kommt
Das Buch ist eigentlich für jeden Software Entwickler gut geeignet, da es sehr verständlich und praxisorientiert ist – zudem gefällt mir sehr gut der Schreibstil und die gesamte Machart des Buches .