Відмінність між сховищем та рівнем обслуговування?


191

У шаблонах дизайну OOP, яка різниця між шаблоном сховища та рівнем обслуговування?

Я працюю над додатком ASP.NET MVC 3 і намагаюся зрозуміти ці шаблони дизайну, але мій мозок просто не отримує цього ... поки !!

Відповіді:


330

Шар репозиторію надає додатковий рівень абстрагування доступу до даних. Замість того, щоб писати

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

2
@Sam Striano: Як ви бачите вище, мій IRepository повертає IQueryable. Це дозволяє додавати додаткові, де умови та відкладене виконання в рівні обслуговування, не пізніше. Так, я використовую одну збірку, але всі ці класи розміщуються в різних просторах імен. Немає підстав створювати багато збірок у невеликих проектах. Простір імен та розділення папок добре працює.
LukLed

81
Навіщо повертати моделі перегляду в сервісі? Хіба служба не припускає наслідувати, якщо ви мали б декількох клієнтів (мобільний / веб)? Якщо це так, то viewmodel може відрізнятися від різних платформ
Ryan

12
Погоджений з @Ryan, службовий рівень повинен повертати об'єкт сукупності або колекцію об'єктів об'єкта (не IQueryable). Потім, наприклад, на об’єкті Ui відображається на SomeViewModel від Automapper, наприклад.
Ельдар

5
@Duffp: Вам не потрібно створювати сховища для кожної сутності. Ви можете використовувати загальну реалізацію та прив'язуватися IRepository<>до GenericRepository<>своєї бібліотеки IOC. Ця відповідь дуже стара. Я думаю, що найкращим рішенням є об'єднання всіх сховищ в одному класі, що називається UnitOfWork. Він повинен містити сховище кожного типу та один метод, який називається SaveChanges. Усі сховища повинні мати один контекст EF.
LukLed

2
замість повернення viewmodel з сервісного шару, ви повинні повернути DTO і перетворити його у viewModels за допомогою автоматизатора .. іноді вони однакові, коли вони не будуть вам вдячні, що ви реалізували YGTNI "Ви збираєтеся Потрібно »
hanzolo

41

Як сказав Карнотавр, сховище несе відповідальність за відображення ваших даних із формату зберігання для ваших бізнес-об'єктів. Він повинен керувати як читанням, так і записом даних (видалення, оновлення теж) із та у сховище.

Мета рівня обслуговування, з іншого боку, полягає в об'єднанні бізнес-логіки в єдине місце для сприяння повторному використанню коду та роз'єднанню проблем. Що зазвичай це означає для мене на практиці при створенні сайтів Asp.net MVC, це те, що я маю цю структуру

[Контролер] викликає [Службу (служб)], хто викликає [сховище]]

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

У контролерах це тому, що це допомагає тримати мене СУХОМО. Дуже часто зустрічається те, що мені потрібно використовувати ту саму фільтрацію або логіку десь в іншому місці, і якщо я помістив її в контролер, я не можу використовувати його повторно.

У сховищах це відбувається тому, що я хочу мати змогу замінити своє сховище (або ORM), коли станеться щось краще. І якщо у мене є логіка в сховищі, мені потрібно переписати цю логіку, коли я зміню сховище. Якщо мій сховище повертає лише IQueryable, а сервіс виконує фільтрацію з іншого боку, мені потрібно буде лише замінити відображення.

Наприклад, нещодавно я замінив кілька моїх сховищ Linq-To-Sql на EF4, а ті, де я залишився вірним цьому принципу, могли замінити за лічені хвилини. Де я мав деяку логіку, це було замість них години.


Я згоден з тобою, Мікаель. Насправді, я застосував той самий сценарій у своєму технологічному блозі freecodebase.com, і я застосував перший підхід до коду в цій реалізації. Вихідний код можна також завантажити тут.
Іриска

Я досліджував загальну тему застосування шаблону репозиторію у існуючому додатку MVC. Це замовний план з діючими записами ORM та іншими конвенціями Rails / Laravel і має деякі архітектурні проблеми для роботи, яку я зараз роблю. Одне, на що я натрапив, - це те, що репозиторії "не повинні повертати ViewModels, DTO або запити об'єктів", а повинні повертати об'єкти репозиторію. Я роздумую над тим, де служби взаємодіють із об'єктами сховища за допомогою таких методів, як onBeforeBuildBrowseQueryі може використовувати конструктор запитів для зміни запиту.
каліграфічно-іо

@Toffee, ваше посилання порушено, чи можете ви оновити його, мені потрібен вихідний код для цієї реалізації.
Хамза Ханзада

24

Прийнята відповідь (і її сотні разів відкликано) має великий недолік. Я хотів вказати на це у коментарі, але він просто заринеться там у 30-ти коментарях, що вказують тут.

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

Перш ніж ви зробите ту саму помилку, ось ще кілька причин на додаток до моєї історії вище:

Повернення ViewModel з рівня обслуговування - це величезне "ні". Це як сказати:

  1. Якщо ви хочете скористатися цими послугами, вам краще використовувати MVVM, а ось ViewModel, який вам потрібно використовувати. Ой!

  2. Служби роблять припущення, що вони будуть десь відображатися в інтерфейсі. Що робити, якщо його використовує програма, що не користується інтерфейсом, наприклад веб-сервіси або служби Windows?

  3. Це навіть не справжній ViewModel. Справжній ViewModel має спостережливість, команди тощо. Це просто POCO з неправильним ім'ям. (Дивіться мою історію вище, чому імена мають значення.)

  4. Захоплюючий додаток краще бути презентаційним шаром (ViewModels використовується цим шаром), і він краще зрозуміє C #. Ще одне!

Будь ласка, не робіть цього!


3
Мені просто довелося прокоментувати це, хоча я знаю, що це не додає до дискусії: "Це просто POCO з невірним іменем". << - Це добре виглядатиме на футболці! :) :)
Мефіштое

8

Зазвичай сховище використовується як ліси для заселення ваших організацій - сервісний рівень вийшов би і подав запит. Цілком ймовірно, що ви помістите сховище під службовий рівень.


Отож, у програмі ASP.NET MVC, що використовує EF4, можливо щось подібне: SQL Server -> EF4 -> Репозиторій -> Сервісний шар -> Модель -> Контролер і навпаки?
Сем

1
Так, ваше сховище може бути використане для отримання легких об'єктів з EF4; і ваш рівень обслуговування міг би бути використаний для надсилання цих даних спеціалізованому менеджеру моделі (Модель у вашому сценарії). Контролер зателефонує до вашого спеціалізованого менеджера моделей, щоб це зробити ... Погляньте на мій блог для Mvc 2 / 3. У мене є діаграми.
CarneyCode

Тільки для уточнення: EF4 у вашому сценарії - це те, де Model на моїх діаграмах, а Model у вашому сценарії - це спеціалізовані менеджери моделей за моїми діаграмами
CarneyCode

5

Шар репозиторію реалізується для доступу до бази даних та допомагає розширити операції CRUD над базою даних. Тоді як сервісний рівень складається з бізнес-логіки програми і може використовувати рівень репозиторію для реалізації певної логіки, що включає базу даних. У додатку краще мати окремий рівень сховища та рівень обслуговування. Маючи окремі шари сховища та сервісу, зробіть код більш модульним і відокремте базу даних від бізнес-логіки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.