Donc, une situation que je rencontre assez souvent est celle où mes modèles commencent à:
- Devenez des monstres avec des tonnes et des tonnes de méthodes
OU
- Vous permettre de leur passer des morceaux de SQL, afin qu'ils soient suffisamment flexibles pour ne pas nécessiter un million de méthodes différentes
Par exemple, disons que nous avons un modèle de "widget". Nous commençons par quelques méthodes de base:
- get ($ id)
- insérer ($ record)
- mise à jour ($ id, $ record)
- supprimer ($ id)
- getList () // récupère une liste de Widgets
C'est très bien et dandy, mais alors nous avons besoin de quelques rapports:
- listCreatedBetween ($ start_date, $ end_date)
- listPurchasedBetween ($ start_date, $ end_date)
- listOfPending ()
Et puis le reporting commence à devenir complexe:
- listPendingCreatedBetween ($ start_date, $ end_date)
- listForCustomer ($ customer_id)
- listPendingCreatedBetweenForCustomer ($ customer_id, $ start_date, $ end_date)
Vous pouvez voir où cela se développe ... finalement, nous avons tellement d'exigences de requête spécifiques que j'ai besoin d'implémenter des tonnes et des tonnes de méthodes, ou une sorte d'objet "requête" que je peux transmettre à une seule -> requête (requête $ query), méthode ...
... ou mordez simplement la balle et commencez à faire quelque chose comme ceci:
- list = MyModel-> query ("start_date> X AND end_date <Y AND en attente = 1 AND customer_id = Z")
Il y a un certain attrait à avoir une seule méthode comme celle-ci au lieu de 50 millions d'autres méthodes plus spécifiques ... mais il semble parfois "mal" de bourrer une pile de ce qui est fondamentalement SQL dans le contrôleur.
Existe-t-il une «bonne» façon de gérer des situations comme celle-ci? Semble-t-il acceptable de bourrer des requêtes comme celle-ci dans une méthode générique -> query ()?
Y a-t-il de meilleures stratégies?
la source
Réponses:
Les modèles d'architecture d'application d'entreprise de Martin Fowler décrivent un certain nombre de paterns liés à l'ORM, y compris l'utilisation de l'objet de requête, ce que je suggère.
Les objets de requête vous permettent de suivre le principe de responsabilité unique, en séparant la logique de chaque requête en objets de stratégie gérés et gérés individuellement. Soit votre contrôleur peut gérer directement leur utilisation, soit déléguer cela à un contrôleur secondaire ou à un objet d'assistance.
En aurez-vous beaucoup? Certainement. Certains peuvent-ils être regroupés en requêtes génériques? Oui encore.
Pouvez-vous utiliser l'injection de dépendances pour créer les objets à partir de métadonnées? C'est ce que font la plupart des outils ORM.
la source
Il n'y a pas de bonne façon de procéder. Beaucoup de gens utilisent des ORM pour résumer toute la complexité. Certains des ORM les plus avancés traduisent les expressions de code en instructions SQL compliquées. Les ORM ont également leurs inconvénients, mais pour de nombreuses applications, les avantages l'emportent sur les coûts.
Si vous ne travaillez pas avec un ensemble de données massif, la chose la plus simple à faire est de sélectionner la table entière en mémoire et de filtrer en code.
Pour les applications de reporting internes, cette approche est probablement appropriée. Si l'ensemble de données est vraiment volumineux, vous commencerez à avoir besoin de nombreuses méthodes personnalisées ainsi que d'index appropriés sur votre table.
la source
Certains ORM vous permettent de construire des requêtes complexes à partir de méthodes de base. Par exemple
est une requête parfaitement valide dans l' ORM Django .
L'idée est que vous disposez d'un générateur de requêtes (dans ce cas
Purchase.objects
) dont l'état interne représente des informations sur une requête. Des méthodes telles queget
,filter
,exclude
,order_by
sont valides et renvoient un nouveau générateur de requêtes avec un état mis à jour. Ces objets implémentent une interface itérable, de sorte que lorsque vous les parcourez, la requête est exécutée et vous obtenez les résultats de la requête construite jusqu'à présent. Bien que cet exemple soit tiré de Django, vous verrez la même structure dans de nombreux autres ORM.la source
Il y a une troisième approche.
Votre exemple spécifique montre une croissance exponentielle du nombre de méthodes nécessaires à mesure que le nombre de fonctionnalités requises augmente: nous voulons pouvoir offrir des requêtes avancées, en combinant toutes les fonctionnalités de requête ... si nous le faisons en ajoutant des méthodes, nous avons une méthode pour un requête de base, deux si nous ajoutons une fonctionnalité facultative, quatre si nous en ajoutons deux, huit si nous en ajoutons trois, 2 ^ n si nous ajoutons n fonctionnalités.
C'est évidemment impossible à maintenir au-delà de trois ou quatre fonctionnalités, et il y a une mauvaise odeur de beaucoup de code étroitement lié qui est presque copié-collé entre les méthodes.
Vous pouvez éviter cela en ajoutant un objet de données pour contenir les paramètres et avoir une seule méthode qui construit la requête en fonction de l'ensemble de paramètres fourni (ou non fourni). Dans ce cas, l'ajout d'une nouvelle fonctionnalité telle qu'une plage de dates est aussi simple que l'ajout de setters et de getters pour la plage de dates à votre objet de données, puis l'ajout d'un peu de code dans lequel la requête paramétrée est construite:
... et où les paramètres sont ajoutés à la requête:
Cette approche permet une croissance de code linéaire à mesure que des fonctionnalités sont ajoutées, sans avoir à autoriser des requêtes arbitraires et non paramétrables.
la source
Je pense que le consensus général est de garder autant d'accès aux données que possible dans vos modèles dans MVC. L'un des autres principes de conception consiste à déplacer certaines de vos requêtes plus génériques (celles qui ne sont pas directement liées à votre modèle) à un niveau supérieur et plus abstrait où vous pouvez également autoriser son utilisation par d'autres modèles. (Dans RoR, nous avons quelque chose appelé framework). Il y a aussi une autre chose que vous devez considérer et c'est la maintenabilité de votre code. Au fur et à mesure que votre projet se développe, si vous avez accès aux données dans les contrôleurs, il deviendra de plus en plus difficile de le retrouver (nous sommes actuellement confrontés à ce problème dans un projet énorme). pourrait finir par interroger les tables. (Cela peut également conduire à une réutilisation du code qui est à son tour bénéfique)
la source
Votre interface de couche de service peut avoir de nombreuses méthodes, mais l'appel à la base de données peut n'en avoir qu'une.
Une base de données comporte 4 opérations majeures
Une autre méthode facultative peut consister à exécuter une opération de base de données qui ne relève pas des opérations de base de la base de données. Appelons cela Exécuter.
L'insertion et les mises à jour peuvent être combinées en une seule opération, appelée Enregistrer.
Beaucoup de vos méthodes sont des requêtes. Vous pouvez donc créer une interface générique pour répondre à la plupart des besoins immédiats. Voici un exemple d'interface générique:
L'objet de transfert de données est générique et contiendrait tous vos filtres, paramètres, tri, etc. La couche de données serait chargée d'analyser et d'extraire cela et de configurer l'opération dans la base de données via des procédures stockées, sql paramétré, linq etc. Ainsi, SQL n'est pas transmis entre les couches. C'est généralement ce que fait un ORM, mais vous pouvez lancer le vôtre et avoir votre propre mappage.
Donc, dans votre cas, vous avez des widgets. Les widgets implémenteraient l'interface IPOCO.
Donc, dans votre modèle de couche de service,
getList().
Aurait besoin d'une couche de mappage pour gérer la transformation
getList
enet vice versa. Comme d'autres l'ont mentionné, cela se fait parfois via un ORM, mais finalement vous vous retrouvez avec beaucoup de code de type passe-partout, surtout si vous avez des centaines de tables. L'ORM crée par magie du SQL paramétré et l'exécute sur la base de données. Si vous lancez le vôtre, en plus dans la couche de données elle-même, des mappeurs seraient nécessaires pour configurer le SP, linq etc. (Fondamentalement, le sql va à la base de données).
Comme mentionné précédemment, le DTO est un objet composé par composition. Peut-être que l'un des objets qu'il contient est un objet appelé QueryParameters. Ce seraient tous les paramètres de la requête qui seraient configurés et utilisés par la requête. Un autre objet serait une liste d'objets retournés à partir de requêtes, mises à jour, ext. Ceci est la charge utile. Si ce cas, la charge utile serait une liste de liste de widgets.
Donc, la stratégie de base est:
Dans votre cas, je pense que le modèle pourrait avoir de nombreuses méthodes, mais de manière optimale, vous voulez que l'appel à la base de données soit générique. Vous vous retrouvez toujours avec beaucoup de code de mappage passe-partout (en particulier avec les SP) ou du code ORM magique qui crée dynamiquement le SQL paramétré pour vous.
la source