Teil 2: Repository-Pattern - Kapselung der Datenschicht
Das Repository-Pattern ermöglicht es, die Datenschicht zu abstrahieren, wodurch eine generische Vorlage für sämtliche Datenquellen innerhalb eines Projekts implementiert werden kann. Insbesondere im Zeitalter von Microservices-Architekturen und Verbindlichkeiten zu Third-Party-Datenquellen wie SAP oder SharePoint gewinnt das Repository-Pattern zunehmend an Relevanz.
Zu Beginn der Artikelserie haben wir bereits ausgiebig betrachtet, wofür Designpatterns gut sind, was hinter dem Akronym SOLID steckt und welche Auswirkung SOLID auf den Code hat. Der Fokus des letzten Artikels lag auf Dependency Injection, womit Abhängigkeiten zwischen Klassen aufgelöst werden können. Im einem zweiten Schritt erweitern wir unsere Softwarearchitektur um das Repository-Pattern.
In nahezu jedem Projekt ist eine Verbindung zu mindestens einer Datenbank Voraussetzung. Sei es, um durch die Applikation bedingte Einstellungen zu dynamisieren, Benutzer und Rollen zu verwalten oder gar, um komplexe Relationen zwischen Daten in der Applikation abzubilden. Im Grunde ist das auch schnell geschehen: Innerhalb weniger Minuten ist ein Connection String zusammengestellt, der app.config bzw. web.config hinzugefügt, und schon lässt sich der Datenbankkontext in der Applikation konsumieren. Genauso schnell wie die Datenbank in die Applikation eingebunden werden kann, lassen sich binnen weniger Zeilen Code Transaktionen mit der Datenbank ausführen. Für folgende Beispiele verwende ich Entity Framework als ORM (Object-relational Mapping) und demonstriere in Listing 1, wie sich ein Objekt mittels Entity Framework in die Datenbank persistieren lässt. Eine adäquate Transaktion ließe sich auch mit Listing 2 durchführen.
Listing 1: Persistieren eines Objekts in die Datenbank
public class PlacesController : Controller
{
private ExampleDatabase db = new ExampleDatabase();
public async Task<ActionResult> Create([Bind(Include = "Id,Title,Location,Created,Modified,CreatedBy,ModifiedBy")] Place place)
{
if (ModelState.IsValid)
{
place.Id = Guid.NewGuid();
db.Places.Add(place);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(place);
}
}
Listing 2: Alternative zu Listing 1
public class PlacesController : Controller
{
private ExampleDatabase db = new ExampleDatabase();
public async Task<ActionResult> Create([Bind(Include = "Id,Title,Location,Created,Modified,CreatedBy,ModifiedBy")] Place place)
{
if (ModelState.IsValid)
{
place.Id = Guid.NewGuid();
db.Entry(blog).State = EntityState.Added;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(place);
}
}
In einem Projekt mit mehreren Entwicklern gäbe es also für das Hinzufügen neuer Objekte in die Datenbank zwei plausible Lösungen: Nur dort, wo hinzugefügt wird, muss standardmäßig auch noch gelöscht, geändert und selektiert bzw. abgefragt werden. Bei der Fülle von Methoden und plausiblen Konstellationen für das Durchführen von Transaktionen ist es nur eine Frage der Zeit, bis der Code Redundanzen, alternative Lösungswege und alles in allem eine intransparente und nur schwer wartbare Struktur aufweist. Je höher die Anzahl von beteiligten Entwicklern, umso kürzer ist erfahrungsgemäß die Zeitspanne, bis das Durcheinander im Code Überhand gewinnt. Eine große Schwierigkeit liegt insbesondere auch darin, neue Entwickler...