Bonjour à tous,

Je planche actuellement sur un projet et je suis devant un choix à faire concernant l'un de mes models, sauf que je sèche. N'ayant pas trouvé de retour d'expérience sur ce sujet, je me tourne vers vous.

Le problème est le suivant :

  • Nous avons un modèle club qui a une ou plusieurs équipes.
  • Lorsque le club est créé, une équipe "par défaut" est créé, puisqu'un club possède forcément une équipe au minimum.
  • Il n'y a pas de limite maximale au nombre d'équipes.
  • Par défaut un club comporte des éléments (blason, nom, adresse, etc) et les équipes en possèdent d'autres (division, effectif, sponsor, etc)

Face à cette situation, nous sommes donc partis sur un model Club hasMany Team, tout simplement, où le club possède les champs propres au club et les team possèdent le reste.

Le souci qui se pose, c'est que la plupart des champs d'une équipe sont redondants. Généralement toutes les équipes du club possèdent le même sponsor, l'effectif de chaque équipe est le même sauf celui de la dernière, et ainsi de suite.

Niveau back-office, il était fastidieux pour les propriétaires du club d'ajouter à chaque fois la même information pour chacune des équipes ; et également dans le cas de mise à jour, un changement de sponsor par exemple, il était ennuyeux de devoir mettre à jour chaque équipe.
Pour pallier à ça, nous avons donc mis en place un système d'héritage :

  • Tous les champs de l'équipe 1 doivent forcément être renseignés
  • A partir de l'équipe 2, les champs vides héritent des propriétés de l'équipe précédente, donc la 2 hérite de la 1, la 3 de la 2, etc...

Mais cela nous pose actuellement des difficultés et pas mal de questions puisque :

  • soit on enregistre les valeurs de l'héritage au moment de la création ou de la mise à jour : dans ce cas de figure, on stocke beaucoup de données identiques en BDD, et dans le cas d'une mise à jour d'une équipe, ça complique la chose puisqu'on ne pourra pas update les équipes qui partage les mêmes propriétés celles-ci ayant alors un sponsors différent, par exemple. A oublier donc.
  • soit on fait les choses "à plat", et quand on récupère l'équipe 7, si elle a des champs vide on récupère également la 6 pour avoir les bonnes données. Le souci c'est que dans la plupart des cas, on se retrouve quand on récupère une équipe à récupérer finalement les propriétés de toutes celles qu'il y a avant. Aujourd'hui c'est ce qu'on a en place, mais c'est gourmand et pas optimal du tout.

Du coup, nous avons réfléchi aux pistes suivantes :

  • soit on fait des HABTM dans tous les sens, pour les sponsors, pour les effectifs, et pour plus ou moins tout, en liant les à l'équipe et aux équipes inférieures sauf si une liaison est déjà établie dans les équipes inférieures. Ca demande pas mal de travail dans le beforeSave() mais ça devrait pouvoir se faire.
  • soit on gère ça avec une seule table "scallable". Je m'explique : on a le club et on lui ajoute un champ "équipes", administrable en back-office qui permet de spécifier le nombre d'équipe (et d'en ajouter via un bouton qui incrémente la valeur, par exemple). Tous les champs qui composent actuellement le model Team passent dans le model Club, et dedans on va stocker des objets/tableaux JSON contenant les données sous forme d'héritage.

Je ne sais pas si je me fais bien comprendre, mais par exemple dans le cas d'un club qui a 6 équipes, les effectifs pourraient ressembler à ça :

{  
   "effectif":  
      {  
         "1":"23",
         "2":"",
         "3":"",
         "4":"14",
         "5":"",
         "6":""
      }
   ]
}

L'avantage c'est que l'on conserve la logique d'héritage et que l'on stocke tout dans un seul modèle. Fini les find qui vont appeler 6 équipes pour être certains d'avoir toutes les données de la sixième.
Autre avantage, c'est que c'est modulaire. On peut ajouter un champ à la volée, ce qui créera un champ avec une classe similaire (effectif en théorie), et un id différent (effectif-7). Ensuite au parsing, on a simplement prendre les valeurs des différents champs pour les remettre dans un objet JSON et hop, c'est bon pour l'update.

C'est une idée simplement, mais elle me semble plus simple et demande moins de dev que des HABTM pour chacun des champs, avec les tables de liaison que ça implique, les règles de vérification dans le beforeSave, etc.

Après, c'est peut être pas top point de optimisation, et effectivement, on récupère les données des autres équipes quand on veut en afficher une, mais ceci dans une seule requête. Et je pense que l'un dans l'autre, stocker un JSON compressé dans un champ text demande certainement moins de ressources qu'une dizaine de tables de liaison.

Voila, j'espère avoir des conseils sur la question.

Merci d'avance ;)

10 réponses


Hello,
Bon, pour être franc, j'ai pas trop bien compris le problème. :D
Mais je vais me baser sur ça :

Le problème est le suivant : 
- Nous avons un modèle club qui a une ou plusieurs équipes.
- Lorsque le club est créé, une équipe "par défaut" est créé, puisqu'un club possède forcément une équipe au minimum.
- Il n'y a pas de limite maximale au nombre d'équipes.
- Par défaut un club comporte des éléments (blason, nom, adresse, etc) et les équipes en possèdent d'autres (division, effectif, sponsor, etc)
Face à cette situation, nous sommes donc partis sur un model Club hasMany Team, tout simplement, où le club possède les champs propres au club et les team possèdent le reste.
Le souci qui se pose, c'est que la plupart des champs d'une équipe sont redondants. Généralement toutes les équipes du club possèdent le même sponsor, l'effectif de chaque équipe est le même sauf celui de la dernière, et ainsi de suite.

Dans ce cas, si toutes les équipes d'un même club on le même sponsor, pourquoi ne pas mettre la colonne sponsor sur le club ? Et pareil pour tout les autres champs redondants.
Ceci implique d'être sûr à 100% que toutes les équipes du même club on le même sponsor.
Si ce n'est pas le cas, dans ce cas il y a pas trop de solution à mes yeux, il faut gérer ça dans le Model Team avec une colonne dédié.
Et si tu veut par exemple modifier le nom du sponsor de toutes les Teams, pourquoi ne pas créer une table Sponsor qui hasMany Team et où Team hasOne Sponsor ?

La j'ai prit l'exemple du sponsor mais sa peut s'appliquer à tout les fields redondants.
Si j'ai mal compris ta question, essaye de m'expliquer plus en détails. ^^

Pakito
Auteur

Si on était sûr à 100% on aurait pas besoin de se prendre la tête. Là le fait est que dans une large majorité des cas, toutes les équipes d'un même club partagent le même sponsor mais qu'il arrive que non. Et c'est pareil pour les autres champs.

On a pensé à faire des team hasOne sponsor, team hasOne effectif, etc, mais à ce moment là le souci qui se pose c'est l'héritage : en modifiant le sponsor de l'équipe 3, ça ne modifiera pas le sponsor des équipes 4 et 5 si celui-ci est vide, puisque celui-ci n'est pas vide et sera set avec la valeur de l'ancien sponsor de l'équipe 3 fraîchement modifié.

En fait, c'est surtout la souplesse que l'on recherche, donc soit on part sur du HABTM en liant le club, qui fait référence, et les équipes indépendamment, et si une équipe a les champs vides, c'est le club qui prend le relais, mais du coup on perd le principe d'héritage entre équipes (ou il faut bidouiller ça), soit on part sur des tableaux JSON qui semblent en l'état actuel des choses la solution la plus souple. Mais la question que je me pose c'est sur les perfs, puisque ça demande du json_decode à chaque lecture.

J'ai pour mémoire qu'une bonne partie de la config de Wordpress et des plugins sont écris comme ça en BDD dans la table options. Donc à priori, c'est exploitable. Et à vrai dire c'est en pensant à WP que cette solution m'est venue.

On a pensé à faire des team hasOne sponsor, team hasOne effectif, etc, mais à ce moment là le souci qui se pose c'est 
l'héritage : en modifiant le sponsor de l'équipe 3, ça ne modifiera pas le sponsor des équipes 4 et 5 si celui-ci est 
vide, puisque celui-ci n'est pas vide et sera set avec la valeur de l'ancien sponsor de l'équipe 3 fraîchement modifié.

Ba ça va dépendre de comment tu modifies tout ça. Tu peut très bien, en modifiant par exemple le sponsor de l'équipe 3 modifie aussi celui de l'équipe 4. Le tout est de faire les bonnes requêtes avec les bonnes conditions et avec un updateAll sa va se faire tout seul.

Sponsor
id name etc
1 Sponsor1
2 Sponsor2
Club
id name etc
1 Club1
2 Club2
Team
id club_id sponsor_id name
1 1 1 Team1 du Club 1
2 1 2 Team2 du Club 1
3 2 0 Team1 du Club 2 //Par exemple, celle-ci n'a pas de sponsor

Quand au perf, tu fait un système de cache sur tes selects, et tu modifie/delete le cache uniquement quand tu modifie la Team et/ou le Club. La fonction Cache::remember de cakephp est juste parfaite pour faire se genre de chose. Mais là ton système d'héritage me parait assez bizarre.

Pakito
Auteur
Ba ça va dépendre de comment tu modifies tout ça. Tu peut très bien, en modifiant par exemple le sponsor
de l'équipe 3 modifie aussi celui de l'équipe 4.

Le souci c'est qu'il ne faut modifier la valeur du sponsor de l'équipe 4 que si il est vide (enfin actuellement c'est comme ça qu'on le gère), puisque l'équipe 4 peut avoir un sponsor différent de l'équipe 3, et donc n'hérite plus de celui de l'équipe 3.
Cela voudrait dire qu'au moment du save, alors qu'on vient de modifier le sponsor de l'équipe 3, il faudrait connaitre l'ancienne valeur du sponsor pour le comparer à celui de l'équipe 4. Du coup, ça coince. Ou alors il faut tout stocker en session quand on est sur l'edit, pour pouvoir comparer ensuite. Mais je trouve ça assez lourd et peu modulable.
(il manque vraiment un snippet citation sur le forum !)

Avec tes liaisons, je vois l'idée et elle rejoint la piste que j'énonçais dans mon premier message pour du HABTM. Ou du moins du hasOne.
Le souci que je vois avec ton modèle, c'est que si j'ai des données comme celles-ci :

Team
id club_id sponsor_id name
1 1 1 Team1 du Club 1
2 1 2 Team2 du Club 1
3 1 0 Team3 du Club 2
// La team 3 du club 1 a le même sponsor que la team 2, par héritage

Si je vais sur le détail de la team 3, je fais mon find, tout va bien, mais je n'ai pas le sponsor.
Je dois donc faire un autre find pour récupérer le sponsor de la team 2. Et je l'ai, je l'affiche. C'est pas dramatique.

Mais admettons que j'aille sur le détail de la team 17 et que toutes les teams de 3 à 17 n'aient aucun sponsor parce qu'elles héritent de celui de la team 2, je ne vais pas faire 16 find pour récupérer le sponsor.
Après il y a moyen de récupérer ça avec une condition du type :

find first WHERE sponsor > 0 AND team_rank < $this->Team->rank ORDER BY team_rank DESC

(en considérant que le team_rank soit le rang de l'équipe, tu vois l'idée) : du coup on cherche la team de rank supérieur (donc avec un chiffre plus petit) la plus proche ayant un sponsor.

Le souci, c'est que la team 17 peut également hériter d'un effectif, qui n'est pas forcément celui de la team 2 et qui a pu être spécifié plus loin dans la chaîne. Et également d'autres paramètres (une petite dizaine en tout).

On a également tenté de retourner toutes les teams d'un même club pour être certain d'avoir toutes les données d'un coup, mais les requêtes peuvent être assez longues avec ce process. Surtout si on a un contain derrière.

Du coup, je me dis que quitte à tout récupérer d'un coup, autant que ce soit directement au niveau du club, de manière optimisée, modulable et souple. Donc du JSON, à la Wordpress.

Le coup du cache est prévu, et les teams ne change au final que peu, normalement à l'intersaison et à la trêve hivernale. Mais dans l'idée, ce comportement on pourrait l'étendre à d'autres ensembles de models, donc on veut être paré.

Et j'ai conscience que c'est bizarre comme principe d'héritage. Mais si ça ne l'était pas, je ne demanderai pas d'aide ! ;)

Si je vais sur le détail de la team 3, je fais mon find, tout va bien, mais je n'ai pas le sponsor.
Je dois donc faire un autre find pour récupérer le sponsor de la team 2. Et je l'ai, je l'affiche. C'est pas dramatique.

Justement, c'est là où je veux en venir.
Pourquoi récupérer le sponsor de la Team2 vue que la Team3 à pas de sponsor ? C'est ça en faite que je comprend pas trop.
Tu veux mettre un sponsor par "défaut" si la Team à pas de sponsor ? Si oui, dans ce cas met le sponsor par défaut au club de la Team; ou qlors un sponsor par défaut pour chaque Team : default_sponsor_id. Et si le sponsor_id = 0, alors tu prend le default_sponsor_id.

Pakito
Auteur

Toutes les teams ont des sponsors. S'il n'est pas renseigné dans la table, c'est qu'elle a le même sponsor que la team supérieure. Donc dans notre exemple, si le team 3 n'a pas de sponsor, c'est qu'elle a le même que la 2. Si il change pour la deux, il changera aussi pour la 3.

Dans ce cas, tu peux tester avec des sponsor_parent_id où l'id du parent = l'id de la Team au quelle elle hérite pour le sponsor.

Mais je comprends toujours pas pourquoi tu ne veux pas mettre l'ID du sponsor dans la Team 3 même si elle à le même sponsor que la Team2.
D'après ton premier message :

Le souci qui se pose, c'est que la plupart des champs d'une équipe sont redondants. Généralement toutes les équipes du club 
possèdent le même sponsor, l'effectif de chaque équipe est le même sauf celui de la dernière, et ainsi de suite.

C'est que tu as peur que sa prenne de la place car beaucoup de Team auront le même sponsor_id par exemple ? C'est juste des entiers, et dans une BDD sa prend rien comme place.

Pakito
Auteur

Je ne souhaite pas mettre l'ID du sponsor de la team 3 si elle a le même que la deux parce que :

  • ça devient une tannée à gérer en update : si je modifie la deux, comment savoir si je dois aussi modifier la 3 ; le champ n'est pas vide, mais je viens de le modifier pour la 2, comment le comparer à la 3 alors ? Et à la 4, 5, etc ... jusqu'à potentiellement une infinité.
  • On est loin de n'avoir que le sponsor ou l'effectif qui sont des entiers, comme je le disais précédemment, on a une dizaine de champ, dont certains sont des text ou des longtext. C'est déjà moins drôle à doublonner.

En fait j'ai surtout l'impression que tu ne vois pas très bien la structure ni les règles qui la régissent. Je me suis peut-être mal exprimé quelque part.

Salut Pakito,
J'ai pas tout suivi car le topic est assez long et vu l'heure c'est difficile de se plonger a fond dedans...
Mais pour ton histoire de sponsor, pourquoi ne pas laisser le club ajouter des sponsors donc en rajoutant un champ club_id dans ta table sponsors pour lié un sponsor à un club, puis ensuite le laisser sélectionner via un menu déroulant le sponsor lors de la création de l'équipe ?

Pakito
Auteur

Parce que ça n'est pas adapté au besoin. Après, si tu n'as pas tout lu, tu ne l'as peut-être pas compris.
Je pense de toute façon qu'on va faire des tests grandeur nature pour tester l'une et l'autre des pistes que j'ai avancé.