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

193

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

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

Сэм
источник

Ответы:

333

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

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...}     
             }
         );
     }
}
LukLed
источник
2
@Sam Striano: Как вы можете видеть выше, мой IRepository возвращает IQueryable. Это позволяет добавлять дополнительные условия where и отложенное выполнение на уровне сервиса, а не позже. Да, я использую одну сборку, но все эти классы размещены в разных пространствах имен. Нет смысла создавать много сборок в небольших проектах. Пространство имен и разделение папок отлично работают.
LukLed
82
Зачем возвращать модели просмотра в сервис? Разве служба не должна имитировать, если у вас будет несколько клиентов (мобильный / Интернет)? Если это так, то модель просмотра может отличаться на разных платформах,
Райан
12
По согласованию с @Ryan уровень обслуживания должен возвращать объект сущности или коллекцию объектов сущности (не IQueryable). Затем в пользовательском интерфейсе сущность отображается в SomeViewModel, например, Automapper.
Эльдар
5
@Duffp: вам не нужно создавать репозиторий для каждой сущности. Вы можете использовать общую реализацию и привязку IRepository<>к GenericRepository<>вашей библиотеке МОК. Это очень старый ответ. Я думаю, что лучшим решением будет объединить все репозитории в один класс под названием UnitOfWork. Он должен содержать репозиторий всех типов и один вызываемый метод SaveChanges. Все репозитории должны использовать один контекст EF.
LukLed
2
вместо того, чтобы возвращать viewmodel из уровня сервиса, вы должны вернуть DTO и преобразовать его в viewModels с помощью automapper ... иногда они такие же, а когда нет, вы будете благодарны, что реализовали YGTNI "Вы собираетесь Need It »
hanzolo
44

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

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

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

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

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

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

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

Микаэль Элиассон
источник
Я согласен с тобой, Микаэль. Фактически, я применил тот же сценарий в своем техническом блоге freecodebase.com, и в этой реализации я использовал подход «сначала код». Исходный код также можно скачать здесь.
Toffee
Я исследовал общую тему применения шаблона репозитория в существующем приложении MVC. Это индивидуальная структура с ORM, подобным Active Record, и другими соглашениями Rails / Laravel, и у нее есть некоторые архитектурные проблемы для работы, которую я сейчас выполняю. Одна вещь, с которой я столкнулся, заключается в том, что репозитории «не должны возвращать ViewModels, DTO или объекты запросов», а должны возвращать объекты репозитория. Я обдумываю, где службы взаимодействуют с объектами репозитория с помощью таких методов, как onBeforeBuildBrowseQueryи могут использовать построитель запросов для изменения запроса.
-io
@Toffee, ваша ссылка не работает, не могли бы вы обновить ее, мне нужен исходный код для этой реализации.
Hamza
26

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

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

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

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

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

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

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

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

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

Кодирование
источник
3
Мне просто нужно было прокомментировать это, хотя я знаю, что это не добавляет к обсуждению: «Это просто POCO с плохой репутацией». << - Хорошо бы на футболке смотреться! :) :)
Mephisztoe 01
8

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

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

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

Акшай
источник