Come mantenere versioni differenti e personalizzate dello stesso software per più client

43

abbiamo più client con esigenze diverse. Sebbene il nostro software sia in qualche modo modulare, è quasi certo che abbiamo bisogno di regolare la logica di business di ogni modulo qua e là un po 'per ogni cliente. I cambiamenti sono probabilmente troppo piccoli per giustificare la suddivisione del modulo in un modulo distinto (fisico) per ogni cliente, temo problemi con la compilazione, un caos di collegamento. Tuttavia, tali modifiche sono troppo grandi e troppe per configurarle tramite gli switch in alcuni file di configurazione, in quanto ciò potrebbe causare problemi durante l'implementazione e probabilmente molti problemi di supporto, in particolare con gli amministratori di tipo tinker.

Mi piacerebbe che il sistema di generazione crei più build, uno per ogni client, in cui le modifiche sono contenute in una versione specializzata del singolo modulo fisico in questione. Quindi ho alcune domande:

Consiglieresti di lasciare che il sistema di generazione crei più build? Come dovrei memorizzare le diverse personalizzazioni nel controllo del codice sorgente, in particolare svn?

    
posta Falcon 21.03.2011 - 11:21
fonte

9 risposte

8

Ciò che ti serve è l'organizzazione del codice feature branching -like. Nel tuo scenario specifico questo dovrebbe essere chiamato Tronco del tronco specifico del client perché probabilmente utilizzerai anche la ramificazione delle funzionalità mentre sviluppi nuove cose (o risolvi bug).

link

L'idea è di avere un trunk di codice con i rami delle nuove funzionalità unite in. Intendo tutte le funzionalità non specifiche del client.

Poi hai anche i tuoi rami specifici per il cliente dove puoi unire anche le stesse filiali delle funzioni come richiesto (cherry-picking se vuoi).

Queste filiali client hanno un aspetto simile ai rami delle caratteristiche anche se non sono temporanee o di breve durata. Vengono mantenuti per un periodo di tempo più lungo e vengono principalmente incorporati. Ci dovrebbe essere il minor numero possibile di sviluppo del ramo di funzionalità client.

I rami specifici del cliente sono rami paralleli al tronco e sono attivi fintanto che il tronco stesso e non vengono nemmeno uniti nel loro insieme.

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       '========================================== · · ·
        \ client2            \
         '======================================== · · ·
              \ client2-specific feature   /
               '——————————————————————————´
    
risposta data 21.03.2011 - 11:37
fonte
33

Non farlo con i rami SCM. Rendi il codice comune un progetto separato che produce una libreria o un artefatto scheletro del progetto. Ogni progetto cliente è un progetto separato che dipende da quello comune come dipendenza.

Questo è più semplice se l'app di base comune utilizza un framework di distribuzione delle dipendenze come Spring, in modo che sia possibile iniettare facilmente varianti di sostituzione sostitutive di oggetti in ogni progetto del cliente poiché sono richieste funzionalità personalizzate. Anche se non hai già un framework DI, aggiungerne uno e farlo in questo modo potrebbe essere la tua scelta meno dolorosa.

    
risposta data 22.03.2011 - 00:51
fonte
9

Archivia software per tutti i client in un singolo ramo. Non c'è bisogno di divergere le modifiche apportate per diversi clienti. Molto probabilmente, vorrete che il vostro software sia della migliore qualità per tutti i client, e il bugfix dell'infrastruttura principale dovrebbe interessare tutti senza un sovraccarico di fusione non necessario, che potrebbe anche introdurre più bug.

Modulare il codice comune e implementare il codice che differisce in diversi file o proteggerlo con definizioni diverse. Fai in modo che il tuo sistema di costruzione abbia obiettivi specifici per ogni cliente, ogni obiettivo compili una versione con solo il codice relativo a un cliente. Se il tuo codice è C, ad esempio, potresti voler proteggere le funzionalità per diversi client con " #ifdef " o qualsiasi altro meccanismo utilizzato dalla tua lingua per la configurazione della configurazione e preparare un set di definizioni corrispondente alla quantità di funzionalità che un client ha pagato per.

Se non ti piace ifdef s, usa "interfacce e implementazioni", "functors", "file oggetto" o qualsiasi altro strumento che la tua lingua fornisce per memorizzare cose diverse in un unico posto.

Se distribuisci fonti ai tuoi clienti, è meglio che gli script di compilazione abbiano speciali "obiettivi di distribuzione di origine". Una volta richiamato tale target, crea una versione speciale di fonti del tuo software, li copia in una cartella separata in modo che tu possa spedirli e non li compili.

    
risposta data 21.03.2011 - 22:25
fonte
7

Come molti hanno dichiarato: fattore appropriato per il codice e personalizzare in base al codice comune , ciò migliorerà significativamente la manutenzione. Sia che tu stia usando un linguaggio / sistema orientato agli oggetti o meno, questo è possibile (anche se è un po 'più difficile da fare in C di qualcosa che è veramente orientato agli oggetti). Questo è esattamente il tipo di problema che l'ereditarietà e l'incapsulamento aiutano a risolvere!

    
risposta data 22.03.2011 - 01:04
fonte
4

Con molta attenzione

Feature Branching è un'opzione ma trovo che sia piuttosto pesante. Inoltre rende facili le modifiche profonde che possono portare a una biforcazione della tua applicazione se non tenuta sotto controllo. Idealmente, si desidera aumentare il più possibile le personalizzazioni nel tentativo di mantenere il codice base il più comune e generico possibile.

Ecco come farei se non sapessi se è applicabile al tuo codice base senza pesanti modifiche e ridimensionamenti. Ho avuto un progetto simile in cui le funzionalità di base erano le stesse, ma ogni cliente richiedeva un insieme molto specifico di funzionalità. Ho creato un insieme di moduli e contenitori che ho poi assemblato attraverso la configurazione (à laCoC).

quindi per ogni cliente ho creato un progetto che contiene fondamentalmente le configurazioni e lo script di compilazione per creare un'installazione completamente configurata per il loro sito. Occasionalmente inserisco anche alcuni componenti personalizzati per questo client. Ma questo è raro e quando possibile cerco di farlo in una forma più generica e lo spingo verso il basso in modo che altri progetti possano usarli.

Il risultato finale è che ho ottenuto il livello di personalizzazione di cui avevo bisogno, ho degli script di installazione personalizzati in modo tale che quando arrivo al sito del cliente non sembro che sto tweeking il sistema tutto il tempo e come aggiunto MOLTO significativo bonus posso essere in grado di creare test di regressione agganciati direttamente alla build. In questo modo, ogni volta che ottengo un bug specifico per il cliente, posso scrivere un test che asserirà il sistema mentre viene distribuito e quindi può eseguire il TDD anche a quel livello.

quindi in breve:

  1. Sistema strongmente modulare con una struttura di progetto piatta.
  2. Crea un progetto per ciascun profilo di configurazione (cliente, sebbene più di uno possa condividere un profilo)
  3. Assembla i set di funzionalità richieste come prodotto diverso e trattalo come tale.

Se fatto correttamente, l'assemblaggio del prodotto dovrebbe contenere tutti tranne alcuni file di configurazione.

Dopo aver usato questo per un po 'ho finito per creare dei meta-pacchetti che assemblano i sistemi più usati o essenziali come unità principale e usare questo meta-pacchetto per le assemblee dei clienti. Dopo alcuni anni ho finito per avere una grande cassetta degli attrezzi che avrei potuto assemblare molto rapidamente per creare soluzioni per i clienti. Attualmente sto esaminando Spring Roo e vedo se non riesco a spingere ulteriormente l'idea sperando che un giorno possa creare una prima bozza del sistema giusto con il cliente nel nostro primo colloquio ... Immagino si possa chiamarlo User Driven Development; -).

Spero che questo ci abbia aiutato

    
risposta data 22.03.2011 - 03:34
fonte
3

The changes are probably too tiny to justify splitting the module into a distinct (physical) module for each client, I fear problems with the build, a linking chaos.

IMO, non possono essere troppo piccoli. Se possibile, tratterò il codice specifico del cliente usando lo schema di strategia praticamente ovunque. Ciò ridurrà la quantità di codice che deve essere ramificata e ridurrà la fusione richiesta per mantenere il codice generale in sincrono per tutti i client. Semplifica anche i test ... puoi testare il codice generale usando le strategie predefinite e testare separatamente le classi specifiche del cliente.

Se i tuoi moduli sono codificati in modo tale che l'utilizzo della politica X1 nel modulo A richiede l'utilizzo della politica X2 nel modulo B, pensa al refactoring in modo che X1 e X2 possano essere combinati in una singola classe della politica.

    
risposta data 21.03.2011 - 20:28
fonte
1

Potresti usare il tuo SCM per mantenere i rami. Mantieni il ramo principale incontaminato / pulito dal codice personalizzato del client. Fai lo sviluppo principale su questo ramo. Per ogni versione personalizzata dell'applicazione mantenere filiali separate. Qualsiasi buon strumento SCM andrà molto bene con la fusione dei rami (Git viene in mente). Qualsiasi aggiornamento nel ramo principale deve essere unito ai rami personalizzati, ma il codice specifico del client può rimanere nel proprio ramo.

Tuttavia, se possibile, prova a progettare il sistema in modo modulare e configurabile. Lo svantaggio di questi rami personalizzati può essere che si allontana troppo dal core / master.

    
risposta data 21.03.2011 - 11:36
fonte
1

Se stai scrivendo in chiaro C, ecco un modo piuttosto brutto per farlo.

  • Codice comune (ad es. unità "frangulator.c")

  • codice specifico del client, pezzi piccoli e usati solo per ogni cliente.

  • nel codice dell'unità principale, usa #ifdef e # include per fare qualcosa di simile

#ifdef CLIENT=CLIENTA
#include "frangulator_client_a.c"
#endif

Utilizza questo schema ripetutamente in tutte le unità di codice che richiedono la personalizzazione specifica del cliente.

Questo è MOLTO brutto, e porta ad altri problemi, ma è anche semplice, e puoi confrontare i file specifici dei client uno contro l'altro abbastanza facilmente.

Significa anche che tutti i pezzi specifici del client sono chiaramente visibili (ciascuno nel proprio file) in ogni momento, e c'è una chiara relazione tra il file di codice principale e la parte specifica del client del file.

Se diventi davvero intelligente puoi impostare makefile per creare la definizione corretta del client, quindi qualcosa del tipo:

make clienta

costruirà per client_a, e "make clientb" creerà per client_b e così via.

(e "make" senza target fornito può emettere un avviso o una descrizione di utilizzo.)

Ho già usato un'idea simile prima, ci vuole un po 'per impostare, ma può essere molto efficace. Nel mio caso un albero sorgente ha costruito circa 120 prodotti diversi.

    
risposta data 22.03.2011 - 02:15
fonte
0

In git, il modo in cui lo farei è avere un ramo principale con tutto il codice comune, e rami per ogni cliente. Ogni volta che viene apportata una modifica al codice principale, basterà rebase di tutti i rami specifici del client sopra il master, in modo da disporre di un set di patch mobili per i client applicati sulla linea di base corrente.

Ogni volta che stai apportando una modifica per un cliente e noti un bug che dovrebbe essere incluso in altri rami, puoi selezionarlo nel master o negli altri rami che richiedono la correzione (sebbene di rami di client diversi stanno condividendo il codice, probabilmente dovresti averli entrambi che si diramano da un ramo comune dal master).

    
risposta data 21.03.2011 - 23:30
fonte

Leggi altre domande sui tag