Come organizzate un software altamente personalizzato?


28

Sto lavorando a un grande progetto software altamente personalizzato per vari clienti in tutto il mondo. Ciò significa che abbiamo forse l'80% di codice che è comune tra i vari clienti, ma anche un sacco di codice che deve cambiare da un cliente all'altro. In passato abbiamo fatto il nostro sviluppo in repository separati (SVN) e quando è iniziato un nuovo progetto (abbiamo pochi, ma grandi clienti) abbiamo creato un altro repository basato su qualsiasi progetto passato abbia la migliore base di codice per le nostre esigenze. Questo ha funzionato in passato, ma abbiamo riscontrato diversi problemi:

  • I bug corretti in un repository non vengono corretti in altri repository. Questo potrebbe essere un problema di organizzazione, ma trovo difficile correggere e correggere un bug in 5 diversi repository, tenendo presente che il team che gestisce questo repository potrebbe trovarsi in un'altra parte del mondo e non abbiamo il loro ambiente di test , né conoscono il loro programma o quali requisiti hanno (un "bug" in un paese potrebbe essere una "caratteristica" in un altro).
  • Funzionalità e miglioramenti apportati per un progetto, che potrebbero anche essere utili per un altro progetto, vengono persi o se vengono utilizzati in un altro progetto spesso causano grossi mal di testa fondendoli da una base di codice a un altro (poiché entrambi i rami potrebbero essere stati sviluppati in modo indipendente per un anno ).
  • Rifattorizzazioni e miglioramenti del codice apportati in un ramo di sviluppo vengono persi o causano più danni che benefici se si devono unire tutte queste modifiche tra i rami.

Stiamo discutendo come risolvere questi problemi e finora abbiamo trovato le seguenti idee su come risolverlo:

  1. Mantenere lo sviluppo in rami separati ma organizzarsi meglio disponendo di un repository centrale in cui vengono fuse le correzioni di bug generali e tutti i progetti uniscono le modifiche da questo repository centrale al proprio su base regolare (ad es. Quotidianamente). Ciò richiede un'enorme disciplina e molti sforzi per la fusione tra i rami. Quindi non sono convinto che funzionerà e possiamo mantenere questa disciplina, specialmente quando la pressione del tempo aumenta.

  2. Abbandona i rami di sviluppo separati e disponi di un repository di codice centrale in cui vive tutto il nostro codice e fai la nostra personalizzazione con moduli collegabili e opzioni di configurazione. Stiamo già utilizzando i contenitori di Iniezione delle dipendenze per risolvere le dipendenze nel nostro codice e stiamo seguendo il modello MVVM nella maggior parte del nostro codice per separare in modo pulito la logica aziendale dalla nostra IU.

Il secondo approccio sembra essere più elegante, ma abbiamo molti problemi irrisolti in questo approccio. Ad esempio: come gestire le modifiche / aggiunte nel modello / database. Stiamo usando .NET con Entity Framework per avere entità fortemente tipizzate. Non vedo come possiamo gestire le proprietà richieste per un cliente ma inutili per un altro cliente senza ingombrare il nostro modello di dati. Stiamo pensando di risolverlo nel database usando tabelle satellitari (con tabelle separate in cui le nostre colonne extra per un'entità specifica vivono con un mapping 1: 1 all'entità originale), ma questo è solo il database. Come gestisci questo nel codice? Il nostro modello di dati vive in una biblioteca centrale che non saremmo in grado di estendere per ogni cliente utilizzando questo approccio.

Sono sicuro che non siamo l'unica squadra alle prese con questo problema e sono scioccato di trovare così poco materiale sull'argomento.

Quindi le mie domande sono le seguenti:

  1. Che esperienza hai con software altamente personalizzati, quale approccio hai scelto e come ha funzionato per te?
  2. Quale approccio mi consigliate e perché? C'è un approccio migliore?
  3. Ci sono buoni libri o articoli sull'argomento che puoi consigliare?
  4. Hai raccomandazioni specifiche per il nostro ambiente tecnico (.NET, Entity Framework, WPF, DI)?

Modificare:

Grazie per tutti i suggerimenti. La maggior parte delle idee corrispondono a quelle che avevamo già nel nostro team, ma è davvero utile vedere l'esperienza che hai avuto con loro e suggerimenti per implementarli meglio.

Non sono ancora sicuro di come andremo e non prenderò la decisione (da solo), ma lo farò passare nella mia squadra e sono sicuro che sarà utile.

Al momento il tenore sembra essere un singolo repository che utilizza vari moduli specifici del cliente. Non sono sicuro che la nostra architettura sia all'altezza di questo o di quanto dobbiamo investire per adattarla, quindi alcune cose potrebbero vivere in repository separati per un po ', ma penso che sia l'unica soluzione a lungo termine che funzionerà.

Quindi, grazie ancora per tutte le risposte!


Considera di trattare le tabelle del database come codice.

Lo stiamo già facendo nel senso che abbiamo i nostri script di database nel nostro repository di sovversione, ma in realtà non risolve i problemi sopra menzionati. Non vogliamo avere tabelle di stile valore-chiave nel nostro modello di database perché presentano molti problemi. Quindi, come si possono consentire aggiunte al proprio modello per i singoli clienti pur mantenendo un repository di codice condiviso per tutti loro?
Akzen T

Risposte:


10

Sembra che il problema fondamentale non sia solo la manutenzione del repository di codice, ma la mancanza di un'architettura adeguata .

  1. Qual è il nucleo / essenza del sistema, che sarà sempre condiviso da tutti i sistemi?
  2. Quali miglioramenti / deviazioni sono richiesti da ciascun cliente?

Un framework o una libreria standard comprende il primo, mentre il secondo verrebbe implementato come componente aggiuntivo (plugin, sottoclassi, DI, qualunque cosa abbia senso per la struttura del codice).

Un sistema di controllo del codice sorgente che gestisce le filiali e lo sviluppo distribuito probabilmente aiuterebbe anche; Sono un fan di Mercurial, altri preferiscono Git. Il framework sarebbe il ramo principale, ogni sistema personalizzato sarebbe sotto-rami, per esempio.

Le tecnologie specifiche utilizzate per implementare il sistema (.NET, WPF, qualunque cosa) sono in gran parte irrilevanti.

Non è facile , ma è fondamentale per la fattibilità a lungo termine. E ovviamente più a lungo aspetti, maggiore sarà il debito tecnico che dovrai affrontare.

È possibile trovare utile il libro Architettura del software: principi e schemi organizzativi .

In bocca al lupo!


3
Sì. L'architettura è spesso più una cosa psicologica che una cosa tecnica. Se ti concentri completamente sul prodotto, ti ritroverai con una suddivisione del lavoro in cui i componenti sono utili solo per quel prodotto. Se, d'altra parte, ti concentri sulla creazione di una serie di librerie che saranno più generalmente utili, allora costruisci una serie di funzionalità che possono essere implementate in una gamma più ampia di situazioni. La chiave, ovviamente, è trovare il giusto equilibrio tra questi due estremi per la tua situazione particolare.
William Payne,

Penso che ci sia una grande parte condivisa tra molte delle nostre filiali (> 90%), ma l'ultimo 10% si trova sempre in luoghi diversi, quindi ci sono pochissimi componenti in cui non riesco a immaginare alcune modifiche specifiche del cliente ad eccezione di alcune librerie di utilità che non contengono alcuna logica aziendale.
Akzen T

@aKzenT: hmmm ... non immaginare, misura invece. Esamina il codice e osserva tutti i luoghi in cui si è verificata la personalizzazione, crea un elenco dei componenti modificati, osserva la frequenza con cui ogni componente è stato modificato e pensa ai tipi e ai modelli di modifiche che sono stati effettivamente apportati. Sono cosmetici o algoritmici? Stanno aggiungendo o modificando la funzionalità di base? Qual è la ragione di ogni tipo di cambiamento? Ancora una volta, questo è un duro lavoro e potresti non gradire le implicazioni di ciò che scopri.
Steven A. Lowe,

1
Ho accettato questa come risposta, perché non possiamo prendere questa decisione prima di pensare di più alla nostra architettura, identificare parti comuni o che DOVREBBE essere comuni e quindi vedere se possiamo vivere con un singolo repository o se il fork è necessario (per il momento almeno). Questa risposta riflette questo IMO migliore. Grazie per tutti gli altri post, però!
Akzen T

11

Una società per cui ho lavorato ha avuto lo stesso problema e l'approccio per affrontarlo era questo: è stato creato un quadro comune per tutti i nuovi progetti; questo include tutte le cose che devono essere le stesse in ogni progetto. Ad esempio strumenti per la generazione di moduli, esportazione in Excel, registrazione. È stato fatto uno sforzo per assicurarsi che questo quadro comune sia migliorato (quando un nuovo progetto ha bisogno di nuove funzionalità), ma non è mai stato modificato.

Sulla base di tale framework, il codice specifico del cliente è stato mantenuto in repository separati. Quando utile o necessario, correzioni e miglioramenti di bug vengono copiati e incollati tra i progetti (con tutti gli avvertimenti descritti nella domanda). Tuttavia, miglioramenti utili a livello globale vanno nel quadro comune.

Avere tutto in una base di codice comune per tutti i clienti ha alcuni vantaggi, ma d'altra parte, leggere il codice diventa difficile quando ci sono innumerevoli ifs per far sì che il programma si comporti in modo diverso per ogni cliente.

EDIT: un aneddoto per renderlo più comprensibile:

Il dominio di quella società è la gestione del magazzino e un compito di un sistema di gestione del magazzino è quello di trovare una posizione di stoccaggio gratuita per le merci in arrivo. Sembra facile, ma in pratica devono essere osservati molti vincoli e strategie.

A un certo punto, il management ha chiesto a un programmatore di creare un modulo flessibile e parametrizzabile per trovare posizioni di archiviazione, che implementasse diverse strategie e che avrebbero dovuto essere utilizzate in tutti i progetti successivi. Il nobile sforzo ha portato a un modulo complesso, che era molto difficile da capire e mantenere. Nel prossimo progetto, il responsabile del progetto non riuscì a capire come farlo funzionare in quel magazzino, e lo sviluppatore di quel modulo non c'era più, quindi alla fine lo ignorò e scrisse un algoritmo personalizzato per quell'attività.

Qualche anno dopo, il layout del magazzino in cui era originariamente utilizzato questo modulo è cambiato e il modulo con tutta la sua flessibilità non ha soddisfatto i nuovi requisiti; così l'ho sostituito con un algoritmo personalizzato anche lì.

So che LOC non è una buona misura, ma comunque: la dimensione del modulo "flessibile" era ~ 3000 LOC (PL / SQL), mentre un modulo personalizzato per lo stesso compito richiede ~ 100..250 LOC. Pertanto, cercare di essere flessibili ha aumentato enormemente le dimensioni della base di codice, senza ottenere la riusabilità che speravamo.


Grazie per il tuo feedback Questa è un'estensione della prima soluzione descritta e abbiamo pensato anche a questo. Tuttavia, poiché la maggior parte delle nostre biblioteche utilizza alcune entità core e queste entità principali sono generalmente estese per un cliente o un altro, penso che potremmo mettere solo pochissime librerie in questo repository principale. Ad esempio, abbiamo un'entità "Cliente" definita e ORM mappata in una libreria che viene utilizzata da quasi tutte le altre nostre librerie e programmi. Ma ogni cliente ha alcuni campi extra che devono aggiungere a un "Cliente", quindi dovremmo biforcare questa libreria e quindi tutte le librerie a seconda di essa.
Akzen T

3
La nostra idea di evitare innumerevoli "ifs" è di fare ampio uso dell'iniezione di dipendenza e di scambiare moduli completi per clienti diversi. Non sono sicuro di come funzionerà comunque. Come ha funzionato questo approccio per te?
Akzen T

+1, questo corrisponde sostanzialmente alla mia esperienza di progetti che hanno gestito bene. Se utilizzerai l'approccio "ifs per clienti diversi", 2 punti: 1. Non eseguire if (customer1) ..., invece esegui if (configurationOption1) ... e hai opzioni di configurazione per cliente. 2. Cerca di non farlo! Forse l'1% delle volte sarà l'opzione migliore (più comprensibile / facilmente gestibile) rispetto ai moduli specifici della configurazione.
vaughandroid,

@Baqueta: solo per chiarire: si consiglia di utilizzare i moduli per cliente anziché le opzioni di configurazione (ifs), giusto? Mi piace la tua idea di distinguere tra funzionalità anziché clienti. Quindi la combinazione di entrambi sarebbe avere vari "moduli di funzionalità" che sono controllati da opzioni di configurazione. Un cliente viene quindi rappresentato solo come un insieme di moduli di funzionalità indipendenti. Mi piace molto questo approccio, ma non sono sicuro di come progettarlo. DI risolve il problema del caricamento e dello scambio dei moduli, ma come gestite i diversi modelli di dati tra i clienti?
Akzen T

Sì, moduli per funzione e quindi configurazione / selezione per funzionalità per cliente. DI sarebbe l'ideale. Sfortunatamente non ho mai dovuto combinare le tue esigenze di personalizzazione sostanziale per cliente con una singola libreria di dati, quindi non sono sicuro di poter essere di grande aiuto lì ...
Vaughandroid

5

Uno dei progetti su cui ho lavorato ha supportato più piattaforme (più di 5) in numerose versioni di prodotti. Molte delle sfide che stai descrivendo erano cose che abbiamo affrontato, sebbene in modo leggermente diverso. Avevamo un DB proprietario, quindi non avevamo gli stessi tipi di problemi in quell'arena.

La nostra struttura era simile alla tua, ma avevamo un unico repository per il nostro codice. Il codice specifico della piattaforma è andato nelle proprie cartelle di progetto all'interno dell'albero del codice. Il codice comune viveva all'interno dell'albero in base al livello a cui apparteneva.

Avevamo una compilazione condizionale, basata sulla piattaforma in costruzione. Mantenere questo era un po 'una seccatura, ma doveva essere fatto solo quando venivano aggiunti nuovi moduli a livello specifico della piattaforma.

Avere tutto il codice in un unico repository ci ha reso più semplice apportare correzioni di bug su più piattaforme e rilasci contemporaneamente. Avevamo un ambiente di build automatizzato per tutte le piattaforme che fungevano da backstop nel caso in cui il nuovo codice avesse rotto una presunta piattaforma non correlata.

Abbiamo cercato di scoraggiarlo, ma ci sarebbero stati casi in cui una piattaforma necessitava di una correzione basata su un bug specifico della piattaforma che era in codice altrimenti comune. Se potessimo sovrascrivere condizionalmente la compilazione senza far sembrare fugace il modulo, lo faremmo prima. In caso contrario, sposteremmo il modulo fuori dal territorio comune e lo inseriremmo in una piattaforma specifica.

Per il database, avevamo alcune tabelle con colonne / modifiche specifiche della piattaforma. Ci assicureremmo che ogni versione della piattaforma della tabella corrispondesse a un livello di funzionalità di base in modo che il codice comune possa fare riferimento a esso senza preoccuparsi delle dipendenze della piattaforma. Le query / manipolazioni specifiche della piattaforma sono state inserite nei livelli di progetto della piattaforma.

Quindi, per rispondere alle tue domande:

  1. Molti, ed è stato uno dei migliori team con cui ho lavorato. La base di codice a quel tempo era di circa 1 milione di loc. Non sono riuscito a scegliere l'approccio, ma ha funzionato abbastanza bene. Anche col senno di poi, non ho visto un modo migliore di gestire le cose.
  2. Raccomando il secondo approccio che hai suggerito con le sfumature che menziono nella mia risposta.
  3. Non ci sono libri a cui riesco a pensare, ma vorrei iniziare la ricerca sullo sviluppo multipiattaforma.
  4. Istituire un governo forte. È la chiave per assicurarsi che vengano seguiti gli standard di codifica. Seguire tali standard è l'unico modo per mantenere le cose gestibili e mantenibili. Avevamo la nostra parte di entusiasti motivi per infrangere il modello che stavamo seguendo, ma nessuno di questi appelli ha mai influenzato l'intero team di sviluppo senior.

Grazie per aver condiviso la tua esperienza. Giusto per capire meglio: come viene definita una piattaforma nel tuo caso. Windows, Linux, X86 / x64? O qualcosa di più correlato a diversi clienti / ambienti? Stai facendo un buon punto in 4) Penso che sia uno dei problemi che abbiamo. Abbiamo un team di molte persone molto intelligenti e qualificate, ma ognuno ha idee leggermente diverse su come fare le cose. Senza qualcuno chiaramente responsabile dell'architettura è difficile concordare una strategia comune e rischi di perderti nelle discussioni senza cambiare nulla.
Akzen T

@aKzenT - sì, intendevo hardware fisico e sistema operativo come piattaforme. Avevamo un ampio set di funzionalità, alcune delle quali selezionabili da un modulo di licenza. E abbiamo supportato una vasta gamma di hardware. In relazione a ciò, avevamo una directory di layer comune che aveva un sacco di dispositivi con le loro directory in quel layer per l'albero del codice. Quindi le nostre circostanze non erano davvero così lontane da dove ti trovi. I nostri sviluppatori senior si sarebbero scatenati dibattiti l'uno con l'altro, ma una volta presa una decisione collettiva tutti hanno accettato di seguire la linea.

4

Ho lavorato per molti anni su una domanda di amministrazione pensionistica che aveva problemi simili. I piani pensionistici sono molto diversi tra le società e richiedono conoscenze altamente specializzate per l'implementazione di logiche e rapporti di calcolo e anche una progettazione dei dati molto diversa. Posso solo dare una breve descrizione di parte dell'architettura, ma forse darà abbastanza l'idea.

Avevamo 2 team separati: un team di sviluppo principale , che era responsabile del codice del sistema di base (che sarebbe il tuo codice condiviso all'80% sopra) e un team di implementazione , che aveva esperienza di dominio nei sistemi pensionistici ed era responsabile dell'apprendimento del cliente requisiti e script di codifica e report per il client.

Avevamo definito tutte le nostre tabelle da Xml (questo prima del tempo in cui i framework delle entità erano testati nel tempo e comuni). Il team di implementazione progetterà tutte le tabelle in Xml e all'applicazione principale potrebbe essere richiesto di generare tutte le tabelle in Xml. C'erano anche file di script VB associati, Crystal Reports, documenti Word ecc. Per ogni client. (C'era anche un modello di ereditarietà incorporato nell'Xml per consentire il riutilizzo di altre implementazioni).

L'applicazione principale (un'applicazione per tutti i client), memorizzava nella cache tutte le cose specifiche del client quando arrivava una richiesta per quel client e generava un oggetto dati comune (un po 'come un set di record ADO remoto), che poteva essere serializzato e passato in giro.

Questo modello di dati è meno fluido dell'entità / oggetto del dominio, ma è altamente flessibile, universale e può essere elaborato da un set di codice core. Forse nel tuo caso, potresti definire i tuoi oggetti di entità di base solo con i campi comuni e avere un dizionario aggiuntivo per i campi personalizzati (aggiungi una sorta di set di descrittori di dati all'oggetto entità in modo che contenga metadati per i campi personalizzati. )

Avevamo repository di origine separati per il codice di sistema principale e per il codice di implementazione.

Il nostro sistema principale in realtà aveva una logica aziendale molto ridotta, oltre ad alcuni moduli di calcolo comuni molto standard. Il sistema principale funzionava come: generatore di schermo, script runner, generatore di report, accesso ai dati e livello di trasporto.

Segmentare la logica di base e la logica personalizzata è una sfida difficile. Tuttavia, abbiamo sempre pensato che fosse meglio avere un sistema principale in esecuzione su più client, piuttosto che più copie del sistema in esecuzione per ciascun client.


Grazie per il feedback. Mi piace l'idea di avere campi aggiuntivi in ​​un dizionario. Ciò ci consentirebbe di avere una singola definizione di un'entità e mettere tutte le cose specifiche del cliente in un dizionario. Non sono sicuro, tuttavia, se esiste un buon modo per farlo funzionare con il nostro wrapper ORM (Entity Framework). E non sono nemmeno sicuro che sia davvero una buona idea avere un modello di dati condiviso a livello globale invece di avere un modello per ogni funzione / modulo.
Akzen T

2

Ho lavorato su un sistema più piccolo (20 kloc) e ho scoperto che DI e configurazione sono entrambi ottimi modi per gestire le differenze tra i client, ma non abbastanza per evitare il fork del sistema. Il database è suddiviso tra una parte specifica dell'applicazione, che ha uno schema fisso, e la parte dipendente dal client, definita tramite un documento di configurazione XML personalizzato.

Abbiamo mantenuto una singola filiale in mercurial configurata come se fosse consegnabile, ma marchiata e configurata per un cliente fittizio. Le correzioni di bug sono integrate in quel progetto e il nuovo sviluppo delle funzionalità di base avviene solo lì. I rilasci verso i clienti reali ne derivano, archiviati nei propri repository. Teniamo traccia delle grandi modifiche al codice attraverso numeri di versione scritti manualmente e monitoriamo le correzioni di bug utilizzando i numeri di commit.


hai differenziato tra le librerie di base che sono le stesse tra i clienti o biforchi l'albero completo? Ti unisci regolarmente dalla linea principale alle singole forcelle? E se sì, quanto tempo è costato la routine quotidiana di fusione e come hai evitato che questa procedura cadesse a pezzi quando inizia la pressione del tempo (come fa sempre in un punto del progetto)?
Ci

Forchiamo l'intero albero, principalmente perché le richieste dei clienti vengono prima della creazione dell'integrità del sistema. La fusione dal sistema principale avviene manualmente utilizzando mercurial e altri strumenti ed è generalmente limitata a bug critici o aggiornamenti di funzionalità di grandi dimensioni. Cerchiamo di mantenere gli aggiornamenti su grandi blocchi poco frequenti, sia a causa del costo dell'unione, sia perché molti client ospitano i propri sistemi e non vogliamo imporre costi di installazione senza fornire valore.
Dan Monego,

Sono curioso: IIRC mercurial è un DVCS simile a git. Hai notato dei vantaggi nel fare queste fusioni tra filiali rispetto a Subversion o ad altri VCS tradizionali? Ho appena finito un doloroso processo di fusione tra 2 rami sviluppati completamente separati usando la sovversione e pensavo che sarebbe stato più facile se avessimo usato git.
Akzen T

La fusione con mercurial è stata molto, molto più semplice della fusione con il nostro strumento precedente, che era Vault. Uno dei principali vantaggi è che Mercurial è davvero bravo a mettere la cronologia delle modifiche in primo piano e al centro dell'applicazione, il che rende facile tenere traccia di ciò che è stato fatto dove. La parte più difficile per noi è stata quella di spostare i rami esistenti: se si uniscono due rami, mercurial richiede che entrambi abbiano la stessa radice, quindi ottenere quella configurazione richiede un'ultima fusione completamente manuale.
Dan Monego,

2

Temo di non avere esperienza diretta del problema che descrivi, ma ho alcuni commenti.

La seconda opzione, di riunire il codice in un repository centrale (per quanto possibile) e di progettare per la personalizzazione (di nuovo, per quanto praticabile) è quasi sicuramente la strada da percorrere a lungo termine.

Il problema è come pensi di arrivarci e quanto tempo ci vorrà.

In questa situazione, è probabilmente OK avere (temporaneamente) più di una copia dell'applicazione nel repository alla volta.

Ciò ti consentirà di passare gradualmente a un'architettura che supporta direttamente la personalizzazione senza doverlo fare in un colpo solo.


2

Il secondo approccio sembra essere più elegante, ma abbiamo molti problemi irrisolti in questo approccio.

Sono sicuro che uno di questi problemi possa essere risolto, uno dopo l'altro. Se rimani bloccato, chiedi qui o su SO del problema specifico.

Come altri hanno sottolineato, avere una base di codice centrale / un repository è l'opzione che dovresti preferire. Provo a rispondere alla tua domanda di esempio.

Ad esempio: come gestire le modifiche / aggiunte nel modello / database. Stiamo usando .NET con Entity Framework per avere entità fortemente tipizzate. Non vedo come possiamo gestire le proprietà richieste per un cliente ma inutili per un altro cliente senza ingombrare il nostro modello di dati.

Ci sono alcune possibilità, tutte che ho visto nei sistemi del mondo reale. Quale scegliere dipende dalla tua situazione:

  • vivere con il disordine in una certa misura
  • introdurre una tabella "CustomAttributes" (che descrive nomi e tipi) e "CustomAttributeValues" (per i valori, ad esempio memorizzati come rappresentazione di stringa, anche se sono numeri). Ciò consentirà di aggiungere tali attributi in fase di installazione o di esecuzione, con valori individuali per ciascun cliente. Non insistere sul fatto che ogni attributo personalizzato sia modellato "visibilmente" nel modello di dati.

  • ora dovrebbe essere chiaro come usarlo nel codice: avere solo un codice generale per accedere a quelle tabelle e un singolo codice (forse in una DLL plug-in separata, che dipende da te) per interpretare correttamente quegli attributi

  • un'altra alternativa è quella di assegnare ad ogni tabella di entità un campo di stringa grande in cui è possibile aggiungere una singola stringa XML.
  • prova a generalizzare alcuni concetti, in modo che possano essere riutilizzati più facilmente tra clienti diversi. Consiglio il libro di Martin Fowler " Schemi di analisi ". Sebbene questo libro non riguardi la personalizzazione del software, potrebbe essere utile anche per te.

E per un codice specifico: puoi anche provare a introdurre un linguaggio di scripting nel tuo prodotto, in particolare per aggiungere script specifici del cliente. In questo modo non solo crei una linea chiara tra il tuo codice e il codice specifico del cliente, ma puoi anche consentire ai tuoi clienti di personalizzare il sistema in una certa misura da soli.


I problemi che vedo con l'aggiunta di colonne CustomAttributes o XML per memorizzare proprietà personalizzate è che sono molto limitate nelle loro capacità. Ad esempio fare ordinamento, raggruppamento o filtro in base a questi attributi è molto impegnativo. Una volta ho lavorato su un sistema che utilizzava una tabella di attributi extra e diventa sempre più complesso mantenere e gestire questi attributi personalizzati. Per questo motivo stavo pensando invece di mettere questi attributi come colonne in una tabella aggiuntiva che è mappata 1: 1 sull'originale. Il problema su come definire, interrogare e gestire questi è comunque lo stesso.
AkzenT

@aKzenT: la personalizzazione non è gratuita, dovrai scambiare la facilità d'uso rispetto alla personalizzazione. Il mio suggerimento generale è di non introdurre dipendenze in cui la parte centrale del sistema dipende in alcun modo da qualsiasi parte personalizzata, solo viceversa. Ad esempio, quando si introducono queste "tabelle extra" per il cliente 1, è possibile evitare di distribuire tali tabelle e il codice correlato per il cliente 2? Se la risposta è sì, allora la soluzione è ok.
Doc Brown,

0

Ho creato solo una di queste applicazioni. Direi che il 90% delle unità vendute sono state vendute così come sono, senza modifiche. Ogni cliente aveva la propria skin personalizzata e noi servivamo il sistema all'interno di quella skin. Quando è arrivata una mod che ha influenzato le sezioni principali, abbiamo provato a utilizzare la ramificazione IF . Quando è arrivata la mod # 2 per la stessa sezione, siamo passati alla logica CASE che ha consentito un'espansione futura. Questo sembrava gestire la maggior parte delle richieste minori.

Eventuali ulteriori richieste personalizzate minori sono state gestite implementando la logica Case.

Se le mod fossero due radicali, avremmo creato un clone (inclusione separata) e racchiuso un CASE attorno ad esso per includere il diverso modulo.

Correzioni di errori e modifiche sul core hanno interessato tutti gli utenti. Abbiamo testato a fondo nello sviluppo prima di passare alla produzione. Abbiamo sempre inviato notifiche e-mail che accompagnavano qualsiasi modifica e MAI, MAI, MAI pubblicato postazioni di produzione il venerdì ... MAI.

Il nostro ambiente era ASP classico e SQL Server. NON eravamo un negozio di spaghetti ... Tutto era modulare usando Include, Subroutine e Funzioni.


-1

Quando mi viene chiesto di iniziare lo sviluppo di B che condivide l'80% di funzionalità con A, dovrò:

  1. Clona A e modificalo.
  2. Estrai la funzionalità che sia A che B condividono in C che useranno.
  3. Rendi A abbastanza configurabile per soddisfare le esigenze sia di B che di se stesso (pertanto B è incorporato in A).

Hai scelto 1 e non sembra adattarsi bene alla tua situazione. La tua missione è di predire quale di 2 e 3 si adatta meglio.


1
Sembra facile, ma come fare in pratica? Come si può rendere il software così configurabile senza ingombrarlo con "if (customer1)", che diventa non mantenibile dopo un po 'di tempo.
Akzen T

@aKzenT Ecco perché ho lasciato 2 e 3 tra cui scegliere. Se il tipo di modifiche necessarie per supportare il progetto di customer1 con le esigenze di customer2 attraverso la configurazione renderà il codice non mantenibile, allora non farlo 3.
Moshe Revah

Preferisco fare "if (opzione1)" anziché "if (cliente1)". In questo modo con N opzioni posso gestire molti possibili clienti. Ad esempio con N opzioni booleane puoi gestire 2 ^ N clienti, avendo solo N 'if' ... impossibile da gestire con la strategia "if (customer1)" che richiederebbe 2 ^ N 'if'.
Fil
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.