Je travaille sur un grand projet logiciel hautement personnalisé pour divers clients du monde entier. Cela signifie que nous avons peut-être 80% de code qui est commun entre les différents clients, mais aussi beaucoup de code qui doit changer d'un client à l'autre. Dans le passé, nous avons fait notre développement dans des référentiels séparés (SVN) et lorsqu'un nouveau projet a commencé (nous avons peu de clients, mais de gros clients), nous avons créé un autre référentiel basé sur le projet précédent qui a la meilleure base de code pour nos besoins. Cela a fonctionné dans le passé, mais nous avons rencontré plusieurs problèmes:
- Les bogues corrigés dans un référentiel ne sont pas corrigés dans d'autres référentiels. Cela peut être un problème d'organisation, mais je trouve difficile de corriger et de corriger un bogue dans 5 référentiels différents, en gardant à l'esprit que l'équipe qui gère ce référentiel pourrait être dans une autre partie du monde et nous n'avons pas leur environnement de test , ne connaissent ni leur emploi du temps ni leurs exigences (un "bug" dans un pays peut être une "fonctionnalité" dans un autre).
- Les fonctionnalités et les améliorations apportées à un projet, qui pourraient également être utiles pour un autre projet, sont perdues ou si elles sont utilisées dans un autre projet, elles provoquent souvent de gros maux de tête en les fusionnant d'une base de code à une autre (car les deux branches peuvent avoir été développées indépendamment pendant un an ).
- Les refactorisations et les améliorations de code apportées dans une branche de développement sont perdues ou causent plus de tort que de bien si vous devez fusionner tous ces changements entre les branches.
Nous discutons maintenant de la façon de résoudre ces problèmes et, jusqu'à présent, nous avons proposé les idées suivantes pour résoudre ce problème:
Gardez le développement dans des branches distinctes, mais organisez mieux en ayant un référentiel central où les corrections de bogues générales sont fusionnées et en faisant en sorte que tous les projets fusionnent les modifications de ce référentiel central dans les leurs sur une base régulière (par exemple quotidienne). Cela nécessite une grande discipline et beaucoup d'efforts pour fusionner entre les branches. Je ne suis donc pas convaincu que cela fonctionnera et nous pouvons garder cette discipline, surtout lorsque le temps presse.
Abandonnez les branches de développement distinctes et disposez d'un référentiel de code central où tout notre code vit et faites notre personnalisation en ayant des modules enfichables et des options de configuration. Nous utilisons déjà des conteneurs d'Injection de dépendances pour résoudre les dépendances dans notre code et nous suivons le modèle MVVM dans la plupart de notre code pour séparer proprement la logique métier de notre interface utilisateur.
La deuxième approche semble être plus élégante, mais nous avons de nombreux problèmes non résolus dans cette approche. Par exemple: comment gérer les modifications / ajouts dans votre modèle / base de données. Nous utilisons .NET avec Entity Framework pour avoir des entités fortement typées. Je ne vois pas comment nous pouvons gérer les propriétés requises pour un client mais inutiles pour un autre client sans encombrer notre modèle de données. Nous pensons résoudre ce problème dans la base de données en utilisant des tables satellites (ayant des tables séparées où nos colonnes supplémentaires pour une entité spécifique vivent avec un mappage 1: 1 avec l'entité d'origine), mais ce n'est que la base de données. Comment gérez-vous cela dans le code? Notre modèle de données réside dans une bibliothèque centrale que nous ne pourrions pas étendre pour chaque client en utilisant cette approche.
Je suis sûr que nous ne sommes pas la seule équipe aux prises avec ce problème et je suis choqué de trouver si peu de matériel sur le sujet.
Mes questions sont donc les suivantes:
- Quelle expérience avez-vous avec un logiciel hautement personnalisé, quelle approche avez-vous choisie et comment cela a-t-il fonctionné pour vous?
- Quelle approche recommandez-vous et pourquoi? Est-ce qu'il y a une meilleure approche?
- Y a-t-il de bons livres ou articles sur le sujet que vous pouvez recommander?
- Avez-vous des recommandations spécifiques pour notre environnement technique (.NET, Entity Framework, WPF, DI)?
Modifier:
Merci pour toutes les suggestions. La plupart des idées correspondent à celles que nous avions déjà dans notre équipe, mais il est vraiment utile de voir l'expérience que vous avez eue avec elles et des conseils pour mieux les mettre en œuvre.
Je ne suis toujours pas sûr de la direction que nous prendrons et je ne prends pas de décision (seul), mais je transmettrai cela dans mon équipe et je suis sûr que ce sera utile.
Pour le moment, le ténor semble être un référentiel unique utilisant divers modules spécifiques au client. Je ne suis pas sûr que notre architecture soit à la hauteur ou combien nous devons investir pour l'adapter, donc certaines choses peuvent vivre dans des référentiels séparés pendant un certain temps, mais je pense que c'est la seule solution à long terme qui fonctionnera.
Merci encore pour toutes les réponses!
Réponses:
Il semble que le problème fondamental ne soit pas seulement la maintenance du référentiel de code, mais le manque d'architecture appropriée .
Un cadre ou une bibliothèque standard englobe le premier, tandis que le second serait implémenté comme des modules complémentaires (plugins, sous-classes, DI, tout ce qui a du sens pour la structure du code).
Un système de contrôle des sources qui gère les succursales et le développement distribué aiderait probablement aussi; Je suis fan de Mercurial, d'autres préfèrent Git. Le cadre serait la branche principale, chaque système personnalisé serait des sous-branches, par exemple.
Les technologies spécifiques utilisées pour implémenter le système (.NET, WPF, peu importe) sont largement sans importance.
Ce n'est pas facile , mais c'est essentiel pour la viabilité à long terme. Et bien sûr, plus vous attendez, plus la dette technique à laquelle vous devrez faire face est importante.
Vous pouvez trouver le livre Architecture logicielle: principes et modèles organisationnels utile.
Bonne chance!
la source
Une entreprise pour laquelle j'ai travaillé avait le même problème, et l'approche pour y remédier était la suivante: un cadre commun pour tous les nouveaux projets a été créé; cela inclut toutes les choses qui doivent être les mêmes dans chaque projet. Ex: outils de génération de formulaires, exportation vers Excel, journalisation. Des efforts ont été faits pour s'assurer que ce cadre commun est seulement amélioré (lorsqu'un nouveau projet a besoin de nouvelles fonctionnalités), mais jamais bifurqué.
Sur la base de ce cadre, le code spécifique au client a été conservé dans des référentiels séparés. Lorsqu'elles sont utiles ou nécessaires, les corrections de bogues et les améliorations sont copiées-collées entre les projets (avec toutes les mises en garde décrites dans la question). Cependant, des améliorations utiles à l'échelle mondiale entrent dans le cadre commun.
Avoir tout dans une base de code commune pour tous les clients présente certains avantages, mais d'un autre côté, la lecture du code devient difficile lorsqu'il existe d'innombrables
if
s pour faire en sorte que le programme se comporte différemment pour chaque client.EDIT: Une anecdote pour rendre cela plus compréhensible:
la source
L'un des projets sur lesquels j'ai travaillé a pris en charge plusieurs plates-formes (plus de 5) sur un grand nombre de versions de produits. Beaucoup des défis que vous décrivez étaient des choses auxquelles nous avons été confrontés, quoique d'une manière légèrement différente. Nous avions une base de données propriétaire, nous n'avons donc pas eu les mêmes types de problèmes dans ce domaine.
Notre structure était similaire à la vôtre, mais nous n'avions qu'un seul référentiel pour notre code. Le code spécifique à la plate-forme est entré dans leurs propres dossiers de projet dans l'arborescence de code. Le code commun vivait dans l'arbre en fonction de la couche à laquelle il appartenait.
Nous avions une compilation conditionnelle, basée sur la plate-forme en cours de construction. Maintenir cela était un peu pénible, mais cela ne devait être fait que lorsque de nouveaux modules étaient ajoutés à la couche spécifique à la plate-forme.
Le fait d'avoir tout le code dans un seul référentiel nous a permis de corriger les bogues sur plusieurs plates-formes et versions en même temps. Nous avions un environnement de construction automatisé pour toutes les plates-formes pour servir de filet de sécurité au cas où le nouveau code casserait une plate-forme non liée présumée.
Nous avons essayé de le décourager, mais il y avait des cas où une plate-forme avait besoin d'un correctif basé sur un bogue spécifique à la plate-forme qui était dans le code par ailleurs commun. Si nous pouvions outrepasser conditionnellement la compilation sans donner au module une apparence fugitive, nous le ferions en premier. Sinon, nous déplacerions le module hors du territoire commun et le pousserions dans une plate-forme spécifique.
Pour la base de données, nous avions quelques tables qui avaient des colonnes / modifications spécifiques à la plate-forme. Nous veillerions à ce que chaque version de plate-forme du tableau réponde à un niveau de fonctionnalité de base afin que le code commun puisse y faire référence sans se soucier des dépendances de plate-forme. Les requêtes / manipulations spécifiques à la plateforme ont été insérées dans les couches de projet de plateforme.
Donc, pour répondre à vos questions:
la source
J'ai travaillé pendant de nombreuses années sur une application d'administration des pensions qui avait des problèmes similaires. Les régimes de retraite sont très différents d'une entreprise à l'autre et nécessitent des connaissances hautement spécialisées pour la mise en œuvre de la logique de calcul et des rapports, ainsi qu'une conception des données très différente. Je ne peux que donner une brève description d'une partie de l'architecture, mais peut-être que cela donnera assez d'idée.
Nous avions 2 équipes distinctes: une équipe de développement principale , qui était responsable du code du système principal (qui serait votre code partagé à 80% ci-dessus), et une équipe de mise en œuvre , qui avait une expertise de domaine dans les systèmes de retraite, et était responsable de l'apprentissage du client les exigences et le codage des scripts et des rapports pour le client.
Nous avions toutes nos tables définies par Xml (ceci avant l'époque où les frameworks d'entités étaient testés et communs). L'équipe d'implémentation concevrait toutes les tables en Xml, et l'application principale pourrait être invitée à générer toutes les tables en Xml. Il y avait également des fichiers de script VB associés, Crystal Reports, des documents Word, etc. pour chaque client. (Il y avait également un modèle d'héritage intégré au Xml pour permettre la réutilisation d'autres implémentations).
L'application principale (une application pour tous les clients) mettrait en cache toutes les informations spécifiques au client lorsqu'une demande pour ce client arrivait, et elle générait un objet de données commun (un peu comme un jeu d'enregistrements ADO distant), qui pouvait être sérialisé et transmis environ.
Ce modèle de données est moins glissant que les objets entité / domaine, mais il est très flexible, universel et peut être traité par un ensemble de code de base. Dans votre cas, vous pourriez peut-être définir vos objets d'entité de base avec uniquement les champs communs et avoir un dictionnaire supplémentaire pour les champs personnalisés (ajoutez une sorte d'ensemble de descripteurs de données à votre objet d'entité afin qu'il contienne des métadonnées pour les champs personnalisés. )
Nous avions des référentiels sources séparés pour le code système principal et pour le code d'implémentation.
Notre système central avait en fait très peu de logique métier, à l'exception de certains modules de calcul communs très standard. Le système principal fonctionnait comme: générateur d'écran, exécuteur de script, générateur de rapports, couche d'accès aux données et de transport.
La segmentation de la logique de base et de la logique personnalisée est un défi difficile. Cependant, nous avons toujours pensé qu'il était préférable d'avoir un système central exécutant plusieurs clients, plutôt que plusieurs copies du système fonctionnant pour chaque client.
la source
J'ai travaillé sur un système plus petit (20 kloc) et j'ai trouvé que la DI et la configuration sont deux excellents moyens de gérer les différences entre les clients, mais pas assez pour éviter de bifurquer le système. La base de données est divisée entre une partie spécifique à l'application, qui a un schéma fixe, et la partie dépendante du client, qui est définie via un document de configuration XML personnalisé.
Nous avons conservé une seule branche dans Mercurial qui est configurée comme si elle était livrable, mais de marque et configurée pour un client fictif. Les corrections de bugs sont intégrées dans ce projet, et le nouveau développement des fonctionnalités de base ne se produit que là-bas. Les versions destinées aux clients réels sont des succursales, stockées dans leurs propres référentiels. Nous gardons une trace des modifications importantes apportées au code grâce à des numéros de version écrits manuellement et suivons les corrections de bogues à l'aide de numéros de validation.
la source
Je crains de ne pas avoir d'expérience directe du problème que vous décrivez, mais j'ai quelques commentaires.
La deuxième option, de rassembler le code dans un référentiel central (autant que possible), et d'architecturer pour la personnalisation (encore une fois, autant que possible) est presque certainement la voie à suivre à long terme.
Le problème est de savoir comment vous comptez vous y rendre et combien de temps cela va prendre.
Dans cette situation, il est probablement correct d'avoir (temporairement) plus d'une copie de l'application dans le référentiel à la fois.
Cela vous permettra de passer progressivement à une architecture qui prend directement en charge la personnalisation sans avoir à le faire d'un seul coup.
la source
Je suis sûr que chacun de ces problèmes peut être résolu, l'un après l'autre. Si vous êtes coincé, demandez ici sur ou sur SO le problème spécifique.
Comme d'autres l'ont souligné, avoir une base de code centrale / un référentiel est l'option que vous devriez préférer. J'essaie de répondre à votre exemple de question.
Il y a des possibilités, je les ai toutes vues dans des systèmes du monde réel. Lequel choisir dépend de votre situation:
introduire une table "CustomAttributes" (décrivant les noms et les types) et "CustomAttributeValues" (pour les valeurs, par exemple stockées sous forme de représentation sous forme de chaîne, même s'il s'agit de nombres). Cela permettra d'ajouter de tels attributs au moment de l'installation ou de l'exécution, avec des valeurs individuelles pour chaque client. N'insistez pas pour que chaque attribut personnalisé soit modélisé "visiblement" dans votre modèle de données.
maintenant, il devrait être clair comment utiliser cela dans le code: avoir juste un code général pour accéder à ces tables et un code individuel (peut-être dans une DLL de plug-in distincte, qui dépend de vous) pour interpréter correctement ces attributs
Et pour le code spécifique: vous pouvez également essayer d'introduire un langage de script dans votre produit, en particulier pour ajouter des scripts spécifiques au client. De cette façon, vous créez non seulement une ligne claire entre votre code et le code spécifique au client, vous pouvez également permettre à vos clients de personnaliser le système dans une certaine mesure par eux-mêmes.
la source
Je n'ai créé qu'une seule application de ce type. Je dirais que 90% des unités vendues ont été vendues telles quelles, aucune modification. Chaque client avait sa propre peau personnalisée et nous avons servi le système dans cette peau. Lorsqu'un mod est entré qui a affecté les sections principales, nous avons essayé d'utiliser la ramification IF . Lorsque le mod # 2 est entré pour la même section, nous sommes passés à la logique CASE qui a permis une expansion future. Cela semblait gérer la plupart des demandes mineures.
Toutes les autres demandes personnalisées mineures ont été traitées en implémentant la logique de cas.
Si les mods étaient à deux radicaux, nous avons construit un clone (inclusion séparée) et enveloppé un CASE autour de lui pour inclure le module différent.
Des corrections de bugs et des modifications sur le noyau ont affecté tous les utilisateurs. Nous avons testé minutieusement en cours de développement avant de passer à la production. Nous avons toujours envoyé des notifications par e-mail accompagnant tout changement et JAMAIS, JAMAIS, JAMAIS publié de changements de production le vendredi ... JAMAIS.
Notre environnement était Classic ASP et SQL Server. Nous n'étions PAS un magasin de code de spaghetti ... Tout était modulaire à l'aide d'includes, de sous-programmes et de fonctions.
la source
Quand on me demande de commencer le développement de B qui partage 80% de fonctionnalités avec A, je vais soit:
Vous avez choisi 1, et cela ne semble pas bien correspondre à votre situation. Votre mission est de prédire lequel des 2 et 3 est le mieux adapté.
la source