| Autor |
Beitrag |
InfoStudent86
Hält's aus hier
Beiträge: 11
|
Verfasst: Do 24.12.09 11:34
Hallo,
ich muss über Weihnachten ein .NET Projekt für die Uni schreiben. Leider habe ich noch ein paar Probleme mit einer 3 Schichten Architektur.
zu meinem bisherigen Verständnis:
- jede Schicht kennt Maximal die Informationen der unterliegenden.
0) Schicht (DB - Tabellen)
1) Schicht Datenbank (SQL Zugriff / NHibernate etc) z.B. FilmObj
2) Schicht Logic (greift auf die Schicht der Datenbank zu)
3) Schicht Presentation (soll die Daten später anzeigen und nur auf die Logic Schicht zugreifen keinesfalls auf die Datenbank Schicht)
zu meinen Ideen:
1) Schicht Datenbank Zugriff
Interface
C#-Quelltext 1: 2: 3: 4: 5: 6:
| public interface IMovieObj { string Name { get; set; } IList<MovieObj> GetMovies(); IList<MovieObj> GetMovies(string whereConstraint); } |
Object
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| public class MovieObj : IMovieObj { public string Name {get; set;}
public IList<MovieObj> GetMovies() { List<MovieObj> movieList = new List<MovieObj>(); return movieList; }
public IList<MovieObj> GetMovies(string whereConstraint) { List<MovieObj> movieList = new List<MovieObj>(); return movieList; } } |
2) Schicht Logik
Interface
C#-Quelltext 1: 2: 3: 4: 5: 6:
| public interface IMovie { IList<Movie> GetMoviesByName(string name); IList<Movie> GetMoviesByGenre(string gernre); IList<Movie> GetMoviesByGenre(List<string> gernreList); } |
Objekt
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:
| public class Movie : IMovie { private IMovieObj movieObj = new MovieObj();
public IList<Movie> GetMoviesByName(string name) { string whereConstraint = "mov_base =%" + name + "%";
return this.movieObj.GetMovies(whereConstraint); }
public IList<Movie> GetMoviesByGenre(string gernre) { string whereConstraint = "mov_gernre =%" + gernre + "%";
return this.movieObj.GetMovies(whereConstraint); }
public IList<Movie> GetMoviesByGenre(List<string> gerneList) { if (gerneList.Count != 0) {
string whereConstraint = string.Empty; foreach (string genre in gerneList) { whereConstraint += "mov_gernre =%" + genre + "% AND"; } whereConstraint = whereConstraint.Substring(0, whereConstraint.Length - 3);
return this.movieObj.GetMovies(whereConstraint); } return null; } } |
3) Schicht GUI (Projekt Namens Test als Dummy)
Leider noch nicht Vorhanden  aber diese soll nur auf Schicht 2 Zugreifen
Problem ist die Umwandlung des Objekts der Schicht 1 (MovieObj) in das Objekt der Schicht 2 (Movie). Verfolge ich hier einen grundsätzlich Falschen ansatz?
Ich hoffe ihr könnt mir Dabei helfen, auch wenn Weihnachten vor der Tür steht  ))
Grüße
Johannes
Moderiert von Christian S.: Code- durch C#-Tags ersetztModeriert von Kha: Topic aus WinForms verschoben am Do 24.12.2009 um 13:26
Einloggen, um Attachments anzusehen!
|
|
Florian Reischl
Hält's aus hier
Beiträge: 7
|
Verfasst: Do 24.12.09 13:02
Hallo Johannes
Der gepostete Source Code hat mehrere falsche Ansätze
Man sollte nie ein Objekt als seine eigenen Factory verwenden. Sowohl "Movie" als auch "MovieObj" fungieren in dem Beispiel-Code als Factories, was bedeutet man muss sich eine Instanz des Zieltypen erzeugen um an die eigentlichen Objekte zu kommen. Damit ist das Ausgangsobjekt eine Factory und als Businessobjekt invalide. Eine (unschöne) Ausnahme wären statische Methoden. Nichtsdestotrotz würde ich immer eigenständige Factory-Klassen verwenden.
Movie.GetMoviesByGenre baut eine SQL WHERE-Klausel zusammen. Das hat nichts mit Abstraktion zu tun. Im aktuellen Beispiel sind bereits sehr schön zwei Fehler eingebaut:
""mov_base =%" + name + "%";" erzeugt "mov_base =%AnyGenre%" wobei natürlich die Hochkommas fehlen.
Auch mit korrekten Hochkommas ist der Ansatz 100% offen für SQL-Injection. Beispiel wäre ein Genre-String "'; DROP TABLE XYZ; --".
Entsprechend dem ersten Problem mit den Domain-Objekten und Factories sollten natürlich auch die Domain-Objekt Interfaces keine Factory-Methoden beinhalten.
Zur Schichten-Artitektur
Hier gibt's diverse Ansätze...
DTOs:
Man kann zwischen DAL und BLL mit DTOs (Data Transfer Objects) arbeiten, welche reine Datenobjekte aus der Daten-Schicht an die Business-Schicht weitergeben. In diesem fall muss man im BLL alles nochmal auf die eigentlichen Domain-Objekte ummappen.
Interfaces + Factories:
Man kann in einer Core-Library (unter dem DAL) die Interfaces definieren welche vom DAL zurückgeliefert werden. Bei initialisieren des DAL erhält dieser Factories für die einzelnen Domain-Objekte. Wenn die Domain-Objekte eine allgemein gültige Initialisierung zur Verfügung stellen, kann man hier eine "Abstract Factory" verwenden.
Interfaces und Generic Mapper:
Statt der Factories kann man auch einfach die Data-Mapper generisch definieren. Z.B.:
C#-Quelltext 1: 2: 3:
| class MovieMapper<T> where T : IMovie, new() { ... } |
So können diese mit den Interfaces erzeugen und zurückliefern.
Es gibt noch ein paar andere Ansätze, aber das dürfte erstmal reichen
Grüße
Flo
Moderiert von Christian S.: Code- durch C#-Tags ersetzt
|
|
InfoStudent86 
Hält's aus hier
Beiträge: 11
|
Verfasst: Do 24.12.09 13:21
Vielen Dank Flo,
mit den Hochkommas etc ist mir Klar. Ich komme aus der DB - Ecke bzw. verdiene ich dort neben meinem Studium mein Geld. Ich möchte alles mit Bind Variablen machen. Dann bin ich außen vor. Leider habe ich mich bis her noch nie um eine SW - Architektur selbst gekümmert. Außer von kleinern Programmen. Aber ein Projekt sauber aufzubauen ist doch ganz schön schwer.
Unser Prof sagte nur, wir wollen in jeder Schicht ein Interface und ein Objekt haben. Mehr Ansätze hab eich leider nicht. Kannst du mir villeicht grob einen Ansatz sagen, wie ich die Factories am einfachsten rausbekomme ohne statische variablen zu verwenden.
Wenn ich dich richtig verstanden habe, würde ich aus den Movie und MovieObj ein "reines Datenobjekt" machen.
Die Methoden zum auslesen , speichern etc. des Objektes müsste ich dann "static" machen um eine Factory zu verhinden, was total unschön ist.
-> ziel wäre es das MovieObj zu erhalten, ohne ein dummy objekt mit new erzeugen zu müssen (was ja auch logisch ist, ich will ja kein dummy objekt haben, was mir die objekte an sicht zurückgibt)
wie kann ich am einfachsten das static umgehen? ohne einen Mapper zu schreiben und die zwei interfaces zu haben? muss mich leider an die vorgabe halten
Grüße
Johannes
|
|
Florian Reischl
Hält's aus hier
Beiträge: 7
|
Verfasst: Do 24.12.09 14:30
Hallo Johannes
| Zitat: | | Leider habe ich mich bis her noch nie um eine SW - Architektur selbst gekümmert. Außer von kleinern Programmen. Aber ein Projekt sauber aufzubauen ist doch ganz schön schwer. |
Architektur ist auch kein einfaches Thema. Ich würde sagen, die besteht aus 28% Patterns, 70% Erfahrung und 2% reine Programmierung.
| Zitat: | | ...wir wollen in jeder Schicht ein Interface und ein Objekt haben... |
Naja, ich habe mal ein Beispiel nach den Anforderungen zusammengestellt. Allerdings finde ich persönlich, dass die Interfaces zwar Teil der einzelnen Schichten, nicht jedoch Artefakte sein sollten. Im folgenden Beispiel habe ich das IMovieMapper-Interface und die Mapper-Factory mal in den DAL gepackt, würde ich aber normalerweise in die Core-Library stecken. Grund ist, wenn man die Interfaces in der DAL-DLL definiert macht man de facto den BLL komplett vom DAL abhängig. Wenn ich mal ein anderes RDBMS, ODBMS, XML oder auch Services als DAL verwenden will bekomme ich so Probleme.
| Zitat: | | Wenn ich dich richtig verstanden habe, würde ich aus den Movie und MovieObj ein "reines Datenobjekt" machen. |
Ist ein valider und gangbarer Weg (darum hab ich's erwähnt  ), bin ich selbst jedoch kein Freund davon alles n-mal im Speicher hin und her zu mappen. Zwischen DAL und BLL würde ich versuchen das zu vermeiden. Bei der Kommunikation mit dem PL oder dem Interface eines eventuellen SLs ist das natürlich was anderes.
Hier mal ein stark vereinfachtes Beispiel:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102:
| interface IMovie { string Name { get; set; } } interface IDomainObjectFactory<T> { T Create(); } interface IMovieFactory<T> : IDomainObjectFactory<T> where T : IMovie { T CreateMovie(); }
class MapperFactory { public IMovieMapper<T> GetMovieMapper<T>(IMovieFactory<T> factory) where T : IMovie { return new MovieMapper<T>(factory); } } interface IMovieMapper<T> where T : IMovie { IList<T> GetByGenre(string genre); } class MovieMapper<T> : IMovieMapper<T> where T : IMovie { public MovieMapper(IMovieFactory<T> factory) { _factory = factory; } IMovieFactory<T> _factory; private T CreateMovie(IDataRecord record) { T movie = _factory.CreateMovie(); movie.Name = (string)record["Name"]; return movie; } private SqlConnection GetConnection(bool open) { SqlConnection con = new SqlConnection("Server=foo;Database=bar;Integrated Security=sspi"); if (open) con.Open(); return con; } private IList<T> LoadObjects(string sql, params SqlParameter[] parameters) { IList<T> result = new List<T>(); using (SqlConnection con = GetConnection(true)) using (SqlTransaction tran = con.BeginTransaction()) using (SqlCommand cmd = new SqlCommand("SELECT * FROM Movies ...", con)) { foreach (var p in parameters) { cmd.Parameters.Add(p); } using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { result.Add(CreateMovie(reader)); } } } return result; } public IList<T> GetByGenre(string genre) { SqlParameter p = new SqlParameter("@genre", SqlDbType.VarChar, 30); p.Value = genre; return LoadObjects("SELECT Name FROM ....", p); } }
class Movie : IMovie { public string Name { get; set; } } class MovieFactory : IMovieFactory<Movie> { public MovieFactory() { MapperFactory mapperFactory = new MapperFactory(); _mapper = mapperFactory.GetMovieMapper<Movie>(this); } IMovieMapper<Movie> _mapper; public Movie CreateMovie() { return new Movie(); } public IList<Movie> GetByGenre(string genre) { return _mapper.GetByGenre(genre); } Movie IDomainObjectFactory<Movie>.Create() { return this.CreateMovie(); } } |
Wie gesagt, ich würde das IMovieMapper<T> Interface sowie die MapperFactory in die Core-Library stecken. Die MapperFactory ist hier nur als Pseudo-Abstraktion angedeutet. Die sollte ihre Mapper-Informationen z.B. aus der Config bekommen. Hier ist die MEF und der enthaltene "Unity Application Block" recht praktisch.
Auch der MovieMapper<T> ist natürlich nur angedeutet. Vor allem wenn man unterschiedliche RDBs unterstützen will sollte die Erzeugung der ADO.NET Objekte ebenfalls abstrahiert werden.
Alles in allem sollten die Konzepte zu erkennen sein und eine solide Basis darstellen.
Grüße
Flo
|
|
InfoStudent86 
Hält's aus hier
Beiträge: 11
|
Verfasst: Do 24.12.09 14:54
Vielen Dank
da werde ich mich jetzt Durchkämpfen. Ich werde ersteinmal meine fehlenden .NET Kentnisse beheben. Der Ansatz ist aber total Klasse. Ich überlege ob ich das ganze ohne Generics lösen kann. Da sich noch zusätzlich einzuarbeiten ist in der Zeit zu häftig. Ich hab zu viel Zeit für die Implementierung der DB verbracht
ich denke ich werde IMovie , Movie und MovieMapper in die DAL und die MovieFactory in die BLL legen
Grüße und frohe Weihnachten
Johannes
|
|
Florian Reischl
Hält's aus hier
Beiträge: 7
|
Verfasst: Do 24.12.09 15:00
War vielleicht ein bisschen overdoze mit den ganzen Generics. Ich arbeite halt im sogenannten "Enterprise" (wer kam eigentlich auf diesen Namen???) Bereich.
Habe gerade nochmal geschaut. Die IDomainObjectFactory<T> kannst du komplett rauslassen, die brauchst du nicht. Passiert mir automatisch da ich in jeder Schicht immer einen Suppertype habe.
Movie in den DAL zu legen ist aber komplett falsch (und wird auch keine Punkte bringen). Das ist dein Domain-Objekt und das muss in den BLL. Dafür ja auch die Factory.
Grüße
Flo
|
|
InfoStudent86 
Hält's aus hier
Beiträge: 11
|
Verfasst: Do 24.12.09 15:32
Okay
aber jetzt bin ich total verwirrt. wie kann ich das Movie und Imovie in die BLL legen, wenn ich es im DAL erstelle?
dann würde ich ja vom DAL auf den BLL zugreifen, aber ich darf doch nur vom BLL auf DAL zugreifen.
Ich hab mir in VS2008 drei Projekte angelegt für meine drei Schichten. Mit folgendem Inhalt:
(bin noch nicht so weit wie du in deinem super Beispiel mit DB zugriff etc. )
Projekt DAL
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
| public interface IMovie { string Name { get; set; } }
public class Movie : IMovie { public string Name { get; set; } }
public interface IMovieMapper { IList<Movie> GetMovieByGenre(string genreName); }
public class MovieMapper : IMovieMapper { public IList<Movie> GetMovieByGenre(string genreName) { List<Movie> movieList = new List<Movie>(); return movieList; } } |
Project BLL
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
| public interface IMovieFactory { IList<Movie> GetMoviesByGenre(string genreName); }
public class MovieFactory { private IMovieMapper mapper;
public MovieFactory() { this.mapper = new MovieMapper(); }
public IList<Movie> GetMoviesByGenre(string genreName) { return this.mapper.GetMovieByGenre(genreName); } } |
Grüße
Johannes
Moderiert von Christian S.: Code- durch C#-Tags ersetzt
Zuletzt bearbeitet von InfoStudent86 am Do 24.12.09 15:42, insgesamt 1-mal bearbeitet
|
|
Florian Reischl
Hält's aus hier
Beiträge: 7
|
Verfasst: Do 24.12.09 15:42
| Zitat: | | dann würde ich ja vom DAL auf den BLL zugreifen, aber ich darf doch nur vom BLL auf DAL zugreifen. |
Nope. Dafür ja die Interfaces (und die Core DLL)
Core:
IMovie
IMovieFactory
IMovieMapper
MapperFactory (class)
DAL
MoveMapper : IMovieMapper
BLL
Movie : IMovie
MovieFactory : IMovieFactory
* Der BLL holt sich den Mapper (IMovieMapper) über die MapperFactory aus der Core-DLL. Dabei übergibt der BLL die MovieFactory als IMoveFactory an die Core Library
* Die MapperFactory entscheidet (z.B. Anhand der Config) welcher Mapper zu initialisieren ist und übergibt diesem Mapper die IMovieFactory
* Der MoveMapper benutzt die IMoveFactory um neue Instanzen von IMove zu erzeugen und füllt diese mit den Daten.
Damit weiß der DAL nichts von der implementierten Movie Klasse, der BLL nichts von der implementierten MovieMapper Klasse und die Core Library hat eh keine Ahnung
Schöne Weihnachten
Flo
PS: Bin jetzt raus, weil Weihnachten. Schau vielleicht morgen wieder rein.
|
|
InfoStudent86 
Hält's aus hier
Beiträge: 11
|
Verfasst: Do 24.12.09 15:45
Cool Danke,
hast mir sehr geholfen. Wenn du nochmal die Tage Zeit findest, kannst du mir das mit dem Core nochmal erklären. Ist das nicht bisschen geschummelt das alles auszulagern?
|
|
Florian Reischl
Hält's aus hier
Beiträge: 7
|
Verfasst: Fr 25.12.09 11:55
Hallo Johannes
Warum sollte das geschummelt sein?
Grüße
Flo
|
|
InfoStudent86 
Hält's aus hier
Beiträge: 11
|
Verfasst: Sa 26.12.09 10:20
Hy Flo,
ersteinmal vielen Dank, ich habe es so umgesetzt und bin jetzt an den GUI Schönheiten.
Hatte zwar nicht viel von Weihnachten aber ich hab es geschafft.
Ich bin immer davon ausgegangen, das ich bei drei Schichten auch nur drei Projekte habe. Jetzt verwende ich aber zusätzlich noch einen Core. Der Core wird in jeder Schicht Referenziert das wundert mich ein wenig. Deswegen kam ich auf die Idee das dies "Geschummelt" sei.
Auf jedem Fall bin ich sehr zufireden mit meiner Architektur. Gerade weil ich jetzt einfach die Herkunft meiner Daten ändern kann. Wenn ich die Filmverwaltung zu Hause nutzen möchte werde ich sicher keine Oracle DB dafür verwenden. Eine kleine Access DB würde da schon reichen.
Ich überlege schon die ganze Zeit mich mit Silverlight zu beschäftigen. Theortisch müsste ich nur eine weitere Schicht für den Webservice erstelln, der mir die Daten überträgt und den PresentationsLayer austauschen?
Viele Grüße
Johannes
|
|
Florian Reischl
Hält's aus hier
Beiträge: 7
|
Verfasst: Sa 26.12.09 12:11
Hi Johannes
Freut mich zu hören dass jetzt alles funktioniert.
Zu der zusätzlichen Schicht:
Man kann sich die zusätzliche Schicht schon sparen. Dazu verwendet man dann DTOs (oder generische Objekte wie DataTables) welche vom DAL an den BLL übergeben werden. Nachteile sind hier zum einen dass alles im Speicher einmal mehr komplett umgemappt werden muss und schlicht wesentlich mehr Source-Code entsteht. In diesem Fall muss dann auch der BLL jedoch direkt mit dem DAL gekoppelt werden und dadurch wird die Kohäsion eigentlich Schall und Rauch. Der DAL ist dann zwar unabhängig vom BLL, der BLL unterstützt jedoch nur schwer andere Datenquellen. Wie du schon richtig erkannt hast kannst du jetzt ohne weiteres die Datenquelle beliebig austauschen. Wenn du willst kannst du sogar weg von Datenbanken und ein XML File oder einen Web-Service verwenden. Der Ansatz mit der zusätzlichen Core-Library ist relativ neu findet aber immer mehr Zuspruch.
Ein anderer Weg - um sich die zusätzliche Schicht zu sparen - klingt im ersten Moment noch ein bisschen abstruser. Hier vertauscht man die Reihenfolge von BLL und DAL. Im BLL werden zusätzlich zu dein Domain-Objekten Interfaces für die Data-Mapper definiert. Der DAL referenziert nun den BLL - nicht der BLL den DAL - und implementiert die Interfaces. Über eine Config und DI (Dependency Injection) initialisiert der BLL zur Laufzeit die Implementierten Interfaces und arbeitet mit denen. Klingt im ersten Moment etwas befremdlich, funktioniert aber einwandfrei  .
Lange Rede kurzer Sinn. Hauptsache es funktioniert und du hast alles entkoppelt (sogar deutlich mehr als viele andere Ansätze). Vielleicht postest du ja ein kurzes Feedback wenn's dein Prof gesehen hat  .
Grüße
Flo
|
|
InfoStudent86 
Hält's aus hier
Beiträge: 11
|
Verfasst: Sa 26.12.09 16:47
Hi Floh,
ich werde dir natürlich ein Feedback geben. Ich denke es fällt gut aus. Alles was villeicht im .NET falsch ist haut bei mir die DB raus. Das ist und wird immer mein Schwerpunkt bleiben.
Ich hoffe ich werde im neuem Jahr ein bisschen Zeit finden um die Architektur so zu erweitern, das eine DB - Anbindung ,XML sowie Webservice verwendet werden können. Wenn dies erfogreich ist werde ich das ganze hier Dokumentiert posten.
Ich bin der Meinung, das viele zwar "gut" Programmieren können, aber die SW - Architektur lässt doch zu wünschen übrig. Zu mindest habe ich diese Erfahrungen in der UNI aber auch im BETRIEB als Werksstudent gemacht.
So kann ich die vielen Informationen die ich von dir bekommen habe an andere weitergeben. Villeicht erklärt du sich ja auch bereit am Ende nocheinmal über den Code zu schauen
Wichtig wäre mir dabei eine Architektur die
- als GUI - Anwendung
- als WEB - Anwendung zu verwenden wäre.
Dabei sollten der DAL und der BLL identisch sein. Die Presentation Schicht Wäre einmal die GUI und die Web - Anwendung. Evtl. benötigt man die Zwischenschicht fürs WEB, auch wenn dies mehr Code Aufwand wäre.
Die Herkunft der Daten sollte per Config bestimmt werden können, XML, Oracle, MySQL, Webservice ...
Grüße
Johannes
|
|