Laden...

Trennung der Schichten zur Businesslogik (Services) und Daten (Repository Pattern) ASP.net MVC5

Erstellt von Gongo82 vor 9 Jahren Letzter Beitrag vor 9 Jahren 5.889 Views
G
Gongo82 Themenstarter:in
41 Beiträge seit 2014
vor 9 Jahren
Trennung der Schichten zur Businesslogik (Services) und Daten (Repository Pattern) ASP.net MVC5

Ich bin der Empfehlung von @Abt nachgegangen und habe mein bestehendes Projekt in einzelne Layer mittels der Verwendung von Libraries aufgeteilt d.h ich habe nun in meiner Solution 4 Projekte und wollte euch Fragen ob das so richtig ist.

Folgende Schichten habe ich angelegt:1. DAL-> führ nur Transaktionen mit der DB durch (holt Daten oder schreibt sie in die DB

  1. BLL-> soll die Logik beinhalten
  2. BOL-> hier sind meine Business Objects (Ado.net)
  3. UI-> ist zuständig für die Interaktion der GUI

In der UI habe ich die dll´s von BOL und BLL hinzugefügt.
Der BLL(Business Logic Layer) hat die dll von BOL und DAL(Data Access Layer).

In meinem DAL habe ich das Repository Pattern implementiert. Eine gute Beschreibung habe ich hier Repository-Pattern-and-Unit-of-Work gefunden.

Mir ist während der Änderung meines bestehenden Projektes aufgefallen, das ich in meinem BLL ziemlich oft das selbe machen muss.

Beispiel:

Ich musste meine UI mit den ganzen Controllern etwas abändern. Wenn eine Action in einem Controller angestoßen wird, übergebe ich die Parameter (Model->BOL) an meinen BLL. Der BLL nimmt die Werte entgegen und ruft den DAL auf und der DAL holt oder schreibt Daten in die DB.

Die Funktionen in meinem BLL welche für einen bestimmten Typ (SeminarTyp) zuständig ist enthält fast die gleichen Funktionen wie die in meinem BaseRepository<T> : IBaseRepository<T>where T : class. Das ist nun ziemlich viel Arbeit das alles abzuändern. Wenn ich z.b. 20 Controller habe muss ich 20 Klassen in meinem BLL anlegen und diese haben dann wiederum die gleiche Funktionen wie die BaseRepository<T> : IBaseRepository<T>where T : class. Ich hoffe ihr versteht was ich zu erklären versuche...

Zum besseren Verständnis:


        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include="id,Seminarbezeichnung,Preis")]    tbl_Seminar_Typ tbl_seminar_typ)
        {
            if (ModelState.IsValid)
            {
                seminarLogic.create(tbl_seminar_typ);
                return RedirectToAction("Index");
            }

            return View(tbl_seminar_typ);
        }

BLL besitzt die Funktion:



private IUnitOfWork uow = null;
private SeminarTypRepository repo = null;

public SeminarTypLogic()
        {

            uow = new UnitOfWork();
            repo = new SeminarTypRepository(uow);
        }

public void create(tbl_Seminar_Typ semTyp)
        {
            repo.Insert(semTyp);
        }

repo.Insert ruft dann im DAL folgendes auf:



public void Insert(T entity)
        {
            dynamic obj = dbSet.Add(entity);
            this._unitOfWork.Db.SaveChanges();
        }

Das ganz funktioniert auch ohne Probleme. Create, Read, Delete läuft bestens nur das Updaten funkioniert nicht...

Fehlt da was?


 public class BaseRepository<T> : IBaseRepository<T>
        where T : class
    {
        private readonly IUnitOfWork _unitOfWork;
        internal DbSet<T> dbSet;

 public IUnitOfWork UnitOfWork { get { return _unitOfWork; } }
 internal DbContext Database { get { return _unitOfWork.Db; } }

public void Update(T entity)
        {
            dbSet.Attach(entity);
            this._unitOfWork.Db.SaveChanges();
        }
}

16.835 Beiträge seit 2008
vor 9 Jahren

Ja passt soweit.
Fehlen nur noch so Kleinigkeiten wie Dependency Injection aber das ist jetzt mal nicht schlimm.

Ich würde den DatenbankContext bzw. den UoW Container in der Anwendung erstellen und diesem dann den Service mitgeben.

So kannst Du einen Container in mehreren Servicen nutzen; die Services dürfen sich nämlich untereinander kennen.

Save würde ich als eigene Methode im Repository machen.
Zudem sollte ein generisches Repository den Typ zurück geben, mit dem man arbeitet, und nicht "dynamic". Genauer gesagt eben T.


Es ist wahrlich so, dass sich die Service- und Repository-Methoden ähneln können und auch meist werden; ist bei mir genau so.
Ein Service ist aber dazu da zB Aufgaben zu bünden.

Sprich UserService.LockUser() sperrt eben den User nicht nur über UserRepository.Lock() sondern schickt eben auch noch zB eine Mail raus EmailService.SendUserLockMail().
Dafür ist die Business-Schicht eigentlich da.

Es ist vielleicht ein wenig mehr initiale Arbeit; dafür sparst Du dir unfassbar viel Arbeit bei zukünftigen Anpassungen: Du musst es eben nur noch an einer Stelle tun!

Wenn Du Dich geduldest: ich schreib seit ner Woche an einem Blogartikel über ASP.NET MVC Architektur + Beispielcode und Best Practises nach meiner Erfahrung.
Dauer aber noch bis zum WE.

2.207 Beiträge seit 2011
vor 9 Jahren

Hallo Gongo82,

das sieht gut aus.

  1. Schreibe Methodennamen immer gross.
  2. Schreibe private Variablen immer mit "_" als Prefix.

Wenn du DI benutzt, kannst du beispielsweise Ninject megaleicht mit Nuget installieren. Kostet nichts (Weder Geld noch Aufwand 😉 ). Beherze die Tipps von Abt und ich denke das kann gut kommen 😉.

Zu dem Update: Ja, was du machst ist ein "Attach". Dann ein SaveChanges. Du Attachst und speiherst danach. Aber woher soll er wissen, dass sich was geändert hat?


 public virtual void Update(T toUpdate)
        {
            _dataBaseContext.Entry(toUpdate).State = EntityState.Modified;
            _dataBaseContext.SaveChanges();
        }

sollte gehen 😉

SaveChanges kann, wie Abt gesagt hat, auch in eine eigene Methode.

Achte beim UoW noch auf die richtige Nutzung des Dispose bzw. Usings.

Gruss

Coffeebean

G
Gongo82 Themenstarter:in
41 Beiträge seit 2014
vor 9 Jahren

Gerne werde ich deinen Beitrag zu Architekturen lesen wollen...bin gespannt.

Warum kann ich kein Update durchführen?
Create, Read, Delete funktionieren aber update leider nicht...

G
Gongo82 Themenstarter:in
41 Beiträge seit 2014
vor 9 Jahren

Hallo Coffeebean,

danke für deinen Beitrag...werde ich gleich mal ausprobieren...
Uns wurde beigebracht Methoden klein zu schreiben 😉 aber wenn das so üblich ist, werde ich deinen Rat befolgen 😃

16.835 Beiträge seit 2008
vor 9 Jahren

Das ist in Java so, aber nicht in .NET.
Codekonventionen für C#

106 Beiträge seit 2011
vor 9 Jahren

Hallo Gongo82,

Warum dein Update nicht funktioniert, kann ich Dir leider nicht sagen, da ich keine Erfahrung mit dem Persister habe den du nutzt. Aber ich kann dir ein paar allgemeine Tipps geben.

Dein DAL sieht schonmal ganz gut aus. Allerdings nutzt man hier keine BOs zum speichern sondern DAOs(Data Access Object). Dies wird dann normalerweise als Domain bezeichnet.

BLL sieht soweit auch gut aus, aber der BLL holt sich aus dem DAL die DAOs und wandelt diese bei bedarf in Business Objects um. Das kommt daher das bestimmte Beziehungen zwischen den DAOs sehr kompliziert sein können. Was wiederum sehr auf die Perfomance gehen bzw. Superfluous Update erzeugen kann. Desweiteren sollten DAOs nur die Daten und keinerlei Logik enthalten.
Um die Logik kümmert sich dann die BOs bzw. der BLL.
Das heisst jetzt nicht das du bei jeder kleinen Aktion im BLL alle DAOs in BOs umwandeln sollst, sondern nur bei komplizierten Vorgängen, dies musst du aber von Fall zu Fall selber entscheiden.

Die UI kann je nach Bedarf auf den DAL bzw. auf den BLL zugreifen um sich die Daten zu holen die man anzeigen will. Hierbei ist aber auch wieder drauf zu achten das keine BOs oder DAOs an die View geschickt werden, das kann sehr unerwartete Fehler erzeugen die man vermeiden kann, indem man ViewModel nutzt. Dafür steht übrigens auch das "M" in MVC^^

Um die die Arbeit mit den ViewModeln bzw. den BO zu erleichtern, kannst du AutoMapper nutzen, das nimmt dir dabei sehr viel langweilige Arbeit ab. Einfach mal Google danach fragen.

Falls du den DAL komplett von deinen Controllern fernhalten willst, so kannst du auch für deine Logik Interfaces und eine StandardLogik nutzen, so wie du es mit dem BaseRepository machst.

Als weiteres würde ich dir noch IOC (Inversion of Control) ans Herz legen z.B. CastleWindsor. Das Ding ist regelrecht gemacht für MVC Anwendungen.

Und als letzten Hinweis: Bitte überdenke dringend die Benennung deiner Klassen.
"tbl_Seminar_Typ" Widerspricht fast jeder Konvention in Sachen Benennung 😄
Benenne die Klassen nach ihrer Funktion und ohne Unterstriche. Wenn du dich an das hälst was ich oben geschrieben habe, dann wäre "SeminarEditModel" eine passende Bezeichnung.

Viel Erfolg
Rabban

G
Gongo82 Themenstarter:in
41 Beiträge seit 2014
vor 9 Jahren

@Abt wir haben nur mit Java gearbeitet 😃 deswegen dachte ich, das es in C# auch so ist...

2.207 Beiträge seit 2011
vor 9 Jahren

Hallo Gongo82,

was ich noch gerne mache: Ich klemme zwischen die BL und meine Controller im MVC noch Services. Nenne den Namespace "AreaServices" wenn ich mit Areas arbeite, oder "ControllerServices" wenn ich nicht mit Areas arbeite.

Diese nehmen meine Models entgegen und machen all den Krust, den ich sonst im Controller machen würde. Dadurch wird der Code im Controller schlanker und das Try - Catch im Controller wird übersichtlicher.

Die Arbeit macht der Controller- oder AreaService.

Der Unterschied zu den Services und BL-Services besteht somit in Folgendem:

BL-Services sind Services, die absolut nichts mit der UI zu tun haben. Sie machen zB. Sachen wie PDF erstellen oder eine Email rumschicken. Sachen, die Abseits der UI laufen. Area oder ControllerServices machen mehr in Richtung UI. Hätte ich ein Extra Projekt, würden diese aber beide in einem Projekt landen. Jedoch unterschiedliche Namespaces.

Weiter kannst du somit ein "AddUser" beispielsweise sehr gut testen. Erstens mit "AssertWasCalled" kannst du schauen, ob alles aufgerufen wurde, was du magst (Email, DB Insert etc.). Weiter lässt sich der Input von Area- oder ControllerServices leicht produzieren und die Methode gut aufrufen. Es ist eben noch eine Stufe abstrahierter. Somit testest du deine Controller auf ein ViewResult und eventuelle Fehlermeldungen, deine "ArbeitsMethoden" nur auf die explizite Arbeit. Dein COde wird übersichtlicher, die Arbeiten sind schön getrennt (Single-Responsibility, etc.). Durch die Tests hast du auch eine schöne Dokumentation und kannst sehr granular feststellen, wo etwas knallt in deinem Code.

Gruss

Coffeebean

5.658 Beiträge seit 2006
vor 9 Jahren

Hi Gongo82,

@Abt wir haben nur mit Java gearbeitet 😃 deswegen dachte ich, das es in C# auch so ist...

Microsoft hat da andere Konventionen, hier findest du eine Übersicht: Naming Guidelines und ganz allgemein: Codekonventionen für C# .

Christian

Weeks of programming can save you hours of planning

G
Gongo82 Themenstarter:in
41 Beiträge seit 2014
vor 9 Jahren

Hallo Coffeebean,

das mit dem Update läuft jetzt wie geschmiert 😉 Danke dafür...
Ja ich arbeite auch mit Areas da ich verschiedene Benutzertypen und Berechtigungen habe...

Danke für den Hinweis...

Tolles Forum...weiter so...

Muss ich glatt in meiner Thesis erwähnen 😃

Gruß

Gongo82

742 Beiträge seit 2005
vor 9 Jahren

Zum Thema Architektur kann ich noch zwei gute Blogs empfehlen:

  1. http://ayende.com/blog/
  2. http://codebetter.com/gregyoung

Allgemein würde ich mich an ein paar grundlegende Prinzipien halten, der Rest ergibt sich dann schon selbst:

  1. KISS: Favorisiere einfache Lösungen
  2. SoC: Separation of Concerns: Fasse keine verschiedenen Belange in einer Einheit (z.B. Klasse zusammen)
  3. DRY: Wiederhole dich nicht
  4. Schreibe testbaren Code (passiert durch SoC fasst automatisch)
  5. Bleibe nahe an der Domäne: Abstrahiere nicht zu sehr, sondern verwende nahe Begriffe und Modelle, die zur Domäne passen.
  6. Besinne dich abundzu auf Objektorientiertes Design zurück (Stichwort: Business Objects).