Blog

Monoliitista modulaariseen

JAN 30, 2020

Näin otat modulaarisen arkkitehtuurin turvallisesti käyttöön legacy-ohjelmistoissa

Christian Clausen

Christian is a Technical Agile Coach working in Aarhus. He has written a book: Five lines of code - how and when to refactor. Before he was working as a Senior CRM Consultant. He likes wood-working and he has built a climbing wall at his home. He loves helping others and learning. His dream is one day to start a family or live on Mars, whichever comes first.
<br>
<br>
<a href="https://www.eficode.com/christian-clausen">Find out more about Christian</a>

Ohjelmistokehityksessä tiukka kytkentä on yksi suurimmista vihollisistamme. Funktiotasolla se tekee sovelluksestamme vaikeasti muutettavan ja hauraan. Valitettavasti tiukka kytkentä on kuin ohjelmistokehityksen entropiaa, joten sen vähentämiseksi on tehtävä jatkuvasti työtä.

Johdanto

Tässä blogikirjoituksessa selitän, miten modulaarinen arkkitehtuuri otetaan käyttöön legacy-järjestelmässä. Tavoitteemme on esitellä prosessi, joka vie meidät tiukimmin kytketystä järjestelmästä eli monoliitista kohti löyhimmin kytkettyä järjestelmää, jonka organisaatiomme sallii. Haluamme, että erilliset tiimit voivat toimittaa ja testata erillisiä komponentteja, mikä mahdollistaa pienemmät eräkoot ja nopeammat palautesyklit. Haluamme myös, että järjestelmämme on vakaa ja helposti skaalautuva.

Lähtötilanne

identify modules

Jotta tämä opas soveltuu tilanteeseesi, muutaman asian on oltava totta. Oletamme, että järjestelmä on liiketoimintakriittinen ja tällä hetkellä käytössä. Oletamme, että järjestelmää kehittää useampi kuin yksi kehitystiimi. Yleensä nämä tiimit saavat ominaisuuspyyntöjä, joihin liittyy liiketoiminta-arvo tai priorisointi. Oletamme myös, että työskentelemme käännettävällä olio-ohjelmointikielellä, kuten Javalla tai C#:lla. Suuri osa tästä soveltuu mihin tahansa tyyliin, jossa data on kapseloitu, esimerkiksi RESTin kautta kommunikoiviin palveluihin. Oletamme myös, että lähdekoodi on versionhallinnassa.

Rajoitteet

Työskentelemme todellisten ihmisten kehittämien ja käyttämien ohjelmistojen kanssa, joten muutoksen käytännöllisyyden varmistamiseksi on huomioitava muutamia inhimillisiä rajoitteita.

Conwayn laki

Conwayn lain mukaan organisaatio tuottaa ohjelmistoja, jotka jäljittelevät sen viestintärakennetta. Tämä tarkoittaa, ettei ohjelmiston jakaminen osiin yksin riitä. Meidän on varmistettava, että organisaatiomme viestintä määräytyy liiketoiminnan mukaan tavalla, joka vastaa koodissa tavoittelemaamme rakennetta. Jos haluamme irrottaa moduulit toisistaan, meidän on irrotettava myös tiimit toisistaan.

Pidä järjestelmä aina toimivana

Yleensä ”vihreä” tarkoittaa läpäistyjä testejä, mutta käytämme termiä tässä laajemmin: järjestelmä toimii. Olemme ”vihreällä”, kun ohjelmisto on käynnissä ja käyttäjät voivat käyttää sitä. Emme oleta, että ohjelmistossa on automatisoituja testejä. Liiketoimintakriittistä ja käytössä olevaa järjestelmää ei voi sammuttaa, refaktoroida ja avata uudelleen. Ihmiset ovat riippuvaisia järjestelmästä työssään, joten meidän on varmistettava, että järjestelmä pysyy toimivana koko prosessin ajan. Kattava testaus on yksi tapa varmistaa tämä, mutta se ei ole aina mahdollista. Tällöin tarvitsemme mahdollisimman paljon apua muista työkaluista, kuten kääntäjästä. Jos ilmenee kriittinen ongelma, se on pystyttävä korjaamaan mahdollisimman pienellä vaivalla. Palvelutason säilyttäminen Palvelutason säilyttäminen suorituskyvyn, toiminnallisuuden ja virhetason osalta on myös tärkeä rajoite. Järjestelmän ei pitäisi heikentyä muutoksen aikana huomattavasti. Sen pitäisi itse asiassa olla lopulta parempi.

Vaihe 0: Tunnista moduulit

identify modules

Jos haluamme, että jokainen tiimi voi ottaa käyttöön muutoksia itsenäisesti, meidän on ensin määritettävä, mitä ne ottavat käyttöön. Pidetään Conwayn laki mielessä: koodimme moduulit jäljittelevät organisaatiomme viestintää. Ihannetapauksessa viestimme paljon oman tiimimme sisällä ja huomattavasti vähemmän muiden tiimien kanssa, mutta tämä ei toimi, jos tarvitsemamme data tai käyttöoikeudet puuttuvat.

Sovelluksen toiminnallisuus on jaettava siten, että tiimien välisen koordinoinnin ja työn luovutusten tarve jää mahdollisimman vähäiseksi. Hyvää vastuunjakoa voi olla vaikea löytää, mutta hyödyllinen tekniikka on aloittaa datasta. Pyydä jokaista tiimiä määrittämään, minkä datan sen pitäisi omistaa, ja anna data sille. Suurinta osaa datasta vaatii vain yksi tiimi, mutta osa datasta kuuluu usean tiimin tarpeisiin. Tällöin vaihtoehtoja on kolme. Jos suuri osa useammasta kuin yhdestä tiimistä tarvitsee dataa, tiimien yhdistäminen voi olla järkevää. Jos dataa tarvitsee noin kolmasosa kummastakin tiimistä, kaksi tiimiä kannattaisi ehkä jakaa kolmeen tiimiin siten, että yksi vastaa vain yhteisestä osuudesta. Jos tarve on pieni kummassakin tiimissä, data kannattaa todennäköisesti kahdentaa.

isolate moduls

Vaihe 1: Eristä moduulit

Kun olemme tunnistaneet haluamamme moduulit, meidän on tehtävä niistä itsenäisiä. Teemme tämän vaiheittain. Ensimmäinen vaihe on luoda yksi sisääntulopiste. Se kertoo tarkalleen, mikä nykyinen ulkoinen rajapintamme on. Yksi sisääntulopiste mahdollistaa myös sisäisen toteutuksen muuttamisen haluamallamme tavalla niin kauan kuin ulkoinen rajapinta pysyy samana. Tämä vähentää haavoittuvuutta. Turvallinen tapa tehdä tämä on luoda tyhjä luokka, julkisivu. Muutamme nyt kaikki moduulin metodit käytettäviksi vain moduulin sisällä. Tällöin kääntäjä ilmoittaa virheen kaikkialla, missä metodia käytetään moduulin ulkopuolelta. Jokaisen virheen kohdalla muutamme kutsun käyttämään julkisivun uutta julkista metodia. Julkisivu voi nyt kutsua sisäistä metodia, koska se kuuluu moduuliin.

reduce communication

Vaihe 2: Vähennä viestintää

Seuraava vaihe on vähentää moduulien välistä viestintää. Teemme tämän siirtämällä toiminnallisuuden mahdollisimman lähelle dataa. Sen sijaan, että pyydät dataa ja muunnat sitä vasta sen jälkeen, siirrä muunnos datan yhteyteen ja pyydä lopputulos. Tämä koskee sekä moduulien sisäistä toimintaa että moduulirajojen yli tapahtuvaa viestintää. Toiminnallisuuden siirtäminen tarkoittaa myös, että poistamme suurimman osan paluuarvojen tarpeesta. Kun laskenta siirtyy lähemmäs dataa, voimme välittää sen parametreina ja jatkaa kutsuja eteenpäin. Tähän on olemassa useita refaktorointimalleja, mutta ne eivät kuulu tämän kirjoituksen aihepiiriin. Lisätietoja tästä tehtävästä saat seuraavista lähteistä:

Vaihe 3: Poista suora viestintä

Ainoa asia, joka tällä hetkellä estää erillisten moduulien käyttöönoton, ovat kehäriippuvuudet, joita syntyy kutsuttaessa moduuleja, jotka puolestaan kutsuvat meitä. Viimeisen vaiheen jälkeen on helppoa siirtyä täysin push-pohjaiseen järjestelmään. Otamme käyttöön keskitetyn viestijonon, johon kaikki voivat lähettää viestejä. Lisäämme myös jokaiseen moduuliin tarkkailijaluokan, joka voi seurata viestijonoa. Kun viestijonoon tulee asiaankuuluva viesti, tarkkailija kutsuu julkisivun asiaankuuluvaa metodia. Rajoitamme nyt julkisivun metodien käytön vain oman moduulimme sisälle, kuten teimme aiemmin. Kääntäjä kertoo jälleen kaikkialla, missä käytämme julkisivua, ja näissä kohdissa lähetämme sen sijaan asiaankuuluvan tyyppisen viestin keskitettyyn viestijonoon. Jos viestijono on hyvin yksinkertainen, sitä ei välttämättä tarvitse ylläpitää lainkaan. Näin on esimerkiksi silloin, kun käytämme pilviratkaisua tai kyseessä on sisäinen tavallinen jono. Muussa tapauksessa myös sen ylläpitoon on osoitettava tiimi. Nyt kaikella on vain yksi riippuvuus: keskitetty viestijono, jolla ei ole riippuvuuksia. Voimme nyt siirtää moduulimme omiin projekteihinsa ja omiin repoihinsa sekä kääntää ne erillään kaikesta muusta kuin viestijonosta. Tämä tarkoittaa, että voimme myös toimittaa dll-tiedostomme, jar-tiedostomme tai palvelumme itsenäisesti.

common challenges

Yleiset haasteet

Tähän muutokseen liittyy muutamia haasteita. Käymme tässä läpi yleisimmät niistä.

Tiimien välinen viestintä

Kuten aiemmin käsiteltiin, Conwayn lain vuoksi ohjelmistomme muuttaminen ei riitä. Meidän on myös hallittava sitä, miten organisaatio viestii, erityisesti tiimien välillä. Koska arkkitehtuurimme perustuu keskitettyyn viestijonoon, viestinnän tulisi noudattaa samaa mallia: keskitetty API dokumentoi kaikki järjestelmän tukemat metodit. Koska järjestelmämme on nyt push-pohjainen, myös viestintämme tulisi olla sitä. Teemme ominaisuuspyyntöjä muille tiimeille. Sen jälkeen kehitämme uuden ominaisuuden haluamallamme Agile-prosessilla. Kun ominaisuus on valmis, sitä kehittänyt tiimi julkaisee keskitetyssä API:ssa tiedot uusista viestityypeistä, jotka vastaavat tarpeisiimme. Tämän tulisi olla ainoa tarvittava ammatillinen viestintä.

Riippuvuudet ja versiointi

Tavoitteenamme oli irrottaa komponentit toisistaan, jotta voisimme ottaa ne käyttöön erikseen. Tämä herättää luonnollisesti kysymyksen siitä, miten ne pidetään synkronoituna keskenään. Ratkaisemme tämän yleensä käsittelemällä sitä REST API:n tavoin. Koodaamme versionumeron viestiin. Tämän jälkeen jokainen tarkkailija vastaa sopivan metodin kutsumisesta julkisivussa. Tämä tarkoittaa, että koodissamme voi olla samanaikaisesti useita versioita samasta toiminnallisuudesta niin kauan kuin niitä täytyy tukea.

Testaus

Sovelluksen jakaminen tällä tavoin tuo usein esiin koodimme testaamattomia tai puutteellisesti testattuja osia. Kuten toiminnallisuuksien kohdalla, myös testauksessa tulisi pyrkiä siirtämään testit mahdollisimman lähelle dataa. Joka kerta, kun jokin epäonnistuu, meidän tulisi kysyä: ”Miten voimme siirtää tämän virheen yhden tason lähemmäs dataa?”

Täydellisyyden vuoksi esittelemme testauksen olennaiset tasot:

  • Yksikkötestit testaavat moduulin sisäisiä metodeja. Vastuu on tiimillä tai kehittäjällä.

  • Komponenttitestit varmistavat, että moduulille lähetetty viesti johtaa lopulta oikeiden viestien lähettämiseen viestijonoon. Vastuu on tiimillä.

  • Integraatiotestit varmistavat, että useat komponentit käsittelevät viestejä oikein. Vastuu on mukana olevilla tiimeillä yhdessä.

  • Järjestelmätestit testaavat toiminnallisuuksia, joihin liittyy useimpia tai kaikkia komponentteja. Vastuu on organisaatiolla.

  • Tuotantotestit varmistavat, että järjestelmä toimii. Vastuu on organisaatiolla.

Yhteenveto

Tässä vaiheessa meillä on useita tiimejä, joilla kullakin on moduuli, jonka ne voivat rakentaa ja toimittaa itsenäisesti. Olemme vähentäneet järjestelmän haavoittuvuutta eristämällä jokaisen moduulin. Jos haluamme lisäksi skaalata horisontaalisesti, voimme helposti muokata tarkkailijamme käyttämään sisäistä työjonoa ja liittää siihen useampia moduulin instansseja.

Järjestelmä tukee jopa uusien toiminnallisuuksien dynaamista lisäämistä pluginien muodossa. Tämä on mahdollista, koska voimme tilata samat viestit, joita järjestelmä käyttää, tai keksiä uusia viestejä muuttamatta tai vaikuttamatta olemassa olevaan koodiin. Olemme myös huolehtineet siitä, että järjestelmä pysyy toimivassa tilassa alusta loppuun. Rikkomamme asiat ovat aina olleet paikallisia, committien välissä. Niillä ei siis pitäisi olla vaikutusta tuotantoon. Kun olemme rikkoneet asioita, olemme tehneet sen lyhyissä jaksoissa ja aina kääntäjän avustuksella. Olemme mahdollisesti myös avanneet uusia mahdollisuuksia infrastruktuurin näkökulmasta. Jos aloitimme yhdestä sovelluksesta, voimme nyt tehdä keskitetyn viestijonon saataville verkon kautta ja ottaa palveluita käyttöön eri palvelimilla, jolloin syntyy mikropalveluarkkitehtuuri. Voimme jopa jakaa moduulimme niin, että jokaisesta julkisivumme funktiosta tulee oma erikseen käyttöönotettava minimoduulinsa. Tämä olisi serverless-arkkitehtuuri.

Tähän pääseminen voi olla pitkä prosessi, mutta Roomaakaan ei rakennettu päivässä. Pienempien eräkokojen, vankemman koodin ja tämän muutoksen mahdollistamien lyhyempien palautesyklien hyödyt tekevät työstä ehdottomasti vaivan arvoista.

  • DevOps
  • CI/CD

Subscribe to our newsletter