Comment organisez-vous des logiciels hautement personnalisés?


28

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:

  1. 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.

  2. 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:

  1. Quelle expérience avez-vous avec un logiciel hautement personnalisé, quelle approche avez-vous choisie et comment cela a-t-il fonctionné pour vous?
  2. Quelle approche recommandez-vous et pourquoi? Est-ce qu'il y a une meilleure approche?
  3. Y a-t-il de bons livres ou articles sur le sujet que vous pouvez recommander?
  4. 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!


Envisagez de traiter les tables de base de données comme du code.

Nous le faisons déjà dans le sens où nous avons nos scripts de base de données dans notre référentiel de subversion, mais cela ne résout pas vraiment les problèmes mentionnés ci-dessus. Nous ne voulons pas avoir de tables de style valeur-clé dans notre modèle de base de données car elles posent beaucoup de problèmes. Alors, comment autorisez-vous des ajouts à votre modèle pour des clients individuels tout en conservant un référentiel de code partagé pour chacun d'eux?
aKzenT

Réponses:


10

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 .

  1. Quel est le noyau / l'essence du système, qui sera toujours partagé par tous les systèmes?
  2. Quelles améliorations / déviations sont requises par chaque client?

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!


3
Ouaip. L'architecture est souvent plus psychologique que technique. Si vous vous concentrez entièrement sur le produit, vous vous retrouverez avec une répartition du travail où les composants ne sont utiles que pour ce produit. Si, en revanche, vous vous concentrez sur la construction d'un ensemble de bibliothèques qui seront plus généralement utiles, vous construisez un ensemble de capacités qui peuvent être déployées dans un plus large éventail de situations. La clé, bien sûr, est de trouver le bon équilibre entre ces deux extrêmes pour votre situation particulière.
William Payne

Je pense qu'il y a une grande part partagée entre plusieurs de nos succursales (> 90%), mais les 10% derniers sont toujours à des endroits différents, donc il y a très peu de composants où je ne peux pas imaginer des changements spécifiques au client, sauf pour certaines bibliothèques d'utilitaires qui ne contiennent aucune logique métier.
aKzenT

@aKzenT: hmmm ... n'imaginez pas, mesurez à la place. Examinez le code et examinez tous les endroits où la personnalisation a eu lieu, faites une liste des composants modifiés, notez la fréquence à laquelle chaque composant a été modifié et réfléchissez aux types et modèles de modifications qui ont été réellement effectués. Sont-ils cosmétiques ou algorithmiques? Ajoutent-ils ou modifient-ils des fonctionnalités de base? Quelle est la raison de chaque type de changement? Encore une fois, c'est un travail difficile et vous n'aimerez peut-être pas les implications de ce que vous découvrirez.
Steven A. Lowe

1
J'ai accepté cela comme la réponse, parce que nous ne pouvons pas prendre cette décision avant de penser davantage à notre architecture, d'identifier les parties qui sont communes ou qui DEVRAIENT être communes et ensuite voir si nous pouvons vivre avec un seul référentiel ou si le forking est nécessaire (pour le moment au moins). Cette réponse reflète ce meilleur OMI. Merci pour tous les autres messages!
aKzenT

11

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 ifs pour faire en sorte que le programme se comporte différemment pour chaque client.

EDIT: Une anecdote pour rendre cela plus compréhensible:

Le domaine de cette entreprise est la gestion des entrepôts, et l'une des tâches d'un système de gestion d'entrepôts est de trouver un emplacement de stockage gratuit pour les marchandises entrantes. Cela semble facile, mais dans la pratique, de nombreuses contraintes et stratégies doivent être respectées.

À un moment donné, la direction a demandé à un programmeur de créer un module flexible et paramétrable pour trouver des emplacements de stockage, qui a mis en œuvre plusieurs stratégies différentes et aurait dû être utilisé dans tous les projets ultérieurs. Le noble effort a abouti à un module complexe, qui était très difficile à comprendre et à maintenir. Dans le projet suivant, le chef de projet n'a pas pu comprendre comment le faire fonctionner dans cet entrepôt, et le développeur dudit module était parti, alors il l'a finalement ignoré et a écrit un algorithme personnalisé pour cette tâche.

Quelques années plus tard, l'agencement de l'entrepôt où ce module était initialement utilisé a changé et le module avec toute sa flexibilité ne correspondait pas aux nouvelles exigences; donc je l'ai remplacé par un algorithme personnalisé là aussi.

Je sais que LOC n'est pas une bonne mesure, mais de toute façon: la taille du module "flexible" était ~ 3000 LOC (PL / SQL), tandis qu'un module personnalisé pour la même tâche prend ~ 100..250 LOC. Par conséquent, essayer d'être flexible a considérablement augmenté la taille de la base de code, sans gagner la réutilisabilité que nous espérions.


Merci pour vos commentaires. Il s'agit d'une extension de la première solution décrite et nous y avons également pensé. Cependant, puisque la plupart de nos bibliothèques utilisent des entités de base et ces entités de base sont généralement étendues pour un client ou un autre, je pense que nous ne pouvons mettre que très peu de bibliothèques dans ce référentiel de base. Par exemple, nous avons une entité "Client" définie et ORM mappée dans une bibliothèque qui est utilisée par presque toutes nos autres bibliothèques et programmes. Mais chaque client a des champs supplémentaires qu'il doit ajouter à un "client", nous aurions donc besoin de bifurquer cette bibliothèque et donc toutes les bibliothèques qui en dépendent.
aKzenT

3
Notre idée pour éviter d'innombrables «ifs» est de faire un usage intensif de l'injection de dépendances et d'échanger des modules complets pour différents clients. Je ne sais pas comment cela fonctionnera cependant. Comment cette approche a-t-elle fonctionné pour vous?
aKzenT

+1, cela correspond essentiellement à mon expérience des projets qui ont bien géré cela. Si vous allez utiliser l'approche «si pour différents clients», 2 points: 1. Ne faites pas si (client1) ..., faites plutôt si (configurationOption1) ... et ayez des options de configuration par client. 2. Essayez de ne pas le faire! Peut-être que 1% du temps sera la meilleure option (plus compréhensible / facilement maintenable) que d'avoir des modules spécifiques à la configuration.
vaughandroid

@Baqueta: Juste pour clarifier: vous recommandez d'utiliser des modules par client au lieu des options de configuration (ifs), non? J'aime votre idée de différencier les fonctionnalités plutôt que les clients. Ainsi, la combinaison des deux consisterait à avoir différents "modules fonctionnels" qui sont contrôlés par des options de configuration. Un client n'est alors représenté que comme un ensemble de modules de fonctionnalités indépendants. J'aime beaucoup cette approche, mais je ne sais pas comment la concevoir. DI résout le problème du chargement et de l'échange de modules, mais comment gérez-vous les différents modèles de données entre les clients?
aKzenT

Oui, modules par fonctionnalité, puis configuration / sélection des fonctionnalités par client. DI serait idéal. Malheureusement, je n'ai jamais eu à combiner vos exigences de personnalisation substantielle par client avec une seule bibliothèque de données, donc je ne suis pas sûr que je puisse y être beaucoup d'aide ...
vaughandroid

5

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:

  1. Beaucoup, et c'était l'une des meilleures équipes avec lesquelles j'ai travaillé. La base de code à cette époque était d'environ 1M loc. Je n'ai pas pu choisir l'approche, mais cela a bien fonctionné. Même avec le recul, je n'ai pas vu de meilleure façon de gérer les choses.
  2. Je recommande la deuxième approche que vous avez suggérée avec les nuances que je mentionne dans ma réponse.
  3. Pas de livres auxquels je peux penser, mais je ferais des recherches sur le développement multiplateforme en tant que débutant.
  4. Instituer une bonne gouvernance. C'est la clé pour s'assurer que vos normes de codage sont respectées. Suivre ces normes est le seul moyen de garder les choses gérables et maintenables. Nous avons eu notre part de plaidoyers passionnés pour briser le modèle que nous suivions, mais aucun de ces appels n'a jamais influencé toute l'équipe de développement senior.

Merci d'avoir partagé votre expérience. Juste pour mieux comprendre: comment est définie une plateforme dans votre cas. Windows, Linux, X86 / x64? Ou quelque chose de plus lié à différents clients / environnements? Vous faites valoir un bon point en 4) Je pense que c'est l'un des problèmes que nous avons. Nous avons une équipe de beaucoup de gens très intelligents et qualifiés, mais tout le monde a des idées légèrement différentes sur la façon de faire les choses. Sans quelqu'un clairement en charge de l'architecture, il est difficile de se mettre d'accord sur une stratégie commune et vous risquez de vous perdre dans les discussions sans rien changer.
aKzenT

@aKzenT - oui, je voulais dire le matériel physique et le système d'exploitation comme plates-formes. Nous disposions d'un large éventail de fonctionnalités, dont certaines étaient sélectionnables par un module de licence. Et nous avons pris en charge une large gamme de matériel. En relation avec cela, nous avions un répertoire de couches commun qui avait un tas de périphériques avec leurs propres répertoires dans cette couche pour l'arborescence de code. Nos circonstances n'étaient donc pas si éloignées de vous. Nos développeurs seniors auraient animé des débats entre eux, mais une fois la décision collective prise, tout le monde a accepté de suivre la ligne.

4

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.


Merci pour les commentaires. J'aime l'idée d'avoir des champs supplémentaires dans un dictionnaire. Cela nous permettrait d'avoir une définition unique d'une entité et de mettre toutes les informations spécifiques au client dans un dictionnaire. Je ne suis pas sûr cependant qu'il existe un bon moyen de le faire fonctionner avec notre wrapper ORM (Entity Framework). Et je ne sais pas non plus si c'est vraiment une bonne idée d'avoir un modèle de données mondialement partagé au lieu d'avoir un modèle pour chaque fonctionnalité / module.
aKzenT

2

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.


avez-vous différencié les bibliothèques principales qui sont les mêmes entre les clients ou fourchez-vous l'arborescence complète? Fusionnez-vous régulièrement de la ligne principale aux fourches individuelles? Et si oui, combien de temps la routine de fusion quotidienne a-t-elle coûté et comment avez-vous évité que cette procédure ne s'effondre lorsque la pression du temps commence (comme c'est toujours le cas à un moment donné du projet)? Désolé pour tant de questions: p
aKzenT

Nous bifurquons tout l'arborescence, principalement parce que les demandes des clients précèdent l'intégrité du système. La fusion à partir du système principal se fait manuellement à l'aide d'outils mercuriels et autres, et est généralement limitée aux bogues critiques ou aux mises à jour de fonctionnalités importantes. Nous essayons de conserver les mises à jour de gros morceaux peu fréquents, à la fois en raison du coût de la fusion et parce que de nombreux clients hébergent leurs propres systèmes et nous ne voulons pas leur imposer des coûts d'installation sans apporter de valeur.
Dan Monego

Je suis curieux: IIRC mercurial est un DVCS similaire à git. Avez-vous remarqué des avantages à faire ces fusions entre les branches par rapport à Subversion ou à d'autres VCS traditionnels? Je viens de terminer un processus de fusion très douloureux entre 2 branches développées complètement séparées en utilisant subversion et je me demandais si cela aurait été plus facile si nous avions utilisé git.
aKzenT

La fusion avec mercurial a été beaucoup, beaucoup plus facile que la fusion avec notre outil précédent, qui était Vault. L'un des principaux avantages est que mercurial est vraiment bon pour mettre l'historique des changements au centre de l'application, ce qui facilite le suivi de ce qui a été fait où. La partie la plus difficile pour nous a été de déplacer les branches existantes - si vous fusionnez deux branches, mercurial exige qu'elles aient toutes les deux la même racine, donc obtenir cette configuration nécessitait une dernière fusion complètement manuelle.
Dan Monego

2

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.


2

La deuxième approche semble être plus élégante, mais nous avons de nombreux problèmes non résolus dans cette approche.

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.

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 qui sont requises pour un client mais inutiles pour un autre client sans encombrer notre modèle de données.

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:

  • vivre avec l'encombrement dans une certaine mesure
  • 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

  • une autre alternative consiste à donner à chaque table d'entité un grand champ de chaîne dans lequel vous pouvez ajouter une chaîne XML individuelle.
  • essayez de généraliser certains concepts, afin qu'ils puissent être réutilisés plus facilement entre différents clients. Je recommande le livre de Martin Fowler " Analysis patterns ". Bien que ce livre ne traite pas de la personnalisation du logiciel en soi, il peut également vous être utile.

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.


Les problèmes que je vois avec l'ajout d'attributs personnalisés ou de colonnes XML pour stocker des propriétés personnalisées sont qu'ils sont très limités dans leurs capacités. Par exemple, le tri, le regroupement ou le filtrage basés sur ces attributs est très difficile. J'ai déjà travaillé sur un système qui utilisait une table d'attributs supplémentaire et il devient de plus en plus complexe de maintenir et de gérer ces attributs personnalisés. Pour ces raisons, je pensais au lieu de mettre ces attributs sous forme de colonnes dans un tableau supplémentaire qui est mappé 1: 1 à l'original. Le problème sur la façon de définir, interroger et gérer ces derniers est toujours le même.
aKzenT

@aKzenT: la personnalisation n'est pas gratuite, vous devrez faire un compromis entre facilité d'utilisation et personnalisation. Ma suggestion générale est que vous n'introduisez pas de dépendances où la partie centrale du système dépend de quelque façon que ce soit d'une partie personnalisée, mais l'inverse. Par exemple, lors de l'introduction de ces "tables supplémentaires" pour le client 1, pouvez-vous éviter de déployer ces tables et le code associé pour le client 2? Si la réponse est oui, alors la solution est correcte.
Doc Brown

0

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.


-1

Quand on me demande de commencer le développement de B qui partage 80% de fonctionnalités avec A, je vais soit:

  1. Clonez A et modifiez-le.
  2. Extrayez les fonctionnalités que A et B partagent en C qu'ils utiliseront.
  3. Rendez A suffisamment configurable pour répondre aux besoins de B et de lui-même (par conséquent, B est intégré à A).

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é.


1
Cela semble facile, mais comment faire cela en pratique? Comment rendre votre logiciel si configurable sans l'encombrer de "if (customer1)", qui devient impossible à gérer après un certain temps.
aKzenT

@aKzenT C'est pourquoi j'ai laissé 2 et 3 à vous de choisir. Si le type de changements nécessaires pour que le projet de support client1 réponde aux besoins de client2 via la configuration va rendre le code impossible à maintenir, alors n'en faites pas 3.
Moshe Revah

Je préfère faire "if (option1)" au lieu de "if (customer1)". De cette façon, avec N options, je peux gérer un grand nombre de clients potentiels. Par exemple, avec N options booléennes, vous pouvez gérer 2 ^ N clients, n'ayant que N 'if' ... impossible à gérer avec la stratégie "if (customer1)" qui nécessiterait 2 ^ N 'if'.
Fil
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.