Разница между репозиторием и уровнем обслуживания?

В чем разница между шаблоном репозитория и уровнем сервиса в шаблонах проектирования ООП?

Я работаю над приложением ASP.NET MVC 3 и пытаюсь понять эти шаблоны проектирования, но мой мозг просто не понимает этого ... пока !!


person Sam    schedule 19.02.2011    source источник


Ответы (5)


Уровень репозитория дает вам дополнительный уровень абстракции по сравнению с доступом к данным. Вместо того, чтобы писать

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

чтобы получить один элемент из базы данных, вы используете интерфейс репозитория

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

и позвоните Get(id). На уровне репозитория доступны основные операции CRUD.

Уровень обслуживания предоставляет бизнес-логику, которая использует репозиторий. Пример услуги может выглядеть так:

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

В то время как List() метод репозитория возвращает всех пользователей, ListUsers() IUserService может возвращать только тех, к которым у пользователя есть доступ.

В ASP.NET MVC + EF + SQL SERVER у меня есть такой поток коммуникации:

Представления ‹- Контроллеры -> Уровень обслуживания -> Уровень репозитория -> EF -> SQL Server

Уровень обслуживания -> Уровень репозитория -> EF Эта часть работает с моделями.

Представления ‹- Контроллеры -> Уровень обслуживания Эта часть работает с моделями представлений.

РЕДАКТИРОВАТЬ:

Пример потока для / Orders / ByClient / 5 (мы хотим видеть заказ для конкретного клиента):

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

Это интерфейс для обслуживания заказов:

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

Этот интерфейс возвращает модель представления:

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

Это реализация интерфейса. Он использует классы моделей и репозиторий для создания модели представления:

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...}     
             }
         );
     }
}
person LukLed    schedule 19.02.2011
comment
Это очень похоже на мое - person CarneyCode; 19.02.2011
comment
@LukLed - Где работают ваши модели просмотра? Я думаю, что контроллер обслуживает модель представления для представления. Модель представления получает данные из уровня обслуживания, который получает данные из репозитория, который запрашивает объекты EF / POCO. Это звучит правильно? - person Sam; 19.02.2011
comment
@LukLed - Еще пара вопросов. 1) Какой тип возвращать для коллекций из репозитория? 2) Как вы организуете свои сборки / проекты? Храните ли вы репозитории в той же сборке, что и объекты EF? Если вы используете POCO, вы помещаете их в другую сборку? Где находятся службы? - person Sam; 19.02.2011
comment
@Sam Striano: Как вы можете видеть выше, мой IRepository возвращает IQueryable. Это позволяет добавлять дополнительные условия where и отложенное выполнение на уровне сервиса, а не позже. Да, я использую одну сборку, но все эти классы размещены в разных пространствах имен. Нет смысла создавать много сборок в небольших проектах. Пространство имен и разделение папок отлично работают. - person LukLed; 19.02.2011
comment
@LukLed - Так ваш контроллер загружает модель из уровня сервиса или у модели есть методы для этого? - person Sam; 19.02.2011
comment
@LukLed - Также предположим, что у клиента есть коллекция заказов, когда репозиторий возвращает клиента, как он получает заказы, если вы позже позвоните в Customer.Orders? - person Sam; 19.02.2011
comment
+1 Отличный пример! Единственное изменение, которое я хотел бы сделать, это поместить инициализацию модели представления в конструктор. return _clientRepository.Get(id).Select(c => new OrdersByClientViewModel (c)); - person Ryan; 20.02.2011
comment
Зачем возвращать модели просмотра в сервис? Разве служба не должна имитировать, если у вас будет несколько клиентов (мобильный / Интернет)? В этом случае модель просмотра может отличаться на разных платформах. - person Ryan; 17.09.2011
comment
По согласованию с @Ryan уровень сервиса должен возвращать объект сущности или коллекцию объектов сущности (не IQueryable). Затем в пользовательском интерфейсе сущность отображается в SomeViewModel, например, с помощью Automapper. - person Eldar; 03.10.2011
comment
@eldar: Я никогда не писал о возврате IQueryable. В моем примере служба возвращает объект со списком как IEnumerable. Но да, он должен быть отображен для просмотра класса модели, а не передаваться напрямую. Я скоро обновлю свой ответ. - person LukLed; 03.10.2011
comment
Отличный пример. Я думаю, var model = _prderService.GetByClient(id); должно быть: var model = _orderService.GetByClient(id); - person Lasse Christiansen; 11.12.2011
comment
@LukLLu - Как вы обрабатываете перекрестные вызовы служб? Например. У вас есть услуга по продукту, для которой необходимо использовать метод из службы заказов и наоборот? Я предполагаю, что вы просто внедрите эту службу и начнете ее использовать. Но мне также интересно, как будет работать проверка ... - person Ryan; 07.06.2012
comment
@ Райан: Да, я использую инъекции. Прочтите эту статью о проверке на уровне обслуживания: asp.net/mvc/tutorials/older-versions/models-%28data%29/ - person LukLed; 07.06.2012
comment
@LukLed - На самом деле я только что прочитал ту статью, и она мне очень помогла. Он не демонстрирует перекрестные вызовы служб, но я думаю, что это было бы довольно просто. - person Ryan; 07.06.2012
comment
@LukLed Я также хочу создать уровень репозитория в приложении WPF MVVM Entity Frameowrk. Должен ли я создавать отдельный класс репозитория (наследуемый от IRespository) для каждой сущности, созданной EF? - person Joe Slater; 03.07.2013
comment
@AnkurSharma: вам не нужно создавать класс для каждой сущности. Вы можете использовать одну общую реализацию и создать единицу рабочего класса, который собирает все репозитории. - person LukLed; 03.07.2013
comment
@LukLed спасибо за ответ. Не могли бы вы перенаправить меня на пример или пошаговое руководство по этой «общей реализации, единице работы». Я новичок в этом. - person Joe Slater; 03.07.2013
comment
@LukLed - отличный ответ и пример! Я предположил, что это старый пост, что у вас еще нет проекта, который вы создали, чтобы продемонстрировать это, верно? Я пытаюсь понять, как реализовать репозиторий. Должен ли я иметь репозиторий для каждой сущности в моей модели EF? Или я могу использовать для этого ViewModels и создавать репозитории для моделей представлений? Извините за недоразумение .. - person Duffp; 24.07.2013
comment
@Duffp: Вам не нужно создавать репозиторий для каждой сущности. Вы можете использовать общую реализацию и привязать IRepository<> к GenericRepository<> в своей библиотеке IOC. Это очень старый ответ. Я думаю, что лучшим решением будет объединить все репозитории в один класс под названием UnitOfWork. Он должен содержать репозиторий всех типов и один метод с именем SaveChanges. Все репозитории должны использовать один контекст EF. - person LukLed; 24.07.2013
comment
@AnkurSharma: Вот одно руководство о том, как реализовать общий репозиторий (и другие вещи): asp.net/mvc/tutorials/getting-started-with-ef -using-mvc / - person Valentin; 07.10.2013
comment
вместо того, чтобы возвращать viewmodel из уровня сервиса, вы должны вернуть DTO и преобразовать его в viewModels с помощью automapper ... иногда они такие же, а когда нет, вы будете благодарны, что реализовали YGTNI. You Going To Need Это - person hanzolo; 30.05.2014
comment
@hanzolo: Вы правы, и на этом пока все, потому что я давно планировал обновить этот ответ. - person LukLed; 31.05.2014
comment
не должно быть: публичный класс OrderService: IOrderService {не публичный класс OrderService {? - person Frank; 22.08.2014
comment
@RizziFrank: Должно :) Исправлено. - person LukLed; 22.08.2014
comment
@LukLed I think the best solution is to combine all repositories in one class called UnitOfWork Зачем это нужно? DbContext EF уже является UOW. - person Halter; 13.12.2016
comment
@Halter DbContext - это единица работы, но я создаю дополнительный уровень для упрощения доступа. Возьмем для примера Get(int id). DbContext не предоставляет вам этот метод. Вы должны подать Where, а затем взять FirstOrDefault. Хотя написать context.Collection.Where(item => item.Id == id).FirstOrDefault() не составляет большой проблемы, я предпочитаю repository.Get(id). В DbContext много функций, и я видел, как разработчики делали с ним сумасшедшие вещи, и редко это было оправдано. Ограниченный доступ к DbContext с использованием IRepository достаточно в 99% случаев и упрощает код. - person LukLed; 14.12.2016
comment
@LukLed Я точно понимаю, откуда вы, но меня раздирают, потому что многие рекомендуют не использовать шаблон репозитория с EF, поскольку он уже есть (DbContext). Итак, вы бы порекомендовали использовать общий шаблон репозитория, чтобы упростить использование EF, а затем уровень обслуживания для взаимодействия ваших клиентов? Мне определенно нравится простота использования моего универсального шаблона репозитория. - person Halter; 14.12.2016
comment
@Halter - Как я сказал ранее - DbContext - это UOW, но со слишком широким и сложным интерфейсом. Поэтому я завершаю его общим репозиторием, чтобы упростить интерфейс и добавить некоторые полезные функции (например, Get(id)). Нет никакого вреда. Выставлять общий репозиторий как службу неправильно, но использовать его в качестве помощника вполне нормально. - person LukLed; 14.12.2016
comment
@Halter - Другое дело, что ваша реализация общего репозитория. Это действительно плохо. Слишком много new, слишком много ToList, слишком много virtual, слишком много try catch и слишком много IEnumerable. - person LukLed; 14.12.2016
comment
Я думал, что _clientRepository.Get(id) return single entity не будет иметь метода Select (). - person Cheung; 16.06.2017
comment
@lukled Не могли бы вы прояснить две вещи? Сначала вы сказали, что уровень обслуживания использует репо, но я не вижу упоминания IRepository в IUserService. Во-вторых, вы сказали: «В то время как метод репозитория List () возвращает всех пользователей, ListUsers () IUserService может возвращать только тех, к которым у пользователя есть доступ». Как? Я не понимаю связи. - person Howiecamp; 20.06.2017
comment
@Howiecamp: реализация IUserService внутренне использует IRepository. Интерфейс IUserService не наследуется от репозитория. Между репозиторием и сервисом нет наследования. ListUsers() может возвращать отфильтрованных пользователей. Пример: return _repository.List().Where(u => u.BossId == currentUserId). - person LukLed; 20.06.2017
comment
@LukLed Gotcha - в этом есть смысл. Чтобы использовать такую ​​логику в моих сервисах, мне нужно возвращать IQueryables из моего репо, верно? В противном случае я могу вернуть отдельные объекты или IEnumerables или что-то еще, получить весь набор элементов на уровне обслуживания, а затем запустить там запросы linq. - person Howiecamp; 27.06.2017
comment
почему вы возвращаете модель, а не viewModel в этой строке var model = _orderService.GetByClient(id);. Однако на самом деле сервис возвращает viewModel в _orderService . - person StepUp; 23.02.2018
comment
@ryan Полностью с тобой согласен. Возврат ViewModels из сервисов - большой запрет. См. Мои пояснения в ответе ниже. - person CodingYoshi; 15.05.2018
comment
Я также согласен с тем, что ViewModels не должны возвращаться Сервисом. - person MaticDiba; 25.10.2018
comment
Как передать репозиторий с фактическим подключением к данным в общедоступном вызове OrderController (IOrderService orderService) (поскольку IOrderService ожидает IRepository в своем конструкторе). Используя код как есть, я получаю конструктор без параметров, определенный для этого объекта. ошибка. - person Joseph Norris; 05.07.2019
comment
@JosephNorris Вы должны настроить внедрение зависимостей. Это выходит за рамки этого вопроса, но вы найдете много материалов на StackOverflow. - person LukLed; 07.07.2019
comment
Спасибо. Это заняло немного времени, я понял это. - person Joseph Norris; 09.07.2019
comment
IMHO, если ваши методы обслуживания являются сопоставлением 1-1 с методами репозитория, то вы делаете это неправильно. Это просто еще один слой ничего. - person mimsugara; 18.05.2021

Как сказал Карнотавр, репозиторий отвечает за отображение ваших данных из формата хранения на ваши бизнес-объекты. Он должен обрабатывать как чтение, так и запись данных (удаление, обновление тоже) из хранилища и в него.

С другой стороны, цель уровня обслуживания - объединить бизнес-логику в одном месте, чтобы способствовать повторному использованию кода и разделению проблем. На практике это обычно означает для меня при создании сайтов Asp.net MVC, что у меня есть эта структура

[Контроллер] вызывает [службы], который вызывает [репозиторий (и)]

Один принцип, который я нашел полезным, - это сведение к минимуму логики в контроллерах и репозиториях.

В контроллерах это потому, что это помогает мне оставаться СУХИМ. Очень часто мне нужно использовать ту же фильтрацию или логику где-то еще, и если я поместил ее в контроллер, я не смогу использовать ее повторно.

В репозиториях это потому, что я хочу иметь возможность заменить свое хранилище (или ORM), когда появится что-то лучшее. И если у меня есть логика в репозитории, мне нужно переписать эту логику при изменении репозитория. Если мой репозиторий возвращает только IQueryable, а служба выполняет фильтрацию, с другой стороны, мне нужно будет только заменить сопоставления.

Например, я недавно заменил несколько своих репозиториев Linq-To-Sql на EF4, и те, в которых я остался верен этому принципу, можно было заменить за считанные минуты. В тех случаях, когда у меня была некоторая логика, это был вопрос часов.

person Mikael Eliasson    schedule 19.02.2011
comment
Я согласен с тобой, Микаэль. Фактически, я применил тот же сценарий в своем техническом блоге freecodebase.com, и в этом выполнение. Исходный код также можно скачать здесь. - person Toffee; 18.03.2014
comment
Я исследовал общую тему применения шаблона репозитория в существующем приложении MVC. Это индивидуальная структура с ORM, подобным Active Record, и другими соглашениями Rails / Laravel, и у нее есть некоторые архитектурные проблемы для работы, над которой я сейчас работаю. Одна вещь, с которой я столкнулся, заключается в том, что репозитории не должны возвращать ViewModels, DTO или объекты запросов, а должны возвращать объекты репозитория. Я обдумываю, где службы взаимодействуют с объектами репозитория с помощью таких методов, как onBeforeBuildBrowseQuery, и могут использовать построитель запросов для изменения запроса. - person webstackdev; 24.01.2019
comment
@Toffee, ваша ссылка не работает, не могли бы вы обновить ее, мне нужен исходный код для этой реализации. - person Hamza Khanzada; 18.07.2019

Принятый ответ (за который проголосовали сотни раз) имеет серьезный недостаток. Я хотел указать на это в комментарии, но это просто будет похоронено там в 30 комментариях, так что указывайте здесь.

Я взял на себя корпоративное приложение, которое было построено таким образом, и моя первая реакция была WTH? ViewModels в сервисном слое? Я не хотел менять соглашение, потому что на него ушли годы разработки, поэтому я продолжил возвращать ViewModels. Когда мы начали использовать WPF, это превратилось в кошмар. Мы (команда разработчиков) всегда говорили: какая ViewModel? Настоящий (тот, который мы написали для WPF) или сервисный? Они были написаны для веб-приложения и даже имели флаг IsReadOnly для отключения редактирования в пользовательском интерфейсе. Главный, серьезный недостаток и все из-за одного слова: ViewModel !!

Прежде чем вы совершите ту же ошибку, вот еще несколько причин в дополнение к моей истории выше:

Возврат ViewModel из уровня сервиса - это просто нет. Это как сказать:

  1. Если вы хотите использовать эти службы, вам лучше использовать MVVM, и вот модель ViewModel, которую вам нужно использовать. Ой!

  2. Службы предполагают, что они будут где-то отображаться в пользовательском интерфейсе. Что, если он используется приложением без пользовательского интерфейса, например веб-службами или службами Windows?

  3. Это даже не настоящая ViewModel. Настоящая ViewModel имеет наблюдаемость, команды и т. д. Это просто POCO с плохой репутацией. (См. Мою историю выше, чтобы узнать, почему имена имеют значение.)

  4. Потребляющим приложением лучше быть уровнем представления (на этом уровне используются модели представления), и оно лучше понимает C #. Еще одна ох!

Пожалуйста, не делай этого!

person CodingYoshi    schedule 15.05.2018
comment
Мне просто нужно было прокомментировать это, хотя я знаю, что это не добавляет к обсуждению: это просто POCO с плохой репутацией. ‹< - Было бы здорово на футболке! :) :) - person Mephisztoe; 01.03.2019

Обычно репозиторий используется в качестве каркаса для заполнения ваших сущностей - уровень сервиса выходит и отправляет запрос. Вполне вероятно, что вы поместите репозиторий под свой уровень обслуживания.

person CarneyCode    schedule 19.02.2011
comment
Итак, в приложении ASP.NET MVC, использующем EF4, может быть что-то вроде этого: SQL Server - ›EF4 -› Репозиторий - ›Уровень обслуживания -› Модель - ›Контроллер и наоборот? - person Sam; 19.02.2011
comment
Да, ваш репозиторий можно использовать для получения облегченных сущностей из EF4; и ваш уровень обслуживания можно использовать для отправки их обратно специализированному менеджеру модели (модель в вашем сценарии). Контроллер позвонит вашему специализированному менеджеру моделей, чтобы сделать это ... Взгляните на мой блог по Mvc 2/3. У меня есть диаграммы. - person CarneyCode; 19.02.2011
comment
Просто для пояснения: EF4 в вашем сценарии - это то место, где Модель находится на моих диаграммах, а Модель в вашем сценарии - это специализированные менеджеры моделей на моих диаграммах. - person CarneyCode; 19.02.2011

Слой репозитория реализуется для доступа к базе данных и помогает расширить операции CRUD в базе данных. В то время как уровень сервиса состоит из бизнес-логики приложения и может использовать уровень репозитория для реализации определенной логики, связанной с базой данных. В приложении лучше иметь отдельный уровень репозитория и уровень сервиса. Наличие отдельных уровней репозитория и сервисов делает код более модульным и отделяет базу данных от бизнес-логики.

person Akshay    schedule 30.09.2018