Differenza tra repository e livello di servizio?


191

In OOP Design Patterns, qual è la differenza tra il Repository Pattern e un Service Layer?

Sto lavorando su un'app ASP.NET MVC 3 e sto cercando di capire questi schemi di progettazione, ma il mio cervello non lo capisce ... ancora !!

Risposte:


330

Il livello repository offre un ulteriore livello di astrazione sull'accesso ai dati. Invece di scrivere

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

per ottenere un singolo elemento dal database, utilizzare l'interfaccia del repository

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

e chiama Get(id). Il livello repository espone CRUD di base operazioni .

Il livello di servizio espone la logica aziendale, che utilizza il repository. Il servizio di esempio potrebbe essere simile a:

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

Mentre il List()metodo di repository restituisce tutti gli utenti, ListUsers()di IUserService potrebbe restituirne solo uno, l'utente ha accesso a.

In ASP.NET MVC + EF + SQL SERVER, ho questo flusso di comunicazione:

Viste <- Controller -> Livello di servizio -> Livello repository -> EF -> SQL Server

Livello di servizio -> Livello repository -> EF Questa parte funziona su modelli.

Viste <- Controller -> Livello di servizio Questa parte funziona su modelli vista.

MODIFICARE:

Esempio di flusso per / Orders / ByClient / 5 (vogliamo vedere l'ordine per un cliente specifico):

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

Questa è l'interfaccia per il servizio ordini:

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

Questa interfaccia restituisce il modello di visualizzazione:

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

Questa è l'implementazione dell'interfaccia. Utilizza le classi del modello e il repository per creare il modello di visualizzazione:

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: Come puoi vedere sopra, il mio IRepository restituisce IQueryable. Ciò consente di aggiungere ulteriori condizioni dove ed esecuzione differita nel livello di servizio, non in seguito. Sì, utilizzo un assembly, ma tutte queste classi sono collocate in spazi dei nomi diversi. Non c'è motivo di creare molti assiemi in piccoli progetti. Lo spazio dei nomi e la separazione delle cartelle funzionano bene.
LukLed,

81
Perché restituire modelli vista nel servizio? il servizio non suppone di emulare se si dovessero avere più client (mobile / web)? In tal caso, il modello di visualizzazione potrebbe differire da piattaforme diverse
Ryan,

12
Concordato con @Ryan, il livello di servizio dovrebbe restituire l'oggetto entità o la raccolta di oggetti entità (non IQueryable). Quindi, per esempio, l'entità viene mappata su SomeViewModel di Automapper.
Eldar,

5
@Duffp: non è necessario creare un repository per ogni entità. È possibile utilizzare l'implementazione generica e collegarsi IRepository<>alla GenericRepository<>propria libreria IOC. Questa risposta è molto antica. Penso che la soluzione migliore sia combinare tutti i repository in una classe chiamata UnitOfWork. Dovrebbe contenere repository di ogni tipo e un metodo chiamato SaveChanges. Tutti i repository dovrebbero condividere un contesto EF.
LukLed

2
invece di restituire viewmodel dal livello di servizio, dovresti restituire DTO e convertirlo in viewModels usando automapper .. a volte sono uguali, quando non lo farai sarai grato di aver implementato YGTNI "Stai andando a Need It "
hanzolo,

41

Come ha affermato Carnotaurus, il repository è responsabile della mappatura dei dati dal formato di archiviazione agli oggetti business. Dovrebbe gestire sia come leggere e scrivere i dati (eliminare, aggiornare anche) da e verso la memoria.

Lo scopo del livello di servizio è invece quello di incapsulare la logica aziendale in un unico posto per promuovere il riutilizzo del codice e la separazione delle preoccupazioni. Ciò che questo in genere significa per me in pratica quando si costruiscono siti MVC Asp.net è che ho questa struttura

[Controller] chiama [Servizio / i] che chiama [repository (i)]

Un principio che ho trovato utile è mantenere la logica al minimo nei controller e nei repository.

Nei controller è perché mi aiuta a SECCO. È molto comune che devo usare lo stesso filtro o la stessa logica da qualche altra parte e se l'ho inserito nel controller non posso riutilizzarlo.

Nei repository è perché voglio essere in grado di sostituire il mio archivio (o ORM) quando arriva qualcosa di meglio. E se ho una logica nel repository, devo cambiare questa logica quando cambio il repository. Se il mio repository restituisce solo IQueryable e il servizio esegue invece il filtraggio, dovrò solo sostituire i mapping.

Ad esempio, recentemente ho sostituito diversi dei miei repository Linq-To-Sql con EF4 e quelli in cui ero rimasto fedele a questo principio potevano essere sostituiti in pochi minuti. Dove avevo qualche logica, invece, era questione di ore.


Sono d'accordo con te Mikael. In effetti, ho applicato lo stesso scenario nel mio blog tecnico freecodebase.com e ho usato il primo approccio al codice in questa implementazione. Il codice sorgente può essere scaricato anche qui.
Toffee

Ho studiato l'argomento generale dell'applicazione di un modello di repository in un'app MVC esistente. È un framework su misura con un ORM simile a Active Record e altre convenzioni Rails / Laravel e presenta alcuni problemi di architettura per il lavoro che sto svolgendo ora. Una cosa che ho riscontrato è che i repository "non dovrebbero restituire ViewModels, DTO o oggetti query", ma piuttosto dovrebbero restituire oggetti repository. Sto pensando a dove i servizi interagiscono con gli oggetti del repository tramite metodi come onBeforeBuildBrowseQuerye possono usare il generatore di query per modificare la query.
calligrafico-io

@Toffee, il tuo link non funziona, puoi aggiornarlo, ho bisogno del codice sorgente per questa implementazione.
Hamza Khanzada,

24

La risposta accettata (e votata per centinaia di volte) ha un grosso difetto. Volevo evidenziarlo nel commento, ma sarà solo sepolto laggiù in 30 commenti di qualcosa, quindi sottolineando qui.

Ho acquisito un'applicazione enterprise costruita in quel modo e la mia reazione iniziale è stata WTH ? ViewModels nel livello di servizio? Non volevo cambiare la convenzione perché erano trascorsi anni di sviluppo, quindi ho continuato a restituire ViewModels. Ragazzo, si è trasformato in un incubo quando abbiamo iniziato a usare WPF. Noi (il team di sviluppatori) dicevamo sempre: quale ViewModel? Quello vero (quello che abbiamo scritto per il WPF) o quello dei servizi? Sono stati scritti per un'applicazione Web e avevano persino il flag IsReadOnly per disabilitare la modifica nell'interfaccia utente. Maggiore, grande difetto e tutto a causa di una sola parola: ViewModel !!

Prima di commettere lo stesso errore, ecco alcuni altri motivi oltre alla mia storia sopra:

Restituire un ViewModel dal livello di servizio è un no enorme. È come dire:

  1. Se si desidera utilizzare questi servizi, è meglio utilizzare MVVM ed ecco il ViewModel che è necessario utilizzare. Ahia!

  2. I servizi stanno assumendo che verranno visualizzati da qualche parte in un'interfaccia utente. Cosa succede se viene utilizzato da un'applicazione non UI come servizi Web o servizi Windows?

  3. Questo non è nemmeno un vero ViewModel. Un vero ViewModel ha osservabilità, comandi ecc. Questo è solo un POCO con una cattiva reputazione. (Vedi la mia storia sopra per perché i nomi contano.)

  4. L'applicazione che consuma meglio dovrebbe essere un livello di presentazione (i ViewModel sono usati da questo livello) e comprendere meglio C #. Un altro Ouch!

Per favore, non farlo!


3
Ho solo dovuto commentare questo, anche se so che non si aggiunge alla discussione: "Questo è solo un POCO con un brutto nome". << - Sarebbe bello su una maglietta! :) :)
Mephisztoe

8

Di solito un repository viene utilizzato come impalcatura per popolare le entità: un livello di servizio esce e genera una richiesta. È probabile che inserirai un repository nel tuo livello di servizio.


Quindi in un'app ASP.NET MVC che utilizza EF4, forse qualcosa del genere: SQL Server -> EF4 -> Repository -> Livello di servizio -> Modello -> Controller e viceversa?
Sam,

1
Sì, il tuo repository potrebbe essere utilizzato per ottenere entità leggere da EF4; e il tuo livello di servizio potrebbe essere utilizzato per rispedirli a un gestore modelli specializzato (modello nel tuo scenario). Il controller chiamerebbe il tuo gestore modello specializzato per fare questo ... Dai un'occhiata al mio blog per Mvc 2 / 3. Ho degli schemi.
Carney,

Solo per chiarimenti: EF4 nel tuo scenario è dove Model è nei miei diagrammi e Model nel tuo scenario sono gestori di modelli specializzati nei miei diagrammi
CarneyCode

5

Il livello repository è implementato per accedere al database e consente di estendere le operazioni CRUD sul database. Considerando che un livello di servizio è costituito dalla logica aziendale dell'applicazione e può utilizzare il livello di repository per implementare determinate logiche che coinvolgono il database. In un'applicazione, è meglio disporre di un livello repository e di un livello di servizio separati. Avere repository e livelli di servizio separati rende il codice più modulare e disaccoppia il database dalla logica aziendale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.