Mantenimento di versioni in evoluzione di strutture e classi di interoperabilità

5

Un'applicazione C # .NET parla con un componente esterno chiamando una API nota e le strutture di interoperabilità di marshalling dalla risposta del componente.

Questo è già implementato e funziona bene. Tuttavia, una volta che le versioni entrano nell'equazione, le cose diventano un po 'più complicate: il componente si evolverà nel tempo e quindi le strutture di interope saranno mantenute in-sync. L'applicazione deve essere ancora in grado di parlare con i componenti delle versioni precedenti, in modo che debba selezionare le strutture di interoperabilità corrette in fase di esecuzione per una determinata versione del componente.

Ho gettato questo problema nella mia testa per un paio di giorni e non ho trovato nulla di cui sono particolarmente felice. Anche Google non è stato di grande aiuto, quindi ho pensato di provare a scrivere questo post e a postarlo qui per ottenere un feedback. Questo è quello che ho finora.

Raggruppa tutte le versioni delle strutture in una libreria, ha un registro e decide in fase di esecuzione quali strutture utilizzare

Qui, ogni nuova versione delle strutture viene compilata nella nuova versione dell'applicazione .NET e alcuni tipi di registro sono responsabili della mappatura delle versioni dell'API per l'interoperabilità delle versioni delle strutture nella libreria.

Il naming di classe / struct diventa un problema, dal momento che Class1 ora deve essere chiamato Class1_v1_1 o qualche altro modo per disambiguare in base al nome (o quello o usare lo stesso nome e metterli in uno spazio dei nomi separato ).

Carica le versioni delle strutture di interoperabilità da una DLL con versione

Qui, ogni versione delle strutture di interoperabilità viene compilata in una singola DLL e caricata dinamicamente in base alla versione dell'API. Una mappatura dovrebbe ancora essere creata tra la versione dell'API e il nome della DLL.

Il problema di denominazione scompare, ma certamente ci sono molti più elementi mobili per la distribuzione e di conseguenza cose che possono andare storte in fase di runtime.

Come ho detto, non sono contento di nessuno dei due e sento che c'è una soluzione molto evidente / più pulita / elegante che mi sfugge. Non riesco a immaginare di essere la prima persona che ha dovuto supportare le versioni mutevoli di un componente dipendente, quindi c'è qualche schema noto che può fornire indicazioni per scenari come questo?

Grazie in anticipo per eventuali feedback e / o possibili alternative!

    
posta user5877732 03.08.2017 - 13:09
fonte

2 risposte

1

La priorità numero uno dovrebbe essere quella di minimizzare le modifiche all'API di interoperabilità, se si ha il controllo sulla "API conosciuta". La buona progettazione dell'API minimizza i cambiamenti nel tempo.

Entrambe le soluzioni funzionerebbero e la soluzione di dll versione potrebbe essere più pulita, ma più ingombrante, dal momento che probabilmente hai una buona parte dell'API che non cambia tra le versioni. Dovrai gestire il riutilizzo del codice in qualche modo, magari usando una libreria comune.

Rilevo la versione utilizzando una chiamata all'API conosciuta, se possibile, invece di utilizzare un registro. In questo modo non sei obbligato a utilizzare la stessa versione su tutto il computer.

    
risposta data 03.08.2017 - 19:25
fonte
0

Se possibile, l'introduzione di un numero di versione per ogni struttura è un approccio, che è particolarmente apprezzato dalle API REST e dai formati di file (sia binari che di testo normale). La tua applicazione ha quindi la possibilità di aggiornare ogni versione alla successiva. Ciò significa che devi solo preoccuparti di un solo percorso di aggiornamento, e puoi semplicemente collegare gli aggiornamenti insieme se necessario. Se lo si desidera, è possibile aggiungere altri percorsi di aggiornamento per accelerare le cose e si possono anche avere percorsi di downgrade (questi sono spesso visibili in programmi in cui sono mantenute più versioni contemporaneamente e di solito c'è un costo che impedisce a molti clienti di eseguire immediatamente l'aggiornamento).

Quindi, ad esempio, potresti avere una struttura Employee . Forse la prima versione è davvero semplice:

{
    '_version': '1.0'
    'first_name': 'Amelia',
    'last_name': 'Bedelia',
    'employee_id': 123,
    'salary': 65000
}

Ma poi più avanti, supponiamo di voler davvero combinare quel campo del nome. Facciamo in modo che il cambiamento causi presumibilmente che ci sia un buon motivo per farlo! Abbiamo una nuova versione della nostra struttura:

{
    '_version': '1.1'
    'name': 'Amelia Bedelia',
    'employee_id': 123,
    'salary': 65000
}

Ora, potremmo avere solo il ramo codebase sul numero di versione, ma è molto più a prova di futuro e spesso più pulito per aggiornare semplicemente la struttura precedente a qualcosa di compatibile con quella nuova. Ciò potrebbe comportare il riempimento di valori predefiniti ragionevoli, la lettura di informazioni aggiuntive da altrove per compilare spazi vuoti, chiedere agli utenti tali informazioni o qualsiasi altra cosa. L'idea, tuttavia, è quella di garantire che la tua base di codice abbia solo a che fare con la versione più recente della struttura.

Quindi avremmo qualche funzione di aggiornamento con alcune diramazioni come:

if employee['_version'] == '1.0':
    # Let's just make an assumption about names and combine it blindly
    employee = {
        '_version': '1.1',
        'name': employee['first_name'] + ' ' + employee['last_name'],
        'employee_id': employee['employee_id'],
        'salary': employee['salary']
    }
# And so on for other version upgrades, returning the final object
# at the end.

E poi continueremo a farlo finché non avremo la versione corrente (che potrebbe poi essere convertita in un tipo più strong). Questo modello funziona anche bene per gli aggiornamenti del database (è come funziona la gestione consigliata di Android per i loro database SQLite).

Ora, questa risposta è stata di alto livello finora. Ma non è unico il luogo in cui la struttura dei dati può essere rappresentata come un dizionario generico (sebbene sia certamente una struttura di dati versatile e adattabile per lo scambio di informazioni attraverso i confini del sistema). Se i dati possono essere archiviati in un formato binario grezzo e abbiamo sempre la versione in una determinata posizione, possiamo facilmente scegliere quale struttura di basso livello viene utilizzata per interpretare i dati (e quindi applicare l'aggiornamento da lì). Nel qual caso avremmo ancora bisogno di memorizzare le suddette strutture da qualche parte, però (ma il valore deriva dal non dover avere molti percorsi di codice per queste diverse versioni di strutture).

    
risposta data 08.08.2017 - 21:54
fonte

Leggi altre domande sui tag