Pianificare le dipendenze di compilazione per la pipeline
In questa unità viene illustrata la creazione dei pacchetti di codice per semplificarne la condivisione. Si scoprirà perché è consigliabile creare i pacchetti, quali tipi di pacchetti è possibile creare, dove ospitare i pacchetti e come accedervi quando sono ospitati. Si apprenderanno anche informazioni sul controllo delle versioni del pacchetto.
Le codebase diventano sempre più grandi e più complesse. È insolito che un team scriva tutto il codice usato dall'app. Invece, il team include il codice esistente scritto da altri sviluppatori. In un'app possono essere presenti molti di questi pacchetti o dipendenze. È importante gestire le dipendenze in modo attivo per poterle conservare in maniera appropriata e verificare che soddisfino i requisiti di sicurezza.
Viene illustrata la situazione e come se la cava il team. Andy ha riunito il team per discutere di una possibile modifica al codice che aiuterebbe un altro team.
Riunione del team
Andy: Ciao a tutti. Stavo parlando con il team che lavora al sistema back-end di Space Game. Potrebbero sfruttare i modelli usati per il sito Web in un'app back-end che stanno progettando.
Amita: Cosa intendi per modelli?
Andy: Come sai, il sito Web di Space Game è un'applicazione ASP.NET Core. Usa il modello Model-View-Controller (MVC) per separare i dati dal modo in cui questi vengono visualizzati nell'interfaccia utente. Pensavo che si potrebbe creare un pacchetto contenente le classi del modello in modo che qualsiasi app possa usarle.
Amita: Qual è esattamente l'obiettivo?
Andy: Entrambi i team condivideranno lo stesso database. Il gioco invia i punteggi elevati del database che poi vengono letti per essere mostrati nella classifica.
Amita: Ha senso. Come verrà creato il pacchetto?
Andy: Ecco perché volevo parlare con te. Ci sono alcune opzioni e sono in cerca di idee.
Tim: Mi piacerebbe essere d'aiuto, ma prima ho alcune domande. Non ho familiarità con questi concetti e vorrei capire come funzionano.
Che cos'è un pacchetto?
Un pacchetto contiene codice riutilizzabile che può essere usato da altri sviluppatori nei propri progetti, anche se non è stato scritto da loro.
Per i linguaggi compilati, un pacchetto contiene in genere il codice binario compilato, ad esempio i file con estensione DLL in .NET o i file con estensione CLASS in Java. Per i linguaggi che vengono interpretati invece che compilati, ad esempio JavaScript o Python, un pacchetto potrebbe includere il codice sorgente.
In entrambi i casi i pacchetti vengono in genere compressi in formato ZIP o in un formato simile. I sistemi di pacchetti spesso definiscono un'estensione di file univoca, come .nupkg o .jar, per rendere chiaro l'uso del pacchetto. La compressione consente di ridurre i tempi di download e produce inoltre un singolo file per semplificare la gestione.
I pacchetti spesso contengono uno o più file con metadati o informazioni sul pacchetto. Questi metadati possono descrivere lo scopo del pacchetto, specificare le condizioni di licenza, contenere le informazioni di contatto dell'autore e la versione del pacchetto.
Perché compilare un pacchetto?
La compilazione di un pacchetto presenta vantaggi che la duplicazione del codice non ha.
Uno dei motivi per cui la creazione di un pacchetto è preferibile alla duplicazione del codice è evitare la deriva. Quando il codice viene duplicato, ogni copia può divergere rapidamente per soddisfare i requisiti di una particolare app. Risulta difficile eseguire la migrazione delle modifiche da una copia all'altra. In altre parole, si perde la possibilità di migliorare il codice in modi che vanno a vantaggio di tutti.
Per di più, i pacchetti raggruppano le funzionalità correlate in un unico componente riutilizzabile. A seconda del linguaggio di programmazione, un pacchetto può consentire alle app l'accesso a determinati tipi e funzioni, limitando al contempo l'accesso ai dettagli di implementazione.
Un altro buon motivo per creare un pacchetto è il fatto che rappresenta un modo coerente per compilare e testare le funzionalità del pacchetto. Quando il codice viene duplicato, ciascuna app può compilarlo e testarlo in modi diversi. Un insieme di test potrebbe includere controlli che potrebbero essere utili a un altro insieme di test.
Un compromesso è che con un pacchetto è necessario testare e gestire un'altra codebase. È necessario prestare particolare attenzione anche quando si aggiungono le funzionalità. In generale, un pacchetto deve contenere funzionalità utili per molti tipi di app. Ad esempio, Json.NET è un pacchetto NuGet diffuso per .NET che consente di usare i file JSON. Json.NET è open source, quindi la community può proporre i miglioramenti e segnalare i problemi.
Quando più app possono trarre beneficio dallo stesso codice, i vantaggi superano di gran lunga gli svantaggi. Si dovrà gestire una sola codebase, un solo set di test e un solo processo di compilazione.
Come è possibile identificare le dipendenze?
Se l'obiettivo è quello di riorganizzare il codice in componenti separati, è necessario identificare le parti dell'app che possono essere rimosse, inserite in un pacchetto in modo da essere riutilizzabili, archiviate in una posizione centrale e sottoposte al controllo delle versioni. È anche possibile sostituire il proprio codice con componenti di terze parti che sono open source o concessi in licenza.
Esistono molti modi per identificare le possibili dipendenze nella codebase, tra cui l'analisi del codice per individuare i modelli di riutilizzo e l'analisi dell'architettura della soluzione. Ecco alcuni modi per identificare le dipendenze:
Codice duplicato.
Se alcune parti di codice vengono visualizzate in più posizioni significa che il codice può essere riutilizzato. Centralizzare le parti duplicate del codice e reinserirle in un pacchetto in modo appropriato.
Coesione elevata e accoppiamento basso.
Un secondo approccio consiste nel cercare gli elementi di codice con elevata coesione tra loro e un grado di accoppiamento basso con altre parti del codice. In sostanza, la coesione elevata significa mantenere le parti di una codebase correlate tra loro in un'unica posizione. Allo stesso tempo, un accoppiamento basso consiste nel separare il più possibile le parti non correlate della codebase.
Ciclo di vita singolo.
Cercare le parti del codice che hanno un ciclo di vita simile e possono essere distribuite e rilasciate singolarmente. Se il codice può essere gestito da un team separato, significa che può essere incluso in un pacchetto come componente esterno alla soluzione.
Parti stabili.
Alcune parti della codebase potrebbero essere stabili e cambiare raramente. Controllare il repository del codice per trovare il codice con una frequenza di modifica bassa.
Codice e componenti indipendenti.
Quando il codice e i componenti sono indipendenti e non correlati ad altre parti del sistema, è possibile isolarli in dipendenze separate.
È possibile usare vari strumenti che consentono di analizzare ed esaminare la codebase. Esistono vadie opzioni, dagli strumenti che analizzano il codice duplicato e disegnano i grafici delle dipendenze della soluzione agli strumenti che consentono di calcolare le metriche per l'accoppiamento e la coesione.
Quali sono i tipi di pacchetto disponibili?
Ogni linguaggio di programmazione o framework prevede una modalità specifica di compilazione dei pacchetti. I sistemi di pacchetti più diffusi rendono disponibile la documentazione sul funzionamento del processo.
È possibile che si abbia già familiarità con questi sistemi di pacchetti comuni:
- NuGet: librerie di pacchetti .NET
- NPM: librerie JavaScript di pacchetti
- Maven: librerie di pacchetti Java
- Docker:pacchetti software in unità isolate denominate contenitori
Dove sono ospitati i pacchetti?
I pacchetti possono essere ospitati nella propria rete oppure è possibile usare un servizio di hosting. Un servizio di hosting viene spesso definito repository di pacchetti o registro di pacchetti. Molti di questi servizi offrono hosting gratuito per i progetti open source.
Ecco i servizi di hosting più comuni per i tipi di pacchetto appena descritti:
-
I pacchetti NuGet vengono usati per gli artefatti del codice .NET. Questi artefatti includono gli assembly .NET e i file correlati, gli strumenti e talvolta i metadati. NuGet definisce il modo in cui i pacchetti vengono creati, archiviati e usati. Un pacchetto NuGet è essenzialmente una struttura di cartelle compressa con file in formato ZIP e con estensione .nupkg.
-
Un pacchetto NPM viene usato per JavaScript. Un pacchetto NPM è un file o una cartella che contiene file JavaScript e un file package.jsnon che descrive i metadati del pacchetto. Per node.js, il pacchetto contiene in genere uno o più moduli che possono essere caricati dopo che il pacchetto è stato usato.
-
Il pacchetto Maven viene usato per i progetti basati su Java. Ogni pacchetto include un file POM (Project Object Model) che descrive i metadati del progetto ed è l'unità di base per la definizione e l'uso del pacchetto.
-
I pacchetti Docker sono denominati immagini e contengono distribuzioni complete e autonome. In genere, un'immagine Docker rappresenta un componente software che può essere ospitato ed eseguito autonomamente, senza dipendenze di altre immagini. Le immagini Docker sono sovrapposte e possono dipendere da altre immagini.
Un feed di pacchetti si riferisce al server del repository di pacchetti. Questo server può trovarsi su Internet o dietro il firewall sulla rete. Ad esempio, è possibile ospitare i feed NuGet usando prodotti di hosting come Azure Artifacts e MyGet. È anche possibile ospitare i pacchetti in una condivisione file.
Quando si ospitano i pacchetti dietro il firewall, è possibile includere i feed nei propri pacchetti. È anche possibile memorizzare nella cache i pacchetti considerati attendibili nella rete quando i sistemi non possono connettersi a Internet.
Quali elementi costituiscono una buona strategia di gestione delle dipendenze?
Una corretta strategia di gestione delle dipendenze si basa su questi tre elementi:
Standardizzazione.
La standardizzazione del modo in cui si dichiarano e risolvono le dipendenze assicura ripetibilità e prevedibilità del processo di rilascio automatizzato.
Formati di pacchetto e origini.
Ogni dipendenza deve essere inserita in un pacchetto tramite il formato applicabile e archiviata in una posizione centrale.
Controllo delle versioni.
È necessario tenere traccia delle modifiche apportate nel tempo alle dipendenze proprio come si fa con il proprio codice. Ciò significa che è necessario eseguire il controllo delle versioni delle dipendenze.
Chi può accedere ai pacchetti?
Molti feed di pacchetti consentono un accesso illimitato ai pacchetti. Ad esempio, è possibile scaricare Json.NET da nuget.org, senza dover eseguire l'accesso o l'autenticazione.
Per altri feed di pacchetti è richiesta l'autenticazione. È possibile autenticare l'accesso ai feed in diversi modi. Ad esempio, alcuni tipi di feed richiedono un nome utente e una password. Altri feed richiedono un token di accesso, che in genere è costituito da una lunga serie di caratteri che identificano l'utente e le risorse a cui si ha accesso. I token di accesso possono essere impostati in modo da scadere dopo un determinato periodo di tempo.
In che modo viene controllata la versione dei pacchetti?
Lo schema di controllo delle versioni dipende dal sistema di creazione dei pacchetti usato.
I pacchetti NuGet, ad esempio, usano il Versionamento Semantico.
Il Versionamento Semantico è uno schema di controllo delle versioni noto. Ecco il formato:
Major.Minor.Patch[-Suffix]
Sotto viene illustrato il significato di ognuno di questi parametri:
- Una nuova versione principale, Major, introduce modifiche che causano un'interruzione. Per usare una nuova versione principale, le app devono in genere aggiornare le modalità di uso del pacchetto.
- Una nuova versione Minor introduce nuove funzionalità, ma è compatibile con le versioni precedenti.
- Una nuova patch introduce correzioni di bug compatibili con la versione precedente, ma non nuove funzionalità.
- La parte -Suffix è facoltativa e identifica il pacchetto come versione non definitiva. Ad esempio, 1.0.0-beta1 potrebbe identificare il pacchetto come prima build della versione preliminare beta per la versione 1.0.0.
Quando si fa riferimento a un pacchetto, si usa il numero di versione.
Sotto è riportato un esempio di installazione di un pacchetto tramite l'uso di PowerShell e di un numero di versione specifico:
Install-Package Newtonsoft.Json -Version 13.0.1
Cosa accade quando il pacchetto viene modificato?
Quando si fa riferimento a un pacchetto dell'app, in genere si aggiunge o si specifica la versione del pacchetto che si vuole usare.
Molti framework consentono di specificare gli intervalli di versioni del pacchetto che è possibile installare. Alcuni consentono anche di specificare caratteri jolly, che chiamiamo versione mobile.
Ad esempio, in NuGet la versione "1.0" indica la prima versione uguale o superiore alla versione 1.0. "[1.0]" indica di installare solo la versione 1.0 e non una versione più recente.
Ecco alcuni esempi:
Questa notazione: | Seleziona: |
---|---|
(1.0,) | La prima versione maggiore di 1. |
[1.0,2.0] | La prima versione maggiore o uguale a 1.0 e minore o uguale a 2.0 |
(1.0,2.0) | La prima versione maggiore di 1.0 e minore di 2.0 |
[1.0,2.0) | La prima versione maggiore o uguale a 1.0 e minore di 2.0 |
Quando un gestore rilascia una nuova versione del pacchetto, è possibile valutarne le modifiche apportate e testare l'app in base a tali modifiche. Quando si è pronti, è possibile aggiornare il numero di versione del pacchetto nella configurazione e inviare la modifica alla pipeline di compilazione.
Ecco un esempio di come includere il pacchetto Newtonsoft.Json nel file di progetto (con estensione csproj) dell'applicazione C#. Questo esempio specifica la versione 13.0.1 del pacchetto:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>