Comment gérer différentes versions personnalisées du même logiciel pour plusieurs clients

46

nous avons plusieurs clients avec des besoins différents. Bien que nos logiciels soient modulaires dans une certaine mesure, il est presque certain que nous devons ajuster un peu la logique métier de chaque module ici et là pour chaque client. Les modifications sont probablement trop minimes pour justifier la scission du module en un module (physique) distinct pour chaque client. Je crains des problèmes de construction, un chaos de liens. Cependant, ces modifications sont trop volumineuses et trop nombreuses pour être configurées à l'aide de commutateurs dans un fichier de configuration, car cela poserait des problèmes lors du déploiement et entraînerait probablement de nombreux problèmes d'assistance, notamment avec les administrateurs de type bricoleur.

J'aimerais que le système de construction crée plusieurs versions, une pour chaque client, où les modifications sont contenues dans une version spécialisée du module physique unique en question. J'ai donc quelques questions:

Conseilleriez-vous de laisser le système de construction créer plusieurs versions? Comment dois-je stocker les différentes personnalisations dans le contrôle de source, en particulier svn?

Faucon
la source
4
Ça #ifdefmarche pour vous?
ohho
6
Les directives de préprocesseur peuvent rapidement devenir très compliquées et rendre le code plus difficile à lire et à déboguer.
Falcon
1
Vous devriez inclure des détails sur la plate-forme actuelle et le type d'application (bureau / Web) pour de meilleures réponses. La personnalisation dans une application de bureau C ++ est complètement différente d’une application Web PHP.
GrandmasterB
2
@Falcon: depuis que vous avez choisi une réponse en 2011, j'ai beaucoup de doutes, pouvez-vous nous dire si vous avez déjà utilisé SVN de la manière suggérée? Mes objections sont-elles mal fondées?
Doc Brown
2
@Doc Brown: la ramification et la fusion étaient fastidieuses et compliquées. À cette époque, nous utilisions un système de plug-ins avec des plug-ins spécifiques au client ou des "correctifs" qui modifiaient le comportement ou les configurations. Vous aurez des frais généraux, mais cela peut être géré avec Dependendy Injection.
Falcon

Réponses:

7

Ce dont vous avez besoin, c'est d' une organisation du code semblable à une branche . Dans votre scénario spécifique, il convient de parler de branchement de jonction spécifique au client, car vous utiliserez probablement également le branchement de fonctionnalités lors du développement de nouveaux éléments (ou de la résolution de bugs).

http://svnbook.red-bean.com/fr/1.5/svn.branchmerge.commonpatterns.html

L'idée est de faire fusionner un tronc de code avec les branches de nouvelles fonctionnalités. Je veux dire toutes les fonctionnalités non spécifiques au client.

Ensuite, vous avez également vos branches spécifiques au client, dans lesquelles vous fusionnez les mêmes branches que vous le souhaitez (sélection sélective si vous préférez).

Ces branches client ressemblent aux branches de fonctions, bien qu’elles ne soient ni temporaires ni de courte durée. Ils sont maintenus sur une longue période et ils ne sont principalement fusionnés. Il devrait y avoir aussi peu de développement de branches de fonctionnalités spécifiques au client que possible.

Les branches spécifiques au client sont des branches parallèles au tronc et sont actives aussi longtemps que le tronc lui-même et ne sont même pas fusionnées comme un tout.

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´
Robert Koritnik
la source
7
+1 pour les branches. Vous pouvez également utiliser une branche pour chaque client. Je voudrais suggérer juste une chose: utiliser un VCS distribué (hg, git, bzr) au lieu de SVN / CVS pour accomplir cela;)
Herberth Amaral
43
-1. Ce n'est pas à quoi servent les "branches caractéristiques"; cela contredit leur définition de "branches temporaires qui fusionnent lorsque le développement d'une fonctionnalité est terminé".
P expédié
14
Vous devez toujours conserver tous ces deltas dans le contrôle de source et les aligner - pour toujours. À court terme, cela pourrait fonctionner. À long terme, cela pourrait être terrible.
Rapidement maintenant
4
-1, il ne s'agit pas d'un problème de dénomination. L'utilisation de différentes branches pour différents clients n'est qu'une autre forme de duplication de code. Ceci est à mon humble avis un anti-modèle, un exemple comment ne pas le faire.
Doc Brown
7
Robert, je pense avoir très bien compris ce que vous avez suggéré, même avant le montage, mais je pense que c'est une approche horrible. Supposons que vous avez N clients. Chaque fois que vous ajoutez une nouvelle fonctionnalité principale au coffre, il semble que le GDS facilitera la propagation de la nouvelle fonctionnalité dans les N branches. Mais utiliser les branches de cette manière évite trop facilement une séparation claire des modifications spécifiques au client. En conséquence, vous avez maintenant N chances d’obtenir un conflit de fusion à chaque modification du coffre. De plus, vous devez maintenant exécuter N tests d’intégration au lieu d’un.
Doc Brown
38

Ne faites pas cela avec les branches SCM. Faites du code commun un projet distinct produisant une bibliothèque ou un artefact de squelette de projet. Chaque projet client est un projet distinct qui dépend alors du projet commun en tant que dépendance.

Cela est plus facile si votre application de base commune utilise un framework d'injection de dépendances tel que Spring, de sorte que vous puissiez facilement injecter différentes variantes de remplacement d'objets dans chaque projet client, car des fonctionnalités personnalisées sont requises. Même si vous ne possédez pas déjà de framework DI, en ajouter un et le faire de cette façon peut être votre choix le moins pénible.

Alb
la source
Une autre approche simple consiste à utiliser l'héritage (et un projet unique) au lieu d'utiliser l'injection de dépendance, en conservant le code commun et les personnalisations dans le même projet (en utilisant l'héritage, des classes partielles ou des événements). Avec cette conception, les branches client fonctionneraient correctement (comme indiqué dans la réponse sélectionnée) et il était toujours possible de mettre à jour les branches client en mettant à jour le code commun.
drizin
Si je ne manque pas quelque chose, vous suggérez que si vous avez 100 clients, vous allez créer (100 x NumberOfChangedProjects ) et utiliser DI pour les gérer? Si tel est le cas, je resterais définitivement à l’écart de ce type de solution car la
facilité de
13

Stockez les logiciels pour tous les clients dans une seule branche. Il n'est pas nécessaire de diverger les modifications apportées pour différents clients. Dans la plupart des cas, vous voudrez que votre logiciel soit de la meilleure qualité pour tous les clients, et la correction de votre infrastructure principale devrait affecter tout le monde, sans fusion inutile, ce qui peut également engendrer plus de bugs.

Modularisez le code commun et implémentez le code qui diffère selon les fichiers ou protégez-le avec différentes définitions. Assurez-vous que votre système de construction a des cibles spécifiques pour chaque client, chaque cible compilant une version avec uniquement le code associé à un client. Si votre code est C, par exemple, vous voudrez peut-être protéger les fonctionnalités de différents clients avec " #ifdef" ou le mécanisme de votre langage pour la gestion de la configuration, et préparer un ensemble de définitions correspondant au montant des fonctionnalités payées par un client.

Si vous n’aimez pas ça ifdef, utilisez "interfaces et implémentations", "foncteurs", "fichiers objets" ou tout autre outil fourni par votre langage pour stocker différentes choses au même endroit.

Si vous distribuez des sources à vos clients, il est préférable de faire en sorte que vos scripts de construction aient des "cibles de répartition des sources" spéciales. Une fois que vous appelez une telle cible, il crée une version spéciale des sources de votre logiciel, les copie dans un dossier séparé afin que vous puissiez les expédier, sans les compiler.

P Shved
la source
8

Comme beaucoup l'ont dit: factorisez votre code correctement et personnalisez-le en fonction du code commun, ce qui améliorera considérablement la maintenabilité. Que vous utilisiez un langage / système orienté objet ou non, cela est possible (bien que ce soit un peu plus difficile à faire en C que quelque chose de vraiment orienté objet). C’est précisément le type de problème que l’héritage et l’encapsulation aident à résoudre!

Michael Trausch
la source
5

Très soigneusement

L'embranchement est une option mais je trouve que c'est un peu lourd. En outre, cela facilite les modifications en profondeur, ce qui peut conduire à une correction complète de votre application si elle n'est pas contrôlée. Dans l’idéal, vous souhaitez développer autant que possible les personnalisations afin de conserver votre base de code principale aussi générique que possible.

Voici comment je le ferais bien que je ne sache pas si cela est applicable à votre base de code sans modifications et reformulations lourdes. J'avais un projet similaire où les fonctionnalités de base étaient les mêmes mais chaque client avait besoin d'un ensemble de fonctionnalités très spécifiques. J'ai créé un ensemble de modules et de conteneurs que j'assemble ensuite via la configuration (à la IoC).

Ensuite, pour chaque client, j'ai créé un projet qui contient essentiellement les configurations et le script de génération permettant de créer une installation entièrement configurée pour leur site. Parfois, j'y place également des composants personnalisés pour ce client. Mais ceci est rare et j'essaie, chaque fois que cela est possible, de le rendre sous une forme plus générique et de le mettre de côté afin que d'autres projets puissent l'utiliser.

Le résultat final est que j'ai eu le niveau de personnalisation dont j'avais besoin, j'ai eu des scripts d'installation personnalisés de sorte que lorsque je suis sur le site client, je ne ressemble pas à tweking tout le temps sur le système et, comme bonus supplémentaire, je reçois pour pouvoir créer des tests de régression accrochés directement à la construction. Ainsi, chaque fois que je reçois un bogue spécifique à un client, je peux rédiger un test qui assurera le système pendant son déploiement et pourra donc effectuer la TDD même à ce niveau.

donc en bref:

  1. Système fortement modulaire avec une structure de projet plate.
  2. Créer un projet pour chaque profil de configuration (client, bien que plusieurs puissent partager un profil)
  3. Assemblez les ensembles de fonctionnalités requis en tant que produit différent et traitez-le comme tel.

Si cela est fait correctement, votre assemblage de produit devrait contenir presque tous les fichiers de configuration.

Après avoir utilisé cela pendant un certain temps, j'ai fini par créer des méta-packages qui assemblent les systèmes les plus utilisés ou essentiels en tant qu'unité centrale et utilisent ce méta-package pour les assemblages client. Après quelques années, je me suis retrouvé avec une grande boîte à outils que je pouvais assembler très rapidement pour créer des solutions client. J'examine actuellement Spring Roo et vois si je ne peux pas pousser l'idée un peu plus loin en espérant pouvoir un jour créer un premier brouillon du système avec le client lors de notre première interview ... Je suppose que vous pourriez l'appeler User Driven Développement ;-).

J'espère que cela a aidé

Newtopien
la source
3

Les modifications sont probablement trop minimes pour justifier la scission du module en un module (physique) distinct pour chaque client. Je crains des problèmes de construction, un chaos de liens.

OMI, ils ne peuvent pas être trop minuscule. Si possible, je factoriserais le code spécifique au client en utilisant le modèle de stratégie un peu partout. Cela réduira la quantité de code à ramifier et la fusion nécessaire pour que le code général reste synchronisé pour tous les clients. Cela simplifiera également les tests ... vous pouvez tester le code général à l'aide de stratégies par défaut et tester les classes spécifiques au client séparément.

Si vos modules sont codés de telle sorte que l'utilisation de la stratégie X1 dans le module A nécessite l'utilisation de la stratégie X2 dans le module B, envisagez le refactoring afin que X1 et X2 puissent être combinés en une seule classe de stratégies.

Kevin Cline
la source
1

Vous pouvez utiliser votre SCM pour gérer des succursales. Conservez la branche principale vierge / propre du code personnalisé du client. Faire le développement principal sur cette branche. Pour chaque version personnalisée de l'application, maintenez des branches distinctes. Tout bon outil SCM se débrouillera très bien avec la fusion de branches (on pense à Git). Toutes les mises à jour de la branche principale doivent être fusionnées dans les branches personnalisées, mais le code spécifique au client peut rester dans sa propre branche.


Si possible, essayez de concevoir le système de manière modulaire et configurable. L’inconvénient de ces branches personnalisées peut être qu’elles s’éloignent trop du noyau / du maître.

Htbaa
la source
1

Si vous écrivez en clair C, voici une façon plutôt laide de le faire.

  • Code commun (par exemple, unité "frangulator.c")

  • code spécifique au client, petites pièces utilisées uniquement pour chaque client.

  • dans le code de l'unité principale, utilisez #ifdef et #include pour faire quelque chose comme

#ifdef CLIENT = CLIENTA
#include "frangulator_client_a.c"
#fin si

Utilisez-le comme modèle dans toutes les unités de code nécessitant une personnalisation spécifique au client.

C’est TRÈS moche et cela crée d’autres problèmes, mais c’est aussi simple, et vous pouvez facilement comparer des fichiers spécifiques à un client.

Cela signifie également que tous les éléments spécifiques au client sont clairement visibles (chacun dans son propre fichier) à tout moment, et qu'il existe une relation claire entre le fichier de code principal et la partie du fichier spécifique au client.

Si vous êtes vraiment malin, vous pouvez configurer les makefiles pour créer la définition de client correcte, par exemple:

faire clienta

construira pour client_a et "make clientb" fera pour client_b et ainsi de suite.

(et "make" sans cible fournie peut émettre un avertissement ou une description de l'utilisation.)

J'ai déjà utilisé une idée similaire auparavant, sa mise en place prend un certain temps, mais elle peut être très efficace. Dans mon cas, un arbre source a construit environ 120 produits différents.

Rapidement
la source
0

En git, la façon dont je le ferais est d'avoir une branche maître avec tout le code commun et des branches pour chaque client. Chaque fois que vous apportez une modification au code principal, il vous suffit de rebaser toutes les branches spécifiques au client par-dessus le maître, afin de disposer d'un ensemble de correctifs mobiles pour les clients appliqués au-dessus de la ligne de base actuelle.

Chaque fois que vous apportez une modification à un client et que vous remarquez un bogue qui devrait être inclus dans d'autres branches, vous pouvez le sélectionner dans l'un ou l'autre des maîtres ou dans les autres branches qui ont besoin du correctif (bien que différentes branches du client partagent le code , vous devriez probablement les avoir tous les deux dérivant d’une branche commune du maître).

Cercerilla
la source