Różnica między repozytorium a warstwą usługi?

191

Jaka jest różnica między wzorcem repozytorium a warstwą usługi w wzorcach projektowych OOP?

Pracuję nad aplikacją ASP.NET MVC 3 i staram się zrozumieć te wzorce projektowe, ale mój mózg po prostu tego nie rozumie ... jeszcze !!

Sam
źródło

Odpowiedzi:

330

Warstwa repozytorium zapewnia dodatkowy poziom abstrakcji w stosunku do dostępu do danych. Zamiast pisać

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

aby uzyskać pojedynczy element z bazy danych, używasz interfejsu repozytorium

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

i zadzwoń Get(id). Warstwa repozytorium udostępnia podstawowe operacje CRUD .

Warstwa usługi udostępnia logikę biznesową, która korzysta z repozytorium. Przykładowa usługa może wyglądać następująco:

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);
}

Podczas gdy List()metoda repozytorium zwraca wszystkich użytkowników, ListUsers()IUserService może zwrócić tylko tych, z których użytkownik ma dostęp.

W ASP.NET MVC + EF + SQL SERVER mam taki przepływ komunikacji:

Widoki <- Kontrolery -> Warstwa usługi -> Warstwa repozytorium -> EF -> SQL Server

Warstwa serwisowa -> Warstwa repozytorium -> EF Ta część działa na modelach.

Widoki <- Kontrolery -> Warstwa serwisowa Ta część działa na modelach widoków.

EDYTOWAĆ:

Przykład przepływu dla / Orders / ByClient / 5 (chcemy zobaczyć zamówienie dla konkretnego klienta):

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); 
    }
}

Oto interfejs do obsługi zamówień:

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

Ten interfejs zwraca model widoku:

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

To jest implementacja interfejsu. Wykorzystuje klasy modeli i repozytorium do tworzenia modelu widoku:

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
źródło
2
@Sam Striano: Jak widać powyżej, moje IRepository zwraca IQueryable. Umożliwia to dodanie dodatkowych warunków w miejscu i odroczone wykonanie w warstwie usługi, nie później. Tak, używam jednego zestawu, ale wszystkie te klasy są umieszczone w różnych przestrzeniach nazw. Nie ma powodu, aby tworzyć wiele zespołów w małych projektach. Dzielenie przestrzeni nazw i folderów działa dobrze.
LukLed
81
Po co zwracać modele widoku w serwisie? czy usługa nie powinna emulować, jeśli miałbyś mieć wielu klientów (mobilne / internetowe)? W takim przypadku model widoku może się różnić od różnych platform
Ryan
12
Uzgodniona z @Ryan warstwa usługi powinna zwrócić obiekt encji lub kolekcję obiektów encji (nie IQueryable). Następnie na encji interfejsu użytkownika na przykład do SomeViewModel firmy Automapper.
Eldar
5
@Duffp: Nie musisz tworzyć repozytorium dla każdej jednostki. Możesz użyć ogólnej implementacji i powiązać IRepository<>GenericRepository<>w bibliotece IOC. Ta odpowiedź jest bardzo stara. Myślę, że najlepszym rozwiązaniem jest połączenie wszystkich repozytoriów w jednej klasie o nazwie UnitOfWork. Powinien zawierać repozytorium każdego typu i wywoływaną jedną metodę SaveChanges. Wszystkie repozytoria powinny mieć jeden kontekst EF.
LukLed,
2
zamiast zwracać viewmodel z warstwy usługi, powinieneś zwrócić DTO i przekonwertować go na viewModels za pomocą automappera. czasami są takie same, jeśli nie są, będziesz wdzięczny, że zaimplementowałeś YGTNI Need It ”
hanzolo,
41

Jak powiedział Carnotaurus, repozytorium jest odpowiedzialne za mapowanie danych z formatu pamięci na obiekty biznesowe. Powinien obsługiwać zarówno sposób odczytu, jak i zapisywania danych (usuwania, aktualizacji również) zi do pamięci.

Z drugiej strony, celem warstwy usługowej jest hermetyzacja logiki biznesowej w jednym miejscu w celu promowania ponownego użycia kodu i separacji problemów. Co to zwykle oznacza dla mnie w praktyce przy tworzeniu stron Asp.net MVC, to fakt, że mam taką strukturę

[Kontroler] wywołuje [Usługa (y)], który wywołuje [repozytorium (-a)]

Jedną z zasad, które uważam za przydatne, jest ograniczenie logiki do minimum w kontrolerach i repozytoriach.

W kontrolerach dzieje się tak dlatego, że pomaga mi zachować SUCHOŚĆ. Bardzo często muszę używać tego samego filtrowania lub logiki gdzie indziej, a jeśli umieściłem go w kontrolerze, nie mogę go ponownie użyć.

W repozytoriach dzieje się tak dlatego, że chcę móc zastąpić moją pamięć masową (lub ORM), gdy pojawi się coś lepszego. A jeśli mam logikę w repozytorium, muszę zmienić tę logikę po zmianie repozytorium. Jeśli moje repozytorium zwraca tylko IQueryable, a usługa wykonuje filtrowanie z drugiej strony, będę musiał jedynie zastąpić odwzorowania.

Na przykład niedawno zastąpiłem kilka moich repozytoriów Linq-To-Sql EF4, a te, w których przestrzegałem tej zasady, można wymienić w ciągu kilku minut. Tam, gdzie miałem trochę logiki, to kwestia godzin.

Mikael Eliasson
źródło
Zgadzam się z tobą, Mikaelu. W rzeczywistości zastosowałem ten sam scenariusz na moim blogu technologicznym freecodebase.com i w tej implementacji zastosowałem pierwsze podejście do kodu. Kod źródłowy można również pobrać tutaj.
Toffee
Badałem ogólny temat stosowania wzorca repozytorium w istniejącej aplikacji MVC. Jest to specjalnie zaprojektowany framework z ORM podobnym do Active Record i innymi konwencjami Rails / Laravel i ma pewne problemy architektoniczne do pracy, którą teraz wykonuję. Jedną z rzeczy, na które natrafiłem, jest to, że repozytoria „nie powinny zwracać ViewModels, DTO ani obiektów zapytań”, ale raczej powinny zwracać obiekty repozytorium. Zastanawiam się, gdzie usługi wchodzą w interakcję z obiektami repozytorium za pomocą metod podobnych onBeforeBuildBrowseQueryi mogę użyć konstruktora zapytań do zmiany zapytania.
calligraphic-io
@Toffee, link jest uszkodzony, czy możesz go zaktualizować, potrzebuję kodu źródłowego dla tej implementacji.
Hamza Khanzada,
24

Przyjęta odpowiedź (i głosowana setki razy) ma poważną wadę. Chciałem zwrócić na to uwagę w komentarzu, ale zostanie tam pochowany w 30 komentarzach, więc tutaj wskazuję.

Przejęłam tak zbudowaną aplikację korporacyjną, a moją pierwszą reakcją było WTH ? ViewModels w warstwie serwisowej? Nie chciałem zmieniać konwencji, ponieważ minęły lata rozwoju, więc kontynuowałem powrót do ViewModels. Chłopcze, kiedy zaczęliśmy używać WPF, zamieniło się w koszmar. My (zespół deweloperów) zawsze mówiliśmy: który ViewModel? Prawdziwy (ten, który napisaliśmy dla WPF) czy usługowy? Zostały napisane dla aplikacji internetowej, a nawet miały flagę IsReadOnly, aby wyłączyć edycję w interfejsie użytkownika. Poważna, poważna wada i wszystko z powodu jednego słowa: ViewModel !!

Zanim popełnisz ten sam błąd, oto kilka innych powodów oprócz mojej powyższej historii:

Zwrócenie ViewModel z warstwy usługi to ogromne nie. To tak, jakby powiedzieć:

  1. Jeśli chcesz korzystać z tych usług, lepiej korzystaj z MVVM, a oto ViewModel, którego potrzebujesz. Auć!

  2. Usługi zakładają, że gdzieś będą wyświetlane w interfejsie użytkownika. Co się stanie, jeśli jest używana przez aplikację inną niż interfejs użytkownika, taką jak usługi sieciowe lub usługi systemu Windows?

  3. To nawet nie jest prawdziwy ViewModel. Prawdziwy ViewModel ma obserwowalność, polecenia itp. To tylko POCO o złej nazwie. (Zobacz moją historię powyżej, dlaczego nazwy mają znaczenie.)

  4. Aplikacja konsumująca lepiej jest warstwą prezentacji (ViewModels są używane przez tę warstwę) i lepiej rozumie C #. Kolejny ouch!

Proszę nie rób tego!

Kodowanie Yoshi
źródło
3
Po prostu musiałem to skomentować, chociaż wiem, że to nie dodaje się do dyskusji: „To tylko POCO o złej nazwie”. << - To by ładnie wyglądało na koszulce! :) :)
Mephisztoe
8

Zwykle repozytorium jest używane jako rusztowanie do zapełniania twoich bytów - warstwa usługi wychodziła i generowała żądanie. Prawdopodobnie umieścisz repozytorium pod warstwą usług.

CarneyCode
źródło
Więc w aplikacji ASP.NET MVC używającej EF4, może coś takiego: SQL Server -> EF4 -> Repozytorium -> Warstwa usług -> Model -> Kontroler i odwrotnie?
Sam
1
Tak, twoje repozytorium może zostać wykorzystane do uzyskania lekkich bytów z EF4; a warstwa usług może zostać wykorzystana do odesłania ich do wyspecjalizowanego menedżera modeli (Model w Twoim scenariuszu). Kontroler zadzwoni do twojego wyspecjalizowanego menedżera modeli, aby to zrobić ... Rzuć okiem na mojego bloga dla Mvc 2 / 3. Mam diagramy.
CarneyCode
Tylko dla wyjaśnienia: EF4 w twoim scenariuszu to miejsce, w którym Model jest na moich diagramach, a Model w twoim scenariuszu to wyspecjalizowani menedżerowie modeli na moich diagramach
CarneyCode
5

Warstwa repozytorium jest zaimplementowana w celu uzyskania dostępu do bazy danych i pomaga rozszerzyć operacje CRUD na bazie danych. Natomiast warstwa usług składa się z logiki biznesowej aplikacji i może wykorzystywać warstwę repozytorium do implementacji określonej logiki obejmującej bazę danych. W aplikacji lepiej jest mieć oddzielną warstwę repozytorium i warstwę usługi. Posiadanie osobnych warstw repozytorium i usług czyni kod bardziej modułowym i oddziela bazę danych od logiki biznesowej.

Akshay
źródło