Unterschied zwischen Repository und Service Layer?

188

Was ist in OOP-Entwurfsmustern der Unterschied zwischen dem Repository-Muster und einer Service-Schicht?

Ich arbeite an einer ASP.NET MVC 3-App und versuche, diese Entwurfsmuster zu verstehen, aber mein Gehirn versteht sie einfach nicht ... noch nicht !!

Sam
quelle

Antworten:

327

Repository Layer bietet Ihnen zusätzliche Abstraktionsebene über den Datenzugriff. Anstatt zu schreiben

var context = new DatabaseContext();
return CreateObjectQuery<Type>().Where(t => t.ID == param).First();

Um ein einzelnes Element aus der Datenbank abzurufen, verwenden Sie die Repository-Schnittstelle

public interface IRepository<T>
{
    IQueryable<T> List();
    bool Create(T item);
    bool Delete(int id);
    T Get(int id);
    bool SaveChanges();
}

und anrufen Get(id). Die Repository-Schicht macht grundlegende CRUD- Operationen verfügbar .

Die Serviceschicht macht die Geschäftslogik verfügbar, die das Repository verwendet. Ein Beispieldienst könnte folgendermaßen aussehen:

public interface IUserService
{
    User GetByUserName(string userName);
    string GetUserNameByEmail(string email);
    bool EditBasicUserData(User user);
    User GetUserByID(int id);
    bool DeleteUser(int id);
    IQueryable<User> ListUsers();
    bool ChangePassword(string userName, string newPassword);
    bool SendPasswordReminder(string userName);
    bool RegisterNewUser(RegisterNewUserModel model);
}

Während die List()Repository-Methode alle Benutzer zurückgibt, kann ListUsers()IUserService nur diejenigen zurückgeben, auf die der Benutzer Zugriff hat.

In ASP.NET MVC + EF + SQL SERVER habe ich diesen Kommunikationsfluss:

Ansichten <- Controller -> Service-Schicht -> Repository-Schicht -> EF -> SQL Server

Service-Schicht -> Repository-Schicht -> EF Dieser Teil arbeitet mit Modellen.

Ansichten <- Controller -> Serviceebene Dieser Teil arbeitet mit Ansichtsmodellen.

BEARBEITEN:

Beispiel für den Ablauf für / Orders / ByClient / 5 (wir möchten die Bestellung für einen bestimmten Kunden sehen):

public class OrderController
{
    private IOrderService _orderService;

    public OrderController(IOrderService orderService)
    {
        _orderService = orderService; // injected by IOC container
    }

    public ActionResult ByClient(int id)
    {
        var model = _orderService.GetByClient(id);
        return View(model); 
    }
}

Dies ist die Schnittstelle für den Bestellservice:

public interface IOrderService
{
    OrdersByClientViewModel GetByClient(int id);
}

Diese Schnittstelle gibt das Ansichtsmodell zurück:

public class OrdersByClientViewModel
{
     CientViewModel Client { get; set; } //instead of ClientView, in simple project EF Client class could be used
     IEnumerable<OrderViewModel> Orders { get; set; }
}

Dies ist eine Schnittstellenimplementierung. Es verwendet Modellklassen und Repository, um ein Ansichtsmodell zu erstellen:

public class OrderService : IOrderService
{
     IRepository<Client> _clientRepository;
     public OrderService(IRepository<Client> clientRepository)
     {
         _clientRepository = clientRepository; //injected
     }

     public OrdersByClientViewModel GetByClient(int id)
     {
         return _clientRepository.Get(id).Select(c => 
             new OrdersByClientViewModel 
             {
                 Cient = new ClientViewModel { ...init with values from c...}
                 Orders = c.Orders.Select(o => new OrderViewModel { ...init with values from o...}     
             }
         );
     }
}
LukLed
quelle
2
@ Sam Striano: Wie Sie oben sehen können, gibt mein IRepository IQueryable zurück. Dies ermöglicht das Hinzufügen zusätzlicher Where-Bedingungen und die verzögerte Ausführung in der Serviceschicht, nicht später. Ja, ich verwende eine Assembly, aber alle diese Klassen befinden sich in unterschiedlichen Namespaces. Es gibt keinen Grund, in kleinen Projekten viele Baugruppen zu erstellen. Die Trennung von Namespace und Ordner funktioniert gut.
LukLed
81
Warum Ansichtsmodelle im Service zurückgeben? Soll der Dienst nicht emulieren, wenn Sie mehrere Clients (Mobile / Web) haben? Wenn dies der Fall ist, kann sich das Ansichtsmodell von verschiedenen Plattformen unterscheiden
Ryan
12
In Übereinstimmung mit @Ryan sollte die Serviceschicht ein Entitätsobjekt oder eine Sammlung von Entitätsobjekten zurückgeben (nicht IQueryable). Dann werden auf der UI-Entität beispielsweise von Automapper SomeViewModel zugeordnet.
Eldar
5
@Duffp: Sie müssen nicht für jede Entität ein Repository erstellen. Sie können generische Implementierung und binden verwenden , IRepository<>um GenericRepository<>in Ihrer IOC - Bibliothek. Diese Antwort ist sehr alt. Ich denke, die beste Lösung besteht darin, alle Repositorys in einer Klasse namens zu kombinieren UnitOfWork. Es sollte ein Repository jedes Typs und eine aufgerufene Methode enthalten SaveChanges. Alle Repositorys sollten einen EF-Kontext gemeinsam nutzen.
LukLed
2
Anstatt viewmodel von der Service-Schicht zurückzugeben, sollten Sie DTO zurückgeben und es mit Automapper in viewModels konvertieren. Manchmal sind sie gleich, wenn sie nicht vorhanden sind, sind Sie dankbar, dass Sie YGTNI "You're Going To" implementiert haben Need It "
Hanzolo
41

Wie Carnotaurus sagte, ist das Repository für die Zuordnung Ihrer Daten aus dem Speicherformat zu Ihren Geschäftsobjekten verantwortlich. Es sollte sowohl das Lesen als auch das Schreiben von Daten (Löschen, Aktualisieren) vom und zum Speicher behandeln.

Der Zweck der Serviceschicht besteht andererseits darin, die Geschäftslogik an einem einzigen Ort zusammenzufassen, um die Wiederverwendung von Code und die Trennung von Bedenken zu fördern. In der Praxis bedeutet dies für mich in der Praxis beim Erstellen von Asp.net MVC-Sites, dass ich diese Struktur habe

[Controller] ruft [Service (s)] auf, der [Repository (s)] aufruft]

Ein Prinzip, das ich als nützlich empfunden habe, besteht darin, die Logik in Controllern und Repositorys auf ein Minimum zu beschränken.

In Controllern liegt es daran, dass es mir hilft, trocken zu bleiben. Es kommt sehr häufig vor, dass ich die gleiche Filterung oder Logik an einer anderen Stelle verwenden muss, und wenn ich sie in den Controller einbaue, kann ich sie nicht wiederverwenden.

In Repositorys liegt es daran, dass ich meinen Speicher (oder ORM) ersetzen möchte, wenn etwas Besseres eintritt. Und wenn ich Logik im Repository habe, muss ich diese Logik neu schreiben, wenn ich das Repository ändere. Wenn mein Repository nur IQueryable zurückgibt und der Dienst andererseits die Filterung durchführt, muss ich nur die Zuordnungen ersetzen.

Zum Beispiel habe ich kürzlich mehrere meiner Linq-To-Sql-Repositorys durch EF4 ersetzt, und diejenigen, bei denen ich diesem Prinzip treu geblieben war, konnten innerhalb weniger Minuten ersetzt werden. Wo ich eine Logik hatte, war es stattdessen eine Frage der Stunden.

Mikael Eliasson
quelle
Ich stimme dir zu, Mikael. Tatsächlich habe ich das gleiche Szenario in meinem Tech-Blog freecodebase.com angewendet und bei dieser Implementierung den Code-First-Ansatz verwendet. Der Quellcode kann auch hier heruntergeladen werden.
Toffee
Ich habe das allgemeine Thema der Anwendung eines Repository-Musters in einer vorhandenen MVC-App untersucht. Es ist ein maßgeschneidertes Framework mit einem Active Record-ähnlichen ORM und anderen Rails / Laravel-Konventionen und hat einige architektonische Probleme bei der Arbeit, die ich gerade mache. Eine Sache, auf die ich gestoßen bin, ist, dass Repositorys "keine ViewModels, DTOs oder Abfrageobjekte zurückgeben sollten", sondern Repository-Objekte zurückgeben sollten. Ich überlege, wo Services mit Repository-Objekten über Methoden wie interagieren onBeforeBuildBrowseQueryund den Abfrage-Generator verwenden können, um die Abfrage zu ändern.
Kalligraphie-io
@Toffee, dein Link ist kaputt, kannst du ihn bitte aktualisieren, ich brauche den Quellcode für diese Implementierung.
Hamza Khanzada
24

Die akzeptierte Antwort (und hunderte Male positiv bewertet) weist einen großen Fehler auf. Ich wollte dies im Kommentar hervorheben, aber es wird nur in 30 Kommentaren vergraben, die hier darauf hinweisen.

Ich habe eine Unternehmensanwendung übernommen, die auf diese Weise erstellt wurde, und meine erste Reaktion war WTH ? ViewModels in der Service-Schicht? Ich wollte die Konvention nicht ändern, da sie jahrelang weiterentwickelt worden war, und fuhr daher mit der Rückgabe von ViewModels fort. Junge, es wurde zu einem Albtraum, als wir anfingen, WPF zu verwenden. Wir (das Entwicklerteam) sagten immer: Welches ViewModel? Der echte (der, den wir für die WPF geschrieben haben) oder der Dienst? Sie wurden für eine Webanwendung geschrieben und hatten sogar das IsReadOnly- Flag, um die Bearbeitung in der Benutzeroberfläche zu deaktivieren. Großer, großer Fehler und alles wegen eines Wortes: ViewModel !!

Bevor Sie denselben Fehler machen, sind neben meiner obigen Geschichte noch einige weitere Gründe aufgeführt:

Das Zurückgeben eines ViewModel von der Service-Schicht ist ein großes Nein Nein. Das ist wie zu sagen:

  1. Wenn Sie diese Dienste nutzen möchten, verwenden Sie besser MVVM. Hier ist das ViewModel, das Sie verwenden müssen. Autsch!

  2. Die Dienste gehen davon aus, dass sie irgendwo in einer Benutzeroberfläche angezeigt werden. Was ist, wenn es von einer Nicht-UI-Anwendung wie Webdiensten oder Windows-Diensten verwendet wird?

  3. Das ist nicht einmal ein echtes ViewModel. Ein echtes ViewModel hat Beobachtbarkeit, Befehle usw. Das ist nur ein POCO mit einem schlechten Ruf. (Siehe meine Geschichte oben, warum Namen wichtig sind.)

  4. Die konsumierende Anwendung ist besser eine Präsentationsebene (ViewModels werden von dieser Ebene verwendet) und sie versteht C # besser. Noch ein Autsch!

Bitte tu das nicht!

Codierung von Yoshi
quelle
3
Ich musste dies nur kommentieren, obwohl ich weiß, dass es nicht zur Diskussion beiträgt: "Das ist nur ein POCO mit einem schlechten Ruf." << - Das würde auf einem T-Shirt gut aussehen! :) :)
Mephisztoe
8

Normalerweise wird ein Repository als Gerüst verwendet, um Ihre Entitäten zu füllen. Eine Service-Schicht wird ausgehen und eine Anfrage stellen. Es ist wahrscheinlich, dass Sie ein Repository unter Ihre Service-Schicht stellen.

CarneyCode
quelle
Also in einer ASP.NET MVC-App mit EF4 vielleicht so etwas: SQL Server -> EF4 -> Repository -> Service Layer -> Modell -> Controller und umgekehrt?
Sam
1
Ja, Ihr Repository könnte verwendet werden, um leichtgewichtige Entitäten von EF4 abzurufen. und Ihre Service-Schicht kann verwendet werden, um diese an einen spezialisierten Modellmanager (Modell in Ihrem Szenario) zurückzusenden. Der Controller würde sich dazu an Ihren spezialisierten Modellmanager wenden ... Schauen Sie sich kurz meinen Blog für Mvc 2/3 an. Ich habe Diagramme.
CarneyCode
Nur zur Verdeutlichung: In EF4 in Ihrem Szenario befindet sich Model in meinen Diagrammen und Model in Ihrem Szenario sind spezialisierte Modellmanager in meinen Diagrammen
CarneyCode
4

Die Repository-Schicht ist für den Zugriff auf die Datenbank implementiert und hilft bei der Erweiterung der CRUD-Operationen für die Datenbank. Während eine Serviceschicht aus der Geschäftslogik der Anwendung besteht und die Repository-Schicht verwenden kann, um bestimmte Logik zu implementieren, die die Datenbank betrifft. In einer Anwendung ist es besser, eine separate Repository- und Service-Schicht zu haben. Durch separate Repository- und Service-Schichten wird der Code modularer und die Datenbank von der Geschäftslogik entkoppelt.

Akshay
quelle