nous avons plusieurs clients avec des besoins différents. Bien que notre logiciel soit modulaire dans une certaine mesure, il est presque certain que nous devons ajuster la logique métier de chaque module ici et là un peu pour chaque client. Les modifications sont probablement trop minimes pour justifier la division du module en un module (physique) distinct pour chaque client, je crains des problèmes de construction, un chaos de liaison. Pourtant, ces changements sont trop importants et trop nombreux pour les configurer par des commutateurs dans certains fichiers de configuration, car cela entraînerait des problèmes lors du déploiement et probablement beaucoup de problèmes de support, en particulier avec les administrateurs de type bricoleur.
J'aimerais que le système de génération 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:
Recommanderiez-vous de laisser le système de build créer plusieurs builds? Comment dois-je stocker les différentes personnalisations dans le contrôle de code source, svn en particulier?
Ce dont vous avez besoin est branchement de fonctionnalités - comme une organisation de code. Dans votre scénario spécifique, cela devrait être appelé Branche de tronc spécifique au client car vous utiliserez probablement aussi la branche de fonctionnalité pendant que vous développez de nouvelles choses (ou résolvez des bogues) ).
http://svnbook.red-bean.com/en/1.5/svn.branchmerge.commonpatterns.html
L'idée est de fusionner un tronc de code avec les branches des 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 où vous fusionnez également les branches des mêmes fonctionnalités selon vos besoins (sélection des cerises si vous le souhaitez).
Ces branches clientes ressemblent aux branches fonctionnelles bien qu'elles ne soient pas temporaires ou de courte durée. Ils sont maintenus sur une plus longue période de temps et ils sont principalement fusionnés uniquement. Il devrait y avoir aussi peu de développement de branche 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 dans leur ensemble.
feature1
———————————.
\
trunk \
================================================== · · ·
\ client1 \
`========================================== · · ·
\ client2 \
`======================================== · · ·
\ client2-specific feature /
`——————————————————————————´
Ne faites pas cela avec des branches SCM. Faites du code commun un projet distinct qui produit une bibliothèque ou un artefact squelette de projet. Chaque projet client est un projet distinct qui dépend alors du commun comme dépendance.
Cela est plus simple si votre application de base commune utilise un cadre d'injection de dépendances comme Spring, de sorte que vous pouvez 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 n'avez pas encore de framework DI, en ajouter un et le faire de cette façon peut être votre choix le moins douloureux.
Stockez les logiciels pour tous les clients dans une seule succursale. Il n'est pas nécessaire de diverger les modifications apportées pour différents clients. Très probablement, vous souhaiterez que votre logiciel soit de la meilleure qualité pour tous les clients, et le correctif de votre infrastructure principale devrait affecter tout le monde sans fusionner les frais généraux inutiles, ce qui peut également introduire plus de bogues.
Modularisez le code commun et implémentez le code qui diffère dans différents fichiers ou gardez-le avec des définitions différentes. Faites en sorte que votre système de génération ait des cibles spécifiques pour chaque client, chaque cible compilant une version avec uniquement le code lié à un client. Si votre code est C, par exemple, vous souhaiterez peut-être protéger les fonctionnalités de différents clients avec "#ifdef
"ou quel que soit le mécanisme de votre langage pour la gestion de la configuration des builds, et préparez un ensemble de définitions correspondant au montant des fonctionnalités payées par un client.
Si vous n'aimez pas les ifdef
s, utilisez "interfaces et implémentations", "foncteurs", "fichiers objets" ou tout autre outil fourni par votre langage pour stocker différentes choses en un seul endroit.
Si vous distribuez des sources à vos clients, il est préférable de faire en sorte que vos scripts de génération aient des "cibles de distribution de source" spéciales. Une fois que vous invoquez une telle cible, elle crée une version spéciale de sources de votre logiciel, les copie dans un dossier séparé afin que vous puissiez les expédier et ne les compile pas.
Comme beaucoup l'ont dit: factorisez votre code correctement et personnalisez-le en fonction du code commun - cela améliorera considérablement la maintenabilité. Que vous utilisiez ou non un langage/système orienté objet, cela est possible (bien qu'il soit un peu plus difficile à faire en C que quelque chose qui est vraiment orienté objet). C'est précisément le type de problème que l'héritage et l'encapsulation aident à résoudre!
Très soigneusement
La fonction de branchement est une option mais je trouve que c'est un peu lourd. Il facilite également les modifications en profondeur, ce qui peut conduire à une fourchette pure et simple de votre application si elle n'est pas maîtrisée. Idéalement, vous souhaitez pousser autant que possible les personnalisations afin de garder votre base de code principale aussi commune et générique que possible.
Voici comment je le ferais bien que je ne sache pas s'il est applicable à votre base de code sans modifications et re-factorisations lourdes. J'avais un projet similaire où la fonctionnalité de base était la même mais chaque client avait besoin d'un ensemble de fonctionnalités très spécifique. J'ai créé un ensemble de modules et de conteneurs que j'assemble ensuite par configuration (à l'IoC).
puis pour chaque client, j'ai créé un projet qui contient essentiellement les configurations et le script de construction pour créer une installation entièrement configurée pour leur site. Parfois, j'y place également des composants sur mesure pour ce client. Mais c'est rare et chaque fois que possible, j'essaye de le faire sous une forme plus générique et de le pousser vers le bas pour que d'autres projets puissent les utiliser.
Le résultat final est que j'ai obtenu le niveau de personnalisation dont j'avais besoin, j'ai reçu des scripts d'installation personnalisés de sorte que lorsque j'arrive sur le site client, je n'ai pas l'air de tweking le système tout le temps et comme un bonus TRÈS important supplémentaire que j'obtiens pour pouvoir créer des tests de régression accrochés directement sur la build. De cette façon, à chaque fois que j'obtiens un bogue spécifique au client, je peux écrire un test qui confirmera le système lors de son déploiement et pourra ainsi faire du TDD même à ce niveau.
donc en bref:
S'il est fait correctement, l'assemblage de votre produit doit contenir tous les fichiers de configuration sauf quelques-uns.
Après l'avoir utilisé 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 clients. Après quelques années, j'ai fini par avoir une grande boîte à outils que je pouvais assembler très rapidement pour créer des solutions client. J'étudie actuellement Spring Roo et vois si je ne peux pas pousser l'idée un peu plus loin en espérant qu'un jour je pourrai créer un premier projet du système avec le client lors de notre première interview ... je suppose que vous pourriez l'appeler Développement piloté par l'utilisateur ;-).
J'espère que cela a aidé
Les modifications sont probablement trop minimes pour justifier la division du module en un module (physique) distinct pour chaque client, je crains des problèmes de construction, un chaos de liaison.
OMI, ils ne peuvent pas être trop petits. Si possible, je prendrais en compte le code spécifique au client en utilisant le modèle de stratégie à peu près partout. Cela réduira la quantité de code qui doit être ramifié et réduira la fusion requise pour garder le code général 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, pensez à une refactorisation afin que X1 et X2 puissent être combinés en une seule classe de stratégie.
Si vous écrivez en C simple, voici une façon plutôt laide de le faire.
Code commun (par exemple, unité "frangulator.c")
code spécifique au client, morceaux qui sont petits et utilisés uniquement pour chaque client.
dans le code de l'unité principale, utilisez #ifdef et #include pour faire quelque chose comme
# ifdef CLIENT = CLIENTA # inclut "frangulator_client_a.c" # endif
Utilisez-le comme modèle à plusieurs reprises dans toutes les unités de code qui nécessitent une personnalisation spécifique au client.
Ceci est TRÈS moche, et conduit à d'autres problèmes, mais c'est aussi simple, et vous pouvez comparer les fichiers spécifiques au client les uns contre les autres assez facilement.
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 spécifique au client du fichier.
Si vous devenez vraiment intelligent, vous pouvez configurer des makefiles pour créer la définition de client correcte, donc quelque chose comme:
faire clienta
va construire pour client_a, et "make clientb" va faire pour client_b et ainsi de suite.
(et "make" sans cible fournie peut émettre un avertissement ou une description d'utilisation.)
J'ai utilisé une idée similaire auparavant, la 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.
Vous pouvez utiliser votre SCM pour maintenir des succursales. Gardez la branche principale vierge/propre du code personnalisé du client. Faites le développement principal sur cette branche. Pour chaque version personnalisée de l'application, maintenez des branches distinctes. Tout bon outil SCM fonctionnera très bien avec la fusion de branches (Git me vient à l'esprit). 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/maître.
En git, la façon dont je le ferais est d'avoir une branche principale avec tout le code commun, et des branches pour chaque client. Chaque fois qu'une modification du code principal est effectuée, il suffit de rebaser toutes les branches spécifiques au client au-dessus du maître, de sorte que vous ayez un ensemble de correctifs mobiles pour les clients qui sont appliqués au-dessus de la ligne de base actuelle.
Chaque fois que vous effectuez une modification pour 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 maître ou dans les autres branches qui ont besoin du correctif (bien que différentes branches de clients partagent le code , vous devriez probablement les avoir tous les deux en train de dériver une branche commune du maître).