L'orientation des objets est particulièrement utile car ces types de scénarios surviennent et vous donne des outils pour concevoir raisonnablement des abstractions qui vous permettent d'encapsuler la complexité.
La vraie question ici est, où encapsulez-vous cette complexité?
Permettez-moi donc de prendre un peu de recul et de parler de la «complexité» dont je parle ici. Votre problème (si je comprends bien, corrigez-moi si je me trompe) est un modèle de persistance qui n'est pas un modèle effectivement utilisable pour les tâches que vous devez effectuer avec les données. Il peut être efficace et utilisable pour d'autres tâches, mais pas pour vos tâches.
Alors, que faisons-nous lorsque nous avons des données qui ne présentent pas un bon modèle pour nos moyens?
Traduire. C'est la seule chose que vous puissiez faire. Cette traduction est la «complexité» dont je parle ci-dessus. Alors maintenant que nous acceptons de traduire le modèle, nous devons décider de quelques facteurs.
Faut-il traduire les deux directions? Les deux directions vont-elles être traduites de la même manière que dans:
(Tbl A, Tbl B) -> Obj X (lire)
Obj X -> (Tbl A, Tbl B) (écrire)
ou les activités d'insertion / mise à jour / suppression représentent-elles un type d'objet différent de sorte que vous lisez des données en tant qu'obj X, mais que les données sont insérées / mises à jour depuis obj y? Laquelle de ces deux façons vous souhaitez aller, ou si aucune mise à jour / insertion / suppression n'est possible sont des facteurs importants dans l' endroit où vous souhaitez mettre la traduction.
Où traduisez-vous?
Revenons à la première déclaration que j'ai faite dans cette réponse; OO vous permet d'encapsuler la complexité et ce à quoi je fais référence ici est le fait que non seulement vous devriez, mais vous devez encapsuler cette complexité si vous souhaitez vous assurer qu'elle ne s'échappe pas et ne s'infiltre pas dans tout votre code. Dans le même temps, il est important de reconnaître que vous ne pouvez pas avoir une abstraction parfaite, alors vous inquiétez moins de cela que d'en avoir une très efficace et utilisable.
Encore une fois maintenant; votre problème est: où mettez-vous cette complexité? Eh bien, vous avez le choix.
Vous pouvez le faire dans la base de données en utilisant des procédures stockées. Cela a souvent pour inconvénient de ne pas très bien jouer avec les ORM, mais ce n'est pas toujours vrai. Les procédures stockées offrent certains avantages, notamment les performances. Les procédures stockées peuvent cependant nécessiter beaucoup de maintenance, mais c'est à vous d'analyser votre scénario particulier et de dire si la maintenance sera plus ou moins importante que d'autres choix. Personnellement, je suis très habile avec les procédures stockées, et en tant que tel, ce fait de talent disponible réduit les frais généraux; jamais sous - estimer la valeur de prendre des décisions basées sur ce que vous ne connaissez. Parfois, la solution sous-optimale peut être plus optimale que la bonne solution car vous ou votre équipe savez comment la créer et la maintenir mieux que la solution optimale.
Les vues sont une autre option dans la base de données. Selon votre serveur de base de données, ceux-ci peuvent être hautement optimaux ou sous-optimaux ou même pas efficaces du tout, l'un des inconvénients peut être le temps de requête en fonction des options d'indexation disponibles dans votre base de données. Les vues deviennent un choix encore meilleur si vous n'avez jamais besoin de modifier les données (insérer / mettre à jour / supprimer).
En dépassant la base de données, vous disposez de l'ancienne veille d'utilisation du modèle de référentiel. Il s'agit d'une approche éprouvée qui peut être très efficace. Les inconvénients ont tendance à inclure la plaque de la chaudière, mais les référentiels bien factorisés peuvent éviter une certaine quantité de cela, et même lorsque ceux-ci entraînent des quantités malheureuses de la plaque de la chaudière, le référentiel a tendance à être un code simple, facile à comprendre et à entretenir, ainsi qu'à présenter une bonne API /abstraction. Les référentiels peuvent également être bons pour leur testabilité unitaire que vous perdez avec les options dans la base de données.
Il existe des outils comme le mappeur automatique qui peuvent rendre l'utilisation d'un ORM plausible où ils peuvent faire la traduction entre le modèle de base de données de orm en modèles utilisables, mais certains de ces outils peuvent être difficiles à maintenir / à comprendre se comportant davantage comme de la magie; bien qu'ils créent un minimum de code de surcharge entraînant moins de surcharge de maintenance lorsqu'ils sont bien compris.
Ensuite, vous vous éloignez de plus en plus de la base de données , ce qui signifie qu'il y aura de plus grandes quantités de code qui traiteront du modèle de persistance non traduit, ce qui sera vraiment désagréable. Dans ces scénarios, vous parlez de placer la couche de traduction dans votre interface utilisateur, ce qui semble être le cas maintenant. C'est généralement une très mauvaise idée et elle se dégrade terriblement avec le temps.
Maintenant, commençons à parler de fou .
Ce Object
n'est pas la seule abstraction globale qui existe. Il y a eu une profusion d'abstractions développées au cours des nombreuses années pendant lesquelles l'informatique a été étudiée et même avant cela à partir de l'étude des mathématiques. Si nous voulons commencer à faire preuve de créativité, commençons par parler des abstractions connues disponibles qui ont été étudiées.
Il y a le modèle d'acteur.C'est une approche intéressante car elle dit que tout ce que vous faites est d'envoyer des messages à un autre code qui délègue efficacement tout le travail à cet autre code, ce qui est très efficace pour encapsuler la complexité loin de tout votre code. Cela pourrait fonctionner dans la mesure où vous envoyez un message à un acteur disant «J'ai besoin que Obj X soit envoyé à Y» et que vous ayez un réceptacle en attente d'une réponse à l'emplacement Y qui traite ensuite Obj X. Vous pouvez même envoyer un message "J'ai besoin d'Obj X et de calculs Y, Z" et vous n'avez même pas besoin d'attendre; la traduction se produit de l'autre côté de cette passe de message et vous pouvez simplement continuer si vous n'avez pas besoin de lire son résultat. Cela peut être un léger abus du modèle d'acteur pour vos besoins, mais tout dépend;
Une autre limite d'encapsulation est les limites du processus. Ceux-ci peuvent être utilisés pour séparer la complexité très efficacement. Vous pouvez créer le code de traduction en tant que service Web où la communication est simple HTTP, en utilisant SOAP, REST, ou si vous voulez vraiment votre propre protocole (non suggéré). STOMP n'est pas tout à fait un mauvais protocole plus récent. Vous pouvez également utiliser un service démon normal avec un canal de mémoire publicisé système pour communiquer à nouveau très rapidement en utilisant le protocole que vous choisissez. Cela a en fait de très bons avantages:
- Vous pouvez exécuter plusieurs processus qui effectuent la traduction pour la prise en charge des versions plus anciennes et plus récentes en même temps, vous permettant de mettre à jour le service de traduction pour publier un modèle d'objet V2, puis de mettre à jour séparément le code consommateur pour travailler avec le nouvel objet. modèle.
- Vous pouvez faire des choses intéressantes comme épingler le processus à un cœur pour les performances, vous obtenez également une certaine sécurité en matière de sécurité dans cette approche en faisant que le seul processus en cours d'exécution avec les privilèges de sécurité pour toucher ces données.
- Vous obtiendrez une limite très forte lorsque vous parlerez de limites de processus qui resteront fixes, garantissant une fuite minimale de votre abstraction pendant longtemps, car l'écriture de code dans l'espace de traduction ne pourra pas être appelée en dehors de l'espace de traduction car elles ne partagera pas la portée du processus, garantissant un ensemble fixe de scénarios d'utilisation par contrat.
- La possibilité de mises à jour asynchrones / non bloquantes étant plus simple.
Les inconvénients sont évidemment plus de maintenance que ce qui est généralement nécessaire, les frais généraux de communication affectant les performances et la maintenance.
Il existe une grande variété de façons d'encapsuler la complexité qui peut permettre à cette complexité d'être placée dans des endroits de plus en plus étranges et curieux de votre système. En utilisant des formes de fonctions d'ordre supérieur (souvent simulées en utilisant un modèle de stratégie ou diverses autres formes étranges de modèles d'objet), vous pouvez faire des choses très intéressantes.
C'est vrai, commençons à parler d'une monade. Vous pouvez créer cette couche de traduction d'une manière très indépendante de petites fonctions spécifiques qui effectuent les traductions indépendantes nécessaires, mais masquer toutes ces fonctions de traduction de façon à ce qu'elles ne soient pas visibles afin qu'elles soient difficilement accessibles au code extérieur. Cela a l'avantage de réduire leur dépendance, ce qui leur permet de changer facilement sans affecter beaucoup de code externe. Vous créez ensuite une classe qui accepte des fonctions d'ordre supérieur (fonctions anonymes, fonctions lambda, objets de stratégie, mais vous devez les structurer) qui fonctionnent sur l'un des beaux objets de type de modèle OO. Vous laissez ensuite le code sous-jacent qui accepte ces fonctions effectuer l'exécution littérale à l'aide des méthodes de traduction appropriées.
Cela crée une frontière où toute la traduction existe non seulement de l'autre côté de la frontière loin de tout votre code; il n'est utilisé que de ce côté, ce qui permet au reste de votre code de ne rien savoir à ce sujet autre que l'emplacement du point d'entrée pour cette limite.
Ok, ouais ça parle vraiment de fou, mais qui sait; vous pourriez être aussi fou (sérieusement, n'entreprenez pas de monades avec un taux de folie inférieur à 88%, il y a un risque réel de blessures corporelles).