Ist es eine schlechte Praxis, dass der Controller das Repository anstelle des Dienstes aufruft?

43

Ist es eine schlechte Praxis, dass der Controller das Repository anstelle des Dienstes aufruft?

um mehr zu erklären:

Ich finde heraus, dass Controller in gutem Design den Service aufrufen und das Service Use Repository verwenden.

aber manchmal in controller habe / brauche ich keine logik und muss nur von db holen und an view übergeben.

und ich kann es tun, indem ich einfach das Repository anrufe - keine Notwendigkeit, den Service anzurufen - ist es eine schlechte Praxis?

mohsenJsh
quelle
Wie rufst du den Service an? Über eine REST-Schnittstelle?
Robert Harvey
2
Ich benutze diesen Designansatz ziemlich häufig. Mein Controller (oder eine zugrunde liegende Komponistenklasse) fordert Daten von dem Repo an oder sendet diese an das Repo und übergibt sie dann an alle Serviceklassen, die eine Verarbeitung durchführen müssen. Es gibt keinen Grund, Datenverarbeitungsklassen mit Datenabruf- / Verwaltungsklassen zu kombinieren, aber ich weiß, dass der typische Ansatz darin besteht, dies auf diese Weise zu tun.
Jimmy Hoffa
3
Meh. Wenn es sich um eine kleine Anwendung handelt und Sie nur versuchen, Daten aus einer Datenbank abzurufen, ist eine Serviceebene Zeitverschwendung, es sei denn , diese Serviceebene ist Teil einer öffentlichen API, z. B. einer REST-Schnittstelle. "Ist Milch gut für dich oder schlecht für dich?" Kommt darauf an, ob du laktoseintolerant bist.
Robert Harvey
4
Es gibt keine feste Regel, dass Sie einen Controller -> Service -> Repository-Struktur über Controller -> Repository haben sollten. Wählen Sie das richtige Muster für die richtige Anwendung. Was ich sagen würde, ist, dass Sie Ihre Bewerbung konsistent machen sollten.
NikolaiDante
Vielleicht könnten Sie einen generischen Service entwickeln, der Ihre Anfrage nur an das Repository weiterleitet und dann zurückgibt. Dies könnte nützlich sein, um eine einheitliche Schnittstelle zu erhalten, und wäre einfach, wenn Sie in Zukunft einen echten Service hinzufügen müssen, um etwas zu tun, bevor Sie das Repository aufrufen.
Henrique Barcelos

Antworten:

31

Nein, stellen Sie es sich so vor: Ein Repository ist (auch) ein Service.

Wenn die Entitäten, die Sie über das Repository abrufen, den größten Teil der Geschäftslogik abwickeln, sind keine weiteren Services erforderlich. Nur das Repository zu haben ist genug.

Auch wenn Sie einige Services haben, die Sie durchlaufen müssen, um Ihre Entitäten zu manipulieren. Holen Sie sich die Entität zuerst aus dem Repository und übergeben Sie sie dann an diesen Service. Es ist sehr praktisch, ein HTTP 404 zu werfen, bevor man es überhaupt versucht.

Auch für Leseszenarien ist es üblich, dass Sie die Entität nur für die Projektion auf ein DTO / ViewModel benötigen. Eine dazwischen liegende Service-Schicht führt dann oft zu einer Menge hässlicher Pass-Through-Methoden.

Joppe
quelle
2
Gut gesagt! Mein Wunsch ist es, Repositorys aufzurufen, und nur in Fällen, in denen ein Repository nicht ausreicht (dh zwei Entitäten müssen mit unterschiedlichen Repositorys geändert werden), erstelle ich einen Dienst, der für diesen Vorgang verantwortlich ist, und rufe ihn vom Controller aus auf.
Zygimantas
Ich hatte einen ziemlich komplizierten Code entdeckt, um die Nutzung eines Dienstes zu rechtfertigen. Absurd, am wenigsten ...
Gi1ber7
Mein Repository gibt also eine Liste von 'Geschäftsobjekten' zurück, die ich in 'XML-Objekte' konvertieren muss. Reicht das aus, um eine Service-Schicht zu haben? Ich rufe für jedes Objekt eine Methode auf, um es in einen anderen Typ zu konvertieren und einer neuen Liste hinzuzufügen.
bot_bot
Direkter DAO-Zugriff ist in Controllern gefährlich, er kann Sie für SQL-Injections anfällig machen und ermöglicht den Zugriff auf gefährliche Aktionen wie "deleteAll". Ich würde dies definitiv vermeiden.
Anirudh
4

Es ist keine schlechte Praxis für einen Controller, ein Repository direkt aufzurufen. Ein "Service" ist nur ein weiteres Tool. Verwenden Sie es also dort, wo es Sinn macht.

NikolaiDante kommentierte:

... Wählen Sie das richtige Muster für die richtige Anwendung. Was ich sagen würde, ist, dass Sie Ihre Bewerbung konsistent machen sollten.

Ich denke nicht, dass Beständigkeit der wichtigste Aspekt ist. Eine "Service" -Klasse soll eine Logik höherer Ebene kapseln, damit der Controller sie nicht implementieren muss. Wenn für eine bestimmte Operation keine "übergeordnete Logik" erforderlich ist, gehen Sie direkt zum Repository.

Um eine gute Trennung von Bedenken und Testbarkeit zu fördern, sollte das Repository eine Abhängigkeit sein, die Sie über einen Konstruktor in den Service einfügen:

IFooRepository repository = new FooRepository();
FooService service = new FooService(repository);

service.DoSomething(...);

Wenn die Suche nach Datensätzen in der Datenbank eine Art parametrisierte Abfrage erfordert, ist eine Serviceklasse möglicherweise ein guter Ort, um Ihr Ansichtsmodell einzusehen und eine Abfrage zu erstellen, die dann vom Repository ausgeführt wird.

Wenn Sie über ein komplexes Ansichtsmodell für ein Formular verfügen, kann eine Serviceklasse die Logik des Erstellens, Aktualisierens und Löschens von Datensätzen einschließen, indem Methoden für Ihre Domänenmodelle / -entitäten aufgerufen und diese anschließend mithilfe eines Repositorys beibehalten werden.

In die entgegengesetzte Richtung gehen: Wenn Ihr Controller einen Datensatz anhand seiner ID abrufen muss, ist das Delegieren an ein Dienstobjekt wie das Schlagen eines Reißzwecks mit einem Vorschlaghammer - es ist weit mehr als erforderlich.

Ich habe festgestellt, dass der Controller in der besten Position ist, um die Transaktion oder ein Unit Of Work-Objekt abzuwickeln . Der Controller oder das Unit Of Work-Objekt wird dann für komplexe Vorgänge an Serviceobjekte delegiert oder für einfache Vorgänge (z. B. das Suchen eines Datensatzes anhand der ID) direkt an das Repository gesendet.

public class ShoppingCartsController : Controller
{
    [HttpPost]
    public ActionResult Edit(int id, ShoppingCartForm model)
    {
        // Controller initiates a database session and transaction
        using (IStoreContext store = new StoreContext())
        {
            // Controller goes directly to a repository to find a record by Id
            ShoppingCart cart = store.ShoppingCarts.Find(id);

            // Controller creates the service, and passes the repository and/or
            // the current transaction
            ShoppingCartService service = new ShoppingCartService(store.ShoppingCarts);

            if (cart == null)
                return HttpNotFound();

            if (ModelState.IsValid)
            {
                // Controller delegates to a service object to manipulate the
                // Domain Model (ShoppingCart)
                service.UpdateShoppingCart(model, cart);

                // Controller decides to commit changes
                store.SaveChanges();

                return RedirectToAction("Index", "Home");
            }
            else
            {
                return View(model);
            }
        }
    }
}

Ich halte eine Mischung aus Services und direkter Arbeit mit Repositories für absolut akzeptabel. Sie können die Transaktion auch in ein Unit Of Work-Objekt einkapseln, wenn Sie dies für erforderlich halten.

Die Aufteilung der Zuständigkeiten sieht folgendermaßen aus:

  • Die Steuerung steuert den Fluss der Anwendung
    • Gibt "404 Not Found" zurück, wenn sich der Warenkorb nicht in der Datenbank befindet
    • Rendert das Formular mit Überprüfungsmeldungen erneut, wenn die Überprüfung fehlschlägt
    • Speichert den Warenkorb, wenn alles ausgecheckt ist
  • Der Controller delegiert an eine Serviceklasse, um Geschäftslogik auf Ihren Domänenmodellen (oder Entitäten) auszuführen. Serviceobjekte sollten keine Geschäftslogik implementieren ! Sie führen Geschäftslogik aus.
  • Controller können für einfache Vorgänge direkt an Repositories delegieren
  • Serviceobjekte nehmen Daten in das Ansichtsmodell auf und delegieren sie an Domänenmodelle, um die Geschäftslogik auszuführen (z. B. ruft das Serviceobjekt Methoden in den Domänenmodellen auf, bevor Methoden im Repository aufgerufen werden).
  • Serviceobjekte werden für die Datenpersistenz an Repositorys delegiert
  • Controller sollten entweder:
    1. Verwalten Sie die Lebensdauer einer Transaktion oder
    2. Erstellen Sie ein Unit Of Work-Objekt, um die Lebensdauer einer Transaktion zu verwalten
Greg Burghardt
quelle
1
-1 für das Einfügen von DbContext in einen Controller anstelle eines Repos. Das Repo ist für die Verwaltung von Datenanbietern gedacht, damit niemand anderes für den Fall, dass sich ein Datenanbieter ändert (von MySQL auf flache JSON-Dateien, z. B. an einem Ort)
Jimmy Hoffa
@JimmyHoffa: Ich schaue tatsächlich auf den Code zurück, den ich geschrieben habe, und um ehrlich zu sein, erstelle ich ein "Kontext" -Objekt für meine Repositorys, ohne die Datenbank zu benötigen. Ich denke, das DbContextist in diesem Fall ein schlechter Name. Ich werde das ändern. Ich verwende NHibernate, und die Repositorys (oder der Kontext, wenn es praktisch ist) verwalten das Datenbankende der Dinge, sodass das Ändern von Persistenzmechanismen keine Codeänderungen außerhalb des Kontexts erfordert.
Greg Burghardt
Sie scheinen den Controller durch das Aussehen Ihres Codes mit dem Repository zu verwechseln ... Ich meine, Ihr "Kontext" ist völlig falsch und sollte auf keinen Fall im Controller vorhanden sein.
Jimmy Hoffa
6
Ich muss nicht antworten und bin mir nicht sicher, ob dies eine gute Frage ist, aber ich stimme ab, weil ich denke, dass Ihr Ansatz schlechtes Design ist. Keine harten Gefühle, ich entmutige nur Kontexte, die Kontrolleuren gehören. IMO sollte ein Controller solche Transaktionen nicht starten und ausführen. Das ist die Aufgabe vieler anderer Stellen. Ich bevorzuge es, dass Controller alles, was die HTTP-Anforderung nicht einfach erfüllt, an einen anderen Ort delegieren.
Jimmy Hoffa
1
Als Repository ist es normalerweise für alle Datenkontextinformationen verantwortlich, um sicherzustellen, dass nichts anderes über Datenprobleme informiert werden muss, als über das, was die Domain selbst wissen muss
Jimmy Hoffa
1

Das hängt von Ihrer Architektur ab. Ich benutze Spring und die Transaktionsverwaltung erfolgt immer über Dienste.

Wenn Sie Repositorys direkt für Schreibvorgänge aufrufen (oder einfache Dienste ohne Logik, die einfach an das Repository delegieren), verwenden Sie wahrscheinlich mehrere Datenbanktransaktionen für einen Vorgang, der in einem Vorgang ausgeführt werden muss. Dies führt zu inkohärenten Daten in Ihrer Datenbank. In der Regel sollten Datenbankvorgänge funktionieren oder fehlschlagen, aber halbfertige Vorgänge verursachen Kopfschmerzen.

Aus diesem Grund halte ich das Aufrufen von Repositorys direkt von Controllern oder die Verwendung einfacher Delegierungsdienste für eine schlechte Praxis. Sie fangen an, es nur zum Lesen zu tun, und sehr bald werden Sie, einer oder Ihre Freunde, damit beginnen, es für Schreiboperationen zu tun.

Rober2D2
quelle