versionat d'aplicacions

Versionat d’aplicacions informàtiques – part 2

En el post anterior d’aquesta sèrie vam veure en què consitia el versionat d’aplicacions informàtiques. Avui entrarem dins d’una aplicació qualsevol per veure’n els components, que també ténen la seva versió. L’objectiu és entendre com impacta l’evolució dels components a l’evolució d’una aplicació.

Components reutilitzables i dependències

Les aplicacions informàtiques no es construeixen des de zero cada cop, això seria inassumible. En lloc d’això, una aplicació es confegeix a partir de peces ja pre-construïdes, a partir de les quals s’escriuen les instruccions que li són exclusives i que la fan única.

Aquestes peces pre-construïdes, els components d’una aplicació, tenen diferents noms, depenent de com estiguin fetes i com estiguin pensades per a ser usades (en són alguns exemples: llibreria, framework, servei web, programari base, sistema operatiu, etc.). Com que el programa està construït a partir d’aquestes peces, diem que el programa té dependència d’elles. El nom dependència és molt adequat perquè el programa depèn de la qualitat i complexitat de les peces sobre les quals està construït, i si el volem fer evolucionar, també hem de tenir en compte l’evolució de les seves dependències.

Aquests components reutilitzables que després es fan servir per a construïr aplicacions sorgeixen de diverses maneres. De vegades, com a part del desenvolupament d’una aplicació concreta, es detecta que una part pot ser extraïble i distribuïble de manera que altres aplicacions se’n puguin beneficiar. Altres cops, es desenvolupa una peça reutilitzable sabent d’entrada que ho serà, perquè es detecta que hi ha una necessitat que no està coberta adequadament.

En qualsevol cas, la versió d’un component reutilitzable té gran importància per les aplicacions que el fan servir, i així parlem que la versió x d’una programa depèn de la versió x d’una llibreria, necessita funcionar sobre la versió x d’un altre cert programari, o que requereix la versió x de tal sistema operatiu. Pot passar que si construïm la nostra aplicació amb una versió diferent d’una de les seves dependències deixi de funcionar correctament.

APIs i la seva evolució

I com ho fan les aplicacions per utilitzar el codi d’aquests components reutilitzables? No és viable agafar codi arbitrari d’un component reutilitzable i executar-lo o copiar-lo, sinó que els components defineixen i documenten quines parts del seu codi exposen al públic, i com s’hi ha d’interactuar, el que s’anomena interfície. Més concretament, interfície de programació d’aplicacions, o les seves sigles en anglès, API.

Una API és una mena de contracte entre un component reutilitzable i aquelles aplicacions que el volen usar. Diu per exemple: si tu em passes la latitud i longitud d’un punt jo et retornaré un mapa que podràs mostrar com una secció d’una pàgina web. I ho diu d’una manera precisa, sabent exactament quin tipus de dades li has de passar i exactament quines estructures de dades et retornarà.

Amb el temps, els components van evolucionant, afegint noves funcionalitats o canviant comportaments, i això queda reflectit en la seva API, que també evoluciona. Parlem de canvis compatibles (o compatibles cap enrere – backwards compatible) quan es fan canvis en una API de manera que no afecten a les aplicacions que fan servir la versió antiga. En canvi, quan els canvis són incompatibles, haurem de prendre alguna acció, com no actualitzar la dependència o canviar el codi de l’aplicació.

Durant els periodes de desenvolupament i manteniment d’una aplicació, normalment és recomanable anar actualitzant les versions de les dependències, i així beneficiar-se de les millores que aporten. Però això no es pot fer sense una planificació: si un component fa un canvi no compatible a la seva API, la nostra aplicació en resultarà afectada, i haurem d’efectuar alguna acció. Sovint fent canvis en el codi, però de vegades no és possible o té un cost massa elevat.

Dependències transitives i conflictes

Els components reutilitzables tampoc no estan programats des de zero, sinó que també estan construïts a partir d’altres components. Aquests altres components són dependències indirectes de la nostra aplicació. Potser alguns d’ells també ja són dependències directes perquè ja els estiguem cridant directament des de la nostra aplicació.

En aquest cas, es poden donar situacions de conflicte, quan necessitem directament una certa versió del component A, però per altra banda fem servir el component B que requereix una versió diferent del mateix component A.
Si les dues versions del component A són compatibles no hi haurà problema, però si són incompatibles tenim un conflicte de versions.
Aquesta situació de conflicte es pot resoldre canviant les versions usades del component A o B, fins a trobar una combinació possible, però de vegades es fa irresoluble, i hem de desistir de fer servir algun dels dos components.
Aquest exemple de dependències transitives entre 3 components us podeu imaginar que s’exten a molts més nivells i es pot complicar encara més.
Al llarg del temps, l’evolució de la nostra aplicació i dels seus components pot fer que una combinació que inicialment era possible deixi de ser-ho, i hem de quedar-nos amb una versió antiga d’un component si volem seguir-lo fent servir.
En definitiva, la idea a emportar-se és que la versió dels components en que es basa la nostra aplicació és important i no es pot negligir.

Podem saber si una API ha fet canvis compatibles llegint la documentació del component. La iniciativa de versionat semàntic (Semantic Versioning) intenta establir un estàndard en la nomenclatura de la versió de manera que només veient-ne el número ja podem saber si serà compatible o no, sense haver d’anar als detalls de la documentació.
Per exemple, la versió 2.8 és compatible amb la 2.7, però la 3.0 ja no ho serà.
De vegades, però, les autores dels components no són conscients d’introduïr canvis incompatibles, i t’ho trobes de sobte durant el desenvolupament.

Versionat d’aplicacions: Com ho fem a Jamgo

A Jamgo fem servir eines estàndard com Maven o Composer per a gestionar les dependències de les aplicacions que construïm.

Aquestes eines permeten especificar en un fitxer quina versió, o rang de versions, necessita la nostra aplicació. En el moment de construïr-la, l’eina estudia totes dependències directes i indirectes, i acaba seleccionant les versions de tots els components necessaris per acabar muntant l’aplicació. Si hi ha algun conflicte, l’eina ens avisarà i podrem fer l’acció que més ens convingui.

Internament, també intentem en la mesura del possible reutilitzar el codi dels diferents projectes en què participem. Aquest és un repte del qual parlem sovint, i hem fet sessions tècniques conjuntes per decidir la millor manera d’actuar. També documentem i versionem les nostres APIs.

Conclusions

Hem vist com les aplicacions informàtiques estan construïdes per components que interactuen entre ells mitjançant les seves APIs. Les evolucions dels components fan que les APIs canvïin de manera compatible o incompatible. Aquests canvis poden tenir impacte en l’evolució de la nostra aplicació. A Jamgo fem servir les eines i processos adequats per tenir en compte aquestes situacions i resoldre-les de la millor manera possible.