Comment créer un meilleur code OO dans une application basée sur une base de données relationnelle où la base de données est mal conçue

19

J'écris une application Web Java qui se compose principalement d'un tas de pages similaires dans lesquelles chaque page a plusieurs tables et un filtre qui s'applique à ces tables. Les données de ces tables proviennent d'une base de données SQL.

J'utilise myBatis comme ORM, ce qui n'est peut-être pas le meilleur choix dans mon cas, car la base de données est mal conçue et mybatis est un outil plus orienté base de données.

Je constate que j'écris beaucoup de code en double car, en raison de la mauvaise conception de la base de données, je dois écrire différentes requêtes pour des choses similaires car ces requêtes peuvent être très différentes. Autrement dit, je ne peux pas facilement paramétrer les requêtes. Cela se propage dans mon code et au lieu de remplir des lignes sur des colonnes de ma table avec une simple boucle, j'ai du code comme:

obtenir A Data (p1, ..., pi);

obtenir les données B (p1, ..., pi);

obtenir les données C (p1, ..., pi);

obtenir des données D (p1, ..., pi); ...

Et cela explose rapidement lorsque nous avons différentes tables avec différentes colonnes.

Cela ajoute également à la complexité du fait que j'utilise "wicket", qui est en fait un mappage d'objets avec des éléments html dans la page. Donc mon code Java devient un adaptateur entre la base de données et le frontal, ce qui m'a fait créer beaucoup de câblage, du code passe-partout avec une logique entremêlée.

La solution correcte serait-elle d'envelopper les mappeurs ORM avec une extralayer qui présente une interface plus homogène avec la base de données ou existe-t-il une meilleure façon de gérer ce code spaghetti que j'écris?

EDIT: Plus d'informations sur la base de données

La base de données contient principalement des informations sur les appels téléphoniques. La mauvaise conception consiste en:

Tables avec un ID artificiel comme clé primaire qui n'a rien à voir avec la connaissance du domaine.

Pas de déclencheurs, de chèques ou de clés étrangères uniques.

Champs avec un nom générique qui correspondent à différents concepts pour différents enregistrements.

Les enregistrements qui peuvent être classés uniquement en croisant avec d'autres tables avec des conditions différentes.

Colonnes qui doivent être des nombres ou des dates stockées sous forme de chaînes.

Pour résumer, un design désordonné / paresseux tout autour.

DPM
la source
7
La correction de la conception de la base de données est-elle une option?
RMalke
1
Veuillez expliquer en quoi la base de données est mal conçue.
Tulains Córdova
@Renan Malke Stigliani Malheureusement non, car il existe un logiciel hérité qui en dépend, cependant j'ai reflété certaines tables avec une conception légèrement différente et les ai remplies, ce qui simplifie le code. Cependant, je ne suis pas fier de cela et je préfère ne pas dupliquer les tableaux sans discernement
DPM
1
Ce livre pourrait vous donner quelques eideas sur la façon dont vous pouvez commencer à corriger le problème de la base de données et garder le code hérité en marche: amazon.com/…
HLGEM
4
La plupart des problèmes que vous citez. . . ne le sont pas. L'utilisation de clés de substitution plutôt que de clés naturelles est en fait une recommandation assez standard de nos jours; pas du tout "mauvaise conception". L'absence de contraintes et l'utilisation de types de colonnes inappropriés est un meilleur exemple en ce qui concerne la "mauvaise conception", mais cela ne devrait pas du tout affecter votre code d'application (sauf si vous prévoyez d'abuser de ces problèmes?).
ruakh

Réponses:

53

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 Objectn'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).

Jimmy Hoffa
la source
4
Wow, quelle réponse extraordinairement complète. Je voterais favorablement plus d'une fois si SE seul me le permettait.
Marjan Venema
11
Quand sortira la version du film?
yannis
3
@JimmyHoffa Bravo monsieur !!! Je vais mettre cette réponse en signet et montrer à ma fille quand elle grandira.
Tombatron
4

Ma suggestion:

Créez des vues de base de données qui:

  1. Donnez des noms significatifs aux colonnes
  2. Faites le «croisement avec d'autres tables avec des conditions différentes» afin de pouvoir cacher cette complexité.
  3. Convertissez des nombres ou des dates stockés sous forme de chaînes en nombres et dates respectivement.
  4. Créez un caractère unique là où il n'y en a pas, selon certains critères.

L'idée est de créer une façade qui émule un meilleur design en plus de la mauvaise.

Ensuite, faites en sorte que l'ORM se rapporte à cette façade au lieu des vraies tables.

Cela ne simplifie cependant pas les insertions.

Tulains Córdova
la source
Utiliser les vues de base de données ressemble à une excellente idée et au plan d'action le plus élégant qui résume la laideur au niveau le plus bas, pour une raison que je n'avais pas envisagée. Je vous remercie.
DPM
3

Je peux voir comment votre schéma de base de données existant vous fait écrire du code et des requêtes plus spécifiques pour des tâches qui pourraient autrement être abstraites avec un schéma mieux conçu, mais cela ne devrait pas entraver votre capacité à écrire un bon code orienté objet.

  • N'oubliez pas les principes SOLIDES .
  • Écrivez du code qui peut facilement être testé à l'unité (ce qui passe souvent par les principes SOLID suivants).
  • Gardez votre logique métier distincte de votre logique d'affichage.
  • Lisez la documentation et les exemples d' Apache Wicket - ce framework peut probablement vous faire économiser plus de code standard que vous ne le pensez, alors apprenez à l'utiliser efficacement.
  • Conservez la logique qui doit gérer la base de données dans une couche distincte qui fournit une interface propre avec laquelle votre logique métier peut fonctionner. De cette façon, si vous (ou un futur responsable de maintenance) avez la possibilité d'améliorer le schéma, ils peuvent le faire sans trop de changements dans la logique métier.

Lorsque vous travaillez avec un schéma de base de données qui n'est pas parfait, il est facile de se plaindre de toutes les façons dont cela rend votre travail plus difficile, mais à un moment donné, vous devez mettre de côté ces plaintes et en tirer le meilleur parti.

Considérez-le comme une opportunité d'utiliser votre créativité pour écrire du code propre, réutilisable et facilement maintenable malgré le schéma imparfait.

Mike Partridge
la source
1

En répondant à votre question initiale sur un meilleur code orienté objet, je vous suggère d'utiliser des objets parlant SQL . ORM va intrinsèquement à l'encontre des principes orientés objet, car il opère sur un objet, et l'objet en POO est une entité autosuffisante, qui a toutes les ressources pour atteindre son objectif. Je suis sûr que cette approche pourrait simplifier votre code.

En parlant de l'espace problématique, c'est-à-dire de votre domaine, j'essaierais d'identifier les racines agrégées . Ce sont des limites de cohérence de votre domaine. Des limites qui doivent absolument conserver sa cohérence à tout moment. Les agrégats communiquent via des événements de domaine. Si vous avez un système assez grand, vous devriez probablement commencer à le diviser en sous-systèmes (appelez-le SOA, Microservice, Systèmes autonomes, etc.)

J'envisagerais également d'utiliser CQRS - cela peut grandement simplifier votre écriture et votre lecture. Assurez-vous de lire l' article d' Udi Dahan sur ce sujet.

Zapadlo
la source