Différence entre le référentiel et la couche de service?

191

Dans les modèles de conception POO, quelle est la différence entre le modèle de référentiel et une couche de service?

Je travaille sur une application ASP.NET MVC 3 et j'essaie de comprendre ces modèles de conception, mais mon cerveau ne comprend tout simplement pas ... encore !!

Sam
la source

Réponses:

330

Repository Layer vous offre un niveau supplémentaire d'abstraction sur l'accès aux données. Au lieu d'écrire

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

pour obtenir un seul élément de la base de données, vous utilisez l'interface du référentiel

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

et appelez Get(id). La couche de référentiel expose les opérations CRUD de base .

La couche de service expose la logique métier, qui utilise le référentiel. Un exemple de service pourrait ressembler à ceci:

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

Alors que la List()méthode du référentiel renvoie tous les utilisateurs, ListUsers()de IUserService ne peut en renvoyer que ceux, l'utilisateur a accès à.

Dans ASP.NET MVC + EF + SQL SERVER, j'ai ce flux de communication:

Vues <- Contrôleurs -> Couche de service -> Couche de référentiel -> EF -> SQL Server

Couche de service -> Couche de référentiel -> EF Cette partie fonctionne sur les modèles.

Vues <- Contrôleurs -> Couche de service Cette partie fonctionne sur les modèles de vue.

ÉDITER:

Exemple de flux pour / Orders / ByClient / 5 (nous voulons voir la commande pour un client spécifique):

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

Ceci est l'interface pour le service de commande:

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

Cette interface renvoie le modèle de vue:

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

Il s'agit de l'implémentation de l'interface. Il utilise des classes de modèles et un référentiel pour créer un modèle de vue:

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
la source
2
@Sam Striano: Comme vous pouvez le voir ci-dessus, mon IRepository renvoie IQueryable. Cela permet d'ajouter des conditions where supplémentaires et une exécution différée dans la couche de service, pas plus tard. Oui, j'utilise un assembly, mais toutes ces classes sont placées dans des espaces de noms différents. Il n'y a aucune raison de créer de nombreux assemblages dans de petits projets. La séparation des espaces de noms et des dossiers fonctionne bien.
LukLed
81
Pourquoi renvoyer des modèles de vue dans le service? le service n'est-il pas supposé émuler si vous aviez plusieurs clients (mobile / web)? Si tel est le cas, le modèle de vue peut différer des plates
Ryan
12
En accord avec @Ryan, la couche de service doit renvoyer un objet entité ou une collection d'objets entité (pas IQueryable). Ensuite, l'entité ui correspond à SomeViewModel par Automapper par exemple.
Eldar
5
@Duffp: Vous n'êtes pas obligé de créer un référentiel pour chaque entité. Vous pouvez utiliser la mise en œuvre générique et se lient IRepository<>à GenericRepository<>votre bibliothèque du CIO. Cette réponse est très ancienne. Je pense que la meilleure solution est de combiner tous les référentiels dans une classe appelée UnitOfWork. Il doit contenir un référentiel de chaque type et une méthode appelée SaveChanges. Tous les référentiels doivent partager un contexte EF.
LukLed le
2
au lieu de renvoyer viewmodel à partir de la couche de service, vous devriez retourner DTO et le convertir en viewModels à l'aide de l'Automapper. Parfois, ils sont identiques, quand ils ne le sont pas, vous serez reconnaissant d'avoir implémenté YGTNI "Vous allez à Need It "
hanzolo
41

Comme l'a dit Carnotaurus, le référentiel est responsable du mappage de vos données du format de stockage vers vos objets métier. Il doit gérer à la fois la lecture et l'écriture des données (suppression, mise à jour également) depuis et vers le stockage.

Le but de la couche de service, d'autre part, est d'encapsuler la logique métier dans un seul endroit pour promouvoir la réutilisation du code et la séparation des préoccupations. Ce que cela signifie généralement pour moi dans la pratique lors de la création de sites Asp.net MVC, c'est que j'ai cette structure

[Le contrôleur] appelle [Service (s)] qui appelle [référentiel (s)]

Un principe que j'ai trouvé utile est de garder la logique au minimum dans les contrôleurs et les référentiels.

Dans les contrôleurs, c'est parce que cela m'aide à me garder SEC Il est très courant que je doive utiliser le même filtrage ou la même logique ailleurs et si je l'ai placé dans le contrôleur, je ne peux pas le réutiliser.

Dans les référentiels, c'est parce que je veux pouvoir remplacer mon stockage (ou ORM) quand quelque chose de mieux se présente. Et si j'ai une logique dans le référentiel, je dois réécrire cette logique lorsque je change le référentiel. Si mon référentiel ne renvoie que IQueryable et que le service effectue le filtrage en revanche, je n'aurai besoin que de remplacer les mappages.

Par exemple, j'ai récemment remplacé plusieurs de mes référentiels Linq-To-Sql par EF4 et ceux où j'étais resté fidèle à ce principe pourraient être remplacés en quelques minutes. Là où j'avais une certaine logique, c'était plutôt une question d'heures.

Mikael Eliasson
la source
Je suis d'accord avec toi Mikael. En fait, j'ai appliqué le même scénario dans mon blog technique freecodebase.com et j'ai utilisé la première approche du code dans cette implémentation. Le code source peut également être téléchargé ici.
Toffee
J'ai étudié le sujet général de l'application d'un modèle de référentiel dans une application MVC existante. C'est un framework sur mesure avec un ORM de type Active Record et d'autres conventions Rails / Laravel, et a quelques problèmes d'architecture pour le travail que je fais actuellement. Une chose que je suis tombée sur est que les référentiels "ne doivent pas renvoyer des ViewModels, des DTO ou des objets de requête", mais plutôt des objets de référentiel. Je réfléchis à l'endroit où les services interagissent avec les objets du référentiel via des méthodes telles que onBeforeBuildBrowseQueryet peuvent utiliser le générateur de requêtes pour modifier la requête.
calligraphic-io
@Toffee, votre lien est cassé, pouvez-vous le mettre à jour, j'ai besoin du code source pour cette implémentation.
Hamza Khanzada
24

La réponse acceptée (et votée des centaines de fois) présente un défaut majeur. Je voulais le souligner dans le commentaire, mais cela sera simplement enterré là-bas dans 30 commentaires, donc soulignant ici.

J'ai repris une application d'entreprise qui a été construite de cette façon et ma première réaction a été WTH ? ViewModels dans la couche de service? Je ne voulais pas changer la convention parce que des années de développement s'étaient écoulées, alors j'ai continué à renvoyer ViewModels. Boy, cela s'est transformé en cauchemar lorsque nous avons commencé à utiliser WPF. Nous (l'équipe de développeurs) disions toujours: quel ViewModel? Le vrai (celui que nous avons écrit pour le WPF) ou celui des services? Ils ont été écrits pour une application Web et avaient même l' indicateur IsReadOnly pour désactiver la modification dans l'interface utilisateur. Majeur, défaut majeur et tout cela à cause d'un seul mot: ViewModel !!

Avant de faire la même erreur, voici quelques autres raisons en plus de mon histoire ci-dessus:

Renvoyer un ViewModel à partir de la couche de service est un énorme non non. C'est comme dire:

  1. Si vous souhaitez utiliser ces services, il vaut mieux utiliser MVVM et voici le ViewModel que vous devez utiliser. Aie!

  2. Les services supposent qu'ils seront affichés quelque part dans une interface utilisateur. Que faire s'il est utilisé par une application non UI telle que des services Web ou des services Windows?

  3. Ce n'est même pas un vrai ViewModel. Un vrai ViewModel a une observabilité, des commandes, etc. C'est juste un POCO avec un mauvais nom. (Voir mon histoire ci-dessus pour savoir pourquoi les noms sont importants.)

  4. L'application consommatrice est mieux une couche de présentation (les ViewModels sont utilisés par cette couche) et elle comprend mieux C #. Un autre Aïe!

S'il vous plaît, ne faites pas ça!

CodageYoshi
la source
3
Je devais juste commenter cela bien que je sache que cela n'ajoute rien à la discussion: "C'est juste un POCO avec un mauvais nom." << - Ce serait joli sur un T-shirt! :) :)
Mephisztoe
8

Habituellement, un référentiel est utilisé comme échafaudage pour peupler vos entités - une couche de service sortira et émettra une demande. Il est probable que vous placiez un référentiel sous votre couche de service.

CarneyCode
la source
Donc, dans une application ASP.NET MVC utilisant EF4, peut-être quelque chose comme ceci: SQL Server -> EF4 -> Repository -> Service Layer -> Model -> Controller et vice-versa?
Sam le
1
Ouais, votre référentiel pourrait être utilisé pour obtenir des entités légères d'EF4; et votre couche de service pourrait être utilisée pour les renvoyer à un gestionnaire de modèle spécialisé (modèle dans votre scénario). Le contrôleur appellerait votre gestionnaire de modèles spécialisé pour faire cela ... Jetez un coup d'œil à mon blog pour Mvc 2/3. J'ai des diagrammes.
CarneyCode
Juste pour clarifier: EF4 dans votre scénario est l'endroit où Model est sur mes diagrammes et Model dans votre scénario sont des gestionnaires de modèles spécialisés dans mes diagrammes
CarneyCode
5

La couche de référentiel est implémentée pour accéder à la base de données et permet d'étendre les opérations CRUD sur la base de données. Alors qu'une couche de service consiste en la logique métier de l'application et peut utiliser la couche référentiel pour implémenter une certaine logique impliquant la base de données. Dans une application, il est préférable d'avoir une couche de référentiel et une couche de service distinctes. Le fait d'avoir des couches de référentiel et de service séparées rend le code plus modulaire et dissocie la base de données de la logique métier.

Akshay
la source