6 Ohjelmistoarkkitehtuurit
|
|
|
- Emma Hukkanen
- 10 vuotta sitten
- Katselukertoja:
Transkriptio
1 Esipuhe Tämä teos on tarkoitettu yliopistotason opetusmateriaaliksi kursseille, joilla käsitellään ohjelmistoarkkitehtuureja. Sen kohderyhmänä ovat erityisesti opiskelijat, jotka perehtyvät syvällisesti ohjelmistotuotantoon tai ohjelmistotekniikkaan. Teoksen toivotaan antavan myös teollisuudessa työskenteleville ohjelmistoammattilaisille yleiskuvan ohjelmistoarkkitehtuurien keskeisistä teemoista. Koska teoksesta on haluttu tehdä kattava, monet sen sisältämistä asiakokonaisuuksista voidaan käsitellä vain pintapuolisesti. Toisaalta monesta tällaisesta asiakokonaisuudesta on jo nyt kirjoitettu kokonaisia oppikirjoja. Tämän teoksen tavoitteena on antaa tiivistetty esitys myös tällaisista aiheista. Tämän teoksen aihepiiri liittyy läheisesti ohjelmistotuotantoon. Oletamme, että lukijalla on perustiedot paitsi ohjelmoinnista yleensä myös ohjelmistokehitysprosesseista. Monessa kohdin oletamme myös, että lukija hallitsee ainakin pintapuolisesti olioparadigman ja UML:n (Unified Modeling Language). Kuten monet muut ohjelmistotekniset tuotokset, tämäkin teos on syntynyt kiinteässä yhteistyössä useiden tahojen kanssa, joita kaikkia yhdessä ja erikseen haluamme kiittää. Jan Bosch on ystävällisesti antanut käyttöömme materiaalia, jota olemme hyödyntäneet kirjan tekstissä. Maarit Harsu ja Tommi Myllymäki ovat auttaneet tuoterunkoja koskevan luvun kirjoittamisessa. Kirjan eri osia ovat ansiokkaasti kommentoineet Ilkka Haikala, Juha Hautamäki, Mika Korhonen, Tero Laine, Mika Pussinen, Petri Selonen ja Tarja Systä. Haluamme myös kiittää materiaalin erilaisia esiversioita opiskelussaan käyttäneitä opiskelijoita Tampereen teknillisen yliopiston ohjelmistoarkkitehtuurikurssilla vuosina ja kurssien toteuttamiseen osallistunutta henkilökuntaa, sekä teollisuudessa pidettyjen koulutustilaisuuksien osallistujia, joiden kommentit on myös pyritty ottamaan huomioon teosta kirjoitettaessa. Lisäksi kiitämme
2 6 Ohjelmistoarkkitehtuurit Ohjelmistotekniikan laitoksen henkilökuntaa ja Practise-tutkimusryhmää ymmärryksestä proffien kirjaprojektin aikana sekä teollisuusyhteistyökumppaneitamme kannustuksesta. Kirjan kirjoittamista on tukenut Nokia Säätiön apuraha. Tampereella, Kai Koskimies ja Tommi Mikkonen sähköposti:
3 7 Sisällys Esipuhe... 5 Sisällys Johdanto Arkkitehtuuri ohjelmistokehityksessä Mikä on ohjelmistoarkkitehtuuri? Ohjelmistoarkkitehtuurin määritelmä Arkkitehtuurin tehtävät Arkkitehtuurin dokumentointi Arkkitehtuuripainotteinen ohjelmistokehitysprosessi Arkkitehtuurin käyttö toteutusvälineenä Arkkitehtuurin ja organisaation yhteys Arkkitehtuurinäkymät Arkkitehtuurin abstraktiotasoista Johdatus teoksen muihin lukuihin Yhteenveto Harjoitustehtäviä Ohjelmistoarkkitehtuurin kuvaus Arkkitehtuurikuvauksen merkitys Arkkitehtuurikuvaukseen liittyvät käsitteet Arkkitehtuurikuvauksen abstraktiotaso Näkökulmat arkkitehtuurien kuvauksessa Arkkitehtuurikuvausten tyypit Arkkitehtuuriviipaleiden kuvaus Arkkitehtuuridokumentit...41
4 8 Ohjelmistoarkkitehtuurit 2.8 Malliperustainen arkkitehtuuri Yhteenveto Harjoitustehtäviä Komponentit ja rajapinnat Komponentit Komponentin määritelmä Komponentit arkkitehtuurin yksikköinä Komponentit ohjelmistokehityksen yksikköinä Rajapinnat Rajapinnan käsite Tarjotut ja vaaditut rajapinnat Rajapintojen määrittely Komponenttien räätälöinti Komponentin tilan muuttaminen Vaadittujen rajapintojen toteuttaminen Periytymisen avulla toteutettu räätälöinti Yhteenveto Harjoitustehtäviä Komponenttien vuorovaikutustekniikat Komponentit ja niiden vuorovaikutus Rajapinnat Roolirajapinnat Usean komponentin välinen vuorovaikutus Kutsun siirtäminen Edustajakomponenttien käyttö Takaisinkutsut Tapahtumiin perustuva vuorovaikutus Rajapintariippuvuuksien poistaminen Luontiriippuvuus ja sen purkaminen Yhteenveto Harjoitustehtäviä Suunnittelumallit Johdatus suunnittelumalleihin
5 Mikä on suunnittelumalli? Suunnittelumallit ohjelmistotekniikassa Suunnittelumallin sisältö Suunnittelumallien kuvaaminen Antisuunnittelumallit Esimerkki: Rekursiokooste-suunnittelumalli Ongelma: hierarkkisen oliokokoelman hallinta Vaihe 1: kooste ja lehdet Vaihe 2: koosteen ja lehtien samaistaminen Vaihe 3: erityyppiset oliot Vaihe 4: koosteen eriyttäminen Suunnittelumallin esittäminen Suunnittelumallien edut ja ongelmat Suunnittelumallien tarjoamat edut Suunnittelumalleihin liittyviä ongelmia Suunnittelumallit ja UML Yhteenveto Harjoitustehtäviä Arkkitehtuurityylit Arkkitehtuurityyli ryhmittelyn perustana Kerrosarkkitehtuurit Tietovuoarkkitehtuuri Palveluperustaiset arkkitehtuurityylit Asiakas-palvelin-arkkitehtuurit Viestinvälitysarkkitehtuurit Sovellusaluekohtaiset arkkitehtuurityylit Malli-näkymä-ohjain -arkkitehtuurit Tietovarastoarkkitehtuurit Tulkkipohjaiset arkkitehtuurit Esimerkki: auton polttoaineenkulutuksen valvontaohjelmisto Arkkitehtuurityylit ja UML Yhteenveto Harjoitustehtäviä...152
6 10 Ohjelmistoarkkitehtuurit 7 Tuoterunkoarkkitehtuurit Arkkitehtuurin rooli ohjelmistokehityksessä Tuoterunkoarkkitehtuurit ja niiden hyödyt Tuoterungon edut Tuoterunko investointina Tuoterungon rakentaminen ja evoluutio Ohjelmistokehitysprosessi tuoterunkoa käytettäessä Prosessin yleiskuvaus Alustakehitysprosessi Tuotekehitysprosessi Muunneltavuus tuoterungossa Muunneltavuus vaatimustasolla Muunneltavuus suunnittelussa ja toteutuksessa Muunneltavuus testauksessa Tuoterunko ja organisaatio Tuoterunkoarkkitehtuurin kerrosmalli Resurssialusta Arkkitehtuurialusta Sovellusalusta Sovelluskerros Esimerkki: auton valvonnan tuoterunko Tuoterunkojen ongelmia Yhteenveto Harjoitustehtäviä Ohjelmistokehykset Johdatus ohjelmistokehyksiin Mikä on ohjelmistokehys? Erikoistamisrajapinta Kehykset ja suunnittelumallit Hollywood-periaate Erikoistamismekanismit Kehykset ohjelmistokehitysparadigmana Kehyslajit Abstraktit kehykset
7 Muunneltavat kehykset Plugin-kehykset Koottavat kehykset Kehysten strukturointi Kerroksittaiset kehykset Hierarkkiset kehykset Komponenttikehysten käyttö Kehysten suunnittelu Kehysten suunnittelu ja oliosuunnittelu Kehysten iteratiivinen rakentamisprosessi Kehyksen kehitysprosessin vaiheet Kerroksittaisen kehyksen suunnittelu Erikoistamismallit Kehysten riskejä Tekninen vaativuus ja monimutkaisuus Kehysten yhdistely Monoliittisuus Laadullinen varianssi Dokumentointi Yhteenveto Harjoitustehtäviä Arkkitehtuurien arviointi Johdatus arkkitehtuurin arviointiin Arvioinnin kohdentaminen Arvioinnin perusteet Arvioinnin sidosryhmiä Arkkitehtuurin arviointi osana ohjelmistoprosessia Arkkitehtuurin arviointimenetelmät ATAM Esittelyosio Analyysiosio Testausosio Raportointiosio...233
8 12 Ohjelmistoarkkitehtuurit 9.5 Esimerkki: auton kunnonvalvontaohjelmiston arkkitehtuurin arviointi Arvioinnin ongelmia Yhteenveto Harjoitustehtäviä Kirjallisuusviitteet Hakemisto...247
9 OSA I Perusteet
10 1 Johdanto Ohjelmistoarkkitehtuurit ovat muodostuneet selvästi omaksi ohjelmistotekniikan alueekseen verrattain myöhään, vasta 1990-luvun loppupuoliskolla. Sitä ennen ohjelmistoarkkitehtuureja pidettiin lähinnä vain korkeamman tason suunnitteluna. Nykyisin ohjelmistoarkkitehtuurien merkitys ohjelmistotekniikan alueena kasvaa jatkuvasti. Uusia arkkitehtuuriaiheisia oppikirjoja julkaistaan, niitä käsitteleviä uusia konferenssisarjoja perustetaan ja arkkitehtuuriin liittyvät kysymykset yleistyvät alan opetuksessa ja tutkimuksessa. 1.1 Arkkitehtuuri ohjelmistokehityksessä Ohjelmallisesti toteutettujen sovellusten ymmärtämisessä ja rakentamisessa on liikuttu primitiivisistä välineistä kohti suurempia kokonaisuuksia (kuva 1.1). Alussa sovellukset pyrittiin näkemään lähinnä muistipaikkoihin ja rekistereihin talletettujen lukujen ja merkkijonojen käsittelynä (esim. assembler-kielet). Sitten opittiin tunnistamaan näiden muodostamia rakenteita, joita toteuttamaan kehitettiin kieliin rakenteisia tietotyyppejä (esim. C, Pascal). Vastaavasti tunnistettiin Sovelluskäsitteet Luvut, merkkijonot ("nimi") Tietorakenteet ("osoite") Toiminnot ("talletus") Tietoabstraktiot ("tili") Palveluyksiköt ("tilin hallinta") Sovellusalue ("pankkiliiketoiminta") Toteutuskäsitteet Muistipaikat, rekisterit Rakenteiset tietotyypit Aliohjelmat Luokat Komponentit, agentit, rajapinnat Tuoterungot, kehykset, alustat Kuva 1.1: Ongelma-alueen ja toteutustekniikan käsitetason siirtyminen kohti suurempia kokonaisuuksia
11 16 Ohjelmistoarkkitehtuurit Johdanto 17 toimenpiteitä, joita näille rakenteille tuli suorittaa; nämä esitettiin kielissä aliohjelmina. Sitten opittiin näkemään sovelluksissa suurempia käsitteitä, joihin liittyy sekä tietoa että käyttäytymistä. Näitä toteuttamaan kehitettiin kieliin tietoabstraktio eri muodoissa. Tietoabstraktio kokoaa yhteen tietorakenteita ja niihin liittyviä operaatioita, ja antaa mahdollisuuden käsitellä tietoa vain näiden operaatioiden kautta. Eräs tietoabstraktion muoto on olioparadigman tuoma luokkakäsite, jossa abstraktiin tietotyyppiin on yhdistetty laajennettavuus (periytyminen). Tämän jälkeen havaittiin tarve muodostaa suurempia kokonaisuuksia loogisesti yhteen kuuluvista palveluista, jotka esitettiin tietyt rajapinnat toteuttavina komponentteina (tai agentteina, jos palvelut olivat proaktiivisia). Vähitellen yksittäisen sovelluksen käsite hämärtyi toteutuksen kannalta: eri sovellukset jakoivat yhteisiä komponentteja ja kirjastoja. Lisäksi opittiin ymmärtämään, että monilla myös eri sovellusalueille tarkoitetuilla ohjelmistoilla on olennaisesti sama perusarkkitehtuuri, ja kehitettiin ohjelmistoalustoja (platform), jotka toteuttavat tällaisen arkkitehtuurin tarvitseman yleisen infrastruktuurin. Edelleen ymmärrettiin, että eri sovellukset ovat usein läheistä sukua toisilleen sekä sovelluksen toteutusrakenteen että toiminnallisuuden kannalta. Tällä tavoin tunnistettiin tietyille sovellusalueille sovellustai tuoteperheitä (product/application family), joiden toteuttamisen tueksi kehitettiin lähinnä olioparadigman sisällä sovelluskehyksen käsite. Ohjelmistojen luominen tapahtuu näin yhä enemmän arkkitehtuuritason käsitteiden ja toteutusmekanismien pohjalta. Tähän suuntaukseen on vaikuttanut ennen muuta järjestelmien jatkuva monimutkaistuminen ja kasvaminen, uudelleenkäytön merkityksen kasvaminen ja yleinen arkkitehtuuritason ratkaisuja korostava teknologian kehitys (esimerkiksi mobiili-, hajautus- ja komponenttiteknologiat). Arkkitehtuurin merkitys näkyy monessa asiassa ohjelmistokehitysprosessissa. Inkrementaalinen ja rinnakkainen ohjelmistokehitys saavutetaan yleensä arkkitehtuuritason ratkaisuilla. Arkkitehtuuri on tärkeä työnjaon kannalta: vain arkkitehtuuritasolla tunnistettavat osat järjestelmää ovat mielekkäitä työn jakamisen yksiköitä. Arkkitehtuuri on olennainen myös testauksen ja ylläpidon kannalta: hyvä arkkitehtuuri jakaa ohjelmiston osiin, jotka on helppo testata erillisinä ja jotka paikallistavat ylläpidossa tarvittavat muutostyöt. Arkkitehtuuri antaa korkean abstraktiotason näkymän ohjelmistoon, mikä mahdollistaa aikaisempaa monimutkaisempien järjestelmien tarkastelun ja myös erilaisten sidosryhmien välisen kommunikoinnin järjestelmästä. Arkkitehtuurin eriytyminen omaksi alueekseen on tukenut erilaisten standardiratkaisujen kehittämistä ja dokumentoimista arkkitehtuuritasolle, jolloin myös arkkitehtuurien käytölle ja määrittelemiselle voidaan asettaa selkeät perussäännöt. Arkkitehtuurien painoarvoa kasvattaa se, että arkkitehtuurin määritteleminen ja dokumentointi mahdollistaa järjestelmän arvioinnin joidenkin kriittisten tekijöiden osalta jo hyvin varhaisessa vaiheessa ohjelmistokehitystä, jolloin järjestelmän muuttaminen on vielä halpaa. Koska arkkitehtuuri toimii yleisenä ohjenuorana ohjelmistokehitykselle ja ohjelmiston ylläpidolle, arkkitehtuurivaiheessa siirrytään ongelman käsitteistöstä ratkaisun käsitteistöön. Epäonnistuneesta järjestelmästä voidaan usein syyttää arkkitehtuuria. Huono arkkitehtuuri voi ilmetä monin eri tavoin järjestelmän kehittämisen, käyttämisen ja ylläpidon aikana. Pahin mahdollisuus on, että järjestelmää ei pystytä lainkaan toteuttamaan tarkoitetussa laajuudessa kyseisessä organisaatiossa. Arkkitehtuuri voi esimerkiksi tehdä olemassa olevasta teknologiasta oletuksia, jotka eivät pidä paikkaansa. Vastaavasti arkkitehtuuri voi tehdä toteuttamisen niin vaikeaksi, ettei sitä pystytä tekemään suunnitellussa aikataulussa. Arkkitehtuurin heikkouksien takia järjestelmä ei ehkä pysty tehokkaasti suoriutumaan kuin pienistä tieto- tai käyttäjämääristä, tai arkkitehtuuri saattaa tehdä esimerkiksi käyttöliittymästä niin hitaan, ettei sitä voi järkevästi käyttää. Arkkitehtuuri voi tehdä myös järjestelmän testauksesta tai ylläpidosta vaikeaa ja kallista, jos se ei tue osien erillistä testausta tai muutosten tekemistä. Jos arkkitehtuuri ei esimerkiksi selkeästi erota ympäristöstä (esim. käyttöjärjestelmä) riippuvia osia, järjestelmän siirtäminen uuteen ympäristöön voi olla vaikeaa. Epäonnistuminen voi tapahtua myös ensimmäisen version onnistuneen käyttöönoton jälkeen. Koska arkkitehtuuri on keskeinen tekijä ohjelmistojen uudelleenkäytössä, epäonnistuminen arkkitehtuurisuunnittelussa voi käytännössä estää suunnitellun laajuisen uudelleenkäytön.
12 18 Ohjelmistoarkkitehtuurit Johdanto Mikä on ohjelmistoarkkitehtuuri? Vaikka ohjelmistoarkkitehtuureista puhutaan paljon, usein niillä tarkoitetaan hivenen eri asiaa puhujan kokemusmaailman mukaisesti. Tarkastelemme seuraavassa lähemmin ohjelmistoarkkitehtuurin määritelmää Ohjelmistoarkkitehtuurin määritelmä Ohjelmistoarkkitehtuureille on esitetty useita erilaisia määritelmiä kirjallisuudessa. IEEE:n arkkitehtuurien kuvaamista koskeva standardi määrittelee ohjelmistoarkkitehtuurin järjestelmän perusorganisaatioksi, joka sisältää järjestelmän osat, niiden keskinäiset suhteet ja niiden suhteet ympäristöön sekä periaatteet, jotka ohjaavat järjestelmän suunnittelua ja evoluutiota (IEEE 2000). Olennaista määritelmässä on, että arkkitehtuuri ei kata pelkästään järjestelmän jakamista osiin, vaan myös näiden osien välisiä suhteita ja niiden kehittymistä. Koska suhteet ovat usein luonteeltaan ajoaikaiseen käyttäytymiseen liittyviä, arkitehtuuri koskee paitsi rakennetta myös käyttäytymistä. Toisaalta arkkitehtuuri ei koske ainoastaan ohjelmiston staattista rakennetta (koodin rakennetta), vaan myös ohjelman suorituksen aikaisia rakenteita, esimerkiksi dynaamisia oliorakenteita. Yksinkertaisuuden vuoksi ohjelmiston arkkitehtuuria tarkastellaan usein jostakin tietystä näkökulmasta, esimerkiksi tiedostorakenteen, loogisen rakenteen, prosessirakenteen jne. näkökulmista. Olennaista on myös, että arkkitehtuuriin katsotaan kuuluvan tiettyjen ratkaisujen perustelu, ei ainoastaan kuvaus. Lisäksi arkkitehtuuriin yleensä liittyy joukko sääntöjä ja periaatteita, jotka säätelevät sitä, miten järjestelmiä kehitetään tähän arkkitehtuuriin perustuen. Säännöt, joita tietyn arkkitehtuurin mukaan rakennettavassa järjestelmässä on noudatettava, voivat koskea teknologian käyttöä (esim. käytettävä JavaBeans-komponentteja), algoritmien valintaa (esim. lajittelussa käytettävä quicksortia), tietorakenneratkaisuja (esim. joukot toteutettava kahteen suuntaan linkitettyinä listoina), sekä suunnittelu- ja toteutusmalleja (esim. kommunikointiin käytettävä Observer-suunnittelumallia). Voidaankin ajatella, että arkkitehtuuri on järjestelmän perustuslaki. Sitä on noudatettava järjestelmää rakennettaessa, ja sitä voi- daan muuttaa vain erittäin painavilla perusteilla. Samaa asiaa korostavat Catalysis-suunnittelumenetelmän kehittäjät d'souza ja Wills, jotka määrittelevät arkkitehtuurin joukoksi päätöksiä, jotka estävät toteuttajia ja ylläpitäjiä olemasta tarpeettoman luovia (D Souza ja Wills 1998). Toisin sanoen arkkitehtuuri määrittelee rajat, joiden puitteissa järjestelmää on rakennettava ja ylläpidettävä. Arkkitehtuuri määrittelee siis järjestelmän ytimen, joka pysyy olennaisesti samana kehityksen ja ylläpidon aikana. Niitä osia, jotka eivät kuulu tähän ytimeen, voidaan vapaasti muunnella tarkoituksenmukaisemmiksi. Tämä ajattelutapa pätee erityisen hyvin nk. tuoterunkoarkkitehtuureihin, joihin tutustumme myöhemmin tässä teoksessa; tällöin vaihtuvat osat vastaavat sovelluskohtaisia osia. Tämä määritelmä on sikäli yleinen, että se sallii myös arkkitehtuurit, joissa tärkein osa ei ole varsinainen ohjelmistokuvaus, vaan esimerkiksi jokin tietty menettelytapa, jonka avulla ohjelmistoa voidaan muunnella. Esimerkiksi jokin tietty käyttöjärjestelmä voi vaatia, että kaikki resurssit näkyvät sovellusohjelmille niitä hallinnoivien palvelinten kautta. Tällaisia periaatteita kutsutaan joskus myös arkkitehtuurien filosofioiksi Arkkitehtuurin tehtävät Arkkitehtuurin tehtävänä on ottaa kantaa keskeisiin ohjelmiston ratkaisuihin, jotka voivat koskea paitsi yleistä jakamista osiin myös osien välistä komminkointitapaa, prosesseja ja niiden välistä kommukointia, tiedon saantitapoja ja pysyvyyden toteuttamista, toiminnallisuuden sijoittelua eri järjestelmän osiin, hajautetun järjestelmän fyysistä rakennetta, järjestelmän kykyä käsitellä suuria tieto- ja käyttäjämääriä, tehokkuutta, varautumista tulevaisuuden tarpeisiin, uudelleenkäytettävyyttä jne. Koska monia näistä ratkaisuista on vaikea arvioida etukäteen, joudutaan arkkitehtuurin toimivuutta usein kokeilemaan osin puutteellisella tai keinotekoisesti rakennetulla järjestelmällä. Näiden kokeilujen systematisointi johtaa ns. inkrementaaliseen kehitykseen. Tällöin ensimmäinen vaihe joutuu tyypillisesti ottamaan kantaa perusarkkitehtuuriin, ja muut vaiheet tuovat olemassaolevaan runkoon enemmän ja enemmän toiminnallisuutta (Boehm 1988). Arkkitehtuuri perustuu ohjelmiston ositukseen tietyllä periaatteella. Ohjelmistojen dekompositio (osiin pilkkominen) voidaan teh-
13 20 Ohjelmistoarkkitehtuurit Johdanto 21 dä monilla perusteilla, esimerkiksi toiminnallisuuteen, yleisyyteen, hajautukseen, käsitemalliin, joustavuusnäkökulmaan ym. perustuen. Aspektiperustainen ohjelmointi pyrkii strukturoimaan ohjelmiston paitsi perinteisen arkkitehtuurin, myös erilaisten arkkitehtuurin määrittelemien rakenneyksiköiden yhteisten aspektien (kuten vaikkapa virheidenkäsittely, tiedon pysyvyys, hajautus, tietty järjestelmän ulkoisesti havaittava piirre jne.) kannalta (Filman et al. 2005). Koska tällaiset aspektit ovat yleensä riippumattomia komponenttijaosta, aspektit edustavat komponentteja läpileikkaavia (cross-cutting) viipaleita järjestelmästä. Näin järjestelmä voidaan samanaikaisesti strukturoida useassa eri ulottuvuudessa. Kärjistettynä tämä näkemys johtaa ajatukseen, että ohjelmistot ovat perusluonteeltaan moniulotteisia rakenteita ja että mikään yksittäinen komponenttijako ei pysty tavoittamaan tätä ominaisuutta Arkkitehtuurin dokumentointi Jos arkkitehtuuri ei ole selkeästi dokumentoitu, sitä ei ole oikeastaan olemassa: arkkitehtuuri materialisoituu kuvauksena. Vaikka järjestelmän dokumentoimattomasta arkkitehtuurista voidaan puhua, eri henkilöt tekevät tällöin todennäköisesti hyvin erilaisia olettamuksia siitä, mikä kuuluu järjestelmän arkkitehtuuriin ja mikä ei. Vaikka järjestelmällä olisikin alun perin ollut selkeä arkkitehtuuri, arkkitehtuurikuvauksen puuttuminen johtaa jossain vaiheessa järjestelmän ja sen arkkitehtuurin rapautumiseen (Clements et al. 2003). Periaatteena tulisi olla, että ohjelmistoprojektia, joka ei ole saavuttanut selvää arkkitehtuurin kuvausta, ei pitäisi päästää jatkamaan: projektilta puuttuu sen perustuslaki, jolloin sen kehitys saattaa helposti muuttua anarkiaksi. 1.3 Arkkitehtuuripainotteinen ohjelmistokehitysprosessi Arkkitehtuuripainotteisessa ohjelmistokehitysprosessissa korostetaan arkkitehtuurin suunnittelua ja arviointia keskeisenä vaiheena ennen siirtymistä yksityiskohtaiseen suunnitteluun ja toteutukseen. Arkkitehtuuri pyritään johtamaan inkrementaalisesti ja iteratiivisesti lähtien arkkitehtuurin kannalta merkittävistä vaatimuksista. Arkkitehtuuriin vaikuttavat toisaalta kaikkein keskeisimmät toiminnalliset vaatimukset ja toisaalta laadulliset (ei-toiminnalliset) vaatimukset. Prosessi etenee tyypillisesti siten, että ensimmäinen versio arkkitehtuurista tehdään toiminnallisten vaatimusten pohjalta, ja sitä arvioidaan sitten vasten laadullisia vaatimuksia. Tarpeen vaatiessa arkkitehtuuria uudistetaan niin, että kukin laadullinen vaatimus täyttyy. Kun kaikki laadulliset vaatimukset on saatu näin täytettyä, järjestelmän perusarkkitehtuuri on valmis. Tämän pohjalta edetään yksityiskohtaiseen suunnitteluun, toteutukseen ja testaukseen, jolloin saadaan ensimmäinen, keskeisimmät toiminnalliset vaatimukset täyttävä toteutus. Tämän jälkeen tarkastellaan sekundaarisia toiminnallisia vaatimuksia ja toteutetaan ne perusarkkitehtuurin pohjalle. Tässäkin vaiheessa voidaan vielä havaita tarvetta muuttaa arkkitehtuuria. Toteutus tehdään yleensä inkrementaalisesti vaatimus (tai kokokoelma toisiinsa liittyviä vaatimuksia) kerrallaan. Mikäli sovelluksen käyttöönoton jälkeen tulee uusia vaatimuksia tai vaatimukset muuttuvat, joudutaan prosessi periaatteessa käymään läpi alusta lähtien: jos esimerkiksi uudet vaatimukset ovat arkkitehtuurin kannalta merkittäviä, joudutaan myös arkkitehtuuri muuttamaan tai ainakin arvioimaan uudestaan. Jos arkkitehtuurisuunnittelussa sovelletaan käyttötapauksiin (use case) pohjautuvaa prosessia, primääriset ja sekundääriset toiminnalliset vaatimukset saadaan jakamalla käyttötapaukset arkkitehtuurin kannalta kriittisiin ja vähemmän kriittisiin. Käyttötapaukset voivat tällöin suoraan edustaa vaatimuksia. Kuvassa 1.2 esitetty prosessimalli on monessa suhteessa idealistinen mutta antaa kuitenkin karkean mallin, jota voidaan soveltaa käytännöllisissä prosessimalleissa. Vaikka arkkitehtuurin suunnittelun lähtökohtana ovat yleensä tärkeimmät toiminnalliset vaatimukset, lopullisen arkkitehtuurin muodon sanelevat kuitenkin usein pikemmin ei-toiminnalliset, laatuun liittyvät vaatimukset ("*ilities"). Varsinkin joustavuus ja muunneltavuus sekä toisaalta tehokkuus ja muistin kulutus ovat keskeisiä, arkkitehtuuriin usein vastakkaisesti vaikuttavia tekijöitä. Toisaalta voidaan kysyä, minkä suhteen halutaan olla joustavia, sillä kaikkien ohjelmiston ominaisuuksien kannalta joustavuuden vaatiminen on epärealistista. Jos joustavuuden kohdetta ei määritellä etukäteen, voi tuloksena olla järjestelmä, joka on periaatteessa joustava mutta jossa
14 22 Ohjelmistoarkkitehtuurit Johdanto 23 Uusia tai muuttuneita vaatimuksia Käyttöönotto Toissijaiset toiminnalliset vaatimukset Testaus Arkkitehtuurin kannalta merkittävät vaatimukset Alustava arkkitehtuurisuunnittelu Toteutus Alustava arkkitehtuuri Arkkitehtuurin muunnos Vaatimusanalyysi Yksityiskohtainen suunnittelu Arvioi laatuominaisuus Toiminnalliset perusvaatimukset Laatuvaatimukset Perusarkkitehtuuri Kuva 1.2: Arkkitehtuuriperustainen ohjelmistokehitysprosessi väärät tässä tapauksessa siis vakiona pysyvät asiat voidaan muuttaa helposti, mutta käytännössä muuttuvat asiat vaativat enemmän työtä. Vaikka arkkitehtuurisuunnittelussa on periaatteessa kyse kokoelmasta yhteensopiva ratkaisuita, joiden avulla voidaan täyttää kaikki vaatimukset, toteutuksista on usein luettavissa, että jokin vaatimus tai jotkin vaatimukset ovat selvästi dominoineet arkkitehtuurisuunnittelua. Tyypillisesti jokin arkkitehtoonisesti merkittävistä vaatimuksista on niin paljon tärkeämpi kuin muut, että se on käytännössä johtanut tiettyyn perusarkkitehtuuriin, johon muut vaatimukset on sitten pakotettu. Arkkitehtuurin eräs olennainen tehtävä onkin kuvata, kuinka (tai missä määrin) arkkitehtoonisesti merkittävät vaatimukset täyttyvät annetulla arkkitehtuurilla. Tässä mielessä arkkitehtuuri voidaan nähdä kokoelmana korkean tason suunnitteluratkaisuja, jotka pyrkivät tiettyjen, erityisesti laadullisten vaatimusten ja niistä seuraavien ongelmien ratkaisuun. ei OK OK 1.4 Arkkitehtuurin käyttö toteutusvälineenä Toinen puoli arkkitehtuuripainotteista ohjelmistokehitystä on, että uusia ohjelmistoja toteutetaan yhä enemmän perustuen olemassaolevaan arkkitehtuuriin. Tämä johtuu yleisestä pyrkimyksestä lisätä ohjelmistojen uudelleenkäyttöä, joka ilmenee erilaisina ohjelmistoalustoina. Kun ohjelmistoalustalla on arkkitehtuuri, joka sovelluskehittäjän täytyy ottaa huomioon, arkkitehtuurille tulee toteutusvälineen rooli, joka on verrattavissa ohjelmointikielen rooliin. Arkkitehtuurin tarjoamat välineet ovat vain korkeammalla tasolla kuin ohjelmointikielen tarjoamat välineet. Sovelluskehittäjän kannalta olennaiseksi ongelmaksi tulee pikemmin se, miten sovelluksen vaatimukset saadaan toteutettua alustan tarjoamalla arkkitehtuurilla, kuin se, miten ne saadaan toteutettua tietyllä ohjelmointikielellä. Tässä roolissa arkkitehtuurilta edellytetään ominaisuuksia, jotka tavallisesti liitetään ohjelmointikieliin. Arkkitehtuurin tulee olla määritelty täsmällisesti ja täydellisesti (syntaksi ja semantiikka), ja sen dokumentoinnin tulee olla käyttäjälle suuntautunutta. Arkkitehtuurilla voidaan myös ajatella olevan staattiset ja dynaamiset oikeellisuussäännöt kuten ohjelmointikielillä (esimerkiksi tyyppisäännöt). Työkalujen kannalta kiinnostava havainto on, että arkkitehtuurille voidaan rakentaa samantapaisia tukiympäristöjä kuin ohjelmointikielille, tukemaan esimerkiksi arkkitehtuurin oikeellisuussääntöjen noudattamista, koodin generointia, visualisointia, takaisinkääntämistä ym. Suuntaus kohti arkkitehtuuritason toteutusvälineistöä antaa aiheen puhua uudesta ohjelmointiparadigmasta, arkkitehtuuriperustaisesta ohjelmoinnista. Tarkastellaan esimerkkinä Enterprise Java Beans -teknologiaa (EJB). Kun sovellus tehdään EJB:n pohjalle, olennainen suunnitteluongelma on se, miten sovelluksen käsitteet toteutetaan EJB:n tarjoamilla välineillä esimerkiksi mitkä asiat toteutetaan nk. istuntopavulla (session bean), mitkä nk. säilykepavulla (entity bean) ei niinkään miten Javalla toteutetaan tietyt asiat. Toinen esimerkki on graafisen käyttöliittymän toteutus esimerkiksi Swingin (Java-ympäristön GUI-kehys) pohjalle. Tässäkään tapauksessa ongelmana ei ole niinkään Javan käyttö sinänsä, vaan Swingin tarjoamien mekanismien käyttö toteutusvälineenä.
15 24 Ohjelmistoarkkitehtuurit Johdanto Arkkitehtuurin ja organisaation yhteys Järjestelmän arkkitehtuurilla on myös läheinen yhteys järjestelmää kehittävään tai ylläpitävään organisaatioon. Mielekkäästi toimiva organisaatio alkaa muistuttaa rakenteeltaan järjestelmän arkkitehtuuria. Tämä johtuu viime kädessä siitä, että tyypillisesti komponentti, joka on arkkitehtuurin perusyksikkö, annetaan yhden henkilön kehitettäväksi. Toisiinsa liittyvät komponentit pyritään antamaan tietylle ryhmälle, joka saa näin vastuulleen tyypillisesti jonkin alijärjestelmän. Tämä tulee selvästi näkyviin esimerkiksi tuoterunkoarkkitehtuurien kohdalla: ohjelmistoalustasta vastaa tavallisesti oma organisaation yksikkö, kun taas tuotekohtaisesta ohjelmistosta vastaa oma yksikkö. Vastaavasti organisaatio, joka vuodesta toiseen ylläpitää samaa ohjelmistoa, yleensä jakaa ylläpidon arkkitehtuurin sallimalla tavalla. Yleisesti ilmiö tunnetaan nimellä Conwayn laki (Conway 1968). Ohjelmiston kehittämiseen liittyy useita sidosryhmiä (stakeholder), joita ovat esimerkiksi kehittäjät, ylläpitäjät, testaajat ja käyttäjät. Kullakin sidosryhmällä on yleensä oma näkökulmansa ohjelmistoon, ja tästä näkökulmasta seuraavat odotukset ja vaatimukset. Usein eri sidosryhmien vaatimukset ovat keskenään ristiriitaisia. Arkkitehdin keskeisenä tehtävänä on määritellä arkkitehtuuri, joka tuottaa vaatimusten välille siedettävän kompromissin riittävän monen sidosryhmän kannalta. 1.6 Arkkitehtuurinäkymät Arkkitehtuureja voidaan tarkastella eri näkökulmista (viewpoints). Tässä yhteydessä kutsumme arkkitehtuurin kuvausta tietystä näkökulmasta (arkkitehtuuri)näkymäksi (view). Tätä on havainnollistettu kuvassa 1.3. Kukin näkymä mallintaa jonkin asian järjestelmästä. Kun kyse on saman järjestelmän eri näkymistä, niillä on luonnollisesti paljon riippuvuuksia: jos jotain kohtaa muutetaan yhdessä näkymässä, täytyy mahdollisesti useita kohtia muuttaa muissa näkymissä. Yhdessä näkymät muodostavat kattavan, osittain päällekkäisen kuvauksen järjestelmän arkkitehtuurista. Erilaisilla työkaluilla voidaan jossain määrin tuottaa automaattisesti näkymiä olemassaolevista järjestel- Näkymä Näkökulma Näkymä Kuva 1.3: Arkkitehtuurinäkymiä Näkymä Järjestelmä mistä (reverse engineering) tai koodia näkymien perusteella (code generation). Työkalut voivat myös varmistaa, että näkymät ovat keskenään yhtäpitäviä. Tarkastelemme eri näkökulmia lähemmin luvussa Arkkitehtuurin abstraktiotasoista Järjestelmän arkkitehtooninen malli Arkkitehtuurilla ymmärretään usein ainoastaan ohjelmointitason rakenteita. Tämä on kuitenkin hyvin rajoittunut näkökulma, sillä arkkitehtuurin tarjoamat edut vaativat yleensä useiden erilaisten abstraktiotasojen hyödyntämistä. Hyvin korkealla abstraktiotasolla jonkin järjestelmäkategorian perusrakenne voidaan antaa esimerkiksi nk. arkkitehtuurityylinä tai referenssiarkkitehtuurina; tällainen kuvaus määrittelee arkkitehtuuriin liittyvät käsitteet yleisin tai sovellusaluekohtaisin termein, ottamatta kantaa yksittäiseen järjestelmään. Toisaalta arkkitehtuurikuvaus voidaan antaa konkreettisen järjestelmän lähdekoodin rakenteena eri tarkkuustasoilla riippuen käyttötarkoituksesta. Viime aikoina eri abstraktiotasoilla olevien arkkitehtuurikuvausten väliset transformaatiot ovat herättäneet suurta mielenkiintoa.
16 26 Ohjelmistoarkkitehtuurit Johdanto 27 Eräs konkreettinen hanke on OMG:n (Object Management Group) MDA-hanke, malliperustainen arkkitehtuuri (Model-Driven Architecture) (Object Management Group 2003). Malliperustaisen arkkitehtuurin tavoitteena on tarjota ohjelmoijalle mahdollisuus rakentaa abstrakteja kuvauksia järjestelmän arkkitehtuurista, ja tämän jälkeen generoida sopiva toteutustason arkkitehtuuri jollekin tietylle sovellusalustalle noudattaen tämän nimenomaisen alustan käytäntöjä. Generointi voi tapahtua joko automaattisesti tai suunnittelijan avustamana. Malliperustaisen arkkitehtuurin olennaisena piirteenä voidaankin pitää eri tasojen välisiä mallitransformaatioita, joiden avulla saadaan abstraktimpia tai yksityiskohtaisempia näkymiä arkkitehtuuriin. Samalla arkkitehtuurin merkitys korostuu, sillä se on esitettävä tarkasti ja yksiselitteisesti transformaatioiden mahdollistamiseksi. 1.8 Johdatus teoksen muihin lukuihin Teos koostuu kolmesta toisiaan täydentävästä kokonaisuudesta. Näistä ensimmäinen on johdanto ohjelmistoarkkitehtuurien luonteeseen ja peruskäsitteisiin; osa koostuu tästä johdannosta ja seuraavasta luvusta, jossa tarkastellaan arkkitehtuurien kuvaamista yleisesti. Lukujen yhteinen tavoite on tarjota riittävä käsitteellinen ja terminologinen perusta, jotta seuraavien osien läpikäyminen olisi mahdollista. Teoksen toinen osa käsittelee arkkitehtuureja lähtien liikkeelle arkkitehtuurin peruselementeistä ja siitä, ja millaisilla ratkaisuilla hallitaan niiden välisiä suhteita. Tässä arkkitehtuuri nähdään pienessä mittakaavassa: käymme läpi joukon tekniikoita, joiden avulla tiettyjä komponenttien suhteisiin liittyviä ongelmia voidaan ratkaista. Ohjelmistoarkkitehtuuri on hyvin pitkälle komponenttien välisten riippuvuuksien hallintaa, jolloin arkkitehtuuriratkaisuilla pyritään näiden riippuvuuksien poistamiseen tai heikentämiseen. Toisen osan luvut esittelevät arkkitehtuurin perusyksiköt, komponentit ja rajapinnat sekä kuvaavat erilaisia tapoja hallita komponenttien välisiä riippuvuuksia. Koska käytännössä riippuvuuksia hallitaan usein nk. suunnittelumallien (design pattern) avulla, myös tämä alue tulee suhteellisen kattavasti käsiteltyä tässä osassa: käymme läpi erityyppisiä suunnittelumalleina(kin) esitettyjä perusratkaisuita. Lisäksi suunnittelumallin käsite esitellään erikseen omassa lu- vussaan. Osan tavoitteena on tarjota konkreettista apua käytännön ongelmiin arkkitehtuurisuunnittelussa. Teoksen kolmas ja viimeinen osa keskittyy arkkitehtuuriin suuressa mittakaavassa, jolloin arkkitehtuurin käsitettä tarkastellaan järjestelmätasolla pikemmin kuin yksittäisten komponenttien tasolla. Arkkitehtuuriratkaisujen osalta tarkastelemme kaikkein korkeimmalla järjestelmätasolla sovellettavia ajatusmalleja, nk. arkkitehtuurityylejä, jotka määräävät järjestelmän perusluonteen. Osa sisältää myös tuoterunkoarkkitehtuurien esittelyn ja niiden suositun olioperustaisen toteutustekniikan, sovelluskehysten, kuvauksen. Lisäksi tarkastelemme arkkitehtuurien arviointia ja esittelemme systemaattisen tavan suorittaa arkkitehtuurikatselmus järjestelmän laatuominaisuuksien selvittämiseksi. 1.9 Yhteenveto Ohjelmistoarkkitehtuuri on osa ohjelmistosuunnittelua; kaikki suunnitteluasiat eivät kuitenkaan ole arkkitehtuuria. Komponenttien väliset suhteet kuuluvat arkkitehtuuriin, mutta niiden sisäiset asiat eivät. Arkkitehtuuriin sisältyvät myös ratkaisujen perustelut. Ohjelmistoarkkitehtuuri voidaan nähdä rakennettavan järjestelmän "perustuslakina", joka antaa rajat yksityiskohtaiseen suunnitteluun ja toteutukseen. Arkkitehtuuriperustainen ohjelmistokehitys korostaa arkkitehtuurin roolia kehitysprosessia ohjaavana suunnitteluartifaktina. Ohjelmistoarkkitehtuuri voidaan kuvata eri asioita korostavista näkökulmista käsin ja eri abstraktiotasoilla Harjoitustehtäviä 1.1 Suuressa ohjelmistoprojektissa (tai tuotelinjassa) voi työskennellä useita satoja ohjelmistosuunnittelijoita. Miten mielestäsi arkkitehtuurisuunnittelu tulisi sisällyttää osaksi ohjelmistosuunnittelua tällaisessa ympäristössä?
17 28 Ohjelmistoarkkitehtuurit 1.2 Mitä ongelmia huonosti määritelty arkkitehtuuri voi aiheuttaa toteutusvaiheessa? Millainen ohjelmistokehitysprosessin tulisi olla, jotta näiltä ongelmilta vältyttäisiin? 1.3 Etsi www:stä kolme ohjelmistoarkkitehtuurin määritelmää (esim. joita pidät tavalla tai toisella oikeaan osuvina (älä kuitenkaan valitse tässä kirjassa esitettyä määritelmää). Vertaile määritelmiä keskenään ja tässä kirjassa annetun määritelmän kanssa. Millainen ohjelmistoarkkitehtuurin määritelmä vastaisi eniten sitä intuitiivista käsitystä, joka sinulla on tähän mennessä ohjelmistoarkkitehtuureista? 1.4 Kuvaa jonkin tuntemasi järjestelmän (esim. jokin harjoitustyö) arkkitehtuuri yhdellä vapaamuotoisella tavalla. Miten hyvin tämä kuvaus vastaa tehtävässä 1.3 esitettyjä ohjelmistoarkkitehtuurin määritelmiä? 1.5 Etsi ja suomenna seuraavien käsitteiden määrittelyt www:stä: a) product-line architecture, b) architectural style, c) design pattern, d) component, e) application framework. 1.6 Argumentoi puolesta tai vastaan seuraavaa väitettä: Ohjelmistojen rakentamisessa pitäisi korkean tason suunnittelu antaa tehtäväksi vain ohjelmistoarkkitehdin tutkinnon suorittaneille henkilöille, samaan tapaan kuin rakennusten suunnittelu annetaan vain arkkitehdin koulutuksen saaneiden henkilöiden tehtäväksi. 1.7 Argumentoi puolesta tai vastaan seuraavaa väitettä: Ohjelmistoarkkitehdin koulutuksen pitäisi poiketa ohjelmistosuunnittelijan koulutuksesta, samaan tapaan kuin rakennusarkkitehdin ja rakennesuunnittelijan koulutus eroavat toisistaan. 1.8 Minkälaisia muunnoksia mielestäsi tarvitaan arkkitehtuurien abstraktiotasojen välille malliperustaista arkkitehtuuria toteutettaessa? Mitkä tasot mielestäsi tarvitsevat tukea suunnittelijalta, ja mitkä voidaan toteuttaa automaattisesti? Minkälaiset työkalut voisivat auttaa malliperustaisuuden toteuttamisessa?
18 2 Ohjelmistoarkkitehtuurin kuvaus Ohjelmistoarkkitehtuurin kuvaus on keskeinen dokumentti, jossa arkkitehtuuri materialisoituu. Tarkastelemme tässä luvussa arkkitehtuurikuvauksen merkitystä ja erilaisia lähestymistapoja arkkitehtuurin kuvaukseen. 2.1 Arkkitehtuurikuvauksen merkitys Ohjelmiston arkkitehtuuri on tärkein ohjelmistoa luonnehtiva informaatio. Tästä syystä ohjelmiston eri sidosryhmien välinen kommunikointi koskee usein arkkitehtuuriin liittyviä kysymyksiä. Jotta kaikilla olisi sama käsitys ohjelmiston arkkitehtuurista, arkkitehtuurilla tulisi olla mahdollisimman kattava ja yksiselitteinen kuvaus. Vain tällaiseen kuvaukseen pohjautuen on mahdollista ilmaista täsmällisiä arkkitehtuuria (ja ylipäänsä koko järjestelmää) koskevia faktoja. Arkkitehtuurilla on näin tärkeä merkitys järkevän kommunikaation mahdollistavana ohjelmistoartefaktina, joka antaa paitsi keskeiset ohjelmistoratkaisut myös käsitteistön ja sanaston, johon pohjautuen järjestelmästä voidaan puhua. Nykyisin teollisuudessakin ymmärretään arkkitehtuurikuvausten merkitys, ja niistä on tullut keskeinen dokumentti ohjelmistokehitysprosesseissa. Ohjelmistoarkkitehtuurien kuvaus on kuitenkin vielä verrattain uusi ja edelleen kehittyvä ohjelmistotekniikan alue. Ohjelmiston arkkitehtuuri on pikemmin järjestelmää koskeva spesifikaatio kuin jokin järjestelmän ominaisuus, joka voitaisiin päätellä suoraan järjestelmästä. On tosin olemassa (puoli)automaattisia tekniikoita, joilla arkkitehtuuritason informaatiota voidaan tuottaa takaisinmallintamalla (reverse engineering) järjestelmää, mutta täl-
19 30 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 31 laiset tekniikat tuottavat tyypillisesti vain arkkitehtuurin kuvaukseen tarvittavaa tietoa, eivät varsinaisia arkkitehtuurikuvauksia. Käytännöllisistä syistä on mahdollista ajatella, että arkkitehtuurin kuvaus on ohjelmistoarkkitehtuurin konkreettinen ilmenemismuoto: arkkitehtuuria ei ole olemassa ilman sen kuvausta. Tämä pätee myös silloin, kun itse järjestelmä on olemassa. Tällaisessa tilanteessa järjestelmän arkkitehtuurista puhuminen on erityisen vaarallista, koska eri ihmiset voivat olemassaolevan järjestelmän perusteella helposti tehdä erilaisia tulkintoja siitä, mikä on järjestelmän arkkitehtuuri ja mitkä asiat kuuluvat siihen. Varsinaisen rakenteen lisäksi arkkitehtuuria siis käytetään myös selittämään järjestelmää. Toisaalta arkkitehtuurin kuvaus voidaan antaa eri tarkkuustasoilla. Olennaista on, että kukaan ei tee arkkitehtuurista oletuksia, jotka menevät yli sen kuvauksen. Tätä on usein kuitenkin vaikea välttää, jos kuvaus annetaan hyvin abstraktilla tasolla. Arkkitehtuurikuvaus voitaisiin antaa esimerkiksi seuraavasti: järjestelmä on J2EE-teknologiaan ja asiakas-palvelin-arkkitehtuuriin perustuva liiketoimintasovellus. Tämä on sinänsä validi kuvaus, koska se perustuu käsitteisiin, joiden voi olettaa olevan yleisesti tunnettuja. Sen perusteella voidaan keskustella järjestelmän perusratkaisusta hyvin yleisellä tasolla, vaikkapa sanomalla, että järjestelmä sopisi paremmin web-palveluksi (web service). Mitään tarkempia oletuksia esimerkiksi siitä, mitkä osat ovat asiakaspäässä ja mitkä palvelinpäässä ei voi tämän perusteella tehdä. Siitä huolimatta monet, jotka tietävät jotakin järjestelmän tarkoitetusta toiminnallisuudesta, tekevät todennäköisesti mielessään mahdollisesti tiedostamattaan erilaisia tätä koskevia oletuksia. Arkkitehtuurikuvaus on keskeinen kommunikointiväline arkkitehdin ja toteutus- tai ylläpitoryhmän välillä. Jos arkkitehtuurin tarkka kuvaus on olemassa ainoastaan arkkitehdin päässä, ryhmä tulee varmasti tekemään ratkaisuja, jotka ovat ristiriidassa tuon kuvauksen kanssa. Tällaiset ongelmat voivat tulla esiin vasta hyvin myöhäisessä vaiheessa, ja ne voivat maksaa yritykselle paljon. Arkkitehdin velvollisuutena on siksi huolehtia siitä, että ryhmällä on käytettävissään arkkitehtuurin kuvaus, joka kertoo täsmällisesti, mitkä asiat kuuluvat arkkitehtuuriin ja mitä arkkitehtuuri sallii. Arkkitehtuurin kuvaustekniikat liittyvät läheisesti työkalutukeen. Työkalutukea ei tarvita ainoastaan arkkitehtuurimallien ja -näkymien tuottamiseen, vaan myös varmistamaan, että nämä mallit ovat keskenään ristiriidattomia ja että yksityiskohtaisessa suunnitte- lussa ja toteutuksessa noudatetaan näitä malleja. Työkaluja voidaan myös käyttää tuottamaan automaattisesti arkkitehtuurinäkymiä toisten mallien tai olemassaolevan lähdekoodin perusteella. Tässä suhteessa standardoidut kuvaustekniikat, kuten UML, tarjoavat hyvän pohjan työkalukehitykselle. Ohjelmistotekniikan pitkän tähtäyksen suuntauksena on lisätä ohjelmistojen kuvauksen abstraktiotasoa, ja tuottaa toimivia järjestelmiä yhä korkeamman tason kuvauksista. Arkkitehtuurikuvaus on korkeimman abstraktiotason ratkaisukuvaus, joten voisi ajatella, että tällaisesta kuvauksesta voitaisiin joskus tuottaa toimivia järjestelmiä automaattisesti. Tämä edellyttää kuitenkin arkkitehtuurikuvaukselta täsmällisyyttä ja kattavuutta, joka on toistaiseksi mahdollista vain hyvin rajatussa mielessä, esimerkiksi jollakin kapealla sovellusalueella. 2.2 Arkkitehtuurikuvaukseen liittyvät käsitteet Jotta ymmärrettäisiin mitä asioita ohjelmistoarkkitehtuurin kuvaukseen sisältyy, on tarkasteltava käsitteitä, jotka tavalla tai toisella liittyvät arkkitehtuureihin. IEEE on julkaissut arkkitehtuurien kuvaamisesta standardin (IEEE Recommended Practice for Architectural Description of Software-Intensive Systems, IEEE Std ), jossa annetaan arkkitehtuureja koskeva käsitekaavio UML:n luokkakaaviona. Kuvassa 2.1 on esitetty hieman yksinkertaistettu versio tästä kaaviosta. Kuvan 2.1 mukaisesti jokainen järjestelmä täyttää yhden tai useampia tehtäviä (mission) toimintaympäristössään. Järjestelmällä on sidosryhmiä (stakeholder), joista kullakin on omat tavoitteensa ja huolenaiheensa (concern) järjestelmän suhteen. Jos järjestelmällä on arkkitehtuuri, sillä täytyy olla myös täsmälleen yksi arkkitehtuurikuvaus. Huomaa, että vaikka tämä malli esittää arkkitehtuurin ja arkkitehtuurikuvauksen erillisinä käsitteinä, arkkitehtuuri voi mallin mukaan olla olemassa vain, jos siitä on kuvaus. Arkkitehtuurikuvaus muodostuu erilaisista näkymistä (view), jotka seuraavat tiettyjä näkökulmia (viewpoint). Näkökulma määrää tietyn esitysmuodon ja tavoitteen kuvaukselle, näkymä on yksittäiselle järjestelmälle annettu osittainen arkkitehtuurikuvaus, joka noudattaa jonkin näkökulman esitysmuotoa. Tarkastelemme näkökulmia ja näkymiä lähemmin jatkossa.
20 32 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 33 Environment has Mission fulfills System 1..* 1..* has an identifies 1..* Architecture 1 described by Architectural description 1..* organized by provides selects Rationale Konkreettinen arkkitehtuuri Referenssiarkkitehtuuri Metaarkkitehtuuri Yksityiskohtainen suunnittelu Organization * 1..* Stakeholder 1..* View has 1..* 1..* is addressed to conforms to Concern Viewpoint 1..* Tuoterunkoarkkitehtuuri Tuotearkkitehtuuri Lähdekoodi Kuva 2.1: IEEE:n standardin mukainen (yksinkertaistettu) käsitemalli arkkitehtuureille Kuva 2.2: Arkkitehtuurityypit ja niiden väliset suhteet Toisaalta arkkitehtuurikuvaus muodostuu malleista (esimerkiksi UML-kaavioista), joita näkymät hyödyntävät. Kuhunkin näkymään voi liittyä useita malleja, ja malli voi esiintyä useassa näkymässä. Arkkitehtuurikuvauksen tulee antaa myös perustelu (rationale), joka kertoo, miksi kyseiset ratkaisut on tehty. 2.3 Arkkitehtuurikuvauksen abstraktiotaso Tietojenkäsittelytehtävä voidaan kuvata eri abstraktiotasoilla esimerkiksi epäformaalina algoritmina, pseudokoodiesityksenä, parametroituna koodirunkona (esim. C++:n template) tai suoritettavana ohjelmakoodina. Samaan tapaan ohjelmistoarkkitehtuuri voidaan antaa esimerkiksi yleisenä, abstraktina ratkaisumallina tietyn tyyppisille järjestelmille, jonkin järjestelmäperheen yhteisen arkkitehtuurin kuvauksena, tai yksittäisen konkreettisen järjestelmän kuvauksena. Arkkitehtuurikuvausta annettaessa on syytä tehdä selväksi, minkä tyyppisestä arkkitehtuurista on kyse. Kuvassa 2.2 on esitetty eri arkkitehtuurityypit ja niiden väliset suhteet UML:n luokkakaaviona. Riippuvuussuhde (katkonuoli) tarkoittaa, että lähde noudattaa kohdetta (esimerkiksi tuotearkkitehtuuri noudattaa tuoterunkoarkkitehtuuria). Kuvaan on otettu mukaan myös yksityiskohtainen suunnittelu ja lähdekooditoteutus, jotka eivät luonnollisesti ole arkkitehtuuritason kuvauksia. Meta-arkkitehtuuri ei kuvaa ohjelmistoarkkitehtuuria itseään, vaan käsitteistön ja mekanismit, joilla varsinaisia arkkitehtuurikuvauksia annetaan. Meta-arkkitehtuuri voi näin kuvata esimerkiksi komponenttikategorioita ja niiden välisiä suhteita. Esimerkki meta-arkkitehtuurista on UML:n profiili, joka on annettu (tietyn tyyppisten) arkkitehtuurien kuvaamiseen. Profiili mekanismina on UML:n metamallin laajennos, joka erikoistaa joitakin UML:n standardielementtejä ja antaa niitä koskevia ylimääräisiä rajoitteita. Profiili voisi esimerkiksi erikoistaa UML:n komponenttielementin uudeksi mallielementiksi, joka edustaa tiettyä komponenttikategoriaa. Yritys voi antaa meta-arkkitehtuurikuvauksen ohjenuorana, jonka noudattamista edellytetään yrityksen kehittämien järjestelmien arkkitehtuurikuvauksissa. Meta-arkkitehtuuria voi abstraktissa mielessä ajatella kielioppina, jonka mukaisesti varsinaisia arkkitehtuureja kuvataan. Referenssiarkkitehtuuri ei myöskään kuvaa mitään konkreettista järjestelmää, vaan antaa yleisen ratkaisumallin tietyn tyyppisten järjestelmien tai niiden osien arkkitehtuureille. Toisin kuin meta-arkkitehtuuri, referenssiarkkitehtuuri kuvaa arkkitehtuuriratkaisun, mutta abstraktilla tasolla liittämättä sitä mihinkään todelliseen järjestelmään. Esimerkiksi yleiset arkkitehtuurityylit ja -mallit, joita käsit-
21 34 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 35 telemme myöhemmin tässä kirjassa, voidaan esittää referenssiarkkitehtuurina. Samoin on usein mielekästä antaa sovellusaluekohtaisia (tai jopa yrityskohtaisia) referenssiarkkitehtuureja, jotka kuvaavat tietyllä sovellusalueella hyviksi havaittuja arkkitehtuuriratkaisuja ja niihin liittyviä erikoiskäsitteitä. Konkreettinen arkkitehtuuri esittää yksittäisen ohjelmiston arkkitehtuurin. Esimerkiksi tietyn sovelluksen arkkitehtuuridokumentti antaa näin aina konkreettisen arkkitehtuurin, joskin siinä voidaan viitata eri referenssi- ja meta-arkkitehtuureihin, joita konkreettinen arkkitehtuuri noudattaa. Sekä konkreettinen arkkitehtuuri että referenssiarkkitehtuuri noudattavat aina jotakin meta-arkkitehtuuria, vaikka tätä ei välttämättä tiedostettaisi. Arkkitehtuurikuvaus nimittäin perustuu aina joihinkin käsitteisiin, joiden oletetaan olevan tunnettuja, ja nämä käsitteet muodostavat meta-arkkitehtuurin. Jos esimerkiksi UML:ää käytetään arkkitehtuurikuvauksiin, meta-arkkitehtuurina on UML:n metamalli. Jos arkkitehtuurikuvaus perustuu UML:n profiileihin, metaarkkitehtuurina on tämän metamallin eräs laajennus. Jos kuvaukseen käytetään erityistä arkkitehtuurin kuvauskieltä (ADL, Architecture Description Language), meta-arkkitehtuurina on tämän kielen spesifikaatio. Mitä erikoistuneempia käsitteitä meta-arkkitehtuuri tarjoaa, sitä enemmän se rajoittaa kuvattavia arkkitehtuureja, mutta toisaalta sitä enemmän se myös tukee arkkitehtuurikuvausten antamista rajoitetulla alueella. Periaatteessa on mahdollista antaa meta-arkkitehtuuri, joka tukee yhden ainoan järjestelmän arkkitehtuurin kuvausta. Tuoterunkoarkkitehtuuri kuvaa toisaalta ohjelmistoalustan arkkitehtuurin ja toisaalta antaa säännöt yksittäisten ohjelmistotuotteiden rakentamiseen alustan pohjalle. Edellisen kannalta tuoterunkoarkkitehtuuri on siis konkreettinen arkkitehtuuri, jälkimmäisen kannalta tuoterunkoarkkitehtuuriin saattaa usein sisältyä meta-arkkitehtuurin piirteitä. Palaamme tuoterunkoarkkitehtuureihin luvussa 7. Tuotearkkitehtuuri on yksittäisen ohjelmistotuotteen arkkitehtuuri. Tällainen tuote voi olla rakennettu tuoterungon päälle, jolloin se noudattaa tuoterunkoarkkitehtuuria, tai se voi olla täysin itsenäinen sovellus. Kummassakin tapauksessa tuotearkkitehtuuri on konkreettinen. Arkkitehtuurikeskeisessä ohjelmistokehityksessä arkkitehtuuria noudatetaan yksityiskohtaisessa suunnittelussa, toteutuksessa ja ylläpidossa. Parhaassa tapauksessa noudattamista valvotaan työkalujen avulla. Kuvan 2.2 "noudattaa"-suhteet (katkonuoli) ilmaisevat, millaisia arkkitehtuuriin liittyviä tarkistuksia ohjelmistokehitysprosessissa voi tehdä: lähdekoodi voidaan tarkistaa yksityiskohtaista suunnittelua vasten, suunnittelu voidaan puolestaan tarkistaa konkreettista arkkitehtuuria vasten, ja konkreettinen arkkitehtuuri voidaan lopuksi tarkistaa meta-arkkitehtuuria vasten. 2.4 Näkökulmat arkkitehtuurien kuvauksessa Ohjelmistoarkkitehtuurin tulisi aikaisemman määritelmän mukaan kertoa, millaisia komponentteja järjestelmässä on ja millaisia suhteita niillä on keskenään. Komponentteihin liittyy kuitenkin niin monenlaisia suhteita ja puolia, että näin yleisen määritelmän perusteella on vaikea tietää, mitä asioita arkkitehtuurin kuvaukseen tulisi sisällyttää ja millainen rakenne kuvauksella tulisi olla. Tätä varten on otettu käyttöön käsite näkökulma (viewpoint): arkkitehtuurikuvaus koostuu tiettyjen näkökulmien mukaisista näkymistä (view) järjestelmään. Tässä näkökulma tarkoittaa yleistä, järjestelmästä riippumatonta tapaa kuvata tiettyä arkkitehtuurin kannalta merkityksellistä ohjelmistojen ominaisuutta. Näkymä on varsinainen järjestelmästä riippuva kuvaus, joka noudattaa jotakin näkökulmaa. Näkökulman ja näkymän suhde on samantapainen kuin esimerkiksi luokan ja olion: näkymä on näkökulman ilmentymä tietyn järjestelmän yhteydessä. Näkökulma on ortogonaalinen järjestelmän strukturoinnin kanssa: mitä hyvänsä osaa järjestelmästä voidaan periaatteessa katsoa mistä hyvänsä näkökulmasta, joskin jotkin näkökulmat saattavat käytännössä olla mielekkäämpiä tietyn järjestelmän osan tapauksessa kuin toiset. Nk. 4+1-malli esittää viisi näkökulmaa ohjelmistoarkkitehtuurien kuvaamiseen (Kruchten 1995). Tästä mallista on esitetty myöhemmin hieman toisistaan poikkeavia versioita. Järjestelmän luonteesta riippuen jotkin 4+1 -mallin näkökulmista ovat keskeisiä, jotkin toiset taas vähemmän tärkeitä. On toisaalta mahdollista, että jossakin järjestelmässä on syytä ottaa myös muita näkökulmia arkkitehtuurin kuvaukseen. Tässä teoksessa seuraamme 4+1-mallia, mutta otamme mukaan uuden näkökulman, joka on olennainen erityisesti tuoterunkoarkkitehtuurien yhteydessä. Tämä kuudes näkökulma (variaatio- eli muuntelunäkökulma) käsittelee sitä, miten arkkitehtuuri tukee jär-
22 36 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 37 jestelmien varianssia Seuraavassa on lyhyesti luonnehdittu näitä kuutta näkökulmaa ja niiden mukaisia arkkitehtuurinäkymiä. Skenaarionäkymä (scenario view) tarkastelee järjestelmää mustana laatikkona keskittyen kuvaamaan, kuinka järjestelmä on vuorovaikutuksessa sen ulkopuolella olevien käyttäjien ja muiden järjestelmien kanssa tietyssä järjestelmän käyttöskenaariossa. Skenaarionäkymä kuvaa näin järjestelmän rajapinnat ympäristöönsä. Koska käyttöskenaario koskee tavallisesti järjestelmän keskeisiä toiminnallisia vaatimuksia, se toimii lähtökohtana muiden näkymien muodostamiselle. Muita näkymiä voidaan myös arvioida skenaarionäkymän perusteella: miten näkymässä esitetyt ratkaisut tukevat kyseistä käyttöskenaariota. Skenaarionäkymä on olennainen lähes kaikille järjestelmille. Looginen näkymä (logical view) kuvaa, miten toiminnallisuus on jaettu järjestelmän eri osien kesken ja mitä velvollisuuksia kullakin komponentilla on. Looginen näkymä kuvaa näin, miten skenaarionäkymän esittämä toiminnallisuus saadaan aikaan järjestelmän eri osien välisellä yhteistyöllä. Looginen näkymä voi koskea yhtä hyvin koodirakenteita (esim. luokkia) kuin ajoaikaisia rakenteita (esim. olioita). Loogista näkymää käytetään yksityiskohtaisen suunnittelun pohjana. Looginen näkymä on olennainen kaikille järjestelmille. Prosessinäkymä (process view) kuvaa, miten järjestelmän toiminta on jaettu loogisiin prosesseihin ja miten nämä prosessit kommunikoivat keskenään. Prosessinäkymä on olennainen rinnakkaisuutta vaativille järjestelmille (esim. hajautetut järjestelmät). Prosessinäkymän avulla voidaan arvioida esimerkiksi järjestelmän suorituskykyä ja skaalautuvuutta. Kehitysnäkymä (development view) kuvaa, miten järjestelmä on jaettu osiin, jotka voidaan toteuttaa erillisinä yksikköinä. Kehitysnäkymä on olennainen erityisesti suurten ohjelmistojen tapauksessa. Tätä näkymää käytetään projektisuunnittelussa, kustannusarvioinnissa, projektin hallinnassa jne. Fyysinen näkymä (physical view) kuvaa, millaisista fyysisistä prosessointiyksiköistä järjestelmä koostuu, miten nämä ovat yhteydessä toisiinsa ja mitä fyysisiä ohjelmistoartefakteja (esim. komponentteja, resurssitiedostoja, tietokantoja ym.) kuhunkin prosessointiyksikköön sijoitetaan. Fyysinen näkökulma on olennainen hajautetuille järjestelmille. Muuntelunäkymä (variability view) kuvaa, millaista muuntelua järjestelmä tukee. Muuntelunäkökulma kuvaa näin järjestelmän variaatiopisteet (so. kohdat järjestelmässä, joissa muuntelu tapahtuu), niiden edellyttämät vaatimukset muunnelmille ja niiden käytön muunnelmien toteuttamiseen. Muuntelunäkökulma kuvaa siis järjestelmän toteutusalustana sovelluksille, määritellen järjestelmän laajennusrajapinnan. Muuntelunäkökulma on olennainen tuoterunkojen tapauksessa, mutta se voi olla hyödyllinen esimerkiksi ylläpidon kannalta mille hyvänsä ohjelmistolle. Muuntelunäkökulmaa tarvitaan myös mm. järjestelmän uudelleenkäytettävyyden arvioimiseksi. Yritys tai projekti määrittelee tyypillisesti käytettävät näkökulmat tarpeidensa mukaisesti, ja antaa samalla ohjeistuksen näkymien esittämiseksi koskien esimerkiksi näkymissä käytettäviä kuvaustapoja ja -notaatioita. Esimerkiksi UML tarjoaa periaatteessa välineitä kaikkiin edellä kuvattuihin näkymiin. Tarkastelemme UML:n käyttöä näiden näkymien esittämiseen harjoitustehtävissä. 2.5 Arkkitehtuurikuvausten tyypit Riippumatta siitä, mitä yllä mainituista näkökulmista järjestelmässä halutaan tarkastella, kuvaus voi painottua eri tavoin. Kuvaukset voidaan luokitella kolmen olennaisen ominaisuuden mukaan: (i) käsitteleekö kuvaus rakennetta vai käyttäytymistä, (ii) käsitteleekö kuvaus järjestelmän staattisia vai dynaamisia piirteitä ja (iii) määritteleekö kuvaus tietyn asian kokonaisuudessaan vai antaako se vain esimerkin siitä. Tämän mukaisesti saadaan kuusi erilaista kuvaustyyppiä, jotka on lueteltu seuraavassa. Nämä tyypit ovat periaatteessa riippumattomia näkymistä, joskin jotkut kuvaustyypit voivat olla luontevampia tietyn näkymän yhteydessä kuin toiset. Kuvaustyypit eivät myöskään ole sidoksissa tietynlaisiin järjestelmiin, vaan minkä
23 38 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 39 hyvänsä järjestelmän tapauksessa voidaan käyttää kaikkia kuvaustyyppejä. Rakennekuvauksessa (structural view) tarkastellaan järjestelmän rakenteellisia suhteita. Esimerkiksi oliojärjestelmässä tällaisia voivat olla viittaussuhteet, sisältyvyyssuhteet, periytymissuhteet jne. Ajan merkitys on rakennekuvauksessa vähäinen. Rakenne voi kuitenkin koskea sekä koodin rakennetta että ajoaikana esiintyviä rakenteita. Käyttäytymiskuvaus (behavioral view) kuvataan, miten ohjelmistoyksiköt (esim. komponentit) käyttäytyvät ollessaan vuorovaikutuksessa toistensa kanssa. Toimintojen ajallisella järjestyksellä on tärkeä merkitys käyttäytymiskuvauksessa. Käyttäytymis- ja rakennekuvauksella on usein riippuvuuksia: jos komponentti esimerkiksi pyytää toisen komponentin palvelua käyttäytymiskuvauksessa, komponenttien välillä tulee olla käyttösuhde rakennekuvauksessa. Staattinen kuvaus (static view) tarkastelee sellaisia järjestelmän ominaisuuksia, jotka koskevat järjestelmän staattista esitystä (esim. lähdekoodia). Esimerkiksi oliojärjestelmässä staattinen rakennekuvaus esittää tyypillisesti luokkien välisiä suhteita. Dynaaminen kuvaus (dynamic view) esittää järjestelmän ajoaikana ilmeneviä ominaisuuksia. Esimerkiksi oliojärjestelmässä dynaaminen rakennekuvaus voi kuvata olioiden välisiä suhteita. Esimerkkikuvaus kuvaa erään mahdollisen ilmentymän kiinnostuksen kohteena olevasta ilmiöstä, pikemmin kuin koko ilmiön. Esimerkiksi dynaaminen rakenne-esimerkkikuvaus voi kuvata yhden mahdollisen oliokonfiguraation, joka ajoaikana saattaa esiintyä. Määrittelykuvaus esittää kiinnostuksen kohteena olevan ilmiön kokonaisuudessaan, ei ainoastaan esimerkkiä siitä. Esimerkiksi dynaaminen käyttäytymisen määrittelykuvaus voi määritellä tietyn luokan olioiden täydellisen käyttäytymisen. Kuvaustyypin valinta riippuu paitsi teknisestä tarkoituksenmukaisuudesta myös siitä, kuinka helppoa on antaa ja ymmärtää kuvaus. Esimerkkikuvaus on usein sekä helpompi antaa että ymmärtää kuin määrittelykuvaus. Toisaalta normaaleihin tilanteisiin keskittyvät esimerkkikuvaukset saattavat johtaa siihen, että tärkeitä erikoistapauksia ei oteta riittävästi huomioon. Yleensä pyritään selkeyden vuoksi siihen, että kuvaus ei sekoita vastakkaisia tyyppejä: ei ole yleensä mielekästä antaa esimerkiksi kuvausta, joka on sekä staattinen että dynaaminen tai joka on sekä määrittelevä että esimerkinomainen. Toisaalta tästä ei tarkasti ottaen pidetä kiinni edes UML:ssä, joka sallii esimerkiksi tiettyjen dynaamisten asioiden esittämisen staattisessa luokkakaaviossa. Myös esimerkki- ja määrittelykuvauksia sekoitetaan UML:ssä: sekvenssikaaviolla on mahdollista antaa esimerkki komponenttien välisestä vuorovaikutuksesta, mutta myös täydellinen algoritminen toimintakuvaus tai jokin näiden sekamuoto. 2.6 Arkkitehtuuriviipaleiden kuvaus Ohjelmistoarkkitehtuurin yleisesti hyväksyttyyn kuvaukseen sisältyy ongelma, jota vasta viime vuosina on alettu paremmin ymmärtää. Kun järjestelmän arkkitehtuuri ajatellaan sen muodostamien komponenttien rakenteena ja näiden välisinä suhteina, oletetaan, että on vain yksi tapa jakaa järjestelmä osiin, esimerkiksi erillisiin komponentteihin. Tämä oletus, joka kyllä yksinkertaistaa järjestelmän hallintaa, ei kuitenkaan yleisesti pidä paikkaansa. Todellisuudessa vähänkin monimutkaisempi järjestelmä koostuu lukuisista loogisista kokonaisuuksista, joista jotkut on järkevää esittää omina komponentteinaan, mutta jotkut taas koostuvat eri komponenttien yhdistelmistä tai useiden komponenttien sisään jäävistä osista. Ohjelmistotekniikassa aletaan yhä paremmin ymmärtää, että mikään yksittäinen tapa jakaa järjestelmä osiin ei voi tuottaa kaikkia tällaisia loogisesti yhteenkuuluvia kokonaisuuksia erillisinä ohjelmayksikköinä. Kutsumme järjestelmän primäärin jakoperusteen kanssa ristiin menevää mutta jonkin kriteerin perusteella loogisesti yhteenkuuluvaa rakennetta (arkkitehtuuri)viipaleeksi (architecture/design fragment). Tyypillisiä viipaleita ovat esimerkiksi suunnittelumallien (design pattern) ilmentymät (Gamma et al. 1995), järjestelmän ulospäin näkyvät piirteet (feature) (Jansen et al. 2004) ja jo edellä mainitut aspektit (aspect). Jälkimmäiset ovat jonkin koko järjestelmää tai sen suurta osaa koskevan ominaisuuden tai tarpeen toteutuksia. Aspekti-
24 40 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 41 na voi olla vaikkapa virheenkäsittely, lokitulostus, hajautus, olioiden pysyvyys, tietoturvallisuus jne. Kaikki nämä ovat tarpeita, joiden toteutus tavallisesti sirpaloituu useiden komponenttien sisään. Huomaa, että viipaleet ovat usein keskenään osittain päällekkäisiä: esimerkiksi sama komponentti tai luokka voi esiintyä useissa suunnittelumalleissa ja samalla osallistua eri piirteiden toteutukseen. Ymmärrys arkkitehtuuriviipaleiden olemassaolosta ja luonteesta on olennaista ohjelmistokehityksessä. Jos esimerkiksi järjestelmän jotakin ulospäin näkyvää piirrettä halutaan muuttaa, täytyy piirrettä vastaava viipale pystyä tavalla tai toisella paikallistamaan. Samoin jos esimerkiksi olioiden pysyvyyden toteutus halutaan muuttaa, joudutaan kyseinen viipale etsimään järjestelmästä. Kun näitä viipaleita ei edusta mikään erityinen ohjelmayksikkö, niiden löytäminen on usein työlästä, ellei viipaleen olemassaoloa ole etukäteen selvästi tiedostettu ja dokumentoitu. Järjestelmän viipalointia on tutkittu runsaasti etenkin aspektien yhteydessä, ja myös sitä tukevia ohjelmointikieliä (esim. AspectJ) on kehitetty. Tässä oletamme kuitenkin, että järjestelmä on tehty perinteisellä kielellä ja strukturoitu jollakin kriteerillä erillisiksi ohjelmayksiköiksi (esim. komponenteiksi). Tarkastelemme seuraavassa, miten arkkitehtuuriviipale voidaan kuvata tässä tilanteessa. Arkkitehtuuriviipaleen kuvauksessa on erotettava kaksi asiaa: viipaleen merkitys ja viipaleen ilmeneminen ohjelmistossa. Koska viipaleet ovat osittain päällekkäisiä, viipaleeseen liittyvää merkitysinformaatiota ei voi suoraan liittää ohjelmayksiköihin, jotka esiintyvät viipaleessa. On myös huomattava, että tietty looginen viipale (esimerkiksi tietty suunnittelumalli) voi esiintyä useita kertoja samassa ohjelmistossa. Näin esimerkiksi koodin kommentoimiseen perustuvat tavat viipaleiden merkkaamiseen ovat yleensä epätyydyttäviä. Viipaleen luonteen kannalta on olennaista, että viipale ja sen merkitys kuvataan yhtenä kokonaisuutena koodista erillisenä ohjelmistoartefaktina. Viipale koostuu rooleista ja näiden välille määritellyistä suhteista. Kukin rooli sidotaan johonkin ohjelmaelementtiin (esim. komponentti, luokka, funktio, muuttuja, tms.), joka osallistuu viipaleeseen kyseisessä roolissa. Rooli määrittelee, millaiset elementit siihen voidaan sitoa. Toisaalta viipaleen tulee määritellä, mitä suhteita sen eri rooleihin sidotuilla ohjelmaelementeillä tulee olla keskenään. Toistaiseksi viipaleiden hallintaa tukevat tekniikat ja työkalut ovat vielä tutkimusasteella. Näistä riippumatta on kuitenkin suo- siteltavaa dokumentoida viipale käyttäen esimerkiksi seuraavaa muotoa: Nimi: viipale tulee nimetä yksiselitteisesti kuvaavalla tunnisteella (esim. Copy-and-paste). Laji: viipale tulee luokitella lajinsa perusteella (esim. piirreviipale). Tarkoitus: viipaleen tarkoitus tulee kuvata lyhyesti (esim. "Viipale toteuttaa copy-and-paste-piirteeseen liittyvät toiminnot". Sidosryhmät: minkä sidosryhmän tai -ryhmien kannalta viipale on olennainen (esim. ylläpitäjät). Roolit: luettelo viipaleeseen kuuluvista rooleista ja niiden merkityksestä viipaleen kannalta sekä rajoitteet, joita rooliin sidottavalla ohjelmistoelementillä on (esim. "rooli TextBuffer, sisältää kopioitavan tekstin, edellyttää luokan, joka toteuttaa rajapinnan Text"). Roolien väliset suhteet: mitä suhteita viipale edellyttää ohjelmaelementeiltä, jotka on sidottu sen rooleihin (esim. "rooliin X sidotun luokan tulee periä rooliin Y sidottu luokka"); voidaan antaa esimerkiksi UML:n luokkakaaviona. Sidonnat: mitkä todelliset ohjelmaelementit on sidottu viipaleen rooleihin (esim. "Rooliin TextBuffer on sidottu sovelluksen luokka AppText"); voidaan antaa esim. taulukkomuodossa. Jos viipaleella voi olla useita esiintymiskohtia, riittää kuvata viipale viimeistä kohtaa lukuunottamatta vain kertaalleen, ja antaa kullekin esiintymiskohdalle erillinen sidontojen kuvaus. Viipaleen kuvausmuoto voi yleisestikin vaihdella tarpeen mukaan. Esimerkiksi suunnittelumallien tapauksessa noudatetaan yleensä hieman toisenlaista muotoa, johon palaamme myöhemmin. 2.7 Arkkitehtuuridokumentit Arkkitehtuurin kuvaavan dokumentin muoto ja sisältö vaihtelevat huomattavasti riippuen yrityksen käytännöistä ja kyseisen järjestelmän luonteesta ja laajuudesta. Hyvin pienissä ohjelmistoprojekteissa arkkitehtuuri voidaan kuvata osana yleistä ohjelmistodoku-
25 42 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 43 menttia, kun taas laajoissa järjestelmissä arkkitehtuuri voidaan kuvata useissa erillisissä dokumenteissa eri abstraktiotasoilla. Kaikissa tapauksissa on kuitenkin olennaista, että arkkitehtuurikuvaus on selkeä osa dokumentointia ja helposti löydettävissä, sillä muuten koko arkkitehtuurin käsite hämärtyy. Arkkitehtuuridokumentit ovat keskeinen väline järjestelmän eri sidosryhmien väliselle kommunikaatiolle. Arkkitehtuuridokumentaatio on yksi ohjelmistokehitysprosessin tuotoksista, ja sillä on yleensä voimakkaita riippuvuuksia muihin tuotoksiin. Tyypillisesti arkkitehtuurikuvaus viittaa vaatimusdokumentteihin perustellessaan tiettyjä ratkaisuja, ja toisaalta yksityiskohtaiset suunnitteludokumentit, testidokumentit ja muut tekniset dokumentit puolestaan viittaavat arkkitehtuuridokumenttiin. Seuraavassa on lueteltu erilaisia arkkitehtuuridokumenttityyppejä, joita tavallisesti käytetään teollisuudessa. Tyypit eivät ole välttämättä toisensa poissulkevia, vaan yksi dokumentti voi edustaa samanaikaisesti useaa tyyppiä. Vastaavasti kyse voi olla erillisen dokumentin sijasta myös luvuista yleisemmissä ohjelmistodokumenteissa. Alustava arkkitehtuuridokumentti: Tämä dokumentti kuvaa tärkeimmät arkkitehtuuriratkaisut ja niiden perustelut osana toteutettavuusanalyysia (feasibility study). Dokumentissa voidaan analysoida myös vaihtoehtoisia ratkaisuja sekä niiden hyviä ja huonoja puolia. Dokumenttia käytetään liiketoimintapäätösten, projektisuunnittelun, työmääräarvioinnin, alustavan arkkitehtuurin arvioinnin sekä tarkemman arkkitehtuurisuunnittelun pohjana. Dokumentissa kuvataan tyypillisesti konkreettinen arkkitehtuuri, mutta siihen voi sisältyä myös referenssiarkkitehtuurikuvauksia, joihin annettu arkkitehtuuri perustuu. Järjestelmäarkkitehtuuridokumentti: Tämä dokumentti kuvaa järjestelmän arkkitehtuurin sen ylimmällä tasolla. Erityisesti laajojen järjestelmien tapauksessa dokumentti kuvaa järjestelmän sidokset ympäristöönsä, alijärjestelmien ulkoiset ominaisuudet (esim. rajapinnat) ja niiden keskinäisen vuorovaikutuksen. Vuorovaikutus voidaan kuvata esimerkiksi tarkentamalla, miten tärkeimmät käyttötapaukset toteutuvat alijärjestelmien välisenä vuorovaikutuksena. Pienemmässä järjestelmässä kuvaus voi mennä jo yksittäis- ten komponenttien tasolle. Dokumentti on järjestelmän avainartifakti, joka on pohjana alijärjestelmien arkkitehtuurisuunnittelulle, arkkitehtuurin arvioinnille, tarkennetuille työmääräarvioille, projektisuunnittelulle ja järjestelmätestauksen suunnittelulle. Dokumentti kuvaa tavallisesti konkreettisen arkkitehtuurin, mutta voi sisältää myös esimerkiksi alijärjestelmiä koskevia meta-arkkitehtuurikuvauksia. Alijärjestelmäarkkitehtuuridokumentti: Laajan järjestelmän tapauksessa myös alijärjestelmiin voi sisältyä tärkeitä arkkitehtuuritason päätöksiä. Dokumentissa kuvataan alijärjestelmän arkkitehtuuri järjestelmäarkkitehtuurin antamien reunaehtojen puitteissa. Kuvaus määrittelee alijärjestelmän sisältämien komponenttien ulkoiset ominaisuudet (rajapinnat) ja niiden keskinäisen vuorovaikutuksen. Vuorovaikutus voidaan kuvata esimerkiksi tarkentamalla jo järjestelmäarkkitehtuuridokumentissa esitetyt abstraktit käyttötapaukset komponenttien tasolle. Dokumentti on pohjana alijärjestelmien ja komponenttien yksityiskohtaiselle suunnittelulle ja toteutukselle, työnjaolle, ja yksikkötestauksen suunnittelulle. Dokumentissa kuvataan konkreettinen arkkitehtuuri. Tuoterunkoarkkitehtuuridokumentti: Tuoterunkoarkkitehtuuri voidaan kuvata periaatteessa samalla tavalla kuin minkä hyvänsä järjestelmän arkkitehtuuri, mutta koska tuoterunko palvelee muiden sovellusten toteutusalustana, sen kuvauksella on omia erityispiirteitään. Jos tuoterunkoarkkitehtuuri näkyy kokonaan tai osittain sovelluskehittäjille, dokumentin tulee kuvata, miten tuoterunkoarkkitehtuurin tarjoamaa varianssia käytetään hyväksi sovellusten rakentamiseen. Usein tyydytään antamaan esimerkkejä tuoterunkoarkkitehtuurin soveltamisesta, mutta parempi vaihtoehto on pyrkiä systemaattisesti kuvaamaan tuoterunkoarkkitehtuurin merkitys sovelluksen rakentajan kannalta. Tällaisella kuvauksella on usein meta-arkkitehtuurin piirteitä: siinä kerrotaan, mitä sääntöjä sovellusten tulee noudattaa omassa arkkitehtuurissaan. Tuotearkkitehtuuridokumentti: Tuoterungon pohjalle tehdylle ohjelmistotuotteelle voidaan antaa erillinen arkkitehtuuridokumentti, joka kuvaa toisaalta sen, miten tämän
26 44 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 45 tuotteen tapauksessa on sovellettu tuoterunkoarkkitehtuuria ja toisaalta sen, millaisia tuotekohtaisia tuoterungosta riippumattomia arkkitehtuuriratkaisuja on tehty tuotteessa. Rajapintadokumentti: Tällainen dokumentti täydentää muita arkkitehtuuridokumentteja kuvaamalla täsmällisesti järjestelmän, alijärjestelmän tai tuoterungon tarjoaman ohjelmointirajapinnan (API, application programming interface). Rajapintaan sisältyvät palvelut voidaan spesifioida esimerkiksi tulo- ja jättöehdoilla (ks. luku 3). Jos dokumentin kuvaamilla rajapinnoilla on arkkitehtuuriin liittyviä seuraamuksia niitä käyttäville ohjelmistoille, nämä tulisi myös kuvata dokumentissa. Tällöin rajapintadokumentti saa meta-arkkitehtuurin piirteitä. Arkkitehtuuridokumentin rakenne noudattaa tavallisesti yrityskohtaista ohjeistusta, joka on kehitetty palvelemaan tiettyä sovellusaluetta ja tietyn tyyppisiä järjestelmiä. Tällainen ohjeistus voidaan antaa myös erikseen suurille ohjelmistoprojekteille. Seuraavassa on lueteltua kohtia, jotka yleensä sisältyvät arkkitehtuuridokumenttiin; tämä koskee erityisesti järjestelmä- ja alijärjestelmäarkkitehtuuridokumentteja sekä alustavia arkkitehtuuridokumentteja. Listaa kannattaa verrata kuvassa 2.1 sivulla 32 esitettyyn kaavioon arkkitehtuuriin liittyvistä käsitteistä. Identifiointi: ohjelmiston identifiointi, dokumentin versio ja versiohistoria, ohjelmistoa kehittävä organisaatio, päiväys. Konteksti: ohjelmistoon liittyvät liiketoimintatavoitteet ja ohjelmiston sidosryhmät. Vaatimukset: arkkitehtuurin kannalta merkittävät vaatimukset, mahdolliset referenssiarkkitehtuurit ja tekniset rajoitteet. Ympäristö: ohjelmiston tekninen toimintaympäristö ja vuorovaikutus sen kanssa. Näkymät: valittujen näkökulmien mukaiset arkkitehtuurinäkymät. Arkkitehtuuriviipaleet: ohjelmistossa mahdollisesti identifioidut perusrakennetta leikkaavat poikkikulkevat viipaleet (ks. edellä). Analyysi: kuinka hyvin arkkitehtuuri vastaa vaatimuksiin; ratkaisujen hyvien ja huonojen puolien analyysi, suoritus- Computation Independent Model Platform Independent Model Platform Specific Model Kuva 2.3: Malliperustaisen arkkitehtuurimallin abstraktiotasot kykyarviointi, tunnettujen ongelmien ja ristiriitaisuuksien kuvaus. Perustelu: miksi arkkitehtuurissa on päädytty näihin ratkaisuihin. Kohdat eivät välttämättä esiinny tässä järjestyksessä, eivätkä kaikki kohdat ole aina tarpeellisia. Esimerkiksi alustavassa arkkitehtuuridokumentissa ei ole yleensä vielä tarpeen kuvata arkkitehtuuriviipaleita. 2.8 Malliperustainen arkkitehtuuri Jo aiemmin mainittu OMG:n esittämä malliperustainen arkkitehtuuri (model-driven architecture, MDA) voidaan myös ymmärtää tavaksi kuvata ohjelmiston rakennetta (Object Management Group 2003). Tässä mallissa erilaiset abstraktiotasot muodostavat toisiinsa liittyvän kokonaisuuden. Perusmuodossaan malliperustainen arkkitehtuuri käsittää seuraavat kolme tasoa (kuva 2.3): Laskentariippumaton malli (computation indepentent model, CIM) määrittelee järjestelmän toiminnan abstraktio-
27 46 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 47 tasolla, jossa järjestelmän yhteistoiminta ympäristönsä kanssa on olennaista. Alustariippumaton malli (platform independent model, PIM) määrittelee järjestelmän toiminnan tasolla, jolla se ei ole riippuvainen käytetystä toteutusalustasta. Alustariippuvainen malli (platform specific model, PSM) on matalimman tason malli, jossa mallinnetaan järjestelmä sellaisena, kuin se toimii valitun ohjelmistoalustan kanssa. Koska esitetyt tasot ovat ilmeisen riippuvaisia toisistaan, niiden väliset suhteet ja niiden ylläpito kehityksen aikana ovat olennainen osa malliperustaista arkkitehtuuria. Esimerkiksi muunnokset erityisesti alustariippumattoman ja alustariippuvan mallin välillä voivat tehostaa järjestelmän suunnittelua. Valitettavasti tällaiset mallin käyttöä tehostavat tekniikat ovat vielä pääsääntöisesti kehitteillä, ja monessa tapauksessa ainoana muunnoksena esitetään muunnos alustariippumattonalta UML-tasolta alustariippuvaiselle kooditasolle. Lisäksi jotkin lähteet jättävät laskentariippumattoman mallin vähemmälle huomiolle tai jopa kokonaan esittelemättä. Syy tähän lienee se, että toteutusteknisesti ajatellen alustariippumaton ja alustariippuvainen malli ovat mielenkiintoisempia, sillä laskentariippumattoman mallin perusteella on usein vaikea tehdä järjestelmään liittyviä johtopäätöksiä. Sama pätee muunnoksiin: laskentariippumattoman mallin automaattiset muunnokset alustariippumattoman mallin luomiseksi ovat huomattavasti hankalampia määritellä kuin muunnokset alustariippumattoman ja alustariippuvan mallin välillä. Jonkinlaisena ongelmana voidaan pitää myös sitä, että aina ei ole aivan selvää missä kohtaa mallissa kuuluisi huomioida esimerkiksi suunnittelumallien (luku 5) tai arkkitehtuurityylien (luku 6) käyttö. Yhtäältä alustariippumaton malli sallii niiden käytön, muttei vaadi sitä, ja toisaalta alustariippuvainen malli pakottaa alustakohtaisten ratkaisuiden käyttöön, jotka usein ovat yleisten suunnittelumallien alustakohtaisia toteutuksia. Voidaankin ajatella, että alustariippumattoman ja -riippuvaisen tason välissä on vielä yksi abstraktiotaso, arkkitehtuuririippuvainen malli, jonka tarkoitus on kuvata järjestelmän arkkitehtooniset periaatteet yleisellä tasolla, mutta joka MDA-mallissa voidaan sulauttaa joko PIM:iin tai PSM:iin. 2.9 Yhteenveto Arkkitehtuuria ei ole olemassa ilman arkkitehtuurikuvausta. Tuoterunkoarkkitehtuuri on kuvattava myös sovelluskehittäjän kannalta. Konkreettinen arkkitehtuuri kuvaa yksittäisen järjestelmän arkkitehtuurin, meta-arkkitehtuuri antaa säännöt arkkitehtuurikuvauksille. Arkkitehtuurinäkymä kuvaa järjestelmän arkkitehtuurin jostakin näkökulmasta. Näkökulmia ovat skenaarionäkökulma, looginen näkökulma, prosessinäkökulma, kehitysnäkökulma, fyysinen näkökulma ja muuntelunäkökulma. Eri näkökulmat kuvastavat järjestelmää eri tavalla (rakenne vs. käyttäytyminen, staattinen vs. dynaaminen, esimerkinomainen vs. määrittelevä). Arkkitehtuuriviipaleet kuvaavat jotakin loogisesti yhteenkuuluvaa kokonaisuutta järjestelmässä, joka ei noudata järjestelmän perusrakenteen rajoja. Arkkitehtuuridokumentti on keskeinen ohjelmistoartefakti, jolla on voimakkaita riippuvuuksia muihin artefakteihin Harjoitustehtäviä 2.1 Oletetaan, että järjestelmällä on arkkitehtuurikuvaus, mutta tämä kuvaus eroaa olennaisesti tavasta, jolla järjestelmä on todellisuudessa toteutettu. Onko järjestelmällä arkkitehtuuria? Jos on, onko kuvattu vai toteutettu arkkitehtuuri järjestelmän arkkitehtuuri? Anna argumentteja kumpaankin mielipiteeseen. 2.2 Miten arkkitehtuurin kuvausta ja toteutusta voisi verrata toisiinsa? Miten poikkeamiin tulisi reagoida, eli kumpaa tulee muuttaa? 2.3 Täydennä seuraava taulukko merkitsemällä risti ruutuun, jos kyseisellä UML:n kaaviotyypillä voi esittää sarakkeen ilmaisemaa kuvauksiin liittyvää ominaisuutta. Perustele jokainen risti.
28 48 Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaus 49 Luokkakaavio Sekvenssikaavio Tilakaavio Rakenne Käyttäytyminen Staattinen Dynaaminen Esimerkki Kokonais 2.4 Alla on annettu erään järjestelmän sekvenssikaavio. Päättele tämän perusteella, millainen voisi olla järjestelmää kuvaava luokkakaavio. Pyri esittämään luokkakaaviossa mahdollisimman paljon rakennetta koskevaa informaatiota, joka on jollakin tavalla pääteltävissä tai arvattavissa sekvenssikaavion perusteella. 2.6 Tarkastellaan autojen vuokrausjärjestelmää. Esitä järjestelmän arkkitehtuuri antamalla UML:llä a) looginen staattinen rakennekuvaus ja b) looginen dynaaminen esimerkkikuvaus käyttäytymisestä. 2.7 Mitkä UML:n kaaviotyypit sopivat mielestäsi a) skenaarionäkymän, b) loogisen näkymän, c) prosessinäkymän, d) kehitysnäkymän, e) fyysisen näkymän, d) muuntelunäkymän kuvaamiseen? Perustele. 2.8 Mitkä osapuolet voisivat olla kiinnostuneita ajoneuvon polttoaineen kulutuksen valvontaohjelmistosta? Mihin toiminnallisiin ja ei-toiminnallisiin ominaisuuksiin osapuolet kiinnittäisivät erityistä huomiota? Mitkä näkymät voisivat kiinnostaa mitäkin osapuolta? Entäpä minkälainen abstraktiotaso olisi riittävä eri kohderyhmille? 2.9 Minkälaiset kohderyhmät ovat kiinnostuneita malliperustaisen arkkitehtuurin eri tasoilla esitetyistä asioista? Onko kohderyhmiä, joiden tarvitsemat tiedot on hajautettu mallin eri tasoille? : Main : Control a:sensor b:sensor : Log : Actuator start(a,b) init init init alarm(m) m:message correct ok register(m) alarm(e) correct ok register(e) X e:message X 2.5 Tarkastellaan pankkiautomaattijärjestelmää. Anna esimerkkejä siitä, millaisia asioita kuvattaisiin järjestelmän a) loogisessa arkkitehtuurinäkymässä, joka on dynaaminen esimerkkikuvaus käyttäytymisestä, b) fyysisessä näkymässä, joka on staattinen määrittelykuvaus rakenteesta, c) muuntelunäkymässä, joka on staattinen esimerkkikuvaus rakenteesta. Voit käyttää UML:ää esimerkeissä.
29 50 Ohjelmistoarkkitehtuurit
30 OSA II Arkkitehtuuri pienessä mittakaavassa
31 3 Komponentit ja rajapinnat Komponentit ovat ohjelmistoarkkitehtuurien perusyksiköitä. Tarkastelemme tässä luvussa komponentin ja siihen läheisesti liittyvän rajapinnan käsitteitä. Lopuksi kuvaamme erilaisia tapoja komponentin räätälöintiin tietyn käyttötilanteen vaatimusten mukaan. 3.1 Komponentit Komponentti käsitteenä on tietotekniikan vanhimpia: jo kauan ennen ohjelmistoarkkitehtuurien esiintuloa puhuttiin ohjelmistokomponenteista ja visioitiin, kuinka niitä yhdistelemällä voitaisiin koota sovelluksia samaan tapaan kuin elektronisia laitteita kootaan olemassaolevista komponenteista. Vaikka tällainen visio ei olekaan vielä täysin toteutunut, komponentin käsite on kehittynyt erityisesti uusien komponenttiteknologioiden (esim. J2EE tai.net) myötä. Nykyisin käsitteellä komponentti onkin monissa yhteyksissä varsin konkreettinen ja täsmällinen merkitys, jonka nämä teknologiat antavat Komponentin määritelmä Ohjelmistokomponentille on esitetty kirjallisuudessa erilaisia määritelmiä, jotka eivät välttämättä sovi yhteen nykyisen komponenttikäsityksen kanssa. Määrittelemme tässä ohjelmistokomponentin itsenäiseksi ohjelmistoyksiköksi, joka tarjoaa palvelujaan hyvin määriteltyjen rajapintojen kautta. Määritelmä jättää avoimeksi joukon kysymyksiä, joita tarkastelemme seuraavassa.
32 54 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat 55 Riippuvuudet Komponentti olettaa yleensä tietyn infrastruktuurin olemassaolon. Tällaisen infrastruktuurin voi tarjota jokin komponenttiteknologia, ohjelmistokehys tai tietty sovellus. Komponentti voi myös riippua palveluista, joita se olettaa saavansa muilta komponenteilta vaadittujen rajapintojen (required interface) kautta (tarkastelemme komponentin rajapintoja lähemmin kohdassa 2.3). Komponentin ei tulisi kuitenkaan riippua tietystä toisesta komponentista. Kaikki komponentin riippuvuudet ympäristöstään täytyy olla annettu muodossa, jossa ne ovat komponentin käyttäjän saatavilla. Käyttöönotto Komponentti voidaan ottaa käyttöön yhtenä yksikkönä muista ohjelmayksiköistä riippumatta olettaen, että komponentin vaatimukset ympäristönsä suhteen on täytetty (ks. edellinen kohta). Tilanteesta ja teknologiasta riippuen komponentti voidaan ottaa käyttöön lähdekoodisena tai käännettynä binäärimuodossa. Edellisessä tapauksessa käyttöönotto tapahtuu käännösaikana ja jälkimmäisessä linkkausaikana, sovelluksen alustuksen yhteydessä tai sovelluksen käytön aikana. Mitä myöhemmin komponentti otetaan käyttöön, sitä enemmän tietoa on todennäköisesti saatavilla sopivimman komponentin valitsemiseksi. Koko Komponentin koolle ei ole asetettu mitään yleisiä rajoituksia: komponentti voi olla hyvin pieni, muutamia yksinkertaisia palveluja tarjoava olion kaltainen ohjelmayksikkö, mutta se voi olla myös sovelluksen kokoinen yksikkö, joka tarjoaa kirjavan joukon erikoistuneita palveluja. Esimerkiksi mikä hyvänsä ohjelmointirajapinnan tarjoava sovellus voi olla laajemmassa järjestelmässä komponentin roolissa. Nyrkkisääntönä komponenttipohjaisessa ohjelmistokehityksessä voidaan pitää, että komponentin tulisi olla yhden henkilön hallittavissa. Tämä asettaa komponentin koolle kussakin ympäristössä luontevat rajat. Standardointi Komponentti-idean taustalla on visio komponenttimarkkinoista, joilla eri valmistajat tarjoavat tuotteitaan ja joille sovellusten tekijät voivat kilpailuttaa komponenttien valmistajia. Tällaisten markkinoiden syntyminen edellyttää kuitenkin rajapintojen standardointia. Tämä koskee toisaalta sovellusaluekohtaisten rajapintojen standardointia ja toisaalta komponenttien tarvitseman yleisen infrastruktuurin standardointia. Parhaiten standardointi on onnistunut jälkimmäisessä: Esimerkiksi Sunin EJB-komponenttistandardin (Enterprise JavaBeans) toteuttavat lukuisat kaupalliset ja ei-kaupalliset tuotteet, joista sovellusten tekijät voivat valita parhaat Komponentit arkkitehtuurin yksikköinä Komponentit ovat ohjelmistoarkkitehtuurin pienimpiä rakenteita: arkkitehtuuri käsittelee komponentteja ja niiden välisiä suhteita, mutta ei arkkitehtuuriin kuuluvien komponenttien sisäistä rakennetta. Komponentit muodostavat näin vedenjakajan arkkitehtuurisuunnittelun ja yksityiskohtaisen suunnittelun välille. Toisaalta, jos komponentti on hyvin suuri ja itsessään muodostaa merkittävän osajärjestelmän, sille voidaan kuitenkin antaa oma arkkitehtuurikuvaus. Tällöin komponentit jakavat järjestelmän hierarkkisiin tasoihin, joiden arkkitehtuureista voidaan puhua itsenäisinä kokonaisuuksina. Tarkastellaan esimerkiksi järjestelmää, joka integroi yhteen joukon itsenäisiä sovelluksia niiden tarjoamien ohjelmointirajapintojen avulla. Esimerkki tällaisesta järjestelmästä voisi olla vaikkapa ohjelmistonhallintajärjestelmä, joka integroi yhteen UML-editorin, ohjelmointiympäristön ja tekstityökalun dokumenttien käsittelyä varten. Kukin sovellus toimii tällöin komponenttina ohjelmistonhallintajärjestelmän kannalta, mutta toisaalta kullakin sovelluksella on oma arkkitehtuurinsa, joka ei millään tavalla ole sidoksissa integroidun järjestelmän arkkitehtuuriin. Jälkimmäisen kannalta sovellusten olennainen ominaisuus on vain niiden tarjoama ohjelmointirajapinta. Komponentin ideaan kuuluu, että sen sisäinen rakenne ei näy käyttäjille eikä riipu käyttöympäristöstä. Näin ollen on yleensä syytä erottaa järjestelmän arkkitehtuuri sen sisältämien komponenttien arkkitehtuureista, mikäli jälkimmäisille annetaan arkkitehtuuritason kuvaus. Jos yksittäisen komponentin arkkitehtuuri on olennaisesti sidoksissa sitä käyttävän järjestelmän arkkitehtuuriin, saattaa kyse olla
33 56 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat 57 suunnitteluvirheestä. Toisaalta järjestelmän arkkitehtuuri voi antaa erilaisia yleisiä sopimuksia ja standardiratkaisuja, joita kaikkien sen komponenttien oletetaan noudattavan. Toiminnallisuus on primääri peruste komponentin olemassaololle, joka antaa komponentilla selkeästi ilmaistavissa olevat velvollisuudet. Toiminnallisuuden perusteella muodostettu komponentti kokoaa yhteen loogisesti toisiinsa läheisesti liittyvät palvelut, joiden toteutus usein perustuu samoihin tietorakenteisiin ja joita tyypillisesti käytetään samassa yhteydessä. Komponentilla voidaan myös hallita järjestelmän muunneltavuutta. Tällöin komponentti voi esimerkiksi eristää järjestelmästä tietyn osan, joka halutaan helposti vaihdettavaksi, lisättäväksi tai poistettavaksi. Nk. plugin-arkkitehtuureissa järjestelmän toiminnallisuus määräytyy käynnistysvaiheessa järjestelmän saatavilla olevista komponenteista. Mukaan otetuista komponenteista riippuen koko järjestelmän luonne voi tällöin vaihdella hyvinkin paljon. Tyypillinen esimerkki plugin-arkkitehtuurista on IBM:n Eclipse-kehitysympäristöalusta. Plugin-arkkitehtuurin suhteen käänteinen tilanne on silloin, kun komponentti on tarkoitettu käytettäväksi useissa järjestelmissä, joita ei tunneta etukäteen. Tällöin komponentti tarjoaa yleisiä palveluja, jotka eivät riipu yksittäisestä järjestelmästä. Toisin kuin pluginarkkitehtuurissa varsinainen komponentti on uudelleenkäytettävä, ei se järjestelmä, johon komponentti liitetään. Nk. COTS-komponentit (Commercial Off-The-Shelf) ovat kaupallisia, erikseen saatavilla olevia yleiskäyttöisiä komponentteja, joita järjestelmien kehittäjät voivat ostaa ja hyödyntää ohjelmissaan. Jo pitkään on ymmärretty, että ohjelmistoilla on moniulotteinen rakenne, jota ei voida tavoittaa millään yksittäisellä komponenttijaolla. Esimerkiksi toiminnallisuuden käyttäminen kriteerinä komponenttien määräämisessä (so., kukin komponentti toteuttaa jonkin toiminnallisen kokonaisuuden) on kaikkea muuta kuin yksiselitteistä. Yleisesti ei ole edes mahdollista jakaa järjestelmän toteutuksen toiminnallisuutta erillisiin osiin siten, että kukin looginen toiminnallisuus on täsmälleen yhdessä osassa (komponentissa). Tämä johtuu siitä, että järjestelmää voidaan tarkastella erilaisten huolenaiheiden (concern) kannalta, eikä näillä huolenaiheilla ole yleensä yksi-yhteen-vastaavuutta komponenttien kanssa. Kutsuimme luvussa 2 yhteen huolenaiheeseen liittyviä ohjelman osia (arkkitehtuuri)viipaleiksi. Voidaankin sanoa, että kompo- nentit ja viipaleet ovat arkkitehtuurin perusyksiköitä. Tyypillisiä tällaisia järjestelmän komponenttijaon suhteen poikkikulkevia huolenaiheita ovat erilaiset järjestelmän suurta osaa koskevat toiminnalliset ominaisuudet kuten vaikkapa tietty käyttäjälle näkyvä looginen toiminnallisuus, tiedon pysyvyys tai turvallisuus jne. Huolenaiheisiin perustuvaa ohjelmistosuunnittelua ja sen tarvitsemaa työkalutukea on tutkittu paljon yliopistoissa aspekti-termin (aspect) alla: tämä termi viittaa tiettyyn viipaletekniikkaan, joka on saanut toistaiseksi eniten huomiota. Suunnitteluperiaatetta, jossa järjestelmän arkkitehtuuri kuvataan komponenttirakenteen läpi leikkaavien aspektien avulla, sanotaan aspektiperustaiseksi suunnitteluksi (Aspect-Oriented Design, AOD) (Filman et al. 2005) Komponentit ohjelmistokehityksen yksikköinä Koska ohjelmiston arkkitehtuurilla on keskeinen vaikutus ohjelmistokehitysprosessiin ja kehittävään organisaatioon, myös komponenteille arkkitehtuurin perusyksikköinä tulee tärkeä rooli tältä kannalta. Komponentista tulee työnjaon yksikkö: komponentti annetaan tyypillisesti tietyn henkilön kehitettäväksi ja vastuulle. Komponentin kehittämisellä on usein oma aikataulutuksensa ja resurssointinsa. Organisaatioon kuuluva ryhmä kehittää toisiinsa liittyviä komponentteja, esimerkiksi jonkin alijärjestelmän osia. Tällaisesta työnjaosta seuraa, että organisaation rakenne alkaa muistuttaa järjestelmän rakennetta aiemmin esitetyn Conwayn lain mukaisesti. Komponentti toimii työnjaon yksikkönä myös suunnitteluvaiheen jälkeen: komponentit testataan erillisinä yksikköinä ennen järjestelmätestausta, tuotekonfiguraatio määritellään komponenttien versioiden avulla ja ylläpitotehtävät jaetaan komponenteittain. 3.2 Rajapinnat Yksi tärkeimmistä ohjelmistotekniikan periaatteista on pyrkiä erottamaan toisistaan se, mitä halutaan saada aikaan, ja se, miten tämä tapahtuu. Komponenttien tapauksessa tämä tarkoittaa sitä, että palvelun toteutus on erotettava palvelusta abstraktiona: palvelun käyttäjän ei tulisi riippua tietystä palvelun tuottajasta, komponentista, vaan ainoastaan palvelusta itsestään abstraktina käsitteenä. Tämä abstrakti
34 58 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat 59 käsite esitetään rajapintana, jonka yksi tai useampi konkreettinen komponentti toteuttaa Rajapinnan käsite Minimimuodossaan rajapinta kertoo, miten palvelu otetaan käyttöön, ts. palvelun nimen, parametrit ja niiden tyypit sekä mahdollisen tuloksen tyypin. Tälle kokonaisuudelle käytetään nimitystä kutsumuoto (signature). Laajemmassa mielessä rajapinnan tulisi antaa kaikki se olennainen tieto palvelusta, mitä palvelun käyttäjän on syytä tietää. Tähän sisältyy kutsumuodon lisäksi palvelun määrittely, palvelun laadulliset ominaisuudet (esimerkiksi ajan ja tilan käyttö), mahdolliset sivuvaikutukset, mahdolliset palvelun suorittamisesta aiheutuvat poikkeukset ja mahdolliset riippuvuudet globaaleista resursseista (esim. globaaleista muuttujista tai tiedostoista). Rajapinnat ovat olennainen osa ohjelmistoarkkitehtuuria. Ne määräävät tavat, joilla komponentit kommunikoivat keskenään ja siten arkkitehtuurin keskeiset ominaisuudet. Myös monet arkkitehtuurityylit ja suunnittelumallit, joihin palaamme myöhemmin tässä teoksessa, perustuvat rajapintojen käyttöön. Rajapintojen huolellinen suunnittelu on edellytyksenä paitsi kehitystyön järkevälle jakamiselle myös monille tärkeille ohjelmiston laatuominaisuuksille, kuten testattavuudelle, ylläpidettävyydelle ja joustavuudelle. Rajapintojen suunnittelu alkaa tyypillisesti heti, kun keskeisimmät arkkitehtuuriratkaisut (esimerkiksi arkkitehtuurityylin määrääminen) on tehty. Rajapinnan käsite on kehittynyt ohjelmistotekniikassa vähitellen alkaen varhaisten ohjelmointikielten aliohjelmien kutsumuotomäärittelyistä (esim. Fortran). Myöhemmin modulaariset ohjelmointikielet (esim. Modula-2, Ada) toivat abstraktin tietotyypin käsitteen, jonka avulla joukko tietorakenteita ja niihin liittyviä aliohjelmia koottiin näkyvyyden peittävän moduulirakenteen sisään. Moduuliin liitettiin määritys, joka kertoi, mitkä asiat moduulista olivat käytettävissä sen ulkopuolella. Olioparadigma yleisti edelleen abstraktin tyypin käsitettä antamalla mahdollisuuden tyypin laajentamiseen uusilla ominaisuuksilla säilyttäen yhteensopivuuden alkuperäisen tyypin kanssa (luokkien periyttäminen). Kun toisaalta tuli mahdolliseksi laajentaa ilman toteutusta oleva abstrakti luokka toteuttavalla luokalla, oltiin jo hyvin lähellä ny- kyistä rajapinnan käsitettä. Tällöin abstrakti luokka edustaa rajapintaa, jolle sen aliluokat tarjoavat erilaisia toteutuksia. Tämä on edelleenkin tapa, jolla rajapinnat esitetään esimerkiksi C++:ssa. Toisaalta C++, kuten Javakin, tarjoaa edelleen myös vanhasta moduulikäsitteestä peräisin olevan tavan abstrahoida luokka käyttäjilleen määrittelemällä, mitkä osat siitä ovat näkyvissä ulkopuolelle. Tämä mekanismi ei kuitenkaan johda itsenäiseen rajapintakäsitteeseen, koska se säilyttää kiinteän yhteyden palveluabstraktion ja sen toteutuksen välillä. Abstraktin luokan periyttäminen useille konkreettisille aliluokille mahdollistaa siis usean toteutuksen antamisen samalla rajapinnalle. Toisaalta moniperiytyminen antaa tietylle konkreettiselle luokalle mahdollisuuden toteuttaa useita abstraktilla luokalla esitettyjä rajapintoja. Näin moniperiytymisen ja abstraktit luokat salliva oliokieli (kuten C++) sisältää jo periaatteessa kaiken tarpeellisen tuen yleiselle rajapintakäsitteelle: Rajapinta on oma erillinen nimetty rakenteensa (abstrakti luokka), sen voi toteuttaa yksi tai useampi komponentti (konkreettinen luokka), ja tietty komponentti voi toteuttaa useita rajapintoja. Uudemmissa oliokielissä kuten Javassa ja C#:ssa rajapinnat on nähty jo niin keskeisinä käsitteinä, että niille on annettu kielessä oma syntaksinsa. Javassa ei ole yleisesti moniperiytymistä, mutta rajapintojen tapauksessa tämä sallitaan, jolloin luokka voi toteuttaa useita rajapintoja Tarjotut ja vaaditut rajapinnat Komponentilla ja rajapinnalla voi olla keskenään kaksi erilaista suhdetta: komponentti voi joko tarjota (toteuttaa) tai vaatia rajapinnan mukaisia palveluja. Sanomme vastaavasti, että tietty rajapinta on komponentille joko tarjottu (provided) tai vaadittu (required) rajapinta. Sama rajapinta voi näin olla (ja tavallisesti onkin) yhden komponentin kannalta tarjottu ja toisen kannalta vaadittu. Ohjelmointikielissä on perinteisesti rakenteet, joilla voidaan ilmaista komponentin tarjotut (toteuttamat) rajapinnat. Esimerkiksi Javassa tämä tapahtuu "implements"-määreen avulla luokan otsikossa, ja C++:ssa (jossa rajapinnat kuvataan abstrakteina luokkina) normaalin periytymismääreen avulla. Sen sijaan vaadittujen rajapinto-
35 60 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat 61 PowerSource tarjottu rajapinta Car Car Engine PowerSource jen antamiseen ei näissä kielissä ole erityisiä ilmaisuja. Hyvä käytäntö on listata vaaditut rajapinnat komponentin otsakekommentissa. UML (versio 2.0) sisältää erillisen merkintätavan tarjotuille ja vaadituille rajapinnoille. Esimerkiksi kuvassa 3.1 komponentti (huomaa komponentti-ikoni luokkasymbolin oikeassa ylänurkassa) Car vaatii rajapinnan PowerSource auto tarvitsee jonkin voimanlähteen. Toisaalta komponentti Engine tarjoaa (toteuttaa) tämän rajapinnan. Näin ollen komponentit voidaan liittää toisiinsa rajapinnan kautta kuvan alaosassa olevan kaavion mukaisesti. Javalla esitettynä kuvan 3.1 rajapinta ja komponentit voisivat olla esimerkiksi seuraavan kaltaisia: interface PowerSource { void start(); int temperature(); void stop(); } class Car... {... private PowerSource eng;... public void seteng(powersource e) { eng = e; PowerSource vaadittu rajapinta Engine Kuva 3.1: Tarjotut ja vaaditut rajapinnat UML:ssä } public void run() {... eng.start();... eng.stop();... } } class Engine implements PowerSource... {... public void start() {...} public int temperature() {...} public void stop() {...} } Huomaa, että kuvassa 3.1 esitetty komponenttien liittäminen toisiinsa edellyttää, että rajapinnan vaativalla komponentilla on operaatio, jonka avulla rajapinnan toteuttaja voi rekisteröityä tälle (seteng) Rajapintojen määrittely Nykyaikaisissa ohjelmointikielissä rajapinta siis määritellään tavallisesti siihen kuuluvien palveluiden kutsumuodoilla (signature). Kuten edellä todettiin, rajapintaan liittyy kuitenkin muutakin informaatiota. Erityisesti palveluiden vaikutuksen (semantiikan) kuvaus on olennaista palveluiden käyttäjän kannalta. Palveluiden vaikutuksen formaali spesifiointi ja siihen perustuva ohjelmien oikeaksi todistaminen on ollut pitkään tutkimuskohteena ohjelmistotekniikassa, mutta todellisessa ohjelmistotuotannossa tämä on osoittautunut mahdolliseksi vain suhteellisen rajatussa muodossa. Niinpä käytännössä tyydytään yleensä kuvaamaan vaikutus sanallisella selityksellä, joka liitetään kutsumuotoon. Joissakin tapauksissa voidaan käyttää palveluiden ja niiden parametrien nimeämisessä sopimuksia, jotka yksiselitteisesti kuvaavat myös palveluiden merkityksen (esim. "getterit ja setterit").
36 62 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat 63 Tulo- ja jättöehdot Tavallinen tapa kuvata palvelun merkitys on antaa sille tulo- ja jättöehdot (pre/post condition). Tällöin palvelu muuttaa tuloehtojen kuvaamat olosuhteet jättöehtojen kuvaamiksi olosuhteiksi. Tulo- ja jättöehdot eivät kuitenkaan sano mitään siitä, miten tämä olosuhteiden muuttaminen tapahtuu, jättäen tämän palvelun toteuttajien päätettäväksi. Tulo- ja jättöehdot voidaan ajatella sopimukseksi, joka solmitaan palvelun tarvitsijoiden ja palvelun tarjoajien välille: tuloehdot määrittelevät palvelun tarvitsijoiden velvoitteet, ja jättöehdot määrittelevät palvelun tarjoajan velvoitteet. Tulo- ja jättöehtoihin perustuvaa ohjelmistojen suunnittelua kutsutaankin joskus sopimuspohjaiseksi suunnitteluksi (design-by-contract). Tulo- ja jättöehdot voidaan antaa sanallisina, täysin epäformaaleina kuvauksina, tai enemmän tai vähemmän formaalisti. Pragmaattinen formaali tapa antaa tulo- ja jättöehdot on käyttää kyseisen ohjelmointikielen loogisia lausekkeita. Jotkin kielet (mm. Eiffel) tarjoavatkin loogisiin lausekkeisiin perustuvat tulo- ja jättöehdot kielen rakenteina. Loogisina lausekkeina kuvattujen tulo- ja jättöehtojen etuna on se, että niistä voidaan tuottaa tarkistuskoodia, jolla saadaan sopimusrikkomukset kiinni ohjelmiston testausvaiheessa. Tuotantoversioon tarkistuskoodia ei tarvitse generoida lainkaan, jolloin järjestelmän tehokkuus ei kärsi tarkistuksista. Jos kieli ei suoraan tarjoa tällaisia tulo- ja jättöehtoja omina rakenteina, kurinalainen ohjelmoija voi itse sijoittaa vastaavat tarkistukset palveluiden alkuun ja loppuun. Invariantit Palveluiden tulo- ja jättöehtojen lisäksi voidaan käyttää invariantteja ilmaisemaan ohjelmalta edellytettyä logiikkaa. Invariantti on ehto, jonka tulee olla tosi tietyissä tilanteissa ohjelman suorituksen aikana. Invariantin yksittäiseen olioon (luokkaan) rajoitettu muoto, luokkainvariantti (class invariant), antaa ehdon, joka vallitsee olion attribuuttien arvojen suhteen olion operaatioiden suorituksien välillä, toisin sanoen siis muulloin, kuin jonkin operaation suorituksen aikana. Luokkainvariantti on näin ollen lisättävä mahdollisiin operaatioiden tulo- ja jättöehtoihin konjunktiolla ("and"). Ehdoissa voidaan kuitenkin käyttää yksinkertaisempaakin muotoa, mikäli sen voidaan osoittaa takaavan invariantin voimassaolon. Tarkastellaan seuraavaksi klassista esimerkkiä esi- ja jälkiehtojen käytöstä, pinoa. Yksinkertainen pinorajapinta ja sen toteutus voidaan antaa Javalla seuraavasti: interface Stack { int size(); boolean full(); void push(object x); Object pop(); } class ArrayStack implements Stack { private Object [] storage; private int top; public ArrayStack(int size) { storage = new Object[size]; top = -1; } public int size() {return top+1} public boolean full() {return top == storage.length-1;} public void push(object x) { if (top < storage.length-1) { storage[++top] = x; } else { System.out.println("Overflow"); } } public Object pop() { if (top >= 0) { return storage[top--]; } else { System.out.println("Underflow"); return null; } } } Tässä toteutuksessa on kuitenkin ainakin kaksi merkittävää ongelmaa. Ensinnäkin, varovainen pinon käyttäjä voisi helposti kutsua pop-operaatiota muodossa:
37 64 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat 65 Stack s = new ArrayStack(100);... if s.size() > 0 then {s.pop();} else {... error handling... } Tässä toteutuksessa sama tarkistus tehdään aina kahteen kertaan, sekä palvelun kutsujan että palvelun tarjoajan toimesta. Ongelma liittyy siihen, että palvelun kutsuja ei toteutusta tutkimatta voi tietää, tuleeko hänen tehdä tarkistus, jolloin hän varmuuden vuoksi tekee sen. Toiseksi, jos jokin palvelu epäonnistuu, palvelun tarjoaja antaa yksinkertaisesti virheilmoituksen. Tämä on monien palvelun käyttäjien kannalta huono ratkaisu, koska ne haluaisivat reagoida tilanteeseen omalla tavallaan eivätkä ainakaan tuottaa sovelluksen käyttäjälle mystisiä virheilmoituksia. Kummassakin ongelmassa kyse on siitä, että velvollisuuksia ei ole selkeästi määritelty palvelun pyytäjän ja tarjoajan välillä. Tuloja jättöehtoja käyttämällä nämä velvollisuudet voidaan ainakin tässä tapauksessa antaa suhteellisen helposti seuraavasti: interface Stack {... Object pop() pre size() > 0; post size() = old size() -1;... } Oletimme tässä, että kieli on laajennettu tulo- ja jättöehdot ilmaisevilla pre- ja post-määreillä. Avainsana "old" viittaa kyseisen muuttujan tai funktion arvoon ennen palvelun suoritusta. Rajapinnan kuvaus tällä tavoin kertoo, että palvelun pyytäjän velvollisuutena on huolehtia pinon ei-tyhjyyden tarkistuksesta ennen kutsua. Edellyttäen, että tämä tarkistus on tehty, palvelun toteutus huolehtii siitä, että pinon koko pienenee yhdellä. Toteutus voidaan siis nyt antaa yksinkertaisesti: class ArrayStack implements Stack {... Object pop() { return storage[top--];}... Huomaa, että kyse on tässä kuitenkin vain ohjelmayksiköiden välisestä sopimuksesta, jolla pyritään täsmentämään niiden velvollisuuksia. Mikään ei takaa, että sopimuksella tavoitettaisiin kokonaisuudessaan se vaikutus, joka palvelulla halutaan olevan (siis palvelun merkitys). Lukija voi miettiä, millainen olisi pop-operaation toteutus, joka täyttää jättöehdon mutta ei vastaa sitä, mitä yleensä popoperaatiolla ymmärretään. Poikkeukset rajapinnassa Vaikka tulo- ja jättöehdot tarkistetaan järjestelmän testausvaiheessa, mikään ei takaa sitä, etteikö järjestelmää käytettäessä voisi syntyä tilanne, jossa jotakin tulo- ja jättöehdoilla ilmaistua sopimusta rikotaan. Miten järjestelmä voitaisiin suunnitella niin, että tällaisessakin tilanteessa järjestelmän toiminta olisi hallittua? Nykyiset ohjelmointikielet tarjoavat tämän ongelman ratkaisuun luontevan välineen: poikkeukset (exception). Sopimuspohjaisessa suunnittelussa poikkeus tulkitaan tilanteeksi, jossa palvelun tarjoaja on kykenemätön saattamaan jättöehdot voimaan ja siten rikkoo sopimuksen. Näin ollen palvelun tarjoaja siis aina joko täyttää sopimuksen tai aiheuttaa poikkeuksen kolmatta vaihtoehtoa ei ole. Palvelun pyytäjä voi siten luottaa siihen, että ilman poikkeusta päättynyt palvelu on aina sopimuksen mukainen. Palvelun mahdollisesti synnyttämistä poikkeuksista tulee osa rajapinnan määrittelyä, ja palvelun tarvitsijan velvollisuutena on varautua niihin. Esimerkiksi pinon rajapinta annettaisiin poikkeuksia käyttämällä seuraavasti: interface Stack { int size(); boolean full(); void push(object x) throws Overflow; Object pop() throws Underflow; } Pop-operaation toteutus olisi vastaavasti: public Object pop() throws Underflow { // post-condition: uppermost element removed
38 66 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat 67 } // exceptions: Underflow (stack empty) if (top >= 0) { return storage[top--]; } else { throw new Underflow(this); } Huomaa, että nyt tuloehdon tarkistus jää palvelun tarjoajan velvollisuudeksi, mutta sen rikkomiseen reagointi palvelun tarvitsijan velvollisuudeksi. Hyvä suunnitteluperiaate on antaa palvelun tarjoajan huolehtia kaikista palveluun liittyvistä tarkistuksista aiheuttamalla niiden rikkomisista poikkeus. Näin palveluun liittyvät poikkeukset (tai niihin johtavat ehdot) määräävät implisiittisen tuloehdon palvelulle. Vastaavasti tilanteet, jotka tuloehdon kannalta ovat sallittuja, mutta johtavat muuten epätäydelliseen suoritukseen aliohjelman sisällä, voidaan hoitaa esimerkiksi operaation paluuarvoja käyttämällä. Lisäksi poikkeuksen synnyttävä ehto tulee kuvata täsmällisesti kommentissa tai dokumentaatiossa, formaalisti tai epäformaalisti. 3.3 Komponenttien räätälöinti Komponentin idea uudelleenkäytettävästä ohjelmayksiköstä edellyttää, että komponenttia voidaan käyttää erilaisissa yhteyksissä, joilla saattaa olla hieman toisistaan poikkeavia odotuksia komponentilta. Jos komponentti tarjoaa palvelunsa aina täsmälleen samassa muodossa, sen uudelleenkäyttö voi muodostua varsin rajoitetuksi. Onnistunut uudelleenkäytettävyys edellyttää, että komponenttia voidaan räätälöidä ympäristön tarpeiden mukaan. Tämä asettaa komponentin suunnittelulle haasteita. Suunnittelussa on otettava huomioon varianssin hallinta samaan tapaan kuin tuoterunkojen yhteydessä, joihin palaamme myöhemmin. Räätälöitävää komponenttia voidaan tässä mielessä ajatella pienenä, hyvin erikoistuneena tuoterunkona. Tarkastelemme seuraavassa erilaisia tekniikoita, joita voidaan käyttää komponentin tarjoaman varianssin toteuttamiseen. Vastaavat tekniikat tulevat esille yleisemmin tuoterunkojen ja kehysohjelmistojen yhteydessä Komponentin tilan muuttaminen Yksinkertaisin tapa räätälöidä komponentti on asettaa se tiettyyn tilaan antamalla joillekin komponentin ominaisuuksia kuvaaville tietokentille (attribuuteille tai muuttujille) sopiva arvo. Kenttien arvot voidaan antaa komponentin ilmentymää luotaessa (esimerkiksi rakentajan parametreina) tai erityisillä asetusoperaatioilla (setter). Jälkimmäisessä tapauksessa kyseisiä ominaisuuksia voidaan muuttaa vielä käyttöönoton jälkeenkin. Jos ulkoapäin muutettavissa olevat komponentin ominaisuudet voidaan päätellä komponentin koodista, visuaaliset komponenttityökalut voivat tarjota dialogit ominaisuuksien asettamiseen. Esimerkiksi JavaBeans-teknologiassa komponentin ominaisuudet kuvaavat attribuutit tunnistetaan tiettyjen koodauskonventioiden perusteella. Tyypillinen esimerkki komponentin tilan muuttamiseen perustuvasta räätälöinnistä voisi olla vaikkapa painonappikomponentti, jolla on ominaisuutena napin päällä näkyvä toiminnon nimi. Komponentti voitaisiin antaa Javalla seuraavasti: class Button extends Component... {... String label; public Button() {... } public Button(String arg) { label = arg;... } public setlabel(string arg) { label = arg; }... } Haluttaessa luoda sovelluskohtainen painonappikomponentti voidaan joko käyttää oletusrakentajaa ja ominaisuuden asetusoperaatiota (setlabel) tai luoda suoraan komponentti halutulla toiminnon nimellä käyttäen parametroitua rakentajaa.
39 68 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat Vaadittujen rajapintojen toteuttaminen Ominaisuuksien asettaminen ei muuta komponentin koodia, vaan ainoastaan sen tietosisältöä vaikuttaen siten komponentin suorituspolkuihin. Komponentin käyttäytymistä voidaan muokata voimakkaammin antamalla erilaisia toteutuksia komponentin vaatimille rajapinnoille. Tällöin palvelun suorittava koodi muuttuu, koska palvelun toteutus käyttää hyväkseen jonkin toisen komponentin koodia, ja jälkimmäinen komponentti vaihdetaan toiseksi. Koska vaaditun rajapinnan toteuttava komponentti voidaan vaihtaa milloin tahansa ajoaikana, komponentin käyttäytymistä voidaan varioida saman komponentti-ilmentymän yhteydessä. Ääritapauksessa voidaan komponentin palvelut toteuttaa siten, että varsinainen palvelun suoritus delegoidaan toiselle komponentille. Tällöin alkuperäisen komponentin tarjoama ja vaatima rajapinta on sama; alkuperäisen komponentin tehtäväksi jää toimia välittäjänä palvelun pyytäjän ja tarjoajan kesken. Tällainen ratkaisu voi olla tarpeen, jos halutaan, että rajapinnalla on ajoaikainen edustajansa, johon voidaan tallettaa esimerkiksi tietoa rajapinnan käytöstä (ks. esimerkiksi Edustaja-suunnittelumalli). Tarkastellaan esimerkkinä taas Javan painonappikomponenttia (Button). Kun nappia klikataan, sovelluskohtainen toiminto aktivoituu. Tätä tarkoitusta varten komponentilla on operaatio, jolla sovelluskohtainen komponentti voidaan rekisteröidä kuuntelemaan klikkaustapahtumaa. Tämän komponentin tulee toteuttaa rajapinta ActionListener, johon sisältyy sovelluskohtaisen toiminnon aktivoiva operaatio. Kun painonappia klikataan, Button pyytää sille rekisteröityä sovelluskohtaista komponenttia suorittamaan tämän operaation. Muuttamalla tai lisäämällä kuuntelijakomponentteja painonappikomponentin käyttäytymistä klikkaustilanteessa voidaan muunnella halutulla tavalla. Kuvassa 3.2 on esitetty Button-komponentin räätälöinti antamalla vaaditun rajapinnan ActionListener toteuttava sovelluskohtainen komponentti AppLogic Periytymisen avulla toteutettu räätälöinti Vaadittuihin rajapintoihin perustuva räätälöinti muuttaa komponentin kutsumaa koodia, mutta ei komponentin omaa koodia. Joskus haluttua variointia ei pystytä luontevasti ilmaisemaan vaaditun rajapin- nan erilaisten toteutusten avulla, vaan komponentin omaa koodia halutaan muuttaa. Jos komponentti on toteutettu luokan avulla, tähän voidaan käyttää normaalia periytymistä. Tällöin komponentin luokan perii toinen luokka, joka uudelleenmäärittelee jotkin sen operaatiot. Näin syntyy kokonaan uusi komponentti, joka on periytymisuhteessa alkuperäisen komponentin kanssa. Uudella komponentilla on samat tarjotut rajapinnat kuin alkuperäisellä, mutta joillakin sen tarjoamilla palveluilla on täysin uusi toteutus. Esimerkkinä periytymisen avulla toteutetusta räätälöinnistä tarkastellaan jälleen painonappikomponenttia. Oletetaan, että yritys haluaa, että kaikissa sen tuotteissa esitetään yhtenäisesti painonappien toimintonimet isoilla kirjaimilla. Tätä varten yritys tekee oman painonappikomponentin, joka perii standardin komponentin ja muuttaa rakentajan ja setlabel-operaation toteutuksen: class CompanyButton extends Button { public CompanyButton(String arg) { super(arg); label = arg.touppercase(); } } Button ActionListener public void setlabel(string arg) { label = arg.touppercase(); } AppLogic Kuva 3.2: Komponentin räätälöinti vaaditun rajapinnan toteutuksen avulla Näin kaikissa paikoissa, joissa napin nimi asetetaan, kutsutaan samalla touppercase-funktiota muuttamaan teksti isoiksi kirjaimiksi. Kun yritys ohjeistaa käyttämään ainoastaan erikoistettua painonappia standardin Button-komponentin sijasta, kaikissa yrityksen
40 70 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat 71 sovelluksissa noudatetaan haluttua käytäntöä riippumatta siitä, millaisessa muodossa ohjelmoija antaa nimen. Periytymissuhde komponenttien välillä on kuitenkin ongelmallinen, koska se rikkoo komponenttien riippumattomuusperiaatteen: komponentit eivät saisi suoraan riippua toisistaan vaan ainoastaan rajapintojen kautta. Periytymissuhde johtaa siihen, että komponentit riippuvat toistensa toteutuksesta, mikä voi aiheuttaa monia ongelmia. Tarkastellaan esimerkkinä periytymisen tuomista ongelmista nk. särkyvän yliluokan ongelmaa (fragile base class problem). Oletetaan, että kirjastokomponentti List toteuttaa säiliörajapinnan Container, johon sisältyy operaatiot alkion (addelement) ja toisen säiliön (addcontainer) lisäämiseksi säiliöön. Jälkimmäinen operaatio on toteutettu yksinkertaisesti kutsumalla addelement-operaatiota kullekin parametrina annetun säiliön alkiolle. Oletetaan, että sovelluskehittäjä tarvitsee säiliön, joka pystyy myös pitämään kirjaa säiliössä olevien alkioiden lukumäärästä. Koska alkuperäinen säiliötoteutus ja sen toteuttama rajapinta eivät tue tällaista toiminnallisuutta (mikä on tosin hieman outoa, mutta oletetaan näin esimerkin vuoksi), sovelluskehittäjä päättää laajentaa kirjastokomponenttia periytymistä käyttäen. Samalla hän määrittelee uuden rajapinnan CountedContainer, joka laajentaa Container-rajapintaa uudella operaatiolla alkioiden lukumäärän kysymiseksi. CountedContainer-rajapinnan toteuttaa uusi komponentti CountedList, joka esittelee attribuutin lukumäärän tallettamiseksi ja määrittelee uudelleen operaation addelement siten, että aikaisemman toiminnan lisäksi lukumääräattribuutin arvoa lisätään yhdellä. Koska addcontainer oli alun perin toteutettu addelement-operaation avulla, myös se päivittää lukumäärän oikein. Sovelluskehittäjä voi nyt käyttää uutta säiliötoteutusta sovelluksessaan, ja kaikki toimii hienosti. Kirjaston ylläpitäjä saa nyt hyvän ajatuksen: itse asiassa add- Container-operaatio voitaisiin toteuttaa paljon tehokkaammin alkuperäisessä List-komponentissa, jos siinä käytettäisiin hyväksi suoraan säiliön toteutustietorakennetta, listaa, eikä tukeuduttaisi luokkarajapinnassa määriteltyyn addelement-operaatioon. Koska vaadittu addcontainer-operaation toteutuksen muutos ei riko operaation tuloja jättöehtoja vaan ainoastaan vaihtaa toteutustapaa, kirjaston ylläpitäjä olettaa muutoksen olevan turvallinen eikä vaikuttavan kirjastolla jo oleviin käyttäjiin. Niinpä hän tekee muutoksen ja ilmoittaa kaikil- le kirjaston käyttäjille, että uusi, tehokkaampi versio kirjastosta on saatavilla, ja kehottaa kaikkia kääntämään sovelluksensa vasten uutta kirjastoa. Kun List-komponenttia laajentanut sovelluskehittäjä siirtyy käyttämään parannettua kirjastoversiota, hän kuitenkin huomaa, että hänen sovelluksensa ei enää toimi: lukumääräattribuutti ei enää päivity oikein. Lähempi tarkastelu osoittaa, että syy ongelmaan löytyy addcontainer-operaatiosta. Operaatio ei enää kutsu addelementoperaatiota, joten haluttua päivitystä ei tapahdu. Ongelma on tässä esimerkissä siinä, että kirjastokomponentti on tehty tavalla, joka tekee siitä herkän rikkoutumaan tilanteessa, jossa komponentti periytetään. Operaatioiden toteutukset riippuvat toisistaan, joten niiden erillinen uudelleentoteutus saattaa helposti johtaa virhetilanteisiin. Yhteenvetona voidaan todeta, että periytymisen hyväksikäyttäminen antaa komponentin räätälöintiin varsin voimakkaan työkalun, mutta toisaalta voi johtaa hallitsemattomaan riippuvuuteen komponenttien välillä. 3.4 Yhteenveto Komponentti on itsenäinen ohjelmayksikkö, joka tarjoaa palveluja hyvin määriteltyjen rajapintojen kautta. Komponentit ja viipaleet ovat arkkitehtuurin ja ohjelmistokehitysprosessin perusyksiköitä. Rajapinnan tulee antaa palvelusta kaikki olennainen käyttäjän tarvitsema tieto. Komponentilla on tarjotut ja vaaditut rajapinnat. Rajapintojen suunnittelun tulisi pohjautua rooleihin. Rajapintaan sisältyvän palvelun merkitys voidaan spesifioida tulo- ja jättöehdoilla. Komponentin kykenemättömyys suorittaa pyydetty palvelu tulo- ja jättöehtojen mukaisesti ilmaistaan poikkeuksella. Komponenttia voidaan räätälöidä sen tilaa muuttamalla, antamalla erilaisia toteutuksia vaadituille rajapinnoille sekä periyttämällä komponentin luokka.
41 72 Ohjelmistoarkkitehtuurit Komponentit ja rajapinnat Harjoitustehtäviä 3.1 Miten esittäisit kohdassa esitetyn esimerkin periytymisen avulla toteutetusta komponentin räätälöinnistä käyttäen a) komponentin tilan muuttamiseen perustuvaa tekniikkaa, ja b) komponentin vaadittujen rajapintojen toteutukseen perustuvaa tekniikkaa? 3.2 Anna tekstissä käytetyn pino-komponentin push- ja pop-operaatioille sellaiset formaalit tulo- ja jättöehdot, jotka vastaavat täsmällisesti näiden operaatioiden tavanomaista merkitystä. Voit käyttää sopiviksi katsoamiasi merkintätapoja. Anna esimerkki toteutuksesta, joka täyttää kirjassa annetut tulo- ja jättöehdot pop-operaatiolle, mutta ei vastaa yleistä käsitystä pinon toiminnasta. Keksitkö pinon tilaa kuvaavaa invarianttia, jonka voisit osoittaa oikeaksi antamiesi tulo- ja jättöehtojen avulla? 3.3 Oletetaan, että ohjelmistoinfrastruktuuri tarjoaa luokkainvariantin käsitteen ja operaatiot, joiden avulla invariantti voidaan automaattisesti tarkastaa. Missä tilanteissa invariantti tulisi evaluoida? Entä mitä tapahtuisi, jos invariantti evaluoitaisiin epätodeksi? 3.4 Oletetaan, että sovelluksessa tarvitaan komponentti, joka toteuttaa henkilön toiminnallisuuden tarjoten operaatiot get- Name, setname, getage, growold. Komponentti on toteutettu luokkana. a) Onko seuraava lause oikein vai väärin: minkä hyvänsä henkilön toteutukselta tarvittavan ajoaikaisen muuntelun, jonka voi saada aikaan vaadittujen rajapintojen toteutuksen avulla, voi saada aikaan myös periytymisen avulla. b) Onko seuraava lause oikein vai väärin: minkä hyvänsä henkilön toteutukselta tarvittavan ajoaikaisen muuntelun, jonka voi saada aikaan periytymisen avulla, voi saada aikaan myös vaadittujen rajapintojen toteutuksen avulla. Perustele vastauksesi kummassakin tapauksessa. 3.5 Tarkastellaan tekstissä kuvattua esimerkkiä särkyvän yliluokan ongelmasta. Kumpi mielestäsi menetteli virheellisesti, kirjaston ylläpitäjä vai sovelluskehittäjä? Perustele. Millaisia menettelytapoja suosittelisit, jotta vastaavalta tilanteelta vältyttäisiin projektissasi? 3.6 Seuraavassa taulukossa on sarakkeissa komponentin lisäksi muita ohjelmistoyksiköitä, ja riveillä erilaisia ominaisuuksia. Vastaa kyllä tai ei kuhunkin taulukon kohtaan riippuen siitä, onko kyseisellä ohjelmistoyksiköllä rivillä mainittu ominaisuus. KYLLÄ/EI/? Komponentti Luokka Olio DLL Hyvin määritelty rajapinta Käyttöönotto binäärisenä Ajoaikainen korvaaminen toisella Käännösaikainen korvaaminen Sisäkkäiset yksiköt mahdollisia Syntaktinen rakenne ohj.kielissä 3.7 Selvitä, mitä yleistä tukea komponenteille J2EE-teknologia tarjoaa. Käsittele ainakin seuraavat aiheet: komponenttien pysyvyys, turvallisuus, räätälöinti, versiointi, refleksiivisyys (itsekuvaavuus), komponenttien kommunikointitavat. 3.8 Selvitä, mitä yleistä tukea komponenteille.net-teknologia tarjoaa. Käsittele ainakin seuraavat aiheet: komponenttien pysyvyys, turvallisuus, räätälöinti, versiointi, refleksiivisyys (itsekuvaavuus), komponenttien kommunikointitavat.
42 74 Ohjelmistoarkkitehtuurit
43 4 Komponenttien vuorovaikutustekniikat Yleisperiaate ohjelmistoarkkitehtuurien kehittämisessä on pyrkiä sisäisesti yhtenäisiin komponentteihin, joiden välillä on heikot riippuvuudet (Parnas 1972, Dijkstra 1976). Tämä ei kuitenkaan yleensä seuraa suoraan ratkaistavasta ongelmasta, vaan vaatii erityistä huomiota suunnittelussa. Tarkastelemme tässä luvussa erilaisia perustekniikoita, joiden avulla komponenttien väliset suhteet voidaan järjestää sillä tavoin, että niiden väliset riippuvuudet vähenevät. 4.1 Komponentit ja niiden vuorovaikutus Ohjelmistoarkkitehtuurisuunnittelu on pitkälle riippuvuuksien määrittelemistä, ja sen ymmärtäminen on riippuvuuksien ymmärtämistä. Tämä johtuu siitä, että yleensä tärkeimpiä ohjelmistolle asetettavia vaatimuksia ovat mm. uudelleenkäytettävyys, ylläpidettävyys ja kehitystyön hajautettavuus, joihin vaikuttavat ratkaisevasti ohjelmiston eri osien väliset riippuvuudet ja ohjelmiston osien riippuvuudet ulkoisista tekijöistä. Keskeinen pyrkimys arkkitehtuurissa on siksi vähentää ja selkeyttää riippuvuuksia. Näin yllä mainitut ohjelmiston laatuun pikemmin kuin toimintaan liittyvät vaatimukset saadaan täytettyä. Käytännössä tämä usein tarkoittaa erilaisten epäsuoruuksien, kuten rajapintojen ja välittäjäluokkien, lisäämistä. Pohjimmiltaan arkkitehtuurisuunnittelun voidaankin ajatella olevan haluttujen riippuvuuksien sallimista ja haitallisten riippuvuuksien poistoa ohjelmiston rakenteesta esimerkiksi tehokkuuden, muunneltavuuden tai muistin kulututuksen optimoimiseksi. Ohjelmiston muunneltavuus liittyy läheisesti sen osien välisiin riippuvuuksiin: jos komponentti A ei riipu komponentti B:stä, B:tä
44 76 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 77 voidaan muuttaa ilman, että se vaikuttaa A:han. Erityisesti tuoterunkoarkkitehtuurien suunnittelussa muunneltavuus on keskeisessä asemassa jo järjestelmän luonteen vuoksi: järjestelmän on tuettava tiettyä varianssia ja tiettyä invarianssia, jotka määrittelevät kyseisen tuoteperheen. Palaamme tuoterunkoarkkitehtuureihin myöhemmin tässä kirjassa. Ohjelmistokehityksessä muunneltavuutta edellyttävät myös tuntemattomat vaatimukset: usein jotkin vaatimukset tai ulkopuoliselta ympäristöltä edellytettävät ominaisuudet ovat ohjelmistoa suunniteltaessa tuntemattomia tai epävarmoja, jolloin ohjelmistoprojektissa on varauduttava tältä osin muunneltavuuteen. Tarkastelemme seuraavassa perustekniikoita, joiden avulla komponenttien välisiä riippuvuuksia voidaan heikentää vaikuttamatta olennaisesti haluttuun toiminnallisuuteen. Voidaan ajatella, että nämä tekniikat jättävät komponenttien välille vain välttämättömät loogiset riippuvuudet, poistaen toteutustavasta seuraavat tarpeettomat riippuvuudet. 4.2 Rajapinnat Tarkastelimme jo edellisessä luvussa rajapinnan käsitettä ja sen käyttöä komponenttien välisen vuorovaikutuksen abstrahointiin: palvelun pyytäjä näkee palvelun rajapinnan välityksellä, eikä tunne suoraan palvelun toteuttajaa. Toisin sanoen rajapinnan avulla komponentti voidaan tehdä riippumattomaksi sen tarvitsemien palvelujen toteutuksesta. Kuvassa 4.1 on havainnollistettu tätä yksinkertaisinta ja yleisintä tapaa vähentää komponenttien välistä riippuvuutta. Jos järjestelmän komponentit ja niiden karkeat vastuut ovat tiedossa, voidaan nämä vastuut täsmentää rajapintojen muodossa. Tällöin saadaan siis kullekin komponentille sen tarjoaman rajapinnan määrittely. Jos komponentti tarvitsee toisen komponentin palveluja, tulee jälkimmäisen rajapinnasta edellisen vaadittu rajapinta. Periaatteessa näin voidaan komponenteille muodostaa tarjotut ja vaaditut rajapinnat. Edellä kuvattu, yksinomaan komponenttien tarjoamiin palveluihin perustuva rajapintojen määrittäminen kuitenkin harvoin johtaa hyvään lopputulokseen. Sen seurauksena komponenttien ja niiden toteuttamien rajapintojen välille muodostuu jäykkä yksi-yhteen-suhde. Tarkoituksenmukaisempi ja hienojakoisempi rajapintojen jouk- Kuva 4.1: Rajapinnan käyttö komponenttien välisen riippuvuuden vähentämiseen ko saadaan lähtemällä tarkastelemaan yksityiskohtaisemmin sitä, miten komponenttien toiminnallisuus voidaan toteuttaa eri rooleissa olevien palvelujen tarjoajien avulla. Tarkastelemme tällaista vaadittuihin rajapintoihin perustuvaa rajapintojen määrittämistä lähemmin seuraavassa. 4.3 Roolirajapinnat Palvelut Asiakaskomponentti Palvelijakomponentti Asiakaskomponentti Palvelijakomponentti Komponentti tarjoaa palveluja toiselle komponentille aina jossakin tietyssä roolissa. Tämä rooli ei välttämättä kata kaikkia niitä palveluja, jotka komponentti toteuttaa. Periaatteena on antaa erillinen rajapinta jokaiselle selkeästi identifioidulle roolille, jossa komponentti palvelee toista komponenttia. Tällöin rajapintaa kutsutaan roolirajapinnaksi. Koska komponentti on usein erilaisissa palvelijarooleissa muihin komponentteihin nähden, komponentti toteuttaa näin useita roolirajapintoja. Toisaalta tietyn roolin mukaisen palvelun voi tarjota useampi kuin yksi komponentti. Roolirajapintojen käyttöä on havainnollistettu kuvassa 4.2. Tarkastellaan esimerkkinä graafista käyttöliittymää, jossa painonapin klikkaaminen aiheuttaa tietyn toiminnon. Oletetaan, että klikkaamisen jälkeen painonapin ulkoasu muuttuu. Painonapin to-
45 78 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 79 Palvelut Rooli1 Rooli2 Kuva 4.2: Roolirajapintojen käyttö Palvelijakomponentti Asiakaskomponentti2 Asiakaskomponentti1 Asiakaskomponentti2 Palvelijakomponentti Asiakaskomponentti1 komponentti A (tai jokin sen palvelu) käyttää komponentin B palveluista tiettyä osajoukkoa, on tämä osajoukko mahdollinen roolirajapinta. Näin saatuja potentiaalisia roolirajapintoja voidaan edelleen yhdistellä ja muokata tarpeen mukaan, ja niille voidaan antaa kyseistä roolia kuvaava nimi. On syytä korostaa, että roolirajapinnasta ei kuitenkaan saisi tulla pelkästään kahden komponentin välisen palvelusuhteen abstrahointi, vaan roolirajapinnan tulisi ainakin periaatteessa olla luonteeltaan sellainen, että sitä voivat käyttää ja sen voivat toteuttaa monet komponentit. Rooliperustainen rajapintojen määritys johtaa hienojakoiseen rajapintajoukkoon, jossa komponenttien ja rajapintojen välillä on monta-moneen-suhde. Hienojakoisuudesta seuraa, että järjestelmästä tulee helpommin ylläpidettävä: esimerkiksi muutos tietyn palvelun kutsumuodossa heijastuu vain niissä komponenteissa, jotka todella käyttävät palvelua eivät kaikissa komponenteissa, jotka käyttävät ylipäänsä palvelun tarjoavaa komponenttia. Arkkitehtuurista tulee yleisesti täsmällisempi, kun roolirajapinnat kuvaavat tarkasti komponenttien riippuvuuden tietyistä abstrakteista palveluista. Toisaalta roolirajapintojen käyttäminen lisää rajapintojen lukumäärää ja siten järjestelmän arkkitehtuurin monimutkaisuutta. teuttaa komponentti, joka tarjoaa palvelut mm. tietyn toiminnallisuuden rekisteröimiseen painonappiin (esimerkiksi painonappitapahtumien kuuntelija) ja painonapin ulkoasun päivitykseen. Edellistä palvelua tarvitaan sovelluksen alustuksessa, jälkimmäistä taas kutsutaan aina sovelluksen näytön päivityksen yhteydessä. Palvelut ovat luonteeltaan hyvin erilaisia, ja niitä tarjotaan tyypillisesti eri asiakkaille. Näin komponentti toimii erilaisissa rooleissa erilaisiin asiakkaisiin nähden, ja olisi arkkitehtuurin kannalta ilmeisen edullista määritellä nämä roolit erillisinä rajapintoina. Jos vaikkapa näytön päivityksestä huolehtivalle asiakkaalle näytetään ainoastaan tähän liittyviä palveluja sisältävä (rooli)rajapinta, se ei vahingossakaan pysty käyttämään muita painonappikomponentin palveluja, minkä se pystyisi tekemään, jos sen käytössä olisi kaikki komponentin palvelut tarjoava rajapinta. Roolirajapinnat voidaan löytää esimerkiksi tarkastelemalla käyttötapauksista johdettuja sekvenssikaavioita, jotka kuvaavat komponenttien vuorovaikutusta käyttötapausten toteutuksessa. Jos 4.4 Usean komponentin välinen vuorovaikutus Eräs olioparadigman keskeinen ajatus on ohjelman toiminnan kuvaaminen joukolla olioita (tai komponentteja), jotka pyytävät ja tarjoavat toisilleen palveluja. Arkkitehtuuritasolla tämä ajatus on siinä mielessä ongelmallinen, että se sitoo joukon komponentteja tiettyyn mutkikkaaseen vuorovaikutusympäristöön, ja tekee ne siitä riippuvaiseksi. Tämä puolestaan vaikeuttaa komponenttien uudelleenkäyttöä, järjestelmän ylläpitoa ja ymmärtämistä. Vuorovaikutukseen liittyvän toiminnallisuuden muuntelu on myös vaikeaa, koska se usein edellyttää useiden komponenttien muuttamista. Tarkastelemme tässä tekniikkaa, jolla tällaista riippuvuutta voidaan vähentää. Tämäntyyppinen arkkitehtuuriongelma voidaan ratkaista käyttämällä välittäjää (mediator), jonka kanssa kukin osallistuja kommunikoi (kuva 4.3). Välittäjä toteuttaa osallistujien välisen vuorovaikutuksen, jolloin kukin osallistuja voidaan toteuttaa riippumattomasti muista osallistujista, ja ne kaikki riippuvat ainoastaan välittäjästä. Monta-moneen riippuvuudet muuttuvat näin yksi-moneen-riippu-
46 80 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 81 Coordinator widgetchange(widget) createwidgets() show() Dialog Coordinator ListBox TextField ListBoxI getselected(): str TextFieldI settext(str) Widget changed() Button ButtonI enable() Kuva 4.3: Välittäjän käyttö komponenttien välisessä vuorovaikutuksessa Kuva 4.4: Välittäjän käyttö dialogi-ikkunan komponenttien välisessä vuorovaikutuksessa vuuksiksi. Kun vuorovaikutusta halutaan modifioida, riittää muuttaa välittäjää. Osallistujat ovat käytettävissä myös muissa yhteyksissä suhteellisen helposti, koska se edellyttää vain, että ne pystyvät kommunikoimaan toisen välittäjän kanssa. Tätä luonnollisesti helpottaa, jos välittäjät toteuttavat yhteisen rajapinnan. Tyypillinen esimerkki välittäjän käytöstä on dialogi-ikkunan toteutus. Dialogi koostuu erilaisista käyttöliittymäelementeistä ("widget"), jotka toteutetaan komponenteilla. Dialogin käytön aikana nämä komponentit ovat vuorovaikutuksessa keskenään. Oletetaan esimerkiksi, että dialogissa näytetään alkiolista, tekstikenttä ja painonappi: kun käyttäjä valitsee alkion listasta, valittu alkio näytetään tekstikentässä ja painonappi aktivoidaan. Periaatteessa listakomponentti voisi suoraan kommunikoida tekstikenttä- ja painonappikomponenttien kanssa, mutta silloin kaikki komponentit riippuisivat juuri tästä dialogista ja sen toiminnallisuudesta. Tässä esimerkissä komponenttien keskinäiset riippuvuudet voidaan purkaa antamalla erillinen välittäjä, joka toteuttaa komponenttien välisen vuorovaikutuksen: kukin komponentti kommunikoi vain välittäjän kanssa. Välittäjän voi ajatella tässä olevan komponentti, joka edustaa koko dialogia. Samoja käyttöliittymäkomponentteja voidaan nyt käyttää useissa dialogeissa. Kuvassa 4.4 on ListBox DialogCoordinator TextField Button widgetchange getselected annettu tätä vastaava komponenttikaavio ja kuvassa 4.5 vuorovaikutuksen kuvaava sekvenssikaavio. 4.5 Kutsun siirtäminen settext enable Kuva 4.5: Komponenttien vuorovaikutus Rajapintaa voimakkaampi perustekniikka erottaa palvelun tarvitseva komponentti ja palvelun toteuttava komponentti toisistaan on kutsun siirtäminen (forwarding). Siirtäminen tarkoittaa, että palvelupyynnön saava komponentti ei itse suorita palvelua, vaan välittää kutsun
47 82 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 83 Account Manager Chargable discount(int): int Customer CustomerSupport discount(int): int Kuva 4.6: Siirtämisen käyttö palvelun muunteluun KeyCustomer Support jollekin toiselle komponentille, joka varsinaisesti suorittaa palvelun. Palvelun alkuperäinen pyytäjä ei ole tietoinen välissä olevasta komponentista. Siirtämisen yhteydessä palvelun välittämiseen liittyy siis aina jotain toiminnallisuutta. Siirtämistä käytetään moneen tarkoitukseen, ja sitä sovelletaan yleisesti suunnittelumalleissa. Palvelun rajapinta voi joko säilyä (esim. edustaja) tai muuttua (esim. sovitin) siirtämisen yhteydessä. Kummassakin tapauksessa palvelun toteutus voidaan vaihtaa ajoaikana ilman, että palvelun pyytäjän tarvitsee olla siitä tietoinen. Siirtämisen lisäksi alkuperäisen palvelukutsun saava komponentti voi haluttaessa suorittaa muita toimenpiteitä, jotka eivät ole kiinnostavia palvelun pyytäjän kannalta. Näiden toimenpiteiden laatu riippuu siitä, mihin siirtämisellä loppujen lopuksi pyritään. Palaamme myöhemmin siirtämisen sovelluksiin tietyissä vuorovaikutustekniikoissa. Joskus siirtämistä käytetään palvelun toteutuksen dynaamiseen vaihtamiseen. Kuvan 4.6 esimerkissä Customer-komponentti tarjoaa rajapinnan Chargable, jossa on määritelty asiakasryhmäkohtaisesti lasketun alennuksen tietystä laskusummasta palauttava palvelu discount(int): int. Asiakas voi olla joko tavallinen tai avainasiakas, ja vaihtaa statusta olemassaolonsa aikana. Tämä voidaan toteuttaa siten, että Customer ei itse toteuta discount-palvelua, vaan siirtää palvelupyynnön erilliselle, siihen dynaamisesti liitetylle komponentille, joka laskee alennuksen. Jälkimmäinen komponentti on eri tavallisille ja avainasiakkaille. Tämä komponentti voi toteuttaa myös muita asiakasryhmästä riippuvia palveluja. Kun asiakas vaihtaa statustaan, tämä komponentti vaihdetaan toiseksi asiakasta edustavassa Customer-komponentissa ilman, että komponentin käyttäjän tarvitsee olla siitä tietoinen. Delegoinnilla tarkoitetaan kutsun siirtämisen käyttöä periytymismekanismin toteutukseen. Jos syystä tai toisesta järjestelmässä halutaan dynaamisesti muuntuva luokkahierakia, tämä voidaan to- teuttaa delegoinnilla. Oletetaan esimerkiksi, että käsitettä Car halutaan tarkastella samassa sovelluksessa joskus ajoneuvona (esim. rekisteritiedot), joskus taas tuotteena (esim. erilaiset tuotekonfiguraatiotiedot). Näin luokan Car tulisi joskus periä Vehicle, joskus taas Product. Esimerkiksi operaation "printdescription" toteutus riippuu siitä, kumpi yliluokka on peritty. Delegoinnin yhteydessä perinteinen kerroksittainen olio esitetään useana erillisenä mutta yhteen linkitettynä oliona, olio kutakin luokkakerrosta kohden. Kun Car-olio saa kutsun "printdescription", se tutkii ensin, voiko se itse suorittaa palvelun. Jos se ei voi, se siirtää kutsun "yliluokkaoliolle", tässä tapauksessa esimerkiksi luokan Product ilmentymälle. Tämä toimii samalla tavalla, kunnes lopulta hierarkiassa löytyy olio, joka pystyy antamaan pyydetyn palvelun. Tämä vastaa sitä, että palvelu suoritetaan olioparadigmassa aina alimman sen tarjoavan luokkatason mukaisena. Nk. prototyyppikielissä on perinteinen staattinen periytyminen korvattu jo kielen tasolla delegointimekanismilla. 4.6 Edustajakomponenttien käyttö Suoraviivaisimmin siirtämistä sovelletaan nk. edustajan (proxy) yhteydessä. Eri syistä joudutaan joskus suora pääsy tietyn komponentin palveluihin estämään, ja tarjoamaan palvelu epäsuorasti toisen komponentin, edustajan, kautta. Tämä voi johtua eri syistä: komponentti on eri koneessa kuin sen palveluiden pyytäjä komponentin luonti on raskas toimenpide, jota halutaan lykätä niin pitkälle kuin mahdollista komponentin käyttöä halutaan jollakin tavalla valvoa. Komponentti irrotetaan käyttäjistään edustajan avulla, ja vuorovaikutus komponentin kanssa tapahtuu edustajan välityksellä. Palveluiden käyttäjä ei ole tietoinen edustajan olemassaolosta, vaan näkee edustajan rajapinnan kautta, joka on yhteinen edustajalle ja varsinaiselle toteutuskomponentille. Näin siis palveluiden käyttäjä tulee riippumattomaksi palvelujen varsinaisesta tarjoajasta, mutta ei sen toteuttamasta rajapinnasta. Edustajan käyttöä kuvaava komponenttikaavio on annettu kuvassa 4.7: Provider on varsinaisen palvelun tarjoava komponentti, jota käytetään luokan Proxy kautta.
48 84 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 85 Client... actual.request() Services request() Proxy request() actual Provider request() Kuva 4.7: Edustajan käyttö komponenttien välillä tin palveluja oikeasti tarvitaan, edustaja joko luo tai etsii komponentti-ilmentymän ja siirtää palvelupyynnön sille. Oletetaan esimerkiksi, että kännykässä on työkalu, jolla voidaan sopia yhteinen tapaamisaika. Tätä varten työkalu tarvitsee kontaktihenkilöiden kalenterikomponenttien tarjoamia palveluja, joiden avulla vapaita aikoja haetaan. Haluttujen henkilöiden kalenterikomponentteja tarvitaan kuitenkin vain työkalun käytön ajan. Ratkaisuna voisi olla edustajan käyttö: kullekin kontaktihenkilölle on olemassa kalenteriedustaja, joka hakee tietyn henkilön kalenterikomponentin tämän puhelimelta, kun komponentin palveluja ensimmäisen kerran tarvitaan, ja ohjaa tämän jälkeen palvelupyynnöt tälle komponentille. Kun tapaaminen on sovittu, muiden kalenterikomponentit poistetaan puhelimelta. Edustaja siirtää saamansa palvelupyynnöt edustamalleen komponentille, mutta tekee siinä yhteydessä jotain edustajan tarkoitusperään liittyvää toimintaa (esimerkiksi muokkaa kutsun verkossa siirrettävään muotoon, luo edustamansa komponentin ilmentymän, suorittaa komponentin valvontaan liittyvää toimintaa tms.). Edustajaa käytetään tyypillisesti hajautetuissa komponenttijärjestelmissä (EJB, CORBA), jolloin asiakaskoneella toimivat sovellukset pääsevät käsiksi palvelimella olevaan komponenttiin asiakaskoneella olevan edustajan kautta. Edustaja muokkaa palvelupyynnön verkossa välitettävään muotoon (marshalling), ja lähettää sen palvelimella olevalle edustajalle, joka puolestaan purkaa viestin ja pyytää palvelimella olevaa komponenttia suorittamaan kyseisen palvelun. Vastaavasti edustajat siirtävät palvelun tulokset takaisin palvelun alkuperäiselle pyytäjälle. Nk. älykkäät osoittimet (smart pointer) ovat olio- tai komponenttiviitteitä, joiden käyttö aiheuttaa automaattisesti tiettyä oheistoimintaa. Tämä toiminta voi olla esimerkiksi kyseisen komponentin käytön tarkkailua ja kirjaamista tai käyttöoikeuksien valvontaa. Älykkäiden osoittimien toteutus voi perustua edustajaan. Edustajaa voidaan käyttää myös tilanteessa, jossa jokin komponentti-ilmentymä on hyvin suuri, ja sen luominen kuluttaa paljon resursseja, mutta siihen halutaan viitata, vaikka ei olla varmoja, tarvitaanko sen palveluja. Tällöin voidaan komponentille luoda heti edustaja ja käyttää viitettä edustajaan kuten viitettä varsinaiseen komponenttiin (jota ei siis vielä ole olemassa). Vasta kun komponen- 4.7 Takaisinkutsut Takaisinkutsu (callback) on yleinen mekanismi, jonka avulla palvelun kutsuja voi saada kontrollin palvelun aikana. Tämä antaa palvelun kutsujalle mahdollisuuden suorittaa omia toimenpiteitään tietyssä vaiheessa palvelua. Takaisinkutsua käytetään tyypillisesti tilanteessa, jossa halutaan yleiskäyttöinen ohjelmayksikkö, joka antaa kontrollin (takaisinkutsuen) palvelun pyytäjälle. Takaisinkutsua on havainnollistettu kuvassa 4.8. Takaisinkutsun avulla voidaan tehdä yleiskäyttöisiä komponentteja, jotka pystyvät kutsumaan käyttäjiensä operaatioita tuntematta käyttäjiään etukäteen (ja siis tulematta niistä riippuvaisiksi). Ilman takaisinkutsua ei olisi mahdollista tehdä järjestelmiä, joissa yleiskäyttöinen ohjelmisto ja sitä käyttävä sovelluskohtainen ohjelmisto kutsuvat toisiaan molempiin suuntiin. Olioperustaisissa järjestelmissä takaisinkutsun toteutus perustuu tavallisesti dynaamiseen sidontaan, missä tietyn palvelun toteuttaja määräytyy vasta ajoaikana. Toteuttaja voi olla joko aliluokka, joka perii palvelun pyytäjän tunteman yliluokan, tai komponentti, joka toteuttaa palvelun pyytäjän tunteman rajapinnan. Tarkastelemme tässä jälkimmäistä tapausta. Rajapintoja hyödyntävässä takaisinkutsussa kirjasto tarjoaa rajapinnan, jonka sovelluskohtainen komponentti toteuttaa. Sovellus luo komponentin ilmentymän ja rekisteröi sen kirjastolle. Tämän jälkeen sovellus kutsuu kirjaston palvelua, ja tietyssä vaiheessa kirjasto
49 86 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 87 palvelun kutsu kirjasto sovellus paluu takaisinkutsu takaisinkutsu sekvenssikaaviona : Library : Application service callback Kuva 4.8: Takaisinkutsu graafisesti ja sekvenssikaavion avulla esitettynä kutsuu sovelluskohtaisen komponentin palvelua rajapinnan kautta. Näin kontrolli siirtyy kirjastopalvelun aikana sovelluskohtaiselle komponentille Tarkastellaan seuraavaa esimerkkiä. Oletetaan, että Enginekomponentista halutaan yleistää yleiskäyttöinen kirjastokomponentti. Oletetaan lisäksi, että moottorin öljynpaineen laskiessa halutaan antaa moottorin käyttäjälle mahdollisuus omiin toimenpiteisiin. Engine-komponentin täytyy siis tässä tilanteessa suorittaa takaisinkutsu käyttäjälleen. Esimerkin edellyttämä arkkitehtuuri voidaan toteuttaa kuvan 4.9 mukaisesti siten, että kirjasto tarjoaa paitsi Engine-komponentin myös rajapinnan EngineUser, joka kuvaa ne vaatimukset, jotka komponentti Engine olettaa käyttäjältään. Tässä tapauksessa vaatimus on, että käyttäjän on toteutettava warn-palvelu, jota kutsutaan, kun öljynpaine laskee. Komponentti Car toteuttaa tämän rajapinnan. Car luo Engine-komponentin ilmentymän käyttöönsä asettaen siihen viitteen itseensä (user). Engine-komponentti käyttää tätä viitettä kutsuessaan warn-operaatiota, jonka Car toteuttaa. run() Engine if (oilpressure<limit) { user.warn("...");... PowerSource start() stop() setuser(engineuser) myeng = new Engine(); myeng.setuser(this); myeng.start(); EngineUser warn(str) 4.8 Tapahtumiin perustuva vuorovaikutus Seuraavaksi tarkastelemme palvelun pyytäjän ja tarjoajan välisen suhteen heikentämistä käyttämällä tapahtuman käsitettä. Määrittelemme tapahtuman (event) tilanteeksi, joka voi sattua ohjelman suorituksen aikana, jolla on tunnettu esitystapa ohjelman ajoaikana ja joka edellyttää reagointia joiltakin järjestelmän osilta. Palvelun kutsua voidaan abstrahoida ajattelemalla, että palvelun pyyntötilanne on tapahtuma ja palvelun tarjoaminen on tapahtumaan reagoimista. Kutsumme tapahtuman synnyttävää komponenttia tapahtuman lähteeksi ja siihen reagoivaa komponenttia tapahtuman tarkkailijaksi. Periaatteessa lähteiden ja tarkkailijoiden ei tarvitse tuntea toisiaan: ainoa asia, joka loogisesti liittää lähteen tarkkailijoihin on tapahtuman identiteetti. Käytännössä tapahtumamekanismin toteutustapa kuitenkin yleensä johtaa ainakin jonkinlaisiin riippuvuuksiin komponenttien välillä. Toteutustavassa on otettava kantaa mm. seuraaviin kysymyksiin: kuinka tapahtuma esitetään, kuinka tapahtumat välitetään tark- Car setup() warn(str)... Alusta Sovellus log.output("oil pressure low"); myeng.stop(); Kuva 4.9: Takaisinkutsun käyttö kirjastokomponentin yhteydessä
50 88 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 89 Observer update(event) ActionListener actionperformed(actionevent) Source Component Source register(observer) unregister(observer) Observer Component Kuva 4.10: Tarkkailija-suunnittelumallin mukainen tapahtumankäsittely kailijoille, kuinka tiedetään tietyn lähteen ja tapahtuman tarkkailijat, onko tapahtumien käsittely asynkronista vai synkronista? Yleinen tapa toteuttaa tapahtumamekanismi perinteisessä oliokielessä on soveltaa takaisinkutsun ideaa: tarkkailijat rekisteröityvät lähteelle, joka kutsuu niiden tapahtumankäsittelyoperaatiota tapahtuman sattuessa. Tämä operaatio kuuluu takaisinkutsurajapintaan, jonka tarkkailijat toteuttavat. Tiedot tapahtumasta annetaan tämän operaation parametrina, esimerkiksi tapahtumaoliona. Tapahtumien käsittely on synkronista, jos käsittelyoperaatio on normaali tarkkailijakomponentin palvelu. Tällainen tapahtumankäsittelyarkkitehtuuri on esitetty kuvassa Kuvassa Source-rajapinta tarjoaa palvelut tarkkailijoiden rekisteröitymiselle ja rekisteröitymisen peruuttamiselle. Rajapinnan Observer palvelu update on tapahtumien käsittelyoperaatio, jonka tarkkailijat toteuttavat. Kuvan ratkaisu on esitetty myös suunnittelumallina nimeltä Tarkkailija (Observer). Huomaa, että Tarkkailija-malli korvaa huomattavasti tehottomamman tarkkailijoiden suorittamaan jatkuvaan pollaukseen perustuvan ratkaisun. Tässä mielessä Tarkkailija-mallin käytöllä voi olla positiivinen vaikutus järjestelmän suorituskykyyn. Tarkkailija-malliin perustuvaa tapahtumien käsittelyä käytetään tyypillisesti vuorovaikutteisten sovellusten yhteydessä välittämään kontrollia käyttöliittymäosan ja sovelluslogiikan välillä. Tapahtumat voivat periaatteessa kulkea kumpaankin suuntaan: graafiset elementit (esim. painonapit) voivat ilmoittaa käyttöliittymätapahtumista sovelluslogiikalle oikeiden toimenpiteiden aktivoimiseksi; sovelluslogiikka voi puolestaan ilmoittaa tapahtumilla sovelluksen JMenuItem AppComp AbstractButton addactionlistener(actionlistener) Kuva 4.11: Esimerkki JavaBeans-tapahtumankäsittelystä tilan muutoksista käyttöliittymäkomponenteille, joiden tulisi päivittää näyttö tilan mukaiseksi. Jälkimmäistä tapaa tullaan tarkastelemaan myöhemmin MVC-arkkitehtuurityylin yhteydessä. JavaBeans-arkkitehtuuri ja Swing-kirjasto soveltaa Tarkkailija-mallia yleisesti erilaisille käyttöliittymälähtöisille tapahtumille. JavaBeans-terminologiassa tarkkailijoita kutsutaan tapahtumien kuuntelijoiksi (listener). Esimerkiksi valikossa oleva komento on toteutettu komponentilla JMenuItem, joka toteuttaa rajapinnan (tai oikeastaan abstraktin luokan) AbstractButton. Tähän kuuluu mm. palvelu addactionlistener, jonka avulla ActionListener-rajapinnan toteuttava kuuntelija rekisteröidään valikkokomentokomponentille. ActionListener-rajapinta sisältää tapahtuman käsittelyoperaation actionperformed, jonka parametrina annetaan tapahtumaolio; tähän olioon on talletettu tarkemmat tiedot tapahtumasta. Kuvassa 4.11 on esitetty yksinkertaistettu komponenttikaavio, jossa AppComp edustaa komennon sovelluskohtaisen toiminnon toteuttavaa komponenttia. 4.9 Rajapintariippuvuuksien poistaminen Olemme edellä tarkastelleet komponenttien välisten riippuvuuksien vähentämistä rajapintojen avulla. Joskus tilanne on kuitenkin sellainen, että komponentit eivät syystä tai toisesta voi tuntea edes toistensa toteuttamia rajapintoja. Tyypillisesti tällainen tilanne syntyy, kun halutaan koostaa uusi järjestelmä olemassa olevista komponenteista, jotka on tehty toisistaan riippumatta. Jos komponentteja ei ole tehty
51 90 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 91 AbstractServices request() rekisteröinti Client tapahtuma palvelukutsu ConcreteServices concrequest()... adaptee.concrequest() Adapter request() Kuva 4.12: Sovittimen käyttö adaptee Provider concrequest() Sovitin Komponentti Komponentti Kuva 4.13: Sovittimien käyttö komponenttien integroinnissa vasten jotain yleistä rajapintastandardia, nämä komponentit toteuttavat todennäköisesti toisilleen tuntemattomia rajapintoja. Oletetaan esimerkiksi, että järjestelmä käyttää yleistä tietorakennekirjastoa tiettyjen tietorakenteiden toteutukseen. Jos kirjasto joudutaan syystä tai toisesta vaihtamaan, uusi kirjasto tarjoaa samoille tietorakenteille todennäköisesti hieman erilaisia rajapintoja. Kun järjestelmän kehittäjä haluaa varautua tietorakennekirjaston vaihtamiseen (esimerkiksi kaupallisista syistä, tai koska vanhaa kirjastoa ei enää ylläpidetä), arkkitehtuuri olisi suunniteltava sellaiseksi, että järjestelmä ei riipu tietyn kirjaston tarjoamista rajapinnoista. Perusratkaisu rajapintariippumattomuuden saavuttamiseksi on käyttää sovitinta (adapter): sovittimen avulla komponentti voi kutsua toisen palveluja tuntematta palvelujen toteuttajan tarjoamaa rajapintaa. Sovitin muuntaa tietyn rajapinnan mukaiset palvelukutsut toisen rajapinnan mukaisiksi. Sovitin mahdollistaa näin kahden toisistaan täysin riippumattoman komponentin välisen vuorovaikutuksen. Sovittimen idea on esitetty kuvassa Client edustaa tässä yhteydessä komponenttia tai osajärjestelmää, joka halutaan tehdä riippumattomaksi yksittäisen palvelutarjoajan antamasta rajapinnasta. Sovitin (Adapter) toteuttaa AbstractServices-rajapinnan, jota vasten Client-komponentti on suunniteltu; palvelun (request) toteutuksessa tehdään muutetun rajapinnan vaatimia lisätoimenpiteitä (esim. uusien parametrien laskennat) ja kutsutaan sen jälkeen palvelun varsinaista toteuttajaa (Provider) tämän tarjoaman rajapinnan (ConcreteServices) kautta. Huomaa, että tämä ratkaisu perustuu palvelun siirtämiseen. Yleiskäyttöisen komponentin tulisi usein pystyä kommukoimaan erilaisten ympäristöjen (mahdollisesti toisten komponenttien) kanssa, joita komponentti ei edeltä käsin tunne. Jos esimerkiksi jonkin olemassaolevan järjestelmän tulisi pystyä kutsumaan ennakolta tuntemattoman komponentin palveluja, voidaan käyttää sovitinta mukauttamaan järjestelmä komponentin rajapintaan. Samoin jos kahden toisiaan tuntemattomien komponenttien tulisi pystyä kutsumaan toistensa palveluja, voidaan käyttää sovitinta, joka rekisteröityy toiselle komponentille tarkkailemaan tapahtumia, ja tapahtuman sattuessa kutsuu tiettyä toisen komponentin palvelua. Tässä tapauksessa siis sovittimet toteuttavat tarkkailijarajapinnan, eivät varsinaiset tapahtumaan reagoivat komponentit. Esimerkiksi Sun Microsystemsin BeanBox-työkalussa (ja muissa vastaavissa visuaalisissa komponenttityökaluissa) on juuri tällainen tilanne: ympäristön (BeanBox) tulisi pystyä kutsumaan tiettyjä komponenttien palveluja (esim. muuttamaan niiden ominaisuuksia), reagoimaan tiettyihin tapahtumiin (esim. ominaisuuksien muutoksiin) sekä yhdistelemään komponentteja niin, että ne reagoivat toistensa tapahtumiin (kuva 4.13). Tässä tapauksessa ongelmana on se, että sovittimet pitäisi tuottaa automaattisesti, koska ei haluta että käyttäjä joutuisi niitä tekemään ideana on, että komponenttijärjestelmä syntyy visuaalisen editoinnin tuloksena automaattisesti. Niinpä järjestelmä joutuu generoimaan (ja kääntämään) sovittimet lennossa. Jotta tämä voitaisiin tehdä olemassa oleville komponentti-ilmentymille, täytyy järjestel-
52 92 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 93 män pystyä kysymään komponenteilta niiden ominaisuuksiin ja tapahtumiin liittyviä asioita. Tämä puolestaan edellyttää, että a) järjestelmä ylipäänsä pystyy kysymään komponentti-ilmentymiltä niiden rakenteesta, ja b) on olemassa sopimuksia, joiden pohjalta järjestelmä erottaa ne osat komponentista, jotka liittyvät esimerkiksi tapahtumiin ja ominaisuuksiin. Nämä vaatimukset täyttyvät JavaBeansarkkitehtuurissa a) Javan refleksiivisyyspiirteiden avulla ja b) Java- Beans-sopimusten (nk. esitysmallien) avulla. Joskus on tarpeen päästä kokonaan eroon staattisista palvelurajapinnoista komponenttien välisessä vuorovaikutuksessa. Tämä voi johtua esimerkiksi siitä, että järjestelmään halutaan voida lisätä uusia palveluja tuottavia komponentteja ajoaikana, ilman uudelleenkäännöksiä, eikä näiden palvelujen kutsumuotoja voi tietää etukäteen. Toinen syy voi olla esimerkiksi se, että järjestelmää ei haluta sitoa kiinteisiin rajapintoihin tilanteessa, jossa järjestelmän vaatimuksiin ja toimintaympäristöön sisältyy runsaasti epävarmuustekijöitä. Tällaisissa tapauksissa päädytään usein viestipohjaiseen vuorovaikutukseen, jossa staattiset rajapinnat sisältävät ainoastaan viestien välittämiseen tarvittavan operaation, ja varsinainen palvelu on identifioitu parametreineen viestin sisällä. Näin staattinen kutsumuoto on muunnettu dynaamiseksi viestiformaatiksi, jonka vastaanottava komponentti tulkitsee omalla tavallaan. Tavallisesti komponentit eivät kommunikoi suoraan keskenään, vaan viestien toimittamisesta oikeille vastaanottajille huolehtii erillinen "postitoimisto", viestinvälittäjä. Viestiformaatti on nykyään usein XML-pohjainen, jolloin tarvittavat viestien rakennus- ja purkutyökalut saadaan valmiina. Tarkastelemme myöhemmin arkkitehtuurityylien yhteydessä lähemmin tähän ajatukseen pohjautuvia järjestelmiä Luontiriippuvuus ja sen purkaminen Olemme tarkastelleet tässä lähinnä palvelun pyytäjän ja tarjoajan välisestä käyttösuhteesta johtuvan riippuvuuden heikentämiseen tähtääviä tekniikoita. Käsittelemme seuraavaksi hieman toisentyyppistä riippuvuutta, jossa komponentti luo toisen komponentin ilmentymän. Suoraviivaisessa toteutuksessa luovan komponentin täytyy tuntea luotavan komponentin luokka, mikä on monessa tapauksessa epätoivottavaa. Tarkastelemme tässä tekniikoita, joilla tällainen riippuvuus voidaan heikentää. Kuten monet aikaisemminkin tässä lu- FactoryRegistry register(factory) Platform Factory create(): Product Product service AppInit AppFactory AppProduct <<create>> Kuva 4.14: Luontiriippuvuuden heikentäminen tehtaan avulla vussa annetut ratkaisut, myös nyt kuvattavat ratkaisut on esitetty suunnittelumalleina. Ajatellaan tilannetta, jossa yleiskäyttöinen ohjelmisto esimerkiksi tuoterunko joutuu luomaan joukon sovelluskohtaisia komponentteja. Tällainen tilanne on hyvin yleinen, koska usein on tarpeen kuvata jokin sovelluskohtainen käsitekokonaisuus komponenttina, ja tällaisten komponenttien ilmentymien lukumäärä riippuu sovelluksen käytöstä. Jos sovellusten rakentamista tukee tuoterunko, tämän on pystyttävä luomaan mielivaltainen määrä sovelluskohtaisen komponentin ilmentymiä. Periaatteessa tämä ongelma voidaan ratkaista ottamalla käyttöön rajapinta luontia varten siten, että rajapinnan tarjoaman luontioperaation kutsumuoto sidotaan johonkin luotavien komponenttien yhteiseen rajapintaan, ei yksittäiseen komponenttityyppiin. Tuoterunko tuntee ainoastaan rajapinnat, ei konkreettisia komponentteja. Sovellus luo luontirajapinnan toteuttavan komponentin, nk. tehdaskomponentin (factory), joka antaa luontioperaation sellaisessa muodossa, että se luo tietyn sovelluskohtaisen komponentin ilmentymän. Kun sovellus rekisteröi tehdaskomponentin tuoterungolle, tämä voi kutsua tehdaskomponentin luontipalvelua, joka näin ollen palauttaa sovelluskohtaisen komponentin ilmentymän ilman, että tuoterunko tuntee komponentin tyyppiä. Kuvassa 4.14 on esitetty tämä ratkaisu komponenttikaaviona.
53 94 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 95 Button WinButton Application AbsFactory createbutton(): Button createmenu(): Menu Menu WinMenu WinFactory <<create>> Kuva 4.15: Abstraktin tehtaan soveltaminen käyttöliittymäarkkitehtuurissa Erilaisia Tehdas-variantteja saadaan tämän perusajatuksen pohjalta. Esimerkiksi Tehdasmetodi-suunnittelumallissa (Factory Method) ei ole erikseen tehdasoliota, vaan luontioperaatio on suoraan siinä luokassa, joka käyttää luotuja olioita. Määrittelemällä luontiperaatio uudelleen tämän luokan aliluokassa voidaan luotavien olioiden luokka vaihtaa joksikin sovelluskohtaiseksi luokaksi. Tässä ratkaisussa hyödynnetään periytymistä tavalla, joka sopii paremmin yksityiskohtaisen suunnittelun tasolle. Abstrakti tehdas -suunnittelumallissa (Abstract Factory) tehdas tarjoaa luontiperaatiot tietylle joukolle erilaisia komponentteja, joita käytetään yhdessä; tehdas luo komponentit aina tietyn variantin mukaisina. Esimerkkinä tällaisesta komponenttijoukosta voisivat olla vaikkapa käyttöliittymäelementit, jotka tehdas luo aina tietyn käyttöliittymäkirjaston mukaisina. Kuvassa 4.15 on esitetty tämän tehdasratkaisun sovellus tilanteessa, jossa halutaan tehdä tietystä käyttöliittymäkirjastosta riippumaton sovellus. Tässä kirjasto voidaan vaihtaa toiseksi antamalla tehdasrajapinnalle uusi toteutus, joka luo jonkin toisen graafisen kirjaston mukaisia käyttöliittymäelementtejä Yhteenveto Ohjelmistoarkkitehtuuri ja sen suunnitteleminen perustuvat komponenttien riippuvuussuhteisiin. Mitä riippumattomampia komponentit ovat, sitä paremmin järjestelmää voidaan rakentaa, ylläpitää ja uudelleenkäyttää. Rajapinnat olisi suunniteltava komponenttien palveluroolien mukaisesti (asiakaslähtöinen rajapintasuunnittelu). Palvelun vaikutus voidaan kuvata tulo- ja jättöehdoilla, ja palvelua tarjoavan yksikön sisäinen tila voidaan kuvata luokkainvariantilla. Välittäjäkomponentilla poistetaan keskenään kommunikoivan komponenttijoukon jäsenten riippuvuus toisistaan. Edustajakomponentilla poistetaan riippuvuus tietystä komponentista. Takaisinkutsua käyttäen yleiskäyttöinen komponentti voi kutsua sovelluskohtaisen komponentin toteuttamaa palvelua. Tapahtumilla voidaan vähentää palvelun pyytäjän ja tarjoajan välistä riippuvuutta. Sovittimella voidaan saattaa riippumattomat, toistensa toteuttamia rajapintoja tuntemattomat komponentit kommunikoimaan keskenään. Tehtaalla voidaan vähentää komponentteja tai olioita luovan ohjelmanosan riippuvuutta luotavien luokasta Harjoitustehtäviä 4.1 Eräässä liiketoimintajärjestelmässä on seuraavat komponentit: SecurityManager UserRegister SessionManager LoginManager AdministrationServices Näihin liittyvät seuraavat palvelut: setpassword(user, password)
54 96 Ohjelmistoarkkitehtuurit Komponenttien vuorovaikutustekniikat 97 resetpassword(user, old, new) checkpassword(user, password): Boolean getpassword(user): Password getusers(): set of User createsession(user): Session startsession(session) endsession(session) sessions(): set of Session createuser(...): User adduser(user) removeuser(user) Kuvaa komponenttien riippuvuudet määräämällä sopivat roolirajapinnat, ja antamalla UML:n komponenttikaavio, josta näkyy kunkin komponentin tarjoamat ja vaatimat rajapinnat operaatioineen. Voit tulkita komponenttien velvollisuudet haluamallasi tavalla ja myös lisätä operaatioita tarpeen mukaan. 4.2 Seuraava sekvenssikaavio kuvaa eräiden komponenttien välisen vuorovaikutuksen. Johda tästä kaaviosta mahdolliset roolirajapinnat osallistuville komponenteille. 4.3 Seuraavassa sekvenssikaaviossa on kuvattu tyypillinen vuorovaikutus komponenttien välillä eräässä sovelluksessa. Johda sekvenssikaaviosta mahdolliset roolirajapinnat komponenteille ja kuvaa komponenttien ja rajapintojen suhteet UML:n komponenttikaaviona tarjottuine ja vaadittuine rajapintoineen. Rajapintoja ei tarvitse nimetä, mutta merkitse niihin näkyviin operaatiot. GUIController DeviceManager Log Monitor DeviceDriver activate deactivate markstart getstatusdescr markstop printhistory run getstate stop printlog flushlog getstate A x x B y z u v w p p C m n m D 4.4 Luvun takaisinkutsuesimerkissä öljynpaineen laskeminen alle sallitun rajan voidaan tulkita myös poikkeustilanteeksi, jonka aiheuttaa kirjastoyksikkö. Voitko toteuttaa esimerkin vaatiman toiminnallisuuden käyttämällä poikkeusta takaisinkutsun sijasta? Mitä eroja, etuja ja haittoja on poikkeuksella verrattuna takaisinkutsuun? 4.5 Henkilöillä on ikä ja vanhenemisoperaatio (growolder). Kun henkilö täyttää 50 vuotta, hän järjestää juhlat ja ilmoittaa vuosipäivästään muille henkilöille, jotka ovat rekisteröityneet olemaan kiinnostuneita asiasta. Tiedonannon saatuaan muut onnittelevat (operaatio congratulation) merkkipäiväänsä viettävää henkilöä. Anna luokat/rajapinnat Person, BirthdayEvent, Hero ja Interested sekä niiden vaatimat operaatiot. Käytä UML:n mukaisia notaatioita. 4.6 Laajenna edellisen tehtävän ratkaisua (muuttamatta siihen kuuluvia rajapintoja tai luokkia/komponentteja) tarjoamalla
55 98 Ohjelmistoarkkitehtuurit mahdollisuus yrityksille rekisteröityä kuuntelemaan vuosipäivätiedotuksia. Yritykset tarjoavat distributeannouncementpalvelua, joka lähettää tiedotuksia (merkkijono) sopivalla jakelulla eri ihmisille ja yrityksille. Kun yritys saa tietää merkkipäivästä, se lähettää siitä tiedotuksen sidosryhmilleen. 4.7 Ota selvää, millainen on Tila-suunnittelumalli (State). Esitä se komponenttikaaviona samaan tyyliin kuin tässä luvussa esitetyt muut ratkaisut, ja selitä sen idea. 4.8 Tiloihin perustuva ohjelmointityyli (tai ohjelmointiparadigma) on usein järjestelmien ja jopa ohjelmointikielten (esim. SDL) perustana. Tällöin järjestelmä mallinnetaan tilakoneena. Esimerkiksi UML:ssä tilakoneelle on standardoitu merkintätapa. Jos tilakone on järjestelmän keskeinen mallintamisväline, voi olla järkevää käyttää myös toteutuksessa tilakoneita. Yksi tapa toteuttaa tilakone olioperustaisesti on soveltaa Tila-suunnittelumallia. Kuvaa tilakoneen toteutus UML-luokkakaaviona käyttäen Tila-suunnittelumallia. Toteutukselta vaaditaan, että tilakone voidaan luoda ajoaikana, ja että sitä voidaan myös mielivaltaisella tavalla muuttaa ajoaikana. Voidaan olettaa, että tilakone tulkitsee jostakin lähteestä tulevia tapahtumia, ja tilakone rekisteröityy tälle tarkkailemaan tapahtumia. Selitä myös miten UML:n entry- ja exit-toimenpiteet ja siirtymiin liittyvät toimenpiteet toteutetaan mallissa. 4.9 Oletetaan, että sovellus käyttää yleisen tietorakennekirjaston palveluja pinorakenteen hyödyntämiseksi. Sovellus luo kirjaston luokan XStack ilmentymiä ja käyttää pinoa kutsumalla XStack:in Object pop(), void push(object x) ja Object entry(int i) -operaatioita; jälkimmäinen palauttaa pinon i:nnen alkion pohjalta lukien. Pinoja luodaan ja käytetään eri sovelluksen luokissa, joita kutsutaan tässä nimellä Client1,..., Clientk. Miten suunnittelisit sovelluksen niin, että jos tietorakennekirjasto joudutaan vaihtamaan toiseksi, joka tarjoaa pinoluokan ja siihen liittyvät palvelut, mutta eri nimisinä, itse sovellukseen ei tarvitsisi tehdä suuria muutoksia? Mitä standardiratkaisua tai - ratkaisuja käytät? Anna UML-luokkakaavio, joka kuvaa ratkaisun perusidean. Anna operaatioiden koodi kommenttilaatikoissa (esim. pseudokoodina) siinä määrin kuin on tarpeellista ratkaisun ymmärtämiseksi.
56 5 Suunnittelumallit Suunnittelumallit (design pattern) (Gamma et al. 1995) ovat yksi 1990-luvun ohjelmistosuunnitteluun eniten vaikuttaneista tekniikoista. Monet suunnitteluideat, joiden käyttökelpoisuutta ei voi kiistää, onkin mahdollista pukea suunnittelumalleiksi. Toisaalta suunnittelumalleja on helppo ottaa käyttöön, koska ne eivät edellytä mitään tiettyä teknologiaa, suunnittelumenetelmää tai ohjelmointikieltä. Tästä syystä suunnittelumallien läpimurto onkin ollut nopeaa ja kattavaa. Lisäksi kyseessä on itsensä hyödylliseksi myös todellisessa elämässä osoittanut ajatus, ei ainoastaan teoriassa hyvältä vaikuttava tekniikka. 5.1 Johdatus suunnittelumalleihin Tarkastelemme tässä luvussa suunnittelumalleja arkkitehtuureja tukevana käsitteenä. Tarkoituksena ei ole käydä läpi suunnittelumalliluetteloita ja oppia suurta joukkoa suunnittelumalleja, vaan pikemminkin keskittyä itse suunnittelumallin käsitteeseen. Edellisessä luvussa tarkastelimme jo eräiden yksittäisten suunnittelumallien perusideoita ratkaisuina tiettyihin komponenttien riippuvuuksia koskeviin arkkitehtuuriongelmiin. Emme kuitenkaan esittäneet näitä ratkaisuja suunnittelumallien tapaan systemaattisessa muodossa, vaan vapaamuotoisesti ideatasolla. Tämä on siinä mielessä luontevaa, että kyseiset ratkaisut ovat olleet tunnettuja jo pitkään ennen suunnittelumallien tuloa. Käsittelemme tässä luvussa ainoastaan yhtä uutta suunnittelumallia (Rekursiokooste) esimerkinomaisesti. Pattern-yhteisön kotisivu, sisältää runsaasti informaatiota suunnittelumalleista, linkkejä malliluetteloihin ja muuta vastaavaa materiaalia.
57 102 Ohjelmistoarkkitehtuurit Suunnittelumallit Mikä on suunnittelumalli? Mestarien töiden kopiointi on ikivanha keksintö ja juuri tästä on pohjimmiltaan kyse myös suunnittelumalleissa. Monessa muussa yhteydessä mestarien ratkaisuihin pääsee kuitenkin tutustumaan vain ryhtymällä mestarin oppilaaksi ja osallistumalla varsinaiseen työhön. Suunnittelumallit ovat ottaneet lähtökohdakseen sen, että on mahdollistettava kopiointi myös tilanteissa, joissa jonkun muun suunnittelijan työhön perehtyminen ei ole mahdollista. Tämä on ollut pitkään tunnettu käytäntö monilla muilla tekniikan alueilla. Esimerkiksi koneenrakennuksessa on olemassa standardiratkaisujen kuvauksia tiettyihin yleisiin ongelmatilanteisiin. Ohjelmistotekniikkaan ajatus mestarien malliratkaisuista sopii erityisen hyvin, koska alalla työskentelee toisaalta kokeneita ohjelmistoarkkitehteja ja toisaalta paljon kokemattomia ohjelmoijia. Olisi erittäin toivottavaa, että kokeneitten suunnittelijoiden aikojen kuluessa keräämä tieto hyvistä ratkaisuista saataisiin kaikkien ohjelmistotekniikan ammattilaisten käyttöön helposti omaksuttavassa muodossa. Suunnittelumallit ovat siis tunnettujen ja käytännössä hyväksi havaittujen ratkaisujen kuvauksia yleisiin ohjelmistojen suunnittelua koskeviin ongelmiin tietyissä tilanteissa. Näin suunnittelumallissa on kolme olennaista osaa: ongelma, ongelmayhteys ja ratkaisu Suunnittelumallit ohjelmistotekniikassa Suunnittelumallien käyttö ohjelmistotekniikassa sai inspiraationsa rakennusarkkitehtuurin puolelta. Tunnettu arkkitehti Cristopher Alexander julkaisi 1970-luvun lopulla kirjasarjan, jossa esitettiin ajatus, että hyvin toimiva, laadukas rakennus pohjautuu tiettyihin hyviin ratkaisumalleihin, jotka ovat kehittyneet ja kypsyneet pitkien aikojen kuluessa optimaalisiksi tiettyjen, usein esiintyvien, rajatussa kontekstissa esiintyvien ongelmien ratkaisuiksi. Laadukas rakennus voidaan siten pitkälle ymmärtää tällaisten ratkaisumallien sovellusten yhdistelmänä. Alexander kuvasi kirjassaan tällaisten ratkaisumallien kokoelman, mallikielen, jota systemaattisesti soveltamalla voidaan suunnitella yhdyskuntia, rakennuksia, huoneita. Alexanderin ideat tulivat tunnetuiksi myös eräille ohjelmistotekniikan tutkijoille (Kent Beck, Jim Coplien, Erich Gamma ym.), jotka näkivät idean merkityksen ohjelmistojen suunnittelussa. Aja- tusta alkoi kehitellä pieni mutta aktiivinen ryhmä tutkijoita, ja ensimmäisiä asiaa käsitteleviä artikkeleita julkaistiin 1980-luvun lopulla. Ideaa kehiteltiin edelleen (mm. suunnittelumallien esitystapaa), yksittäisiä suunnittelumalleja alettiin hioa ja suunnittelumalliluetteloita ryhdyttiin keräämään. Merkittävä virstanpylväs oli nk. neljän koplan (Gang of Four, GoF) kirjan julkaiseminen vuonna 1995 (Gamma et al. 1995). Tähän kirjaan oli kerätty 23 yleistä suunnittelumallia; mukana olivat mm. Edustaja, Sovitin, Välittäjä, Tarkkailija, Abstrakti Tehdas ja Tila, joita on ideatasolla käsitelty edellisessä luvussa. Kirja on edelleen, noin kymmenen vuotta julkaisemisensa jälkeen täysin ajankohtainen, mikä on merkittävä saavutus nopeasti muuttuvassa tietotekniikassa ja kuvaa hyvin suunnittelumallien pysyvää arvoa 1. Suunnittelumallin alkuperäiseen olemukseen kuuluu, että se ei ole uusi, hieno ratkaisu johonkin ongelmaan, vaan jo pitkään tunnettu ja eri yhteyksissä kokeiltu ratkaisu, joka esitetään tietyssä muodossa. Suunnittelumallin yleisyys ja hyödyllisyys on GoF-kirjassa pyritty takaamaan viittaamalla vähintään kolmeen toisistaan riippumattomaan järjestelmään, joissa ratkaisua on menestyksellä sovellettu. Käytännössä tästä on muissa yhteyksissä joustettu, jopa siinä määrin, että on ryhdytty puhumaan suunnittelumallien "keksimisestä" (invent), kun pohjimmiltaan pitäisi puhua niiden "löytämisestä" (discover). Toisaalta, jos jokin suunnitteluratkaisu tarjoaa ilmeisiä käytännöllisiä hyötyjä ja on jossakin merkittävässä ympäristössä yleisesti sovellettavissa, ei liene tarvetta tulkita yleisyys- ja koetelluusvaatimusta kovin tiukasti. Usein on mielekästä esimerkiksi koota yrityksen omalla sovellusalueellaan käyttämiä, hyviksi osoittautuneita ratkaisuja ja esittää ne suunnittelumallin muodossa. Myös tiettyyn ohjelmistoalustaan liittyviä suositeltavia suunnitteluratkaisuja voidaan esittää suunnittelumallien muodossa. Esimerkki tällaisesta alustasta on J2EE, (Marinescu 2002). Ongelmana tällaisessa lähestymistavassa on lähinnä se, että suunnittelumallikirjastot tulevat niin laajoiksi, että ajatus kaikkien suunnittelijoiden tuntemista standardiratkaisuista ei enää toteudu. Vaikka suunnittelumalli käsitteenä ei ole sidoksissa mihinkään tiettyyn ohjelmointiparadigmaan, suunnittelumalleja kokoava yhteisö on toiminut lähinnä oliomaailmassa, ja siten suunnittelumallit esi- 1. GoF-kirja ja mm. suunnittelumallien nimet on ansiokkaasti suomennettu Anita Toivosen toimesta (Gamma et al. 2001).
58 104 Ohjelmistoarkkitehtuurit Suunnittelumallit 105 tetään tavallisesti tämän paradigman mukaisina. Pohjaksi oletettu ohjelmointiparadigma voi vaikuttaa hieman siihen, mitkä ratkaisut on järkevää esittää suunnittelumalleina. Jos esimerkiksi oletetaan olioparadigma, ei ole kovin hyödyllistä esittää suunnittelumallia, jonka mukaan käsitehierarkiat kannattaa toteuttaa periytymisen avulla luokkahierarkiana, koska tämä ajatus sisältyy jo oliokielten perusolettamuksiin. Vastaavasti monet suunnittelumallit voivat degeneroitua toisenlaista paradigmaa käytettäessä. Ohjelmistokehitysprosessin kannalta suunnittelumallit tukevat lähinnä arkkitehtuurisuunnittelua ja yksityiskohtaista suunnittelua. Suunnittelumallien menestys on kuitenkin innostanut hakemaan vastaavanlaisia hyviä ratkaisumalleja myös muille ohjelmistotekniikan alueille (ja jopa ohjelmistotekniikan ulkopuolellekin). Muita ohjelmistokehitysprosessin vaiheita tukevia malleja ovat mm. analyysimallit (analysis pattern) (Fowler 1997) ja idiomit (idiom) (Coplien 1997); edellisiä sovelletaan vaatimusanalyysivaiheessa, kun taas jälkimmäiset ovat toteutusvaiheessa sovellettavia koodauskäytäntöjä. Myös termiä arkkitehtuurimalli (architectural pattern) käytetään silloin, kun kyse on kaikkein korkeimmalla tasolla sovellettavasta, koko järjestelmää luonnehtivasta suunnitteluratkaisusta (Buschmann et al. 1996). Tässä kirjassa arkkitehtuurimalleja kutsutaan perinteisemmällä termillä arkkitehtuurityyli; käsittelemme arkkitehtuurityylejä seuraavassa luvussa samaan tapaan vapaamuotoisesti kuin edellisessä luvussa suunnittelumalleja. Suunnittelumallin käyttö ei tuo järjestelmään uutta toiminnallisuutta, vaan se parantaa ohjelmiston jotakin laatuominaisuutta (esim. muunneltavuus, siirrettävyys, ylläpidettävyys, uudelleenkäytettävyys, suorituskyky). Useimmat GoF-kirjan suunnittelumallit pyrkivät lisäämään järjestelmän muunneltavuutta, joka vaikuttaa toisaalta ylläpidettyvyyteen ja uudelleenkäytettävyyteen. Suunnittelumalleja voidaan luokitella myös niiden laatuominaisuuksien suhteen, joita ratkaisut edistävät. Muihin kuin muunneltavuuteen liittyviä suunnittelumalleja on esitetty eri yhteyksissä; tällaisia ovat esimerkiksi suorituskykyä (Smith ja Williams 2001), muistin kulutusta (Noble ja Weir 2001) tai turvallisuutta (Yoder ja Barcalow 1997) parantavat suunnittelumallit Suunnittelumallin sisältö Suunnittelumallin keskeiset osat ovat: Ongelma (problem). Suunnittelumallin ratkaiseman ongelman tulee olla yleinen suunnitteluongelma, joka ei edellytä esimerkiksi tiettyä ohjelmointikieltä. Ongelman tulee ilmetä toistuvasti monenlaisissa järjestelmissä. Muut kuin arkkitehtuuri- tai yksityiskohtaiseen suunnitteluun liittyvät ongelmat rajataan pois. Ongelmayhteys (context). Ongelma ilmenee laajemmassa yhteydessä, jonka suunnittelumalli määrittelee. Yhteys kertoo, millaisissa tilanteissa suunnittelumalli on sovellettavissa. Ongelmayhteys määrää myös ratkaisulle asetettavia vaatimuksia. Vaatimukset liittyvät tavallisesti johonkin laatuominaisuuteen, jota ratkaisun tulee parantaa. Ratkaisu (solution). Myös ratkaisun tulee olla yleinen, ja sen on oltava kuvattavissa yleisesti tunnetuilla formalismeilla (esimerkiksi UML:llä). Ratkaisu täyttää vaatimukset, mutta saattaa johtaa joidenkin muiden laatuominaisuuksien heikkenemiseen. Esimerkiksi GoF-suunnittelumallien soveltaminen usein heikentää hieman suorituskykyä, mutta ei yleensä niin paljon, että sillä olisi merkitystä kokonaisuuden kannalta. Suunnittelumalli koskee aina useita ohjelmayksiköitä (komponentteja, rajapintoja, luokkia, metodeja), jotka ovat järjestyneet tietyllä tavalla ratkaisussa. Suunnittelumalli määrittelee näin suhteet, jotka ratkaisuun liittyvillä komponenteilla on toisiinsa nähden tämän suunnittelumallin kannalta. Sama yksikkö voi toisaalta liittyä useaan eri suunnittelumallin ilmentymään. Suunnittelumallia voidaan pitää aspektin kaltaisena järjestelmän komponenttijakoa täydentävänä, poikkikulkevana rakenteena, joka on olennainen järjestelmän tiettyjen ratkaisujen ymmärtämiseksi. 5.2 Suunnittelumallien kuvaaminen Suunnittelumallit esitetään dokumentteina, joilla on yhtenäinen, systemaattinen rakenne. Dokumentin tulisi periaatteessa antaa kaikki se
59 106 Ohjelmistoarkkitehtuurit Suunnittelumallit 107 informaatio, jota mallin soveltaja tarvitsee. Esitysmuodossa on eri lähteissä jonkin verran vaihtelua, mutta ainakin seuraavassa esitetyt asiat on syytä kirjata suunnittelumallin kuvaukseen. Suunnittelumallin nimi on keskeinen elementti kuvausta: nimestä tulee toivon mukaan osa kaikkien suunnittelijoiden tuntemaa ammattisanastoa. Nimen olisi luonnollisesti oltava yksiselitteinen, sillä ei saisi olla muita ohjelmistoteknisiä merkityksiä eikä samalla suunnittelumallilla saisi olla montaa nimeä. Viimeksi mainittu vaatimus ei ikävä kyllä täysin päde: eri tutkimusryhmät ovat identifioineet samoja ratkaisuja toisistaan tietämättä, ja esitettäneet niitä eri nimisinä. Samasta suunnittelumallista voi olla myös hieman erilaisia variaatioita joko samannimisinä tai erinimisinä. Asiaa vielä jonkin verran hämmentää se, että eri suunnittelumallit voivat olla idealtaan hyvin lähellä toisiaan, jopa samassa malliluettelossa. Suunnittelumallin ratkaisema ongelma pyritään esittämään niin yleisessä muodossa, että se kattaa kaikki mahdolliset mallin sovellukset. Usein ongelma ja ongelmayhteys liittyvät niin läheisesti toisiinsa, että ne esitetään yhdessä. Ongelmayhteydestä seuraa tavallisesti tiettyjä vaatimuksia ratkaisulle, ja toisaalta oletuksia, joihin ratkaisu perustuu. Usein ongelmaa ja ongelmayhteyttä voidaan havainnollistaa esimerkeillä. Itse ratkaisu on suunnittelumallin ydin. Se esitetään usein staattisena rakenteena, esimerkiksi UML:n luokkakaaviona, jota tarvittaessa täydennetään dynaamista vuorovaikutusta havainnollistavana kaaviona, esimerkiksi UML:n sekvenssikaaviona. Joskus käytetään myös pseudokoodikuvausta, erityisesti silloin, kun ratkaisussa on esimerkiksi jokin algoritmisesti olennainen osa tai kun käyttäytymisen yksityiskohdat ovat syystä tai toisesta olennaisia. Edellä selostettujen kohtien lisäksi suunnittelumallin kuvaukseen liitetään tavallisesti sen käyttöä helpottavia osia. Seurauksten kuvaus on eräs tällainen osa. Mikään suunnittelumalli ei tarjoa joka suhteessa optimaalista ratkaisua, vaan kukin suunnittelumalli optimoi tietyt laatuominaisuudet ja heikentää joitakin toisia. Usein esimerkiksi muunneltavuus ja suorituskyky ovat vastakkaisia laatuominaisuuksia siinä mielessä, että kun jokin ratkaisu vahvistaa toista, se heikentää toista. Suunnittelumallin kuvauksessa on pyrittävä mahdollisimman tarkasti kuvaamaan ratkaisun hyvät seuraukset ja huonot seuraukset, jotta suunnittelija voi arvioida, sopiiko kyseinen ratkaisu juuri hänen ongelmaansa. Toteutusnäkökulmat käsitellään myös usein suunnittelumallin kuvauksessa. Suunnittelumalli itsessään ei ota kantaa esimerkiksi ohjelmointikieliin, ja se jättää myös monet toteutustekniset yksityiskohdat avoimiksi, keskittyen vain itse ratkaisun abstraktiin ideaan. Eri kielissä suunnittelumalli voidaan toteuttaa hieman eri tavoin. Toteutustekniset asiat voivat olla käytännössä hyvinkin tärkeitä, ja suunnittelijalle voidaan tässä kohdassa antaa erilaisia esimerkiksi kielikohtaisia vihjeitä toteutustavoista, mutta on tärkeätä pitää toteutusasiat erillään itse loogisesta ratkaisusta suunnittelumallin kuvauksessa. Lisäksi voidaan antaa esimerkkejä suunnittelumallin soveltamisesta. Joskus esimerkit annetaan suoraan ohjelmakooditasolla, jolloin luonnollisesti joudutaan nojaamaan jonkin ohjelmointikielen ominaisuuksiin. Toinen tapa käyttää esimerkkejä on selittää, minkälaisissa todellisissa järjestelmissä suunnittelumallia on käytetty, jolloin suunnittelumallin käyttötarkoitus ja soveltuvuusalue usein selkiytyvät. 5.3 Antisuunnittelumallit Antisuunnittelumallit tai vastasuunnittelumallit ovat yleisesti esiintyviä ratkaisuita perinteisiin ongelmiin, aivan kuten varsinaiset suunnittelumallitkin (Brown et al. 1998). Antisuunnittelumallien tapauksessa ratkaisut eivät ole kuitenkaan hyviä, vaan niissä on perustavaa laatua olevia ongelmia, jotka näkyvät erilaisina epätoivottuina ominaisuuksina ohjelmiston elinkaaren aikana. Antisuunnittelumalliin voidaan liittää myös ohjeet, joiden avulla huono ratkaisu voidaan korvata hyvällä ratkaisulla mahdollisesti oikealla suunnittelumallilla. Antisuunnittelumallia on havainnollistettu kuvassa 5.1. Hiukan paradoksaalisesti huonon ratkaisun kuvaava antisuunnittelumalli voi olla yhtä hyödyllinen kuin hyvän ratkaisun kuvaava suunnittelumallikin. Antisuunnittelumallien hyödyllisyys perustuu siihen, että ne auttavat löytämään ohjelmistosta kohtia, jotka heikentävät ohjelmiston laatua. Jos antisuunnittelumalliin on liitetty oikean suunnittelumallin käyttöön perustuvat muutosohjeet, antisuunnittelumalli voidaan itse asiassa nähdä suunnittelumallina, jossa ongelmayhteys on kuvattu (huonona) suunnitteluratkaisuna. Antisuunnittelumalleja on koottu samaan tapaan kuin suunnittelumallejakin, joskin antisuunnittelumallit ovat luonteeltaan usein
60 108 Ohjelmistoarkkitehtuurit Suunnittelumallit 109 Ongelma Suunnittelumalli Hyvä ratkaisu Halutut seuraukset Asiakas suorita lehtiolioille jokin toimenpide Huono ratkaisu Epätoivotut seuraukset Muokkaus Kuva 5.1: Antisuunnittelumallin käyttö Antisuunnittelumalli Kuva 5.2: Ongelma: asiakas käyttää hierarkian lehtiä rakennetta tuntematta yleisempiä. Tämä johtuu siitä, että on vaikea antaa kovin tarkasti yleistä huonoa ratkaisua, koska huonoja ratkaisuja on niin monia, kun taas hyvät ratkaisut sen sijaan konvergoituvat. Esimerkkejä antisuunnittelumalleista ovat "copy and paste" (koodin kopiointi) tai "blob class" (ylisuureksi paisuva dominoiva luokka) (Brown et al. 1998). Tarkempia antisuunnittelumalleja voisi löytää kysymällä, millä tavalla aloitteleva suunnittelija todennäköisesti ratkaisisi jonkin suunnittelumallin ongelman. Tyypillisiä ohjelmiston ominaisuuksia, jotka viittaavat antisuunnittelumallin käyttöön, ovat esimerkiksi: saman tai lähes saman koodisekvenssin toistuminen, eri vaihtoehtojen staattinen kiinnittäminen ohjelmakoodissa periytymisen ja dynaamisen sidonnan käytön sijaan, tiedon tyypin perusteella tapahtuva haarautuminen, pitkät, useiden komponenttien kautta kulkevat saantiketjut, suuret ja monimutkaiset luokat, epäselvät rajapinnat. Käytännössä toistuvia huonoja suunnitteluratkaisuja löytänee melkein mistä järjestelmästä tahansa, viimeistään silloin, kun järjestelmää on jonkin aikaa ylläpidetty ja muutettu uusien vaatimusten mukaisesti. Toisaalta edellä mainittujen piirteiden löytyminen ohjelmassa ei välttämättä ole merkki huonosta ratkaisusta. Esimerkiksi lievä koodin kopiointi voi joskus olla järkevää, jos sen välttäminen johtaisi tarpeettoman monimutkaiseen luokkarakenteeseen. 5.4 Esimerkki: Rekursiokooste-suunnittelumalli Tässä esimerkissä tutustumme Rekursiokooste-nimiseen suunnittelumalliin. Lähdemme johtamaan suunnittelumallia alkaen mahdollisimman suoraviivaisesta, ensimmäiseksi mieleen tulevasta ratkaisusta, jota voidaan pitää kyseisen ongelman antisuunnittelumallina. Parantelemme sitten ratkaisua ja osoitamme, että loogisen ajatusprosessin tuloksena saamme yleisen Rekursiokooste-suunnittelumallin (Composite). Hyvin todennäköisesti monet suunnittelijat päätyvät ja ovat päätyneet samanlaisen prosessin tuloksena samaan ratkaisuun. Suunnittelumallien ideana on, että jokaisen suunnittelijan ei tarvitse toistaa tätä prosessia (ja pahimmassa tapauksessa päätyä huonompaan ratkaisuun), vaan ratkaisu ja kaikki siihen liittyvä informaatio on saatavissa suoraan selkeästi dokumentoituna Ongelma: hierarkkisen oliokokoelman hallinta Ongelma on seuraava: Oletetaan, että ohjelmassa on hallittava hierarkkisesti (puumaisesti) järjestettyä oliokokoelmaa (kuva 5.2). Haluamme esimerkiksi pystyä suorittamaan kokoelman jokaiselle oliolle jonkin tämän tunteman operaation. Esimerkkinä voisi olla vaikkapa graafinen sovellus, jossa on hierarkkisia ikkunoita. Miten voisimme esimerkiksi suorittaa kaikkien ikkunoiden uudelleenpiirtämisen niin, että operaation kutsujan ei tarvitse tuntea hierarkiaa?
61 110 Ohjelmistoarkkitehtuurit Suunnittelumallit 111 Composite forall() For all children c: c.operation() * children Leaf operation() käyttö: if X is Composite then X.forall() else X.operation();... if X is Composite then Op1(, X, ) else Op2(, X, );... Kuva 5.3: Hierarkian toteutuksen ensimmäinen vaihe * children Item operation() käyttö: item.operation(); íf I have children then for all children c: c.operation() else dosomething(); Kuva 5.4: Hierarkian toteutuksen toinen vaihe Usein (kuten kuvassa 5.2) haluamme suorittaa jonkin operaation kaikille lehtiolioille, ja puun solmuina olevat oliot antavat ainoastaan rakenteen lehtiolioiden kokoelmalle. Oletetaan jatkossa yksinkertaisuuden vuoksi, että näin on asian laita; tämä ei muuta ongelman perusluonnetta. * children Item operation() Vaihe 1: kooste ja lehdet íf I have children then for all children c: c.operation() else dosomething1() Item1 operation() Item2 operation() íf I have children then for all children c: c.operation() else dosomething2() Ensimmäisessä vaiheessa annamme yksinkertaisesti oman luokan koosteelle (Composite) ja oman luokan osalle, lehtioliolle (Leaf), kuten kuvassa 5.3. Koosteluokassa annetaan operaatio (forall), joka käy läpi kaikki osaoliot ja suorittaa kullekin tietyn, lehtiolion luokassa määritellyn operaation (operation). Suoraviivaisen toteutuksen huono puoli on se, että rakenteen käyttäjän on tiedettävä, kutsutaanko koosteolion vai lehtiolion palvelua. Lisäksi ratkaisu sallii vain yhdentyyppisiä kooste- ja lehtiolioita. Ehkä suurin puute ratkaisussa on kuitenkin se, että hierarkian syvyys on rajoitettu kahteen tasoon Vaihe 2: koosteen ja lehtien samaistaminen Seuraavassa vaiheessa toteamme, että samaistamalla koosteet ja lehdet ja antamalla niille yhteisen luokan helpotamme rakenteen käyttäjän ohjelmointia ja mahdollistamme mielivaltaisen syvät rakenteet (kuva 5.4). Nyt rakenteen käyttäjä tuntee vain yhteisen Item-luokan, ja kutsuu sen operaatiota tietämättä, onko kyse koosteesta vai lehdes- Kuva 5.5: Hierarkian toteutuksen kolmas vaihe tä. Operaation alussa tutkitaan, onko oliolla lehtiolioita, ja jos on, operaation kutsu siirretään niille. Jos lehtiolioita ei ole, olio itse on lehtiolio, jolloin suoritetaan varsinainen toimenpide Vaihe 3: erityyppiset oliot Ratkaisun kolmannessa vaiheessa otamme huomioon vaatimuksen, että oliot voivat olla erityyppisiä (kuva 5.5). Aliluokitamme siis Item-luokan yksinkertaisesti uusilla luokilla Item1 ja Item2, joissa operaation toteutus vasta annetaan (tai uudelleenmääritellään). Tilanne ei muutu Item-luokan käyttäjän kannalta, sillä hänen ei tarvitse tuntea aliluokkia, koska oikean toteutuksen kutsuminen hoituu automaattisesti periytymismekanismin avulla.
62 112 Ohjelmistoarkkitehtuurit Suunnittelumallit Suunnittelumallin esittäminen dosomething Item1 operation() * children Item operation() Composite operation() Kuva 5.6: Hierarkian toteutuksen neljäs vaihe Vaihe 4: koosteen eriyttäminen for all children c: c.operation() Edellisen vaiheen ratkaisussa eri lehtioliotyypit suorittavat lopuksi lehtiolioille tehtävät toimenpiteet kukin omalla tavallaan (dosomething1, dosomething2). Jos kuitenkin kyseessä on todellakin lehtiolio, toteutuksessa kysytään tarpeettomasti lasten olemassaolosta, sillä niitä ei tietenkään ole. Vastaavasti jos kyseessä on koosteolio, lapsia oletettavasti aina on. Niinpä meidän kannattaa eriyttää kooste- ja lehtiolioiden aliluokat. Samalla voidaan tarkentaa mallia siirtämällä koostesuhde lähtemään koosteluokasta (kuva 5.6). Tällöin siis palataan takaisin alkuperäisessä mallissa olleeseen eroon koosteen ja lehtiolion välillä, mutta tällä kertaa niin, että eroa ei paljasteta rakenteen käyttäjälle. Vain koosteolion täytyy tietää, miten sen sisällä oleviin olioihin viitataan. Huomaa, että sekä lehtiolioiden että koosteolioiden aliluokkia voi olla tarvittaessa useita: ratkaisu ei ole rajattu kahteen aliluokkaan. Näin olemmekin päätyneet Rekursiokooste-suunnittelumalliin. Tarkastelemme seuraavassa, miten tämä suunnittelumalli kuvataan seuraten pääpiirteissään (joskin huomattavasti lyhentäen) suunnittelumallien klassista esitystapaa (Gamma et al. 1995). Rekursiokooste-suunnittelumalli on esitetty keskeisiltä osiltaan kuvassa 5.7. Suunnittelumallille annetaan nimi, jonka tulisi olla kuvaava ja yksiselitteinen. Tässä tapauksessa mallin suomenkieliseksi nimeksi on valittu Rekursiokooste, koska pelkkä suora suomennos Composite-termistä ("kooste") on jo käytössä monissa muissa yhteyksissä ohjelmistotekniikassa eikä se kuvaisi mallin ideaa kovinkaan hyvin. Sama kritiikki pätee toki myös englanninkieliseen termiin. Ongelma-kohdassa annetaan lyhyt kuvaus suunnittelumallin ratkaisemasta ongelmasta. Tässä tapauksessa ongelmayhteydestä seuraa vaatimus, että rakenteen käyttäjä ei saa riippua rakenteen toteutuksesta. Ratkaisu-kohdassa annetaan UML-luokkakaavio, joka kuvaa ratkaisun staattisen luokkarakenteen kannalta. Tässä tapauksessa ratkaisuun liittyvä dynaaminen vuorovaikutus näkyy osittain operaatiolle annetusta pseudokoodirungosta. Jätämme harjoitustehtäväksi lisätä ratkaisu-kohtaan tyypillisen dynaamisen vuorovaikutuksen kuvaava sekvenssikaavio. Soveltuvuus-kohdassa tiivistetään ongelmayhteys ja ratkaisulle asetetut vaatimukset. Tässä tapauksessa kuvaus lähinnä toistaa ongelmakuvauksen hieman laveammin, mutta yleisesti soveltuvuuskohdassa voidaan antaa mallin soveltamistilanteelle erilaisia kriteerejä. Näiden kriteerien tulisi siis täyttyä, ennen kuin mallin soveltamista kannattaa harkita. Seuraukset-kohdassa luetellaan mallin soveltamisen hyviä ja huonoja seurauksia. Tässä tapauksessa hyvä seuraus on mm. se, että ratkaisu sallii helposti uusien lehtiolio- ja koosteoliotyyppien lisäämisen: ratkaisu ei mitenkään rajoita Item-luokan aliluokkien määrää. Tämä olisi voinut olla esitettynä jo ongelmakuvauksessa vaatimuksena, koska monessa tapauksessa tätä nimenomaan halutaan. Seuraukset-kohdassa esitetään myös eräs mahdollinen huono puoli: ratkaisu ei millään tavalla pysty tukemaan sellaista tilannetta, jossa esimerkiksi lehtiluokkia on useita, ja tiettyyn hierarkiaan sallitaan vain tietynlaisia lehtiolioita. Ratkaisu sallii kaikkien lehtiluokkien ilmentymiä kaikissa hierarkioissa. Tämä vaatimus on hankala toteuttaa staattisen luokkarakenteen tasolla, mutta se voidaan toisaalta helposti ottaa huomioon hierarkioita luotaessa, dynaamisesti.
63 114 Ohjelmistoarkkitehtuurit Suunnittelumallit 115 Nimi: RekursioKooste Ongelma: Miten organisoida hierarkinen oliokokoelma niin, että kokoelman käyttäjän ei tarvitse tuntea hierarkian rakennetta? Ratkaisu: Leaf operation() Soveltuvuus Item operation() * children Composite operation() For all children c: c.operation() Rekursiokoostetta kannattaa käyttää, kun haluat esittää periaatteessa mielivaltaisen syviä hierarkisia oliorakenteita, ja haluat, että rakenteen käyttäjälle koko rakenne näkyy yhtenä abstraktiona Seuraukset Rakenteen käyttäjä ei tule riippuvaiseksi rakenteen eri osista Rakenteen käyttäjän koodi yksinkertaistuu Helppo lisätä uuden typpisiä rakenneosia Ratkaisu ei suoraan tue tilannetta, jossa vain joidenkin lehtiluokkien ilmentymiä sallitaan tietyssä oliohierarkiassa Toteutusnäkökohtia Tarvitaanko linkit myös lapsista koosteolioon? Voiko sama olio olla lapsena useammalle koosteelle? Määritelläänkö koosteluokan omat operaatiot (esim. lisää lapsi) yhteisessä rajapintaluokassa (Item) Onko lapsille määritelty järjestys? Kuka luo ja hävittää hierarkian oliot? Millaista tietorakennetta käytetään lasten tallettamiseen? Kuva 5.7: Rekursiokooste-suunnittelumalli Toteutus-kohdassa käsitellään erilaisia toteutusteknisiä yksityiskohtia ja vaihtoehtoja. Olioiden väliset linkit voidaan esimerkiksi toteuttaa yksi- tai kaksisuuntaisina: koosteolion on luonnollisesti tunnettava lapsensa, jotta se pystyisi siirtämään operaatiokutsun niille, mutta joskus myös lapsen voi olla tunnettava vanhempansa, jos esimerkiksi operaation suoritus edellyttää takaisinkutsua vanhemmalle. Itse hierarkia voidaan toteuttaa joko aitona puuna tai suunnattuna syklittömänä verkkona, jälkimmäisessä tapauksessa sama lehti voi kuulua useille koosteolioille. Tällöin on huolehdittava siitä, ettei operaatiokutsua siirretä virheellisesti useaan kertaan jollekin lehtioliolle. Mielenkiintoinen toteutuskysymys liittyy myös luokkien rajapintoihin ja tyyppiturvallisuuteen. Tämä ongelma seuraa siitä, että luokalla Composite tulee todennäköisesti olla operaatioita, jotka eivät ole mielekkäitä Leaf-luokille (esim. addchild, removechild). Näin ollen on kaksi mahdollisuutta: luokilla Composite ja Leaf on joko sama rajapinta (jolloin Composite-luokan erityisoperaatiot on toteutettava esim. tyhjinä tai virheilmoituksen tuottavina Leaf-luokassa) tai eri rajapinta. Edellisessä tapauksessa menetetään tyyppiturvallisuudessa, koska ajoaikana voidaan joutua tilanteeseen, jossa lehtioliolle yritetään tehdä koosteolion operaatiota. Jälkimmäisessä tapauksessa ohjelmistoon syntyy periaatteessa enemmän riippuvuuksia, koska jotkut käyttäjät joutuvat tekemään eron lehden ja koosteen välillä. Toisaalta, jos koosteen erikoisoperaatiot ovat sellaisia, jotka liittyvät vain hierarkian rakentamiseen tai muuttamiseen mutta eivät sen käyttöön, tällä ei ole juuri merkitystä, koska hierarkian rakentamisessa joudutaan joka tapauksessa erottelemaan lehdet ja koosteet. Näin ollen on useimmiten oikea ratkaisu pitää rajapinnat erillisinä. Esimerkki Rekursiokooste-suunnittelumallin käytöstä voi olla vaikkapa lähdekoodin hallintajärjestelmä, joka tallettaa kooditiedostoja ja niihin liittyviä hakemistohierarkioita (kuva 5.8). Suunnittelumalli mahdollistaa toiminnan, jossa hakemistohierarkia ja kooditiedostot voidaan eristää omaan alijärjestelmäänsä ja kooditiedostoja käsittelevä järjestelmä (SourceManager) näkee tietyn ohjelmistokoodin vain Source-abstraktiona tarvitsematta ottaa kantaa siihen, miten koodi on jaettu hakemistoihin. Esimerkiksi kollektiivinen tulostaminen ja tuhoaminen vaatii periaatteessa puurakenteen läpikäynnin, mutta tämä voidaan täysin piilottaa SourceManager-luokalta. Itse asiassa koko koodin talletustapa voidaan vaihtaa täysin
64 116 Ohjelmistoarkkitehtuurit Suunnittelumallit 117 SourceManager File delete() print() Source delete() print() * children Folder delete() print() add(source) For all children c: c.delete(); Delete folder Kuva 5.8: Esimerkki Rekursiokooste-suunnittelumallin käytöstä toiseksi (vaikkapa tietokantapohjaiseksi) ilman, että se vaikuttaa varsinaiseen hallintajärjestelmään. yleistä kehitystrendiä, jossa pyritään havaitsemaan toistuvia rakenteita ja antamaan niille nimi ja oma esitysmuoto. Näin ovat aikanaan kehittyneet korkean tason ohjelmointikielet. Suunnittelumallien tapauksessa ei ole kuitenkaan nähty tarpeelliseksi antaa niille erityistä tukea ohjelmointikielissä (joitakin poikkeuksia lukuunottamatta, esim. C#). Näyttää siltä, että suunnittelumalleja tullaan jatkossa tukemaan pikemmin ohjelmointiympäristön kuin kielen toimesta. Suunnittelumallien tunteminen helpottaa ja tehostaa yleisesti kommunikointia suunnittelijoiden välillä. Olettaen, että suunnittelijat tuntevat suunnittelumallit, voidaan pelkästään mallin nimeen viittaamalla välittää täsmällisesti suuri määrä informaatiota, jonka selittäminen muuten olisi aikaa vievää ja altista väärinymmärryksille. Tätä voidaan käyttää hyväksi paitsi suullisessa kommunikoinnissa (esimerkiksi katselmoinneissa) myös ohjelmistojen dokumentoinnissa: kertomalla, mitä suunnittelumalleja ohjelmistossa on missäkin kohdassa käytetty, ilmaistaan paitsi suunnitteluratkaisujen sisältöä myös pitkälle niiden perusteluja. 5.5 Suunnittelumallien edut ja ongelmat Kunkin yksittäisen suunnittelumallin edut ja haitat tulisi luetella suunnittelumallin kuvauksessa. Tarkastelemme tässä yleisesti mitä etuja ja ongelmia liittyy suunnittelumallien soveltamiseen käytännössä. On huomattava, että vaikka suunnittelumallit ovatkin osoittautuneet merkittäväksi ohjelmistojen laatua parantavaksi tekniikaksi, niiden käyttöön liittyy myös yleisiä ongelmia, joista on syytä olla tietoinen Suunnittelumallien tarjoamat edut Suunnittelumallien keskeisin hyöty liittyy luonnollisesti niiden alkuperäiseen motivointiin: Suunnittelumallit antavat mahdollisuuden suunnittelukokemuksen siirtämiselle kokeneilta suunnittelijoilta nuoremmille suunnittelijasukupolville. Suunnittelumallit toimivat näin hyviksi havaittujen suunnitteluratkaisujen työkalupakkina. Toisaalta suunnittelumallit tarjoavat uusia, korkeamman tason rakenneabstraktioita, joita yhdistellen ohjelmistoja voidaan rakentaa. Tässä mielessä suunnittelumallit seuraavat ohjelmistotekniikan Suunnittelumalleihin liittyviä ongelmia Suunnittelumalli edistää aina jotakin ohjelmiston laatuominaisuutta, ja tavallisesti heikentää jotain toista. Etenkin GoF-mallit tyypillisesti edistävät ohjelmiston muunneltavuutta (ja siten uudelleenkäytettävyyttä ja ylläpidettävyyttä), mutta heikentävät jonkin verran suorituskykyä. Jälkimmäinen johtuu suunnittelumallin tuomasta epäsuoruudesta (esimerkiksi kutsun siirtämisestä) ja lisääntyneestä dynaamisesta sidonnasta. Vaikka useimmissa tapauksissa suorituskyvyn heikkeneminen on niin marginaalista, ettei sillä ole merkitystä, kriittisissä reaaliaikajärjestelmissä ja sulautetuissa järjestelmissä tällä saattaa hyvinkin olla merkitystä. Toisaalta suunnittelumallit monimutkaistavat yleisesti arkkitehtuuria lisäämällä komponenttien, luokkien ja rajapintojen määrää. Jos suunnittelumallilla saatava hyöty (esimerkiksi muunneltavuudessa) ei käytännössä ole kovin merkittävä, jo pelkästään tämän haittapuolen takia kannattaa joskus luopua suunnittelumallin käytöstä: ei pidä ampua hyttystä tykillä. Arkkitehtuurin monimutkaistuminen on epämiellyttävää varsinkin sellaisissa tapauksissa, joissa jokin aikaisemmin yhtenä luok-
65 118 Ohjelmistoarkkitehtuurit Suunnittelumallit 119 Composite Leaf execute() Item Composite SimpleStatement Statement execute() Kuva 5.9: UML:n mukainen suunnittelumallin käyttö kana esitetty looginen kokonaisuus hajoaa useaksi luokaksi. Näin tapahtuu esimerkiksi, kun Tila-suunnittelumallissa olion (tai komponentin) tila esitetään erillisessä oliossa. Jos kyseinen looginen olio halutaan esimerkiksi sarjallistaa ja tallettaa, täytyy muistaa tallettaa myös tilaolio. Tätä kutsutaan joskus olioiden skitsofreniaksi: aikaisemmin yhtenäisen olion identiteetti jakautuu useaan olioon. Koska suunnittelumalleilla ei ole omaa esitysmuotoa ohjelmointikielissä, niiden sovellukset häviävät koodissa ellei niitä erikseen kuvata joko erillisissä dokumenteissa tai koodissa olevissa kommenteissa. Suunnittelumallien ilmentymien merkitseminen tavalla tai toisella on kuitenkin välttämätöntä suunnittelumallien hyötyjen realisoimiseksi. Näin suunnittelumallien käyttö edellyttää onnistuakseen hyvää dokumentointikäytäntöä. 5.6 Suunnittelumallit ja UML children Block execute() UML ei valitettavasti tarjoa kovinkaan paljon tukea suunnittelumalleille. Yleisesti hyväksytty tapa kuvata suunnittelumallin ilmentymä järjestelmän luokkakaaviossa on piirtää mallien ilmentymät kuvaan katkoviivoitettuina soikioina (nk. kollaboraatiosymboli), ja merkitä suunnittelumallien edellyttämät roolit mukaan kaavioon (kuva 5.9). Valitettavasti tästä voi olla tuloksena melkoinen sotku vähänkin monimutkaisemman kaavion tapauksessa, varsinkin kun jokin luokka saattaa näytellä useita rooleja eri suunnittelumalleissa. * 5.7 Yhteenveto Suunnittelumalli dokumentoi yleisen ratkaisun, ei tiettyä ohjelman osaa. Ohjelmistoja voidaan dokumentoida kertomalla, mitä suunnittelumalleja niissä on sovellettu. Suunnittelumallien suurin hyöty on siinä, että aikaisemmin vain kokeneiden suunnittelijoiden tiedossa olleet ratkaisut saadaan kaikkien ulottuville helppokäyttöisessä muodossa. Suunnittelumallit parantavat myös kommunikaatiota suunnittelijoiden välillä antamalla tietyille suunnitteluratkaisuille kaikkien tunteman nimen. Suunnittelumallien käyttö sinällään ei ole välttämätöntä tai aina edes hyödyllistä, mutta oikein ja harkiten käytettyinä ne parantavat ohjelmiston laatua. Yleisiä ongelmia suunnittelumallien käytössä ovat suunnittelun pirstoutuminen suureksi määräksi luokkia ja monimutkaisiksi suhteiksi niiden välillä sekä suorituskykyongelmat johtuen kasvavasta dynaamisen sidonnan määrästä. Yleinen suunnittelumallien ongelma on myös se, että kommentoimattomina ne eivät mitenkään näy koodissa, koska ohjelmointikielet eivät tue niitä. 5.8 Harjoitustehtäviä 5.1 Suunnittele järjestelmä, jossa käytetään erilaisia graafisia symboleita (ympyrä, kolmio, neliö, piste) sekä niiden muodostamia erilaisia ryhmiä. Esitä UML-luokkakaavio, jonka mukaisella ratkaisulla voit kohdella sekä kuvioryhmiä että yksittäisiä symboleita samalla tavalla. Operaatiot, jotka tulee ottaa huomioon, ovat siirto, piirtäminen ja kuvan poisto (voidaan toteuttaa piirtämällä kuvio taustan värillä). Mitä suunnittelumalleja olet soveltanut ratkaisussasi? 5.2 Oletetaan, että halutaan suunnitella yleinen ohjelmistoalusta (tuoterunko), jonka päälle voidaan helposti rakentaa erilaisia vuokrausjärjestelmiä (autonvuokraus, videovuokraus, kone-
66 120 Ohjelmistoarkkitehtuurit Suunnittelumallit 121 vuokraus,...). Vuokrausjärjestelmään ajatellaan kuuluvan aina mm. käsitteet asiakas, vuokrauskohde, vuokratoimipiste, varasto, asiakastili. Nämä toteutetaan kussakin vuokrausjärjestelmässä komponenteilla, jotka noudattavat samoja rajapintoja. Alustan on annettava tukea uusien käsiteilmentymien luomiselle ajoaikana (esim. uuden asiakkaan luomiselle käyttöliittymästä annettujen tietojen perusteella). Esitä, miten Abstrakti tehdas -mallia voidaan soveltaa tässä ohjelmistoalustassa takaamaan, että alusta voi luoda käsiteilmentymiä yhtenäisellä tavalla. 5.3 Selitä Rakentaja-suunnittelumalli (Builder). Käsittele lyhyesti ainakin mallin motivointi, rakenne ja seuraukset. Voit käyttää lähteenä esimerkiksi Gamman kirjaa (ks. kirjallisuusluettelo) tai webbiä. Miten tämä suunnittelumalli liittyy Rekursiokooste-malliin? 5.4 Oletetaan, että sähköpostijärjestelmässä halutaan pystyä luomaan osoitelistoja, jotka voivat koostua yksittäisistä osoitteista tai muista osoitelistoista. Osoitteeseen tai osoitelistaan voidaan liittää yksiselitteinen lyhennenimi. Järjestelmässä on komponentti Sender, jolla on viestin lähetyksen suorittava operaatio send; tämä operaatio saa parametrikseen viestin ja osoitetiedot (osoitelista tai yksittäinen osoite). Toisaalta järjestelmässä on komponentti AddressBook, joka huolehtii osoitteista ja niiden lyhennenimistä, erilaisista osoitehauista ym. Komponentilla on mm. operaatio addaddressitem, jonka avulla osoitetietoja voidaan lisätä, ja operaatio getaddressitem, joka palauttaa tietyllä lyhennenimellä olevan osoitteen/osoitelistan. Osoitteita/osoitelistoja voidaan lukea mm. käyttäjältä tekstikentistä tai ascii-tiedostoista, joissa osoitelistat annetaan tietyllä formaatilla. Komponentti Control huolehtii järjestelmän ohjauksesta ja käyttäjän komentojen suorituksesta. Suunnittele järjestelmä siten, että sen rajapinnat ja mahdollisimman suuri osa komponenteista eivät riipu siitä, tuetaanko osoitelistoja vai ei. Esitä ratkaisu UML-luokka- ja komponenttikaavioina. Käytä stereotyyppejä identifioimaan suunnittelumallien mukaiset roolit. 5.5 Täysin sulutettu aritmeettinen lauseke koostuu yhteenlasku (+) ja kertolaskuoperaattoreista (*), kokonaisluvuista, muuttujanimistä sekä sulutetuista alilausekkeista tuotossäännön Expr -> (Expr+Expr) (Expr*Expr) identifier number mukaisesti. Esimerkkilauseke voisi olla vaikkapa ((1+Y)+(23+(8*X))). Anna tällaisen aritmeettisen lausekkeen olioesityksen rakennemalli UML:n luokkakaaviona. Sovella Rekursiokoostesuunnittelumallia (Composite). 5.6 Lisää Rekursiokooste-mallin kuvaukseen ratkaisu-kohtaan sekvenssikaavio, joka kuvaa malliin liittyvän ajoaikaisen vuorovaikutuksen tyypillisessä esimerkkitilanteessa. 5.7 Oletetaan, että henkilöt muodostavat ruokakuntia ja että ruokaavustuksia jaetaan sekä yksittäisille henkilöille että ruokakunnille. Ruokakunta koostuu periaatteessa mielivaltaisesta joukosta (oletettavasti sukulaisuussuhteessa olevia) henkilöitä tai aliruokakuntia. Ruoka-avustusten jakaja ei halua tietää avustusten kohteesta muuta, kuin että kohteelta voi kysyä sen kokoa (henkilöissä) sekä avun tarvetta ja että kohde voi ottaa vastaan avun. Henkilön avun tarve riippuu henkilön iästä jonkin kaavan mukaisesti. Anna UML-luokkakaavio henkilöille ja ruokakunnille Rekursiokooste-suunnittelumallin mukaisesti. 5.8 Yliopiston kurssien suoritus on prosessi, jonka eri kohdissa voidaan soveltaa erilaisia hyviksi koettuja käytäntöjä. Kuvaa jokin itse hyväksi kokemasi kurssien suoritukseen liittyvä käytäntö suunnittelumallien esimerkin mukaan. Noudata suunnittelumallien formaattia niin pitkälle kuin on järkevää ja muuta sitä tarpeen mukaan. Anna suunnittelumallille kuvaava nimi, esitä sen ratkaisema ongelma ja siihen liittyvät vaatimukset, kuvaa ongelman ratkaisu ja siihen liittyvät elementit sekä ratkaisun seuraukset.
67 122 Ohjelmistoarkkitehtuurit
68 OSA III Arkkitehtuuri suuressa mittakaavassa
69 6 Arkkitehtuurityylit Arkkitehtuurityylit ovat pohjimmiltaan suunnittelumallien idean yleistys koko järjestelmän arkkitehtuurin kantavaksi periaatteeksi (Shaw ja Garlan 1996). Itse asiassa ei ole aina aivan selvää, milloin tietty ratkaisuperiaate on suunnittelumalli ja milloin arkkitehtuurityyli, erityisesti silloin kun jokin suunnittelumalli esimerkiksi Tarkkailija-malli yleistetään arkkitehtuurin perustaksi. Vaikka tällä ei yleensä ole kovinkaan suurta merkitystä, periaatteellisena erona voidaan pitää sitä, että suunnittelumalli esiintyy järjestelmässä usein monena ilmentymänä ratkaisten erilaisia paikallisia suunnitteluongelmia yhdenmukaisesti, kun taas arkkitehtuurityyli määrää järjestelmän kokonaisrakenteen. Tässä luvussa tutustumme yleisimpiin arkkitehtuurityyleihin, niiden yleisiin käyttötarkoituksiin ja mahdollisuuksiin arkkitehtuurisuunnittelussa sekä niiden kuvaamiseen UML:ää käyttäen. 6.1 Arkkitehtuurityyli ryhmittelyn perustana Usein arkkitehtuurityyliä käytetään apuna varsinaisen järjestelmän hahmottamisessa. Tällöin arkkitehtuurityylin mukaisessa järjestelmässä joukko komponentteja on samassa roolissa tyylin kannalta. Siksi tällaiset arkkitehtuurityylit voidaankin ymmärtää paitsi varsinaisen toteutuksen rakenteen selityksenä myös ryhmittely- tai hahmotustekniikkana. Tärkeimmät ryhmittelyyn käytetyt arkkitehtuurityylit ovat kerrosarkkitehtuurit ja tietovuoarkkitehtuurit. Näistä erityisesti kerrosarkkitehtuuria voidaan käyttää lähes minkä tahansa järjestelmän kuvaamisessa.
70 126 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit Kerrosarkkitehtuurit Kerrosarkkitehtuuri koostuu tasoista, jotka on järjestetty jonkin abstrahointiperiaatteen mukaan nousevaan järjestykseen. Usein tämä abstrahointiperiaate tarkoittaa skaalaa laite/ihminen: laite-päässä olevat tasot ovat matalammalla abstraktiotasolla kuin ihmistä lähellä olevat tasot. Edelliset tyypillisesti tarjoavat laitetta tai käyttöjärjestelmää lähellä olevia primitiivisiä toimintoja, jälkimmäiset taas graafisen käyttöliittymän sovellusalueeseen liittyvine palveluineen. Käytännössä kerrokset voivat kuitenkin olla luonteeltaan niin teknisiä, että abstraktiotasoja on vaikea tunnistaa. Tällöin kerrostusperiaatteen ratkaisee se, että ylempi kerros käyttää hyväkseen alemman palveluja: abstraktiotasoltaan korkeammat palvelut toteutetaan käyttäen abstraktiotasoltaan matalampia palveluja. Kerrokset voidaan näin identifioida (tai ainakin tarkentaa) myös jälkeenpäin käyttösuhteiden mukaan. Kerrosarkkitehtuuria voidaan edellä esitetyn mukaisesti ajatella myös tapana nähdä järjestelmä organisoituna käyttösuhteiden mukaan jaoteltuihin osiin, jotka koostuvat samassa asemassa käyttösuhteiden kannalta olevista komponenteista. Kerrosarkkitehtuuri on siis toisaalta järjestelmän rakentamista helpottava dekompositioperiaate, mutta toisaalta myös sen ymmärtämistä helpottava ryhmittelyperiaate. Kerrosarkkitehtuurin perusajatus Kerrosarkkitehtuurin perusajatus siis on, että tietyllä tasolla oleva komponentti tai yksittäinen palvelu toteutetaan käyttäen hyväksi alemman kerroksen tarjoamia komponentteja tai palveluja. Eri syistä johtuen tästä perusideasta joudutaan kuitenkin yleensä aina poikkeamaan, ja puhdas kerrosarkkitehtuuri on itse asiassa varsin harvinainen. Poikkeamia on kahdenlaisia: palvelukutsu voi kulkea alemmasta kerroksesta ylempään (hierarchy breach), tai palvelukutsu voi ohittaa kerroksia kulkiessaan ylhäältä alas (bridging). Kerrosten ohittaminen on usein tarpeen tehokkuussyistä, sillä usein palvelu löytyy tehokkaampana alemmilta kerroksilta. Ohitus voi olla myös tarpeen yksinkertaisesti siitä syystä, että kyseistä palvelua ei ole suoraan saatavilla välittömästi alemmalta kerrokselta. Kerrosten ohittamista ei yleensä pidetä vakavana kerrosarkkitehtuurista poikkeami- Ei ohituksia Ohituksia Kuva 6.1: Kerrosarkkitehtuurin graafisia esityksiä sena, joskin runsaasti käytettynä sekin saattaa johtaa kerrosarkkitehtuurin idean heikentymiseen. Kutsun kulkeminen alemmasta ylempään kerrokseen on sen sijaan vakava kerrosarkkitehtuurin ongelma, jos siitä seuraa, että alempi kerros tulee riippuvaiseksi ylemmästä. Joissakin tapauksissa on kuitenkin välttämätöntä, että alempi kerros kutsuu ylempää. Tämä tapahtuu tilanteissa, joissa alemman kerroksen on sovitettava oma palvelunsa ylemmän kerroksen mukaan, ja siksi oman palvelunsa aikana kutsuttava koodia, joka sisältyy ylempään kerrokseen. Tämä on tyypillinen takaisinkutsutilanne, ja tällainen kutsu onkin toteutettava takaisinkutsuperiaatteella (ks. kohta 4.7), jotta alempi kerros ei tulisi riippuvaiseksi ylemmästä. Alempi kerros tarjoaa jonkin rekisteröintioperaation, jota käyttäen ylempi kerros rekisteröi koodinsa alemman kerroksen käyttöön. Kerrosarkkitehtuurien kuvaaminen Ohituksia Kerrosarkkitehtuurien havainnollistamiseen käytetään erilaisia kuvaustapoja (kuva 6.1). Suosituin on kerrosvoileipämuoto, jossa kerroksen ohitus voidaan kuvata porrastamalla. Tällainen esitys voidaan tehdä myös ympyrämuotoisena, jolloin kerrosvoileivän päät taivutetaan yhteen. Kullakin kerroksella on joukko rajapintoja, jotka kerros toteuttaa, ja toinen joukko rajapintoja, jotka kerros tarvitsee (kuva 6.2). Ylemmän kerroksen tarvitsemien rajapintojen tulisi täsmätä alem-
71 128 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 129 Kerros A Erikoistamisulottuvuus Komp1 Komp2 Komp3 Core GUI Default GUI App GUI GUI Looginen ulottuvuus Kerros B Core Logic Default Logic App Logic Logic Komp4 Komp5 Komp6 Kuva 6.2: Kerrosten välisen rajapinnan käyttö- ja tarjontasuhde Käyttöliittymä Sovelluslogiikka Sovellusaluelogiikka Tietokanta Kuva 6.3: Esimerkki kerrosarkkitehtuurin käytöstä Core Suppport Default Support App Support Support Kuva 6.4: Kaksiulotteinen kerrosarkkitehtuuri avulla, hajautus ym.) tarjoava kerros, sen yläpuolella on sovellusalueen logiikan ja käsitteet toteuttava kerros, sitten yksittäisen sovelluksen logiikan toteuttava kerros, ja ylimpänä yksittäisen sovelluksen käyttöliittymän toteuttava kerros. Esimerkiksi vakuutusjärjestelmässä sovellusaluekerros sisältää kaikille vakuutuksille yhteiset asiat, kuten asiakas, laskutus jne., kun taas sovelluskerros sisältää vaikkapa liikennevakuutuslogiikan. Tällainen arkkitehtuuri tarjoaa mahdollisuuden uudelleenkäyttää kahta alinta kerrosta, kun tehdään uusi vakuutussovellus. Toisaalta, olettaen että kerrosten rajapinnat on hyvin määritelty, järjestelmä voidaan vaihtaa toisen infratuen (esim. tietokannan) päälle toteuttamalla alin kerros uudestaan. Kerrosarkkitehtuurin ulottuvuudet man kerroksen tarjoamien rajapintojen kanssa. Jos tällaisia rajapintoja ei ole selkeästi määritelty, ne voidaan saada selville irrottamalla kerros järjestelmästä ja tutkimalla, mitkä palvelut jäävät ylemmän kerroksen kannalta ilman toteuttajaa, ja mitkä palvelut jäävät irrotetun kerroksen kannalta ilman toteuttajaa. Kun kerroksen tarjoamat ja tarvitsemat rajapinnat on selvillä, kerroksen toteutus voidaan vaihtaa toiseksi ilman, että se vaikuttaa muuhun järjestelmään. Tyypillinen esimerkki kerrosarkkitehtuurista on liiketoimintajärjestelmä, joka on jaettu neljään kerrokseen (kuva 6.3). Alimpana on yleistä infrastruktuuritukea (esim. tiedon pysyvyys tietokannan Kerrosarkkitehtuuri on joskus moniulotteinen siinä mielessä, että kerrostus voidaan ajatella useampaan kuin yhteen suuntaan (kuva 6.4). Kaksiulotteinen tapaus on yleinen erityisesti erilaisten ohjelmaalustojen yhteydessä: silloin dimensiot ovat tavallisesti "looginen" (perinteinen, sovelluksen tai sovellusalueen logiikkaan tai toteutustekniikkaan perustuva kerrosjako) ja "yleisyys" (kuinka yleiskäyttöisiä komponentit ovat). Tämän esittäminen graafisesti yksinkertaisella tavalla voi osoittautua monimutkaiseksi, sillä lukijalla on usein ennakko-odotuksia siitä, mihin suuntaan abstraktimmat/konkreettisemmat kerrokset pitäisi sijoittaa.
72 130 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 131 rivikerros riippuu sarakekerroksesta P1 P2 P3 P4 P5 P1 P2 X Kuva 6.5: Kerrosarkkitehtuurin riippuvuusmatriisi Kerrosarkkitehtuurin riippuvuuksia kannattaa usein analysoida vaikkapa kuvan 6.5 kaltaisen matriisin avulla, varsinkin silloin, kun kerrosjako ja kerrosten keskinäiset riippuvuudet eivät ole vielä täysin selkiintyneitä. Kun matriisin alkioon laitetaan risti, jos rivikerros riippuu sarakekerroksesta, ristien pitäisi hyvässä kerrosarkkitehtuurissa sijaita lävistäjän yläpuolella mieluiten kapeahkona vyöhykkeenä (jos kerrosten järjestys on ylhäältä alas). Tällöin arkkitehtuurissa on vain vähän ohituksia eikä väärään suuntaan meneviä riippuvuuksia. Väärään suuntaan menevä riippuvuus näkyy lävistäjän toisella puolella olevana ristinä. Jos symmetrinen positio on vapaa, voidaan harkita kerrosten järjestyksen vaihtamista. Kerrosarkkitehtuurin soveltaminen P3 Kerrosarkkitehtuuri on yleinen malli, jota voidaan soveltaa lähes kaikissa järjestelmissä pienemmässä tai isommassa mittakaavassa. Se jakaa järjestelmän korkealla tasolla karkeasti osiin, joiden muodostamana kokonaisuutena järjestelmä on helpompi ymmärtää, ja toisaalta kukin osa voidaan ymmärtää myös omana kokonaisuutenaan. Kerrosarkkitehtuuri on riittävän intuitiivinen ja helposti ym- X X P4 X X P5 X X märrettävä, että sitä voidaan käyttää kommunikoinnin tukena keskusteltaessa järjestelmästä hyvin erilaisten ihmisten (markkinointi, johto, käyttäjät) kanssa. Kerrosarkkitehtuuri ohjaa ohjelmiston riippuvuuksia minimoivaan suunnitteluun, sillä kerrokset riippuvat ihannetapauksissa ainoastaan alemmista kerroksista. Tästä taas seuraa, että järjestelmää on helpompi muuttaa ja ylläpitää. Kukin kerros toteuttaa oman abstraktiotasonsa, johon tietyt toteuttajat, testaajat ja ylläpitäjät voivat erikoistua. Kerrosjako toimii usein organisaation jaon perustana aikaisemmin mainitun Conwayn lain mukaisesti: merkittävillä kerroksilla on oma niistä huolehtiva yksikkönsä. Kerrosarkkitehtuuri tukee myös ohjelmiston uudelleenkäyttöä: uusia sovelluksia voidaan rakentaa alempien kerrosten päälle, ja sovellus voidaan siirtää toiseen ympäristöön toteuttamalla uudestaan ympäristön (esim. käyttöjärjestelmän) alempi abstrahointikerros. Kerrosarkkitehtuurin yleinen ongelma on tehokkuushäviö. Kun tarvittava palvelu pyritään saamaan seuraavaksi alemmalta kerrokselta, se ei tule aina tehokkaimmassa mahdollisessa muodossa. Palvelua joudutaan usein siirtämään alaspäin kerroksissa, jolloin toiminnasta tulee tarpeettoman epäsuoraa. Jos palvelun parametrina välitetään tietoa, tämä tieto joudutaan joskus analysoimaan uudelleen kullakin tasolla mentäessa ylhäältä alaspäin, jolloin tehdään moninkertaista työtä. Joskus kerrosjako voi aiheuttaa epätietoisuutta jonkin komponentin sijoituspaikasta, jos sen tehtävä ei selvästi liity mihinkään kerrokseen. Poikkeusten käsittely on myös kerrosarkkitehtuuriin liittyvä yleinen ongelma. Järjestelmän toiminnat aktivoituvat tyypillisesti ylimmästä kerroksesta (käyttöliittymä) lähtevästä palvelupyynnöstä, joka aiheuttaa alaspäin siirtyvän kutsuketjun. Jos jollakin alimmista kerroksista huomataan virhetilanne ja synnytetään poikkeus, tämä poikkeus palaa kutsuketjua pitkin ylöspäin kunnes sille löytyy käsittelijä. Ongelmana on, että poikkeus käsitellään näin ylemmällä tasolla kuin millä se on syntynyt, jolloin käsittelijä ei välttämättä enää pysty korjaamaan tilannetta. Ääritapauksessa voi esimerkiksi alimmalta tasolta lähtöisin oleva poikkeus edetä aina järjestelmän käyttäjälle saakka, jolle poikkeuksen kuvaava virheilmoitus on täysin mystinen.
73 132 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 133 Tietovuoarkkitehtuuri koostuu prosessointiyksiköistä (filter) ja niitä yhdistävistä tietovirtaa kuljettavista väylistä (pipe), joiden roolit ovat aktiivinen tiedon käsittely ja passiivinen tiedon kuljetus. Kukin prosessointiyksikkö toimii periaatteessa itsenäisesti lukemalla omaa syötevirtaansa ja tuottamalla omaa tulostevirtaansa. Yhdistämällä yhden yksikön tulostevirta seuraavan syötevirtaan saadaan aikaan koottu tietovirran prosessointi. Arkkitehtuuria on havainnollistettu kuvassa 6.6. Ilmeisin esimerkki tämän tyylin soveltamisesta on Unixin sallima prosessien tulosten siirtäminen seuraavan prosessin syötteeksi putkia (pipe) käyttäen, mutta tyylillä on myös muita sovelluskohteita. Tietovuoarkkitehtuurin soveltaminen edellyttää, että kukin prosessointiyksikkö voidaan toteuttaa itsenäisenä, omaa syötettään lukevana ja omaa tulostettaan tuottavana yksikkönä, joka ei riipu muista yksiköistä. Näillä yksiköillä ei saisi olla jaettua tilatietoa, eikä niiden tarvitse tuntea tosiaan: ne riippuvat vain oman syötteensä muodosta. Lisäksi prosessoinnin tulisi tapahtua yhdessä vaiheessa siten, että tietyn tietoalkion prosessointi ei saisi riippua jonkin tulevan tietoalkion prosessoinnista. Jos se riippuu, asia voidaan periaatteessa hoitaa muodostamalla yksikköön sisäinen puskuri, johon tiefilter pipe pipe filter filter pipe Kuva 6.6: Tietovuoarkkitehtuuri Tietovuoarkkitehtuuri Tietovuoarkkitehtuuri (pipes-and-filters architecture) sopii järjestelmiin, joiden toiminta on olennaisesti tietovirran jalostamista ja prosessointia. Tietovuoarkkitehtuurin perusrakenne pipe filter pipe filter tovirtaa luetaan kunnes odotettu tietoalkio saadaan, jonka jälkeen tietovirtaa aletaan lukea puskurista. Tällainen järjestely kuitenkin rikkoo arkkitehtuurin perusidean, jolloin esimerkiksi prosessointiyksiköiden järkevä rinnakkaistaminen tulee vaikeammaksi. (Ääritapauksessa kukin yksikkö voisi lukea aina koko virran ensin omaan puskuriinsa, ja prosessoida sen siellä mielivaltaisessa järjestyksessä, mutta tällöin koko "tietovirran" käsite häviää, koska tieto välitetään yksiköiden välillä yhtenä isona rakenteena.) Tietovuoarkkitehtuurin yksinkertaisin ja yleisin muoto on liukuhihna-arkkitehtuuri (pipeline architecture), jossa tietovirta etenee haarautumatta yhtä prosessointiyksiköiden ketjua pitkin. Tällöin tiedonvälitys voidaan toteuttaa synkronisena periaatteessa kahdella tavalla, joko työntämällä tai vetämällä tietoa. Työntämisvaihtoehdossa tiedon alkuperäinen tuottaja kutsuu ensin ensimmäistä prosessointiyksikköä antamalla parametrina ensimmäisen tietoalkion. Yksikkö tuottaa sen perusteella oman tuotosalkionsa ja antaa sen parametriksi seuraavalle yksikölle. Lopulta viimeinen yksikkö kutsuu tietovirran lopullista käyttäjää viimeisen yksikön tuottama tietoalkio parametrina. Tätä toistetaan, kunnes koko tietovirta on käsitelty. Vetämisvaihtoehdossa tietovirran lopullinen käyttäjä pyytää ensin viimeiseltä prosessointiyksiköltä tulosalkiota. Tämä puolestaan pyytää sitä edelliseltä yksiköltä alkiota jne., kunnes ensimmäinen yksikkö pyytää tiedon lähteeltä ensimmäistä alkiota. Tämä saa sen ja tuottaa sen perusteella oman tuotosalkionsa palauttaen sen sitä pyytäneelle edelliselle yksikölle jne., kunnes lopulta viimeinen yksikkö pääsee palauttamaan oman tuotosalkionsa tietovirran käyttäjälle. Tätä toistetaan, kunnes koko tietovirta on käsitelty. Tietovuoarkkitehtuurin toteutusnäkökohtia Tietovuoarkkitehtuuri tarjoaa suoraviivaisen tavan rinnakkaistaa järjestelmä: kukin prosessointiyksikkö voi periaatteessa toimia omana prosessinaan rinnakkain muiden kanssa. Tämä voi olla hyödyllistä laskennan rakenteen hahmottamiseksi, järjestelmän tehostamiseksi, tai yksinkertaiseksi siksi, että prosessointiyksiköt joka tapauksessa toimivat eri koneissa verkon yli. Rinnakkaistetun tietovuoarkkitehtuurin tehostamiseksi tietoväylät toteutetaan tavallisesti puskurin avulla, jolloin peräkkäin toimivien yksiköiden ei tarvitse toimia samaan tahtiin tietoalkioiden
74 134 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 135 käsittelyn suhteen. Puskuri voi olla kooltaan joko rajoitettu tai rajoittamaton. Rajoittamaton puskuri voi johtaa hyvin suuriin tilavaatimuksiin, jos käsiteltävä tietovirta koostuu paljon tilaa vievistä tietoalkioista. Voimakkaasti rajoitettu puskuri saattaa toisaalta vähentää rinnakkaisuuden astetta, sillä prosessit joutuvat useammin odottamaan. Sopiva puskurien koko löydetään yleensä kokeilemalla. Jos prosessointiyksiköt toimivat likimain samalla nopeudella, rinnakkaistaminen johtaa ihannetapauksessa koko järjestelmän toimimiseen samassa ajassa kuin yksi prosessointiyksikkö. Käytännössä tähän ei kuitenkaan päästä. Puskuri on useimmiten rakenteeltaan jono (fifo), mutta myös käsitteellisesti monimutkaisempia rakenteita voidaan käyttää (esim. puita). Olennaista on vain, että seuraava yksikkö pystyy tunnistamaan puskurista uudet tuotetut, käsittelemättömät osat. Esimerkki: ohjelmointikielen kääntäjä Eräs hivenen erikoisempi esimerkki tietovuoarkkitehtuurista on ohjelmointikielen kääntäjä. Siinä ongelmana on muuntaa jono merkkejä (lähdekielinen esitys) suoritettavaksi konekoodiksi. Tällainen muunnos on niin kompleksinen, että sitä olisi hyvin vaikea ymmärtää ja hallita kokonaisuutena. Niinpä se jaetaan osiin, joista kullekin voidaan kehittää oma sitä tukeva käsitteistö ja teoria. Osat ovat tyypillisesti selaaja (joka muuntaa merkkijonon jonoksi kielen alkioita, kuten tunnisteiksi, erikoissymboleiksi jne.), jäsentäjä (joka muuntaa jonon kielen alkioita syntaksipuuksi) ja semanttinen osa (joka muuntaa syntaksipuun suoritettavaksi koodiksi). Tavallisesti jälkimmäinen osa jaetaan vielä pienempiin osiin: semanttiseksi analysoijaksi, joka varustaa syntaksipuun semanttisella informaatiolla, koodin generoijaksi, joka tuottaa puusta välikoodiesitystä, koodin optimoijaksi, joka tuottaa optimoitua välikoodiesitystä, ja koodin generoijaksi, joka tuottaa varsinaista kohdekoodia. Vaikka kääntäjä on hyödyllistä ymmärtää käsitteellisesti tietovuojärjestelmäksi, käytännössä sitä ei kuitenkaan yleensä toteuteta edellä kuvatuilla tavoilla puhtaana tietovuoarkkitehtuurina. Tämä johtuu mm. siitä, että eri vaiheilla on kääntäjän tapauksessa yhteistä tilatietoa (mm. symbolitaulu), joka rikkoo puhtaan tietovuoarkkitehtuurin. Usein jäsentäjä toimii pääohjelmana, joka kutsuu tarvittaessa muita osia. Toisaalta vaiheet erotetaan lähes aina loogisesti toisis- taan, minkä ilmaisemiseen tietovuoarkkitehtuuri tarjoaa oivan apuvälineen. Tietovuoarkkitehtuurin soveltaminen Tietovuoarkkitehtuurien merkittävin etu on se, että mutkikas tiedon prosessointi voidaan tehdä asteittain, jalostamalla tietoa askel kerrallaan. Näin monet tiedonkäsittelytehtävät, jotka sellaisinaan olisivat hyvin vaikeita, voidaan ymmärtää ja toteuttaa hallitusti. Hyvä esimerkki on aikaisemmin mainittu kääntäjä: lähdekoodin muuttaminen suoritettavaksi konekoodiksi olisi äärimmäisen vaikea toteuttaa yhdessä askeleessa. Toinen tärkeä etu on tietovuoarkkitehtuurin mahdollistama varianssi. Prosessointiyksiköitä voidaan kombinoida monin tavoin: olennaista on vain, että yksiköt ymmärtävät omaa syötevirtaansa. Tietty yksikkö voidaan helposti korvata toisella, kunhan vain uusi yksikkö tunnistaa samaa tietovirtaa. Arkkitehtuuri tarjoaa myös luonnollisen tavan rinnakkaistaa järjestelmä. Tietovuoarkkitehtuuri ei sovi interaktiivisiin järjestelmiin, eikä sitä ole sellaisiin tarkoitettukaan. Tietovuoarkkitehtuuriin perustuvassa järjestelmässä ei ole sellaista globaalia tilaa, jonka näyttäminen käyttäjälle vuorovaikutusta varten olisi mielekästä. Ylipäänsä globaalin tiedon jakaminen prosessointiyksiköiden kesken sopii huonosti tietovuoarkkitehtuurin ideaan. Tiedon välittäminen yksiköiden välillä voi joskus johtaa tarpeettoman työhön: jos tiedon esitys väylissä halutaan pitää suhteellisen korkealla tasolla (esim. standardointi- ja yhteensopivuussyistä), yksiköt joutuvat aina tuottamaan korkean tason esitystä ja sitten taas analysoimaan sitä. Jos esimerkiksi sovitaan standardointisyistä, että tieto välitetään XML-muodossa, joutuvat yksiköt moneen kertaan generoimaan ja analysoimaan XML:ää, mistä voi tulla tehokkuusongelma. Virheiden käsittely voi olla tässä arkkitehtuurissa ongelma hieman samaan tapaan kuin kerrosarkkitehtuurissa. Jos halutaan, että prosessointiyksiköt toipuvat tietovirrassa olevista virheistä ja pystyvät jatkamaan tietovirran käsittelyä niiden jälkeenkin, tarvitaan erityisiä virheestätoipumistekniikoita. Tällaiset voivat perustua esimerkiksi nk. turvallisten (safe) tietoalkioiden määrittelyyn: kun tietovirrassa havaitaan virhe, prosessointiyksikkö ohittaa tietoalkioita seuraavaan turvalliseen tietoalkioon saakka, jonka jälkeen prosessoin-
75 136 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 137 nin oletetaan voivan jatkua normaalisti. Varsinkin kääntäjien yhteydessä tällaisia tekniikoita on tutkittu laajasti. 6.2 Palveluperustaiset arkkitehtuurityylit palvelin1 palvelin2 Palveluperustaiset arkkitehtuurityylit on rakennettu siten, että niissä voidaan ajatella olevan kahdenlaisia rooleja: palvelun tarjoajat ja niiden käyttäjät. Roolit eivät kuitenkaan yleensä ole tiukat, vaan jonkin palvelun tarjoaja voi toimia jonkin toisen palvelun käyttäjänä. Usein palvelun ajatus perustuu johonkin resurssiin, jonka palveluita sen ympärille rakennettu ohjelmistokomponentti voi tarjota ympäristölleen. Käytännössä tällaisella komponentilla voidaan myös valvoa resurssin käyttöä suoraviivaisesti, mikä usein toimii tämän lähestymistavan valintaperusteena. Vastaavasti muut resurssiin vaikuttavat toiminnot, kuten esimerkiksi varmuuskopiointi, on usein helpompi toteuttaa keskitetysti. Palveluperustaisuus voidaan yhdistää esimerkiksi kerrosarkkitehtuuriin, kun palveluita halutaan ryhmitellä. Tällöin tiettyjen palveluiden tarjoaminen yhdistetään suoraan johonkin arkkitehtuurin kerrokseen. Tämä määrittelee yleensä sääntöjä sille, miten palveluita on mahdollista käyttää Asiakas-palvelin-arkkitehtuurit Asiakas-palvelin-arkkitehtuuri on tällä hetkellä ehkä kaikkein yleisimmin käytetty arkkitehtuuriratkaisu. Perusajatuksena on kapseloida tietyn arkkitehtuuritason resurssin hallinta (palvelin) siten, että resurssin käyttäjien (asiakkaiden) ei tarvitse huolehtia resurssin käyttöön liittyvistä teknisistä ongelmista kuten poissulkemisesta, vaan ne voivat pyytää tiettyä resurssiin liittyvää palvelua palvelimelta vapaasti riippumatta muista asiakkaista (kuva 6.7). Siksi asiakas-palvelin-arkkitehtuuri voidaankin mieltää olioparadigmaa vastaavaksi arkkitehtuuritason ratkaisuksi. Asiakas-palvelin-arkkitehtuurin perusrakenne Tyypillisesti joskaan ei välttämättä vuorovaikutus asiakkaan ja palvelimen välillä tapahtuu istunnon (session) puitteissa: istunnon asiakas1 asiakas2 Kuva 6.7: Asiakas-palvelin-arkkitehtuuri aikana suoritetaan jokin asiakkaan kannalta mielekäs palvelukokonaisuus. Yleensä palvelimet odottelevat passiivisina, kunnes jokin asiakas ottaa niihin yhteyttä. Kun asiakas on saanut tehtävänsä hoidetuksi, se päättää istunnon. Istuntoon voi sisältyä transaktioita, joiden eheydestä ja peruuttamisesta palvelin tarpeen mukaan huolehtii. Yleensä kommunikointi asiakkaan ja palvelimen välillä on täsmällisemmin säädeltyä kuin yksittäisten olioiden välillä. Lisäksi palvelin toimii aina omassa säikeessään tai prosessissaan, mikä pitää sen toteutuksen erillään asiakkaista. Jos palvelimen kuormitus kasvaa niin suureksi, että se hidastaa sovellusten toimintaa, voidaan palvelimen sisäinen toteutus muuttaa monisäikeiseksi tai moniprosessointia hyödyntäväksi ja siten kasvattaa kapasiteettia. Joskus palvelimen käytöllä pyritään myös sallimaan variaatiot jonkin resurssin käytössä siten, että variointi ei näy asiakkaille. Palvelin voidaan esimerkiksi erikoistaa plugin-komponenteilla, jotka johtavat eri tavoin toteutettuun resurssin käyttöön. Asiakas-palvelin-arkkitehtuurin soveltaminen asiakas3 Asiakas-palvelin-arkkitehtuurin ehkä suurin etu on selkeä työnjako, joka voi toimia pohjana myös hajauttamiselle. Tässä yhteydessä syntyy tosin helposti huomattavaakin joutokäyntiä, sillä esimerkiksi etämetodikutsu on huomattavasti hitaampi operaatio kuin paikallinen proseduurikutsu. Hajautettuja järjestelmiä tarkasteltaessa voidaan havaita, että monet tietovarastoa hyväksikäyttävät järjestelmät perustuvat asiakas-palvelin-arkkitehtuuriin. Tällöin palvelin hallitsee
76 138 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 139 tietovaraston käyttöä, ja siihen pääsee käsiksi vain palvelimen kautta. Myös erilaiset hajautetut liiketoimintajärjestelmät ja niille tarkoitetut ohjelmistoalustat (esim. EJB, CORBA) soveltavat asiakas-palvelin-arkkitehtuuria. Syynä hajautettavuuteen on se, että yleensä asiakkaat ja palvelimet suunnitellaan toisistaan riippumattomiksi, ja jo etukäteen ajatellaan, että ne tulevat sijaitsemaan eri prosesseissa (tai prosessoreissa). Tästä syystä suunnitteluparadigman käyttö johtaa ratkaisuihin, joissa myös asiakkaiden ja palvelinten ongelmat voidaan eristää siten, että esimerkiksi jonkin asiakkaan toimiessa virheellisesti (esim. core dump) palvelin kykenee edelleen palvelemaan muita asiakkaita. Samaan tapaan palvelimen kaatuessa voi asiakas jatkaa toimintaansa häiriintymättä, tosin olettaen, että se voi tehdä jotakin muutakin kuin kommunikoida palvelimen kanssa. Tämän toteuttaminen käytännön ohjelmistoissa ei kuitenkaan ole helppoa. Lisäksi asiakkaiden poissulkeminen on yksinkertaista. Asiakas-palvelin-arkkitehtuuria ajatellaan tavallisesti hajautettuna järjestelmänä, ja usein asia näin onkin. Toisaalta idea siitä, että tietyn resurssin hallinta kapseloidaan palvelimelle, joka huolehtii esimerkiksi poissulkemisesta ja jonkin resurssin hallitusta käytöstä, ei ole mitenkään sidottu hajautukseen, vaan sitä voi käyttää missä tahansa ympäristössä eristämään jokin resurssi oman hallintayksikkönsä valvontaan. Esimerkiksi Symbian OS:n arkkitehtuurissa asiakas-palvelinideaa sovelletaan siinä, miten monet järjestelmän resurssit näkyvät ohjelmoijalle, vaikka kyse on yhden laitteen sisäisestä arkkitehtuurista: Lähestulkoon kaikki laite- ja ohjelmistorajapinnat on kapseloitu omaksi palvelimekseen. Siten vaikkapa tiedonsiirtoon liittyvät Serial Communications Server (sarjaportti), Telephony Server (puhelinominaisuudet), Socket Server (socket-rajapinta) ja Messaging Server (asynkroninen kommunikaatio) on luonnollista toteuttaa omina palveliminaan. Lisäksi Symbian-ympäristössä on lukuisia muita palvelimia, jotka eivät suoranaisesti liity tietoliikenteeseen, kuten esimerkiksi Window server (ikkunointi) ja Media server (multimediaominaisuudet). Etuna on lisäksi se, että palvelinta ei tarvitse linkittää yhteen sovelluksen kanssa, vaan ne voivat olla toisistaan täysin erillisiä Viestinvälitysarkkitehtuurit Oletetaan, että ollaan suunnittelemassa järjestelmää, johon tiedetään tulevan mukaan joukko keskenään kommunikoivia komponentteja, mutta komponenttien määrästä ja laadusta ei ole etukäteen tarkkaa tietoa, kuten ei myöskään komponenttien käsittelemän tiedon laadusta. Tällöin kiinteisiin staattisiin rajapintoihin perustuvan järjestelmän suunnittelu on vaikeaa ja riskialtista, koska ennen kehityksen aloittamista voi olla vaikea tarkkaan määritellä, mitä kaikkea pitäisi ottaa huomioon. Tällaiseen tilanteeseen sopii viestinvälitysarkkitehtuuri. Viestinvälitysarkkitehtuurin perusajatus Viestinvälitysarkkitehtuuri (Message dispatcher architecture, Implicit invocation architecture, Message bus architecture) tarkoittaa arkkitehtuuria, jossa joukko komponentteja kommunikoi keskenään keskitetyn viestinvälittäjän/väylän kautta. Keskeinen ero asiakaspalvelin-arkkitehtuuriin on, että viestinvälitysarkkitehtuurissa rooleja ei ole kiinnitetty. Viestinvälitysarkkitehtuurissa komponenteilla on yhteinen rajapinta, joka sisältää tarvittavat operaatiot viestien vastaanottamiseen. Viesti sisältää informaation, joka kertoo komponentille, mitä sen tulee tehdä. Tämä merkitsee tietyssä mielessä dynaamista rajapintaa: sisällöltään uudentyyppinen viesti ei muuta järjestelmän staattista rakennetta, mutta uusi komponentti voi käsitellä sen ja tuoda olennaisesti uutta toiminnallisuutta järjestelmään. Toteutus voidaan rakentaa esimerkiksi siten, että komponentit rekisteröityvät välittäjälle kertoen olevansa kiinnostuneita tietynlaisista viesteistä, ja välittäjä puolestaan toimittaa viestit komponenteille sitä mukaa kun niitä lähetetään, tai käyttämällä konfiguraatiotiedostoja, joiden perusteella viestien reititys on mahdollista. Joskus käytetään myös erilaisia jonoja tai postilaatikoita. Toimintaa on havainnollistettu kuvassa 6.8. Viestinvälitysarkkitehtuurin määrittelevät seuraavat ominaisuudet: keskenään kommunikoivien komponenttien joukko
77 140 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 141 Rekisteröityneet komponentit Luo viesti Viestinvälittäjä Viesti Kuva 6.8: Viestinvälitysarkkitehtuuri Yhteinen viestin vastaanottorajapinta Rekisteröityneet komponentit Tulkkaus ja toiminta viestit, joiden avulla komponentit kommunikoivat ilman, että viestin lähettäjä tietää minne viesti pitää toimittaa tai vastaanottaja mistä viesti on peräisin operaatiot, joilla komponentit reagoivat viesteihin säännöt, joiden avulla komponentit ja viestit rekisteröidään järjestelmälle säännöt, joiden avulla välittäjä tietää, mille komponentille viesti on lähetettävä rinnakkaisuusmalli: missä määrin komponentit ja välittäjä toimivat rinnakkain. Viestinvälitysarkkitehtuurit ovat yleistyneet monella alueella. Tämä kuvaa ohjelmistokehityksen joutuvan yhä enemmän ottamaan huomioon ennalta tuntemattomia vaatimuksia tai yleisyyttä ja helppoa laajennettavuutta koskevia vaatimuksia. Tällöin päädytään usein välttämään arkkitehtuurin sitomista voimakkaasti tiettyihin staattisiin rajapintoihin. Tässä suhteessa viestinvälitysarkkitehtuuri tarjoaa hyvän vaihtoehdon, sillä staattinen rajapinta koostuu yksinkertaisimmillaan komponenttien osalta viestin vastaanotosta ja viestinvälittimen osalta lähetyksestä ja rekisteröitymisestä. Varsinainen toiminta kootaan tämän perusrakenteen päälle käyttämällä viestejä laukaisemaan toimintoja eri komponenteissa. Olioparadigmaan tukeuduttaessa viestinvälitysarkkitehtuurissa päädytään joskus kätkemään varsinainen viestinvälitys ohjelmoijalta. Tällöin kommunikointi voi varsinaisten viestien sijaan näyttäytyä joukkona rajapintoja, jotka täytyy toteuttaa esimerkiksi pluginmoduuleja käyttäen, tai esimääriteltynä perintähierarkiana, jossa kantaluokat kätkevät varsinaisen viestinvälityksen. Tämä on yleistä varsinkin sovelluskehystä käytettäessä, jolloin kehys voi hoitaa varsinaisen viestinvälityksen, mutta ohjelmoijalle tarjotaan mahdollisuus toteuttaa uusia toimintoja johonkin tiettyyn tapahtumaan liittyen. Esimerkiksi graafisissa käyttöliittymissä tämä on hyvin tyypillinen toteutustekniikka. Ongelmia tässä lähestymistavassa voi tuottaa se, että aina ei ole aivan selvää, milloin voidaan käyttää suoraa metodikutsua ja milloin pitäisi nojata viestinvälitykseen. Syynä tähän on se, että viestinvälitys voidaan kapseloida niin syvälle sovelluskehykseen, että ohjelmoijan voi olla vaikea saada selville mitä kulissien takana tapahtuu. Viestinvälitysarkkitehtuurin soveltaminen Viestinvälitysarkkitehtuuriin on helppo lisätä uusia ja uudentyyppisiä komponentteja muuttamatta järjestelmää staattisesti, jopa ajoaikana. Komponentteja voidaan myös poistaa jopa ajoaikana: järjestelmä ei kaadu, vaikka joitakin viestejä ei enää otettaisi vastaan. Järjestelmän konfiguraatiosta tulee hyvin joustava ja dynaamisesti muodostettava. Arkkitehtuuri tukee sekä synkronista että asynkronista viestinvälitystä ja rinnakkaisuutta. Arkkitehtuurin ongelmana on viestien muodostamisesta ja tulkinnasta seuraava potentiaalinen tehottomuus. Järjestelmän ylläpitäminen voi myös olla tavallista vaikeampaa, koska järjestelmän ymmärtämistä eivät tue staattiset rajapinnat, vaan on ymmärrettävä myös viestien ajoaikainen rakenne ja sisältö. Itse asiassa voidaan jopa ajatella, että viestien ajoaikainen rakenne ja sisältö luovat uuden, abstraktiotasoltaan korkeamman arkkitehtuurin. Huonona puolena tyylistä voidaan ajatella sen joustavuuden mukanaan tuomaa suunnittelijan vastuuta esimerkiksi välitettävien viestimäärien suhteen. Samoin joissakin tilanteissa voidaan ajautua ongelmiin komponentteihin liitettyjen roolien vuoksi. Eri komponenttien rooleja ei nimittäin aina kunnioiteta uusia ominaisuuksia kiireessä toteutettaessa.
78 142 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit Sovellusaluekohtaiset arkkitehtuurityylit Joillekin sovelluksille on vakiintunut tietynlainen toteutustapa. Toteutusteknisesti sovellusaluekohtaista arkkitehtuurityyliä noudattava järjestelmä voidaan rakentaa palveluiden ja jonkinlaisen kerrostamisen varaan. Käsittelemme seuraavassa joitakin tällaisia arkkitehtuurityylejä. Model attach(observer) detach(observer) notify() getdata() service() attach() getdata() Observer update() * View Malli-näkymä-ohjain-arkkitehtuurit Malli-näkymä-ohjain-arkkitehtuurin (MVC, model-view-controller) perusajatus on erottaa käyttöliittymä varsinaisesta sovelluslogiikasta ja -datasta. Tällä pyritään siihen, että käyttöliittymää olisi helppo muuttaa ja että järjestelmä olisi helppo siirtää toíselle graafiselle alustalle. Lisäksi halutaan, että käyttöliittymä heijastaa aina sovellusdatan tilaa ja näyttää sen tarvittaessa erilaisissa näkymissä aina konsistenssissa, oikeassa muodossa. Malli-näkymä-ohjain-arkkitehtuurin perusajatus Tässä esitetty malli-näkymä-ohjain-arkkitehtuurin malli noudattaa Buschmannin kirjan esittelemää muotoa (Buschmann et al. 1996); mallista on olemassa hieman erilaisia versioita. Järjestelmä jaetaan kolmentyyppisiin osiin: malleihin (model), jotka edustavat jotakin osaa sovellusdatasta tai loogisesta sovelluksen tilasta, näkymiin (view), jotka edustavat jotain osaa näkyvästä käyttöliittymästä, ja ohjaimiin (controller), jotka toimivat eräänlaisina sovittimina mallien ja näkymien välissä pitäen huolta siitä, että ne vastaavat toisiaan (kuva 6.9). Tässä käytetään hyväksi Tarkkailija-suunnittelumallia (Observer): näkymät ja ohjaimet toteuttavat tarkkailijarajapinnan, jonka avulla ne voivat rekisteröityä jo(i)llekin mall(e)ille tarkkailemaan tämän muutoksia. Kun muutoksia on tapahtunut, näkymät ja ohjaimet voivat käydä kysymässä mallilta sen muuttunutta dataa. Malliolion velvollisuutena on hallita sovellusdataa sekä tarjota tätä dataa muuttavat loogiset sovellusoperaatiot. Malli tarjoaa operaatiot, joilla kiinnostuneet voivat ilmoittautua tarkkailemaan sen muutoksia, ja se ilmoittaa näille kun muutoksia on tapahtunut. Näkymä huolehtii siitä, että näyttö päivittyy vastaamaan mallin tilaa. initialize(model) makecontroller() activate() display() update() Kuva 6.9: Malli-näkymä-ohjain-arkkitehtuuri Luonnollisesti samaan malliin voi liittyä useita erilaisia näkymiä riippuen sovelluksesta. Ohjain ottaa vastaan käyttäjän komentoja ja muuntaa ne loogisiksi sovellustoiminnoiksi. Tyypillinen vuorovaikutus alkaa käyttäjän komennosta, jonka ohjain havaitsee (kuva 6.10). Se pyytää mallia suorittamaan vastaa- handle- Event attach() service() Controller initialize(model,view) handleevent() update() Controller Model View service update getdata notify update getdata Kuva 6.10: Malli-näkymä-ohjain-arkkitehtuurin toiminta display
79 144 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 145 van loogisen sovelluspalvelun. Tämän seurauksena mallin tila muuttuu, mistä se informoi kiinnostuneita näkymiä ja ohjaimia. Näkymä kutsuu näytönpäivitysoperaatiotaan, joka käy ensin kysymässä mallilta muuttunutta tietoa. Tämän jälkeen näyttö voidaan päivittää. Vastaavasti ohjain käy kysymässä muuttunutta tietoa ja mahdollisesti muuttaa omaa tilaansa sen perusteella. Jokin komento voi esimerkiksi tulla sen perusteella kielletyksi tai sallituksi, ja ohjaimen täytyy tietää se. Malli-näkymä-ohjain-arkkitehtuurin soveltaminen Malli-näkymä-ohjain-arkkitehtuuri mahdollistaa mallin uudelleenkäytön lähes kaikissa tilanteissa. Tästä syystä se on luonnollinen valinta graafisen käyttöliittymän toteutusmekanismiksi, ja itse asiassa lähes kaikki tällaiset ympäristöt tavalla tai toisella ohjaavat käyttäjää MVC-mallin mukaiseen arkkitehtuuriin. Malli-näkymä-ohjain-arkkitehtuuriin liittyy myös tiettyjä ongelmia. Se tyypillisesti monimutkaistaa järjestelmää lisäten sen luokkia. Jotta järjestelmä ei pirstoutuisi liikaa, kannattaa ohjain- ja näkymäluokat antaa vain isommille käyttöliittymäkokonaisuuksille, kuten dialogeille. Tarkkailija-mallin soveltaminen merkitsee potentiaalisesti paljon päivityskutsuja tarkkailijoille, joista kaikkien ei ehkä tarvitsisi reagoida muutokseen lainkaan. Tämä on ongelmallista erityisesti silloin, kun eri komponentit on hajautettu eri prosesseihin. Toinen tämän arkkitehtuurityylin ongelma on, että näkymä- ja ohjainluokat ovat kiinteästi toisiinsa liittyviä ja että niitä on vaikea uudelleenkäyttää toisistaan irrallaan muissa yhteyksissä (joissakin MVC:n versioissa näkymä- ja tarkkailijaluokat onkin yhdistetty). Muutetun tiedon kysyminen on potentiaalinen tehottomuuden lähde: kukin tarkkailija joutuu kysymään mallilta muuttunutta tietoa käyttäen yleisiä mallin operaatioita, mikä voi olla hidasta; lisäksi eri tarkkailijat joutuvat tekemään samoja tai samantapaisia kyselyjä muutoksen jälkeen. Käytännössä malli-näkymä-ohjain-arkkitehtuurin soveltamistavan määrää usein käytetty GUI-kehys. Tällaiset kehykset perustuvat lähes aina malli-näkymä-ohjain-arkkitehtuurin johonkin muunnelmaan, mutta on myös mahdollista, että kehys ja sitä tukevat työkalut piilottavat tämän arkkitehtuurin sovellusohjelmoijalta Tietovarastoarkkitehtuurit Tietovarastoarkkitehtuurissa (Repository architecture) joukko järjestelmiä tai komponentteja ylläpitää yhteistä tilaa tietovarastossa. Arkkitehtuurista on olemassa erilaisia muunnoksia riippuen siitä, miten aktiivinen rooli tietovarastolla on. Ääritapauksessa arkkitehtuurin voidaan ajatella muistuttavan asiakas-palvelin-arkkitehtuuria, jossa tietovarastoa hallinnoiva palvelin tarjoaa tiedon tallennuspalveluita asiakkailleen. Tietovarastoarkkitehtuurien perusajatus Tietovarastoarkkitehtuurin ydin on jaettu tietovarasto, jota kukin järjestelmän komponentti pääsee tutkimaan ja muuttamaan. Komponentit eivät kommunikoi suoraan vaan tietovaraston kautta. Tietovarastoon kiinnittyneet komponentit voivat toimia samassa prosessissa tai jos tietovarastossa on tiedon lukitusmahdollisuus eri prosesseissa rinnakkain. Tiedon ristiriidattomuuden ylläpitäminen edellyttää yleensä tietovaraston tukea transaktio-käsitteelle: jokin muutostapahtuma, joka koskee useita tietoalkioita, tehdään aina kokonaan tai ei ollenkaan. Järjestelmä voi olla hajautettu tai se voi toimia samassa koneessa. Tyypillisiä esimerkkejä tietovarastoarkkitehtuureista ovat ohjelmointiympäristöt, joissa yhteinen tietovarasto koostuu ohjelman sisäisestä esityksestä, jota erilaiset työkalut käsittelevät. Vastaavanlaisia esimerkkejä ovat erilaiset teksti- ja julkaisujärjestelmät, joissa joukko työkaluja käsittelee samaa tekstin sisäistä esitystä. Myös monet liiketoimintajärjestelmät noudattavat tätä mallia: yhteinen tietovarasto koostuu tällöin erilaisesta liiketoimintaan liittyvästä tiedosta (esim. asiakas-, varasto-, laskutustiedot ym.), jota erilaiset sovellukset käyttävät. Tässä tapauksessa tietovarastoarkkitehtuuri usein yhdistyy asiakas-palvelin-arkkitehtuuriin, kerrosarkkitehtuuriin ja malli-näkymä-ohjain-arkkitehtuuriin. Tietovarastoarkkitehtuurin soveltaminen Tietovarastoarkkitehtuuri tarjoaa luontevan mahdollisuuden järjestelmän rinnakkaistamiseen. Uusia tietovarastoon kiinnittyviä komponentteja tai sovelluksia on helppo lisätä ja poistaa ne eivät riipu
80 146 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 147 toisistaan suoraan. Arkkitehtuuri takaa, että kaikilla osajärjestelmillä on käytössään sama data. Ratkaisuun voi liittyä myös ongelmia. Jatkuva tiedon tulkintaa sisältävä kommunikointi tietovaraston kanssa voi johtaa tehottomaan toteutukseen. Varsinkin jos tietovarasto on palvelimella ja kommunikointi tapahtuu verkon yli, tiheä kommunikointi on yleinen tehokkuusongelma. Lisäksi tässä tilanteessa voi tehokkuusongelmaksi muodostua myös se, että jokin sovellus lukitsee suuren osan tietovarastosta, ja muut sovellukset joutuvat odottamaan. Toinen tavallinen ongelma on, että tietovaraston rajapinnan tai skeeman muuttaminen on vaikeata, koska se vaikuttaa moniin sovelluksiin. Vastaavasti tietovaraston rajapinta on vaikea suunnitella siten, että se on sekä tarpeeksi tehokas että riittävän yleinen palvellakseen kaikkia sovelluksia. Toiminnon kuvaus (ascii, XML,...) Asiakas Tulkki Kielen määritelmä käyttää Kuva 6.11: Tulkkiperustainen arkkitehtuuri Tulos Suoritusalusta Syöte Tulkkipohjaiset arkkitehtuurit Usein on tarpeen pystyä antamaan järjestelmälle syötteenä toiminnallisia kuvauksia. Tilanne voi esimerkiksi olla sellainen, että järjestelmä tarjoaa joukon peruspalveluja, mutta vasta ajoaikana tiedetään, millä tavoin niitä on yhdisteltävä. Toinen tilanne voi olla sellainen, että jollakin sovellusalueella on tietty abstrakti tapa kuvata toiminnallisuutta tällä alueella, ja sovellusten halutaan toimivan useilla erilaisilla alustoilla, joiden avulla abstrakti toiminnallisuus voidaan toteuttaa. Tällöin eri alustoille halutaan toteuttaa järjestelmiä, jotka pystyvät suorittamaan abstrakteja toiminnallisia kuvauksia. Näiden suunnittelu johtaa usein tulkkipohjaiseen arkkitehtuuriin. Tulkkipohjaisen arkkitehtuurin perusajatus Tulkkipohjaisessa arkkitehtuurissa tulkki lukee ja suorittaa tietyn tunnetun muodon mukaista toiminnallista kuvausta käyttäen hyväksi jonkin toteutusalustan palveluja. Jälkimmäinen voi olla esimerkiksi jollekin sovellusalueelle tehty tukiohjelmisto, jonka API:iin tulkki tukeutuu. Esimerkki tulkkiarkkitehtuurin käytöstä siirrettävän abstraktin, toiminnallisen kuvauksen käytöstä on SQL:ää tukeva tietokannanhallintajärjestelmä. Sen avulla voidaan tehdä sovelluksia, jotka ovat helposti siirrettäviä tietokantajärjestelmästä toiseen. Joissakin tapauksissa toteutusalustana voi toimia suoraan toteutuskieli, jolloin ei tarvita erillistä toteutusalustaa. Tulkattavan esityksen tuottaa jokin taho (esim. ihminen tai toinen järjestelmä), jolla ei tässä ole merkitystä. Toimintaa on havainnollistettu kuvassa Tulkittavan esityksen ei tarvitse olla tekstuaalista kieltä, vaan se voi olla esimerkiksi taulukkomuotoinen esitys (esimerkiksi erilaiset sääntöpohjaiset järjestelmät) tai vaikkapa tilakone. Tyypillinen esimerkki tulkkiarkkitehtuurista on skriptausta tukeva järjestelmä, johon järjestelmän käyttäjä voi kirjoittaa omia toiminnallisia kuvauksia ja suorittaa ne saman tien järjestelmän sisällä. Usein XML:ää käytetään toiminnallisten kuvausten esittämiseen. Myös virtuaalikoneeseen perustuvat ohjelmointijärjestelmät (esim. Java) voidaan ymmärtää tulkkiarkkitehtuurin sovelluksiksi. Tulkkipohjaisen arkkitehtuurin soveltaminen Tulkkiarkkitehtuuri tekee suoritettavasta koodista ajoaikaisen tietorakenteen, joka on täysin järjestelmän hallinnassa. Tällöin sitä voidaan tutkia ja muuttaa mielivaltaisesti suorituksen aikana. Näin saavutetaan refleksiivisyys ilman, että varsinainen toteutuskieli sitä millään tavalla tukisi. Tulkittavaa kieltä voidaan helposti muuttaa, varsinkin jos on käytetty Tulkki-suunnittelumallia (Interpreter). Kielen laajentaminen
81 148 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 149 ei tee vanhoja kielellä kirjoitettuja toimintakuvauksia epäkelvoksi, sen sijaan toteutuksen muuttaminen tai rakenteita vastaavien luokkien poistaminen voi sen tietysti tehdä. Jos halutaan muuttaa vain kielen tulkintaa, mutta ei sen rakennetta, riittää muuttaa tulkintaoperaation toteutusta. Tämä taas voidaan tehdä esimerkiksi aliluokittamalla, jolloin olemassaolevaan koodiin ei yleensä tarvitse kajota. Tällä tavoin voidaan esimerkiksi suhteellisen helposti vaihtaa alla oleva toteutusalusta toiseksi. Tulkkiarkkitehtuurin varjopuoli on suoritustehokkuuden heikkeneminen verrattuna natiivikoodin suoritukseen. Tämän syynä voi olla sekä tulkittavan esityksen muodostaminen että epäsuora, tulkitseva suoritus. Tyypillisesti ei-natiivisuoritus on vähintään kertaluokkaa hitaampi kuin natiivisuoritus, ja joskus kertaluokkia voi olla useampiakin. Myös suoritettavan olioesityksen tilavaatimus voi olla suurehko: se saattaa viedä huomattavasti enemmän tilaa kuin vastaava merkkijonoesitys tai konekoodi. Näin ei kuitenkaan välttämättä ole, sillä esimerkiksi Javan tavukoodi on huomattavasti tiiviimpää kuin vastaava puhdas konekoodi. Tulkkipohjaiseen järjestelmään toteutetulla tulkattavalla ohjelmalla voi luonnollisesti olla oma rakenteensa, joka voi edelleen noudatella jotakin arkkitehtuurityyliä. Myös monimutkaisempien järjestelmien rakentaminen on mahdollista. Voidaan esimerkiksi ajatella kerrosarkkitehtuuria, jossa jokainen kerros tarjoaa omanlaisensa tulkattavan kielen, ja jossa kielen abstraktiotaso kasvaa tasolta toiselle siirryttäessä. 6.4 Esimerkki: auton polttoaineenkulutuksen valvontaohjelmisto Kuten edellä mainittiin, arkkitehtuurityylit esiintyvät harvoin täysin puhtaina. Tarkastellaan seuraavaksi erästä useita arkkitehtuurityyliä sisältävää järjestelmää, auton polttoaineenkulutuksen valvontaohjelmistoa (kuva 6.12). Ohjelmiston yhteistoiminta ajoneuvon laitteiston kanssa perustuu CAN-väylää kuuntelevaan komponenttiin CAN- Filter. Kun komponentti huomaa sanoman, joka liittyy polttoaineenkulutukseen, se rakentaa sanoman, jonka se lähettää komponentille CarState. Tämä komponentti käsittelee saamansa tiedon, tallettaa sen tarvittaessa tietokantaan ja tarpeen mukaan päivittää ajotietokoneen näytön. Koska järjestelmän ei oleteta ohjaavan auton toimintaa CANBus CANFilter MessageDispatcherIF send(msg) register(msgtype,component) ViewObserver update() Consumption View CarStateIF XMLMsg Controller handleguievent(event) Message Dispatcher CarState recordconsumption getconsumption register(view) Component Kuva 6.12: Auton polttoaineen kulutuksen valvontaohjelmisto vaan ainostaan keräävän ja anlysoivan tietoa, sillä ei ole kovia reaaliaikavaatimuksia. Edellä esitetyssä esimerkkijärjestelmässä voidaan näkökulmasta riippuen ajatella käytetyn ainakin seuraavia arkkitehtuurityylejä: Msg type(): MsgType receive(msg) Kerrosarkkitehtuurina järjestelmä voidaan ymmärtää siten, että alimmassa kerroksessa on CAN-väylä ja tietokanta, seuraavassa on CANFilter ja mahdollinen tietokannan abstrahoiva komponentti (kuvassa vain rajapintana), seuraavassa viestinvälittäjä sekä viesti- ja komponenttirajapinnat, seuraavassa CarState (ja muut vastaavat kom-ponentit) ja ylimmässä käyttöliittymän toteuttavat näkymä- ja ohjainkomponentit. Tietovuoarkkitehtuuri. Saadut tarpeelliset CAN-viestit kääritään XML-sanomien sisään, ja tämän jälkeen ne lähete- DB
82 150 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 151 tään jatkokäsittelyyn. Tällöin CAN- ja viestinvälitysväylä voidaan ymmärtää putkiksi ja tapahtumankäsittely suodattamiseksi. Viestinvälitysarkkitehtuuri. CAN-väylää kuunteleva ja tietoa varsinaisesti käsittelevä osa kommunikoivat keskenään viestinvälitystä käyttäen. Malli-näkymä-ohjain-arkkitehtuuri. Loogisen tilan (auton tila kulutuksen kannalta) ja käyttöliittymän erottamiseen käytetään MVC-arkkitehtuurin mukaisia komponentteja. Tulkkiarkkitehtuurina järjestelmä voidaan ymmärtää tekemällä oletus, että lähetetyt XML-muotoiset sanomat ovat itse asiassa komentoja, jotka CarState suorittaa. Lisäksi arkkitehtuurissa käytetään jo aiemmin esiteltyjä riippuvuuden poistoon tarkoitettuja ratkaisuja. Esimerkiksi Componentrajapinnan esittely vihjaa mahdollisuuteen luoda uudenlaisia sanomia kuuntelevia komponentteja jatkossa, ja tapahtumien käyttö käyttöliittymäkomponenttien välillä tarjoaa mahdollisuuden rakentaa erilaisia konfiguraatioita. Ratkaisua esiteltäessä kaikkia arkkitehtuurityylejä tai käytettyjä suunnittelumalleja ei useinkaan ole tarkoituksenmukaista nimetä erikseen. Yleensä riittää, että olennaisimmat ja oletetulle kohderyhmälle helpoimmin ymmärrettävät ratkaisut esitetään arkkitehtuurityylien avulla. Esitystapa ja valitut termit luonnollisesti vaikuttavat siihen, miten järjestelmä jäsennetään. Esimerkissämme tietovuo- tai tulkkiarkkitehtuurityylin nimeäminen vaikuttaa jäsentämisen kannalta hieman keinotekoiselta, ja niihin viittaaminen edellä esitetyssä tapauksessa saattaisi olla jopa hämäävää. Sen sijaan kerrosarkkitehtuuri, viestinvälitysarkkitehtuuri ja MVC-malli muodostavat keskenään yhteensopivan ja ohjelmoijalle luontevan tavan jäsentää järjestelmä. 6.5 Arkkitehtuurityylit ja UML UML sinänsä ei ohjaa minkään tietyn arkkitehtuurityylin käyttöön, vaan ainoastaan mahdollistaa halutun tyylin omaksumisen. Joidenkin tyylien, kuten kerrosarkkitehtuurien, käyttöön useimmiten riittää, että malleja luotaessa noudatetaan sopivaa elementtien ryhmittelykäytäntöä tai sopivaa tapaa käyttää pakkauksia, mutta tilanteissa, joissa elementtien rooleillan on suuri merkitys, on tarpeen esittää eri mallielementtien rooli tarkemmin. Tätä tarkoitusta varten UML tarjoaa mekanismin, jolla UML:n metamallia voidaan laajentaa halutun arkkitehtuurityylin (tai -tyylien) käsitteitä käyttäen. Tällöin UMLmalleissa voidaan käyttää stereotyyppejä samaan tapaan kuin suunnittelumallien yhteydessä osoittamaan eri komponenttien rooleja. Tällaisia laajennoksia kutsutaan profiileiksi. Täsmällisesti ottaen profiili on määritelty UML-metamallin laajennokseksi 2, jonka avulla voidaan stereotyyppejä hyödyntäen käyttää järjestelmäkohtaisia termejä. Näiden termien avulla suunnittelija voi osoittaa eri arkkitehtuurityyleissä tarvittavat roolit. Lisäksi OCL-kieltä käyttäen on mahdollista esittää rajoituksia roolien välillä, jolloin esimerkiksi viestinvälitysarkkitehtuurin tapauksessa voidaan tarvittaessa vaatia, että viestinvälitys esimerkiksi tapahtuu aina yleisen viestinvälittäjäkomponentin kautta. Vastaavasti voidaan vaatia, että asiakas-palvelin-arkkitehtuuria käytettäessä asiakkaat eivät koskaan kommunikoi keskenään suoraan, vaan aina jonkin palvelimen kautta. 6.6 Yhteenveto Arkkitehtuurityylejä käyttämällä on mahdollista perustaa suunnittelu koeteltuihin ja hyväksi havaittuihin ratkaisuihin arkkitehtuuritasolla. Arkkitehtuurityylit esiintyvät harvoin puhtaina, vaan samassa järjestelmässä voidaan käyttää useita tyylejä eri tarkoituksiin. Uusia ominaisuuksia varten ei kuitenkaan automaattisesti kannata valita uutta tyyliä, vaan mukailla järjestelmään jo toteutettuja suunnitteluratkaisuja, sillä tämä helpottaa ylläpitoa jatkossa. Arkkitehtuurityylien käyttöä voidaan UML-tasolla valvoa käyttämällä nk. profiileja. 2. Metamallin laajennokset eroavat hivenen UML:n eri versioissa. Yllä esitetty tulkinta on kuitenkin mahdollinen kaikissa versioissa.
83 152 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit Harjoitustehtäviä 6.1 Suunnittele kerrosarkkitehtuuri yhdessä koneessa toimivalle seikkailupelille. Järjestelmän on oltava helposti muutettavissa, jos pelin logiikka muuttuu, ja järjestelmän osia on voitava käyttää myös muiden pelien pohjana. 6.2 Eclipse on plug-in-arkkitehtuuriin perustuva ohjelmistokehitysympäristöalusta ( Kuvaa Eclipse kerrosarkkitehtuurin avulla. 6.3 Yrityksen käyttämät tietojärjestelmät käsittelevät ja jalostavat yritystietoa. Yrityksellä on komponentteja, jotka käsittelevät XML-muotoista tietoa eri tavoin. Esimerkiksi eräs komponentti ottaa sisään tulevista tilausilmoituksista toimitusmääräyksiä ja hälytysrajailmoituksia (jonkin tuotteen alhaisin sallittu varastomäärä alitetaan); toinen komponentti tuottaa toimitusmääräyksistä laskuja; kolmas puolestaan tuottaa hälytysrajailmoituksista ulospäin meneviä tilausmääräyksiä jne. Toisaalta yksi komponentti tuottaa esimerkiksi asiakastiedoista tarjousilmoituksia ja niistä puolestaan tilausilmoituksia (kun asiakas on hyväksynyt tarjouksen). Lisäksi järjestelmiin voidaan liittää esimerkiksi tilastointikomponentteja, jotka tilastoivat läpikulkevaa tietoa muuttamatta sitä. Yritys haluaa luoda komponenttikokoelman, jota käyttäen voidaan helposti luoda erilaisia yritystietoa jalostavia sovelluksia. Mitä arkkitehtuurityyliä voisit soveltaa näissä sovelluksissa, ottaen huomion, että käsiteltävällä tiedolla on sovittu XMLmuoto ja että tietoa käsitteleviä ja jalostavia komponentteja olisi voitava kombinoida eri tavoin? Mitä etuja ja ongelmia on valitsemallasi tyylillä tässä tapauksessa? Anna kaksi esimerkkiä tällaisista yrityssovelluksista kuvaamalla niiden karkea arkkitehtuuri sopivaksi katsomallasi kaaviolla. 6.4 Miten erilaiset arkkitehtuurityylit voisivat hyödyntää aiemmin esiteltyjä (luku 4) riippuvuuden purkamiseen tarkoitettuja tekniikoita? Vaatiiko jokin arkkitehtuurityyli tietyn riippuvuuden purkamista? 6.5 Oletetaan, että olet toteuttamassa mobiililaitetta. Minkälainen arkkitehtuurityyli sopisi parhaiten seuraaviin alijärjestelmiin: a) kommunikointiprotokollapino b) kalenteriohjelmisto c) asetusten ylläpito d) viestintäkeskus (sisältäen mm. sähköpostin ja SMS:n). Kuvaa myös kokonaisarkkitehtuuri, joka integroi kaikki alijärjestelmät. 6.6 Suunnittele karkealla tasolla liikennevalvontajärjestelmän arkkitehtuuri, ja esitä se sopivaksi katsomallasi kaaviolla. Järjestelmässä kameran tiepisteestä ottamaa kuvaa prosessoidaan automaattisesti eri vaiheissa niin, että siitä voidaan lopulta tunnistaa auton rekisterinumero, jota verrataan autorekisterikeskuksen tietokantaan; tunnistusprosessia on voitava varioida riippuen valaistuksesta, ympäristöstä jne. Järjestelmä lähettää poliisille ilmoituksen tietyn auton tekemästä liikennerikkomuksesta. Mitä arkkitehtuurityyliä sovellat? Miksi, minkä ongelman tai vaatimuksen tyyli ratkaisee tässä järjestelmässä? 6.7 Suunnittele karkealla tasolla ravintolavarausjärjestelmän arkkitehtuuri, ja esitä se sopivaksi katsomallasi kaaviolla. Järjestelmällä voidaan mm. tehdä varauksia ravintolaan, peruuttaa niitä, rekisteröidä asiakkaan saapuminen ja asiakkaan siirto pöydästä toiseen. Järjestelmää voidaan käyttää eri toimipisteissä ravintolassa. Mitä arkkitehtuurityyliä sovellat? Miksi, minkä ongelman tai vaatimuksen tyyli ratkaisee tässä järjestelmässä? 6.8 Tee pieni Java-ohjelma, joka laskee keskiarvon lukujoukosta. Käyttöliittymä koostuu perusikkunan lisäksi yhdestä valikosta, jossa on kaksi komentoa: Add (lisää luku) tuottaa dialogin, jossa käyttäjä voi antaa uuden luvun, ja Average (anna keskiarvo) tuottaa dialogi-ikkunan, jossa on tulos ja OK-nappula. Lisäksi toistaiseksi suurin luku näytetään ikkunan yläpalkissa tekstikentässä. Pyri tekemään ohjelma mahdollisimman tarkasti MVC-arkkitehtuurityylin mukaisesti, ja merkkaa ohjelmaan, mikä osa siinä vastaa mallia (Model), mikä näkymää (View) ja mikä ohjainta (Controller). 6.9 Tarkastellaan seuraavia arkkitehtuuriratkaisuja: Välittäjä (ks. luku 4), Viestinvälitysarkkitehtuuri ja Tietovarastoarkkitehtuuri. Näiden rakenne on samanlainen siinä mielessä, että ne koostuvat joukosta ohjelmayksiköitä, jotka eivät ole suoraan yhteydessä toisiinsa vaan yhteiseen keskusosaan. Ratkaisut eroavat toisistaan siinä suhteessa, onko tieto tai kontrolli pääasiassa keskitettyä vai hajautettua. Kontrollilla tarkoitetaan
84 154 Ohjelmistoarkkitehtuurit Arkkitehtuurityylit 155 tässä järjestelmän toimintalogiikan kuvausta korkeimmalla tasolla. Täytä seuraava taulukko sijoittamalla joko kirjain K (keskitetty) tai H (hajautettu) kuhunkin taulukon kohtaan sen mukaan, onko tieto tai kontrolli pääasiassa keskitetty vai hajautettu kyseisessä ratkaisussa. Tieto Kontrolli Välittäjä Tietovarasto Viestinvälitys 6.10 Suunnittele karkealla tasolla kodinhallintajärjestelmän arkkitehtuuri, ja esitä se UML:n luokka- tai komponenttikaaviona. Järjestelmä valvoo erilaisia kodissa olevia laitteita, kuten medialaitteita, verhoja, ovien lukituksia, kodinkoneita ym. Käyttäjä hallitsee järjestelmää erilaisten käyttöliittymien (mm. graafinen ja ääni) avulla. Järjestelmään on voitava lisätä dynaamisesti järjestelmän toiminnan aikana uusia laitteita, joita järjestelmä ei entuudestaan tunne. Mitä arkkitehtuurityyliä soveltaisit? Miksi, minkä ongelman tai vaatimuksen järjestelmässä kyseinen tyyli ratkaisee? Voitko soveltaa useita tyylejä? 6.11 Arkkitehtuurityylit voidaan jaotella toisaalta topologiansa mukaan (lineaarinen, keskitetty) ja toisaalta sen mukaan, edellyttääkö tyyli palvelukohtaisia rajapintoja vai vain yleisen, erityisistä palveluista riippumattoman rajapinnan. Täytä seuraava taulukko sijoittamalla siihen arkkitehtuurityylejä siten, että tyylille tulee oikea topologia- ja rajapintaluokitus. Topologia kortinluku (pankki, luotto, bonus) palautusten käsittely myyntiraporttien tulostus käyttäjän tunnistus käyttäjien hallinnointi. Kaikki liiketoimintaan liittyvä tieto on talletettu tietokantaan. a) Anna järjestelmän kerrosarkkitehtuuri. Sijoita kerroksiin seuraavat komponentit tai alijärjestelmät: AWT (Javan yleinen grafiikkatuki), DBA (tietokannan abstraktio), DB (tietokanta), SaleLogic (yleinen kassapäätelogiikka), CardReader (kortinlukijan abstraktio), CRD (kortinlukijan ajuri), BarCode (viivakoodilukijan abstraktio), RetailerLogic (myymäläketjukohtainen logiikka), SaleGraphics (yleinen graafinen tuki kassapäätesovelluksille), RetailerGUI (myymäläketjukohtainen GUI), Swing ja BCSD (viivakoodilukijan ajuri). b) Anna alustava arkkitehtuurikuvaus SaleLogic-alijärjestelmälle. Käytä luokkakaaviota, jossa on esitetty sovellusalueen keskeiset käsitteet attribuutteineen ja käsitteiden väliset suhteet (domain model). c) Anna koko järjestelmän yleiskuvaus kaaviona, jossa esitetään järjestelmään kuuluvat laitteet, niiden yhteydet ja niissä olevat ohjelmistot. Voit käyttää vapaamuotoista kuvaa. d) Mitä muita arkkitehtuurityylejä kerrosarkkitehtuurin lisäksi voit soveltaa järjestelmässä? Mitä hyötyä näistä tyyleistä on järjestelmän kannalta? Lineaarinen Keskitetty Rajapinnat Palvelukohtaiset Geneeriset Kassapäätesovellus sisältää mm. seuraavat toiminnallisuudet: myyntitapahtuman rekisteröinti tuotetietojen luku viivakoodilukijasta
85 156 Ohjelmistoarkkitehtuurit
86 7 Tuoterunkoarkkitehtuurit Keskeinen ohjelmistotuotannon haaste on ohjelmistojen uudelleenkäyttö: miten voidaan hyödyntää samoja osia useissa eri ohjelmistotuotteissa. Ohjelmistojen uudelleenkäytöstä on tullut monella alueella välttämättömyys, jonka avulla on mahdollista kehittää nopeasti korkeat laatuvaatimukset täyttäviä uusia ohjelmistotuotteita. Suunnitelmallinen ohjelmistojen uudelleenkäyttö pohjautuu aina arkkitehtuuritason ratkaisuihin. Parhaassa tapauksessa on mahdollista antaa jollekin tuotekategorialle yhteinen ohjelmistoarkkitehtuuri (tuoterunkoarkkitehtuuri) ja toteuttaa sitä tukeva ohjelmistoalusta (tuoterunko), jonka päälle yksittäiset tuotteet rakennetaan. Tuoterunkoarkkitehtuurin suunnittelussa on otettu huomioon yksittäisten tuotteiden mahdolliset eroavaisuudet, jolloin tuotekohtaiset osat voidaan hallitusti toteuttaa olemassaolevan alustan päälle. Tarkastelemme tässä luvussa tuoterunkoarkkitehtuurien yleisiä perusteita. Palaamme vielä aihepiiriin luvussa 8 kuvaamalla lähemmin erityisesti olioparadigmaan soveltuvan lähestymistavan tuoterunkoarkkitehtuurin ja sitä tukevan alustan suunnitteluun. Johdantona tuoterunkoarkkitehtuureihin tarkastelemme ensin lyhyesti arkkitehtuurin erilaisia rooleja ohjelmistotuotannossa ja niiden painotuksien muuttumista. 7.1 Arkkitehtuurin rooli ohjelmistokehityksessä Arkkitehtuurin merkitystä ohjelmistotuotannossa voidaan ajatella monelta kannalta. Ensiksi, arkkitehtuuri voi selittää ohjelmiston rakenteen ja luonteen. Tämä on perinteinen arkkitehtuurin tehtävä, joka on tärkeä erityisesti silloin, kun ohjelmiston kehittämisessä ja
87 158 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 159 ylläpidossa on mukana suuri (ja vaihtuva) joukko erilaisia henkilöitä. Kun arkkitehtuurilla on lähinnä selittävä rooli, sillä on merkitystä myös jälkikäteen (siis järjestelmän rakentamisen jälkeen) annettuna. Valitettavasti tällainen jälkikäteen annettu selittävä arkkitehtuurikuvaus katsotaankin monessa tapauksessa riittäväksi. Näin ymmärrettynä arkkitehtuurin oikeellisuus ja kattavuus liittyy ainoastaan siihen, miten hyvin se kuvaa olemassaolevan järjestelmän. Arkkitehtuuri saa huomattavasti merkittävämmän roolin, kun se ymmärretään järjestelmän rakentamista ohjaavana artifaktina. Tällöin arkkitehtuurikuvaus annetaan täsmällisessä muodossa ohjelmistokehitysprosessin alkuvaiheessa, ja sitä noudatetaan tarkasti myöhemmissä vaiheissa koko järjestelmän elinajan. Ohjaavan arkkitehtuurin oikeellisuus mitataan järjestelmän vaatimusten suhteen: arkkitehtuurin on tuettava vaatimusten toteuttamista (ks. luku 9). Toisaalta ohjaavan arkkitehtuurin on oltava riittävän kattava yksityiskohtaisen suunnittelun ja toteutuksen pohjaksi. Koska ohjaavaa arkkitehtuuria käytetään järjestelmän koko elinajan, sen on tuettava myös ylläpitoa. Ohjaavaa arkkitehtuuria voidaan käyttää myös selittävänä arkkitehtuurina, mutta ei toisin päin. Ohjaava arkkitehtuuri johtaa arkkitehtuuripainotteiseen ohjelmistokehitysprosessiin, jota tarkastelimme kohdassa 1.3. Uusia ohjelmistotuotteita rakennetaan kuitenkin yhä enemmän olemassa olevien ohjelmistojen pohjalle. Tähän ohjelmistojen uuddelleenkäyttöä hyödyntävään suuntaukseen johtavat ennen muuta yrityksiin kohdistuvat vaatimukset nopeammasta ja tehokkaammasta tuotekehityksestä. Kun uudelleenkäytettävällä ohjelmistoalustalla on hyvin määritelty arkkitehtuuri, joka on tunnettava yksittäistä tuotetta tehtäessä, arkkitehtuuri saa uuden, entistäkin keskeisemmän roolin: siitä tulee olennainen osa käytettävissä olevaa toteutusvälineistöä. Tällainen tietyn tyyppiset tuotteet mahdollistava arkkitehtuuri poikkeaa luonteeltaan huomattavasti selittävästä tai ohjaavasta arkkitehtuurista: sen ensisijainen tarkoitus on tarjota korkean tason käsitteet ja mekanismit, joiden avulla (tiettyjen rajojen sisällä) tuotteiden vaatimukset voidaan mahdollisimman helposti toteuttaa. Mahdollistava arkkitehtuuri toimii yksittäisen tuotteen tapauksessa luonnollisesti myös ohjaavana ja selittävänä arkkitehtuurina, mutta ei toisin päin. Selittäviä, ohjaavia ja mahdollistavia arkkitehtuureja on havainnollistettu kuvassa 7.1. Yleisenä suuntauksena ohjelmistotekniikassa on ohjaavan ja mahdollistavan arkkitehtuuriroolin korostumi- Arkkitehtuuri selittää järjestelmää Arkkitehtuuri ohjaa järjestelmän rakentamista Arkkitehtuuri mahdollistaa järjestelmiä Kuva 7.1: Arkkitehtuurin roolin kehittyminen ohjelmistoissa nen. Tässä luvussa kuvaamme tuoterunkoarkkitehtuureja mahdollistavien arkkitehtuurien ilmenemismuotona. Mahdollistavat arkkitehtuurit voidaan toisaalta ymmärtää yhtenä vaihtoehtoisena lähestymistapana ratkaista eräs tietotekniikan perusongelma: miten helpottaa tietyn sovellusalueen käsitemaailman siirtämistä suoritettavissa olevaan toteutusmaailmaan. Yleisten ohjelmointikielten tasolla tätä ongelmaa on pyritty ratkaisemaan olioparadigmalla, joka tarjoaa yksinkertaisen toteutusvälineen, olion, sovelluskohtaisten käsitteiden esittämiseen. Toinen kielipohjainen lähestymistapa ongelman ratkaisuksi on sovellussuuntautunut kieli (domain-specific language, DSL), jonka avulla haluttu sovellus voidaan kuvata hyvin korkealla abstraktiotasolla sovellusalueen käsittein. Tällaisesta kuvauksesta tuotetaan sitten automaattisesti suoritettavissa oleva, yleisellä ohjelmointikielellä annettu toteutuskoodi. Kuvassa 7.2 on havainnollistettu näitä erilaisia lähestymistapoja sovellusalueen ja toteutusvälineistön välisen kuilun ylittämiseen. Verrattuna esimerkiksi sovellussuuntautuneen kielen käyttöön tuoterunkoarkkitehtuuriin pohjautuva ratkaisu on yksinkertaisempi ja avoimempi: sovellusohjelmoija antaa sovelluskohtaisen koodin tuoterunkoarkkitehtuurin ehdoilla, käyttäen yleistä ohjelmointikieltä. Sovellusohjelmoijan on tunnettava arkkitehtuuri siltä osin, kuin se vaikuttaa yksittäiseen sovellukseen, mutta hänen ei tarvitse opetella uutta DSL-kieltä. DSL-lähestymistavan haitta on myös ylimääräinen generointivaihe: jos generoituun koodiin joudutaan tekemään käsin muutoksia, mikä usein on välttämätöntä, muutokset menetetään seuraavan generoinnin yhteydessä.
88 DSL 160 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 161 Perinteinen Vaatimukset Tuoterunko Lähdekoodi Kääntäjä Binäärikoodi 4C 3C kustannukset yhtä suuret (A) Perinteinen (B) Tuoterunko Kumulatiivinen kustannus DSLkuvaus DSLgeneraattori Lähdekoodi 2C C I Tuoterunko Lähdekoodi Rajapinta Alusta Kuva 7.2: Yleiskäyttöiseen ohjelmointikieleen, sovellussuuntautuneeseen kieleen ja alustaan perustuva ohjelmistokehitys Tuotteiden lukumäärä Kuva 7.3: Tuoterungon kannattavuuden arviointi (Weiss ja Lai 1999) 7.2 Tuoterunkoarkkitehtuurit ja niiden hyödyt Tuoteperheellä (product family) tarkoitetaan tässä toiminnaltaan ja rakenteeltaan samankaltaisten, tietylle sovellusalueelle toteutettujen ohjelmistotuotteiden muodostamaa joukkoa. Tyypillisesti tuoteperhe kattaa tietyn yrityksen omalle toiminta-alueelleen tekemät ohjelmistotuotteet. Tuoterunko (tai tuotealusta, product platform) on ohjelmisto, joka toteuttaa tuoteperheen yhteisen rakenteen ja toiminnallisuuden. Tuoterungon (ja siihen liittyvän tuoteperheen) arkkitehtuuria kutsutaan tuoterunkoarkkitehtuuriksi (product-line architecture, PLA). Tuoterunkoarkkitehtuuri on siten mahdollistava arkkitehtuuri tuoteperheeseen kuuluville ohjelmistotuotteille Tuoterungon edut Tuoterunkoa hyödynnettäessä toteutuksen perusrakenteita voidaan uudelleenkäyttää kaikissa tuotteissa, mikä puolestaan parantaa laatua, sillä käytettävä koodi on testattu useassa aikaisemmassa tuotteessa useassa eri konfiguraatiossa, nopeuttaa ohjelmistokehitystä, sillä suurin osa koodista on jo olemassa uutta tuotetta kehitettäessä, helpottaa projektin hallintaa, sillä tuoteprojekteissa voidaan käyttää samankaltaisia prosessimalleja, helpottaa siirtymistä projektista toiseen, sillä ympäristö ja käytettävät työkalut ovat jo tuttuja, standardoi tuotteita, sillä yhteiset asiat toimivat samalla tavalla eri tuotteissa (esm. käyttöliittymässä), tehostaa toimintaa, sillä paljon panostusta vaativa arkkitehtuurisuunnittelu on jo etukäteen ainakin suureksi osaksi tehty Tuoterunko investointina Tuoterunkoarkkitehtuurin taloudellista merkitystä voidaan arvioida laskennallisen mallin avulla (kuva 7.3). Siinä selvitetään, miten kannattavaa yhteiseen alustaan perustuvan arkkitehtuurin hyödyntäminen joukossa tuotteita on. Kuvassa suora A kuvaa tilannetta, jossa ei ole panostettu yhteisen arkkitehtuurin suunnitteluun, eli kunkin ohjelmiston toteutus hoidetaan erikseen. Tässä tapauksessa uuden ohjelmiston mukaantulon kustannus on aina tietyn suuruinen (C). Jos
89 162 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 163 eri ohjelmistot ovat eri hintaisia, C:n voidaan tilanteen yksinkertaistamiseksi ajatella tarkoittavan ohjelmistojen kustannusten keskiarvoa. Tällöin siis N:n ohjelmistotuotteen kustannus on N*C. Suora B kuvaa tilannetta, jossa on ensin suunniteltu yhteinen tuoterunkoarkkitehtuuri ja toteutettu sitä tukeva ohjelmistoalusta. Merkitään I:llä tätä vastaavaa kustannusta. Nyt yksittäisen ohjelmiston toteuttaminen on tehokkaampaa, ja tämä kustannus merkitään C':lla. N:n ohjelmiston toteuttaminen maksaa siis I+(N*C'). Jotta tuoterunkoarkkitehtuurin hyödyntäminen olisi kannattavaa, C':n pitäisi olla pienempi kuin C. Jos näin on, säästö yhtä ohjelmistoa kohden (ottamatta huomioon tuoterungon alkuinvestointi I:ta) on C-C'. Vastaavasti N:lle toteutetulle ohjelmistolle säästö on N*(C-C'). Jotta runkoarkkitehtuuriin panostaminen maksaisi itsensä takaisin, N:n pitäisi olla riittävän suuri, siten että I < N*(C-C'). Kuvan esimerkissä toiminta tulee kannattavaksi kolmen ohjelmiston toteuttamisen jälkeen. Toisin sanoen, I voidaan ymmärtää sijoitukseksi tuoterunkoon, ja tuotteiden toteuttamisen nopeutus määrää sijoituksen takaisinmaksuajan. Tässä kuvattu laskennallinen malli on karkeasti yksinkertaistettu. Lisäksi eri sovellusalueilla sovellettavat laskennalliset mallit voivat olla hyvinkin erilaisia: toisilla alueilla investointi tuoterunkoarkkitehtuurin rakentamiseen auttaa automatisoimaan sovellusten toteuttamista paremmin kuin joillakin toisilla sovellusalueilla. Myös tuoterungon tekninen toteutustapa vaikuttaa malliin. Luvussa 8 tarkastellaan esimerkkinä kehystekniikkaan perustuvaa, pelisovelluksille tarkoitettua tuoterunkoa, jossa tuoterunko maksoi itsensä takaisin jo kolmen sovelluksen jälkeen. Tätä voidaan pitää tyypillisenä tilanteena sovellusalueilla, joissa tuotteiden varianssi on hyvin ymmärrettyä ja suhteellisen pientä. Tuoterunkoon siirtymisellä voi olla myös muuhun kuin välittömään taloudelliseen hyötyyn liittyviä perusteluja. Joskus liiketoiminta-alue voi olla esimerkiksi luonteeltaan sellainen, että uusia tuotteita on pakko saada nopeasti markkinoille, jotta ylipäänsä pysyisi varteenotettavana toimijana tällä alueella. Tuoterungon käyttöönotosta seuraavalla toimintojen yhtenäistymisellä voi myös olla erilaisia yrityksen toimintaedellytyksiä parantavia vaikutuksia, joita on vaikea mitata suoraan rahassa tai edes nähdä etukäteen. 7.3 Tuoterungon rakentaminen ja evoluutio Tuoterunkoa hyödyntävään ohjelmistokehitykseen voidaan siirtyä monella tavalla. Sopivin tapa määräytyy liiketoimintaympäristöstä, käytettävistä resursseista, jo olemassa olevasta toteutuksesta ja monesta muusta tilannekohtaisesta seikasta. Yleisesti ottaen ainakin seuraavat tavat ovat mahdollisia (Bosch 2000): Olemassa olevan toteutuksen komponentit voidaan ottaa tuoterungon perustaksi, ja niiden varaan toteutetut tuotteet tuoteperheen jäseniksi. Uusi tuoterunkoon perustuva tuotelinja voidaan rakentaa inkrementaalisesti, jolloin ensimmäisessä vaiheessa tuoterunko tarjoaa vain vähän ominaisuuksia, mutta kehittyy vähitellen täysimittaiseksi tuoterungoksi. Uusia tuotteita varten toteutettu tuoterunko voidaan rakentaa suoraan valmiiksi, jolloin sen varaan rakennetut tuotteet voivat hyödyntää toiminnallisuutta täysimittaisesti. Ensimmäinen uudentyyppinen tuote otetaan perustaksi, joka muokataan tuoterungoksi lisäämällä sen muunneltavuutta. Lisäksi tuoterunkolähestymistapaa käytetään joskus ajan ostamiseen siten, että kehitys aloitetaan mahdollisimman monen tuotteen tarpeita tyydyttävän alustan rakentamisella, ja vasta kun alustan toteutus on riittävän pitkällä, eri tuotteiden tarvitsemat ominaisuudet jäädytetään. Koska tuoterunko koostuu tuotteiden yhteisistä ominaisuuksista, tuotteiden muutostarpeet ohjaavat myös tuoterungon evoluutiota. Toisaalta tuoterungon evoluutioon voi liittyä erityispiirteitä: Tuoterunko voi haarautua. Tällöin tuloksena voi olla kaksi erityyppisiä tuotteita palvelevaa kokonaisuutta. Uusien tuotteiden luominen tuoterunkoon perustuen on välttämätöntä tuoterungon jatkohyödyntämisen kannalta. Tuoterunkoon voidaan lisätä ominaisuuksia, jotka tukevat tietyntyyppisiä tuotteita. Usein käy itse asiassa niin, että yksittäisiä tuotteita varten erikseen toteutettuja ominaisuuksia siirretään tuoterunkoon. Tätä tuoterungolle tyypillistä, usein haitallista ominaisuutta vetää puoleensa toiminnalli-
90 164 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 165 suutta kutsutaan tuoterungon painovoimaksi (force of gravitation). Myös laadullisten ominaisuuksien asteittainen parantaminen saattaa mahdollistaa tuoterungon laajentamisen uudenlaisille tuotteille. 7.4 Ohjelmistokehitysprosessi tuoterunkoa käytettäessä Tuoterunko ei ole ainoastaan tekninen asia, vaan siihen liittyy läheisesti myös toiminnan organisointi ja käytetyt prosessit. Ilman järjestelmällistä toimintatapaa ohjelmiston kehittämisessä ja ylläpidossa monet tuoterungon edut jäävät saavuttamatta. Tarkastelemme seuraavassa ohjelmistokehitysprossin erityispiirteitä tuoterungon tapauksessa. Kohdassa 7.5 käsittelemme lyhyesti tuoterunkoa organisaation näkökulmasta. Sovellusalueen käsitemalli Yhteiset vaatimukset Vaatimusmäärittely Tuotekehitysprosessi Alustan suunnittelu Alustakehitysprosessi Tuotteen toteutus Kuva 7.4: Tuoterungon ohjelmistokehitysprosessi Alustan toteutus Alusta Vaatimusmäärittely Muunneltavuusvaatimukset Tuoterunkoarkkitehtuuri Tuotevaatimukset Toteutusympäristö Tuotekohtainen koodi Tuote Prosessin yleiskuvaus Tuoterunkoon perustuva ohjelmistokehitysprosessi jakautuu kahteen osaan, alustakehitysprosessiin (domain engineering) ja tuotekehitysprosessiin (application engineering) (kuva 7.4). Edellisen tuloksena saadaan itse tuoterunko, jälkimmäisen tuloksena taas saadaan yksittäisiä tuoterunkoon pohjautuvia ohjelmistotuotteita. Näitä edeltää esitutkimusvaihe (feasibility study), jossa arvioidaan tuoterungon hyödyllisyyttä jonkin taloudellisen mallin pohjalta (ks. 7.2) ja tehdään (tai ollaan tekemättä) päätös tuoterungon kehittämisestä. Tuoterunkoarkkitehtuurin hyödyllisyyttä arvioitaessa mietitään, kuinka kannattavaa on panostaa tuoteperheen yhteiseen suunnitteluun. Kannattavuuteen vaikuttaa erityisesti ennakoitu tuoteperheen jäsenten lukumäärä. Esitutkimuksen tuloksena saadaan selville, kannattaako ryhtyä rakentamaan tuoterunkoa kyseiselle sovellusalueelle vai suunnitellaanko ja toteutetaanko perheeseen kuuluvat tuotteet erikseen Alustakehitysprosessi Varsinainen tuoterungon kehitys aloitetaan vaatimusmäärittelyllä, jonka tuloksena saadaan sovellusalueen käsitemalli (domain model), tuotteiden yhteiset vaatimukset sekä tuotteiden muunneltavuusvaatimukset. Sovellusalueen käsitemalli määrittelee sen toiminta-alueen käsitteet, jolla tuotteet on tarkoitettu. Jos sovellusalueena on esimerkiksi vakuutusala, sovellusalueen käsitemalli määrittelee vakuutusliiketoiminnan olennaiset käsitteet ja niiden keskinäiset suhteet. Käsitemalli varmistaa, että kaikki osapuolet ymmärtävät sovellusalueen samalla tavalla. Käsitemalli antaa myös sanaston, jonka avulla yhteiset vaatimukset ja muunneltavuusvaatimukset voidaan esittää. Muunneltavuusvaatimukset määrittelevät, mitkä ominaisuudet tuotteissa voivat vaihdella, missä rajoissa vaihtelu tapahtuu ja milloin yksittäisen tuotteen kohdalla muunnelma kiinnitetään tietyllä tavalla. Kiinnittäminen voi tapahtua joko kehitysaikana (esimerkiksi antamalla uusi tuotekohtainen aliluokka), linkkausaikana (esimerkiksi linkkaamalla tuotteeseen mukaan tietty komponentti), alustusaikana (esimerkiksi valitsemalla käyttöympäristön perusteella jollekin rajapinnalle tietty toteutus) tai tuotteen käytön aikana (esimerkiksi käyttäjä voi räätälöidä käyttöliittymän mieleisekseen).
91 166 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 167 Tarkastelemme muunneltavuuden hallintaa tuoterunkoprosessissa lähemmin kohdassa 7.5. Vaatimusmäärittelyn tulosten perusteella voidaan aloittaa tuoterunkoarkkitehtuurin suunnittelu. Jos esitutkimusvaiheessa on jo kartoitettu mahdollisia arkkitehtuurin perusratkaisuja (esimerkiksi yleinen arkkitehtuurityyli), jokin näistä voidaan ottaa lähtökohdaksi. Lähtökohdaksi voidaan ottaa myös sovellusalueen käsitemalli perinteisen oliosuunnittelun mukaisesti, tai jokin puhtaasti tuotteiden yhteisiin toiminnallisiin vaatimuksiin perustuva alustava ratkaisu. Lähtökohdasta riippumatta tuoterunkoarkkitehtuurin suunnittelu on iteratiivinen prosessi, jossa muunneltavuus on keskeinen laatutavoite. Tässä voidaan soveltaa aikaisemmin luvussa 1 kuvattua arkkitehtuuripainotteista ohjelmistokehitysprosessia. Kun ensimmäinen versio arkkitehtuurista on olemassa, otetaan tarkasteltavaksi yksi muunneltavuusvaatimus kerrallaan ja tutkitaan, täyttääkö arkkitehtuuri tämän vaatimuksen (ts. tukeeko arkkitehtuuri tuotteiden muunneltavuutta tältä osin). Jos ei, arkkitehtuuria muokataan (esimerkiksi lisäämällä rajapintoja, soveltamalla suunnittelumalleja ym.) muunneltavuusvaatimuksen täyttämiseksi. Samalla on myös tarkistettava, että aikaisempia muunneltavuusvaatimuksia ei ole rikottu uudessa arkkitehtuurissa. Mikäli tuotteilla on muita yhteisiä laatuvaatimuksia, nämä käsitellään vastaavalla tavalla. Kun on saatu arkkitehtuuri, joka täyttää annetut muunneltavuusvaatimukset (ja muut laatuvaatimukset), voidaan arkkitehtuuri vielä testata käyttäen arkkitehtuurin arviointimenetelmiä (ks. luku 9). Tämä voi tarkentaa muunneltavuusvaatimuksia ja johtaa arkkitehtuurin korjaamiseen. Rinnan tuoterunkoarkkitehtuurin kanssa on suunniteltava myös se toteutusympäristö (application engineering environment), jonka avulla kehitetään uusia tuotteita. Pelkkä tuoterunkoarkkitehtuuri järjestelmän rakenteen kuvauksena ja sen toteuttava ohjelmistoalusta ei yleensä riitä tällaiseksi ympäristöksi, koska tuotteiden kehittäjille halutaan tarjota helposti ymmärrettävä, selkeä toteutusvälineistö. Mitä paremmin kehittäjä hallitsee tämän välineistön, sitä helpompi hänen on esittää yksittäisten tuotteiden vaatimusten toteutus tuoterungon avulla. Yksinkertaisimmassa tapauksessa toteutusympäristönä voi olla hyvin määritellyistä rajapinnoista koostuva API, joka piilottaa tuoterunkoarkkitehtuurin ja ohjelmistoalustan toteutuksen tuotteiden kehittäjiltä. Usein on kuitenkin tarpeen paljastaa enemmän tai vä- hemmän tuoterunkoarkkitehtuurista tuotteiden kehittäjille, jotta haluttu muunneltavuus saadaan aikaan. Tällöin keskeiseksi ongelmaksi nousee tuoterunkoarkkitehtuurin kuvaaminen tuotteita mahdollistavana artifaktina: mikä yhteys on tuotteiden vaatimuksilla ja tuoterunkoarkkitehtuurilla? Ongelma voidaan ratkaista systemaattisella, tuotteen kehittäjän näkökulmasta tehdyllä tuoterunkoarkkitehtuurin dokumentoinnilla. Tarkastelemme luvussa 8 miten tällainen dokumentaatio voidaan antaa kehystekniikan yhteydessä. On myös mahdollista kehittää erityisiä työkaluja, jotka tukevat tuoterungon käyttöä. Tällaisia ovat esimerkiksi aiemmin mainitut sovellussuuntautuneet erikoiskielet, joiden avulla voidaan kuvata haluttu tuote korkealla abstraktiotasolla ja generoida tällaisesta kuvauksesta automaattisesti tuotekohtainen koodi. Toisaalta myös perinteisiä konfiguraationhallinnan työkaluja voidaan käyttää tuotteen rakentamisen apuna Tuotekehitysprosessi Yksittäinen tuoteprojekti alkaa tavalliseen tapaan tuotteen vaatimusten keräämisellä ja vaatimusanalyysilla. Vaatimusten keräämiseen liittyy asiakkaiden haastatteluja heidän tarpeidensa huomioon ottamiseksi. Tuoterungon tapauksessa on olennaista välittää asiakkaille tieto tuoterungon mahdollisuuksista: mitkä asiat ovat helppoja toteuttaa tuoterungon päälle, mitkä asiat vaikeita ja mitkä tyystin poissuljettuja. Tuoterungosta ja sen tarjoamasta toteutusympäristöstä riippuen tuotteen suunnittelu ja toteutus voi tapahtua hyvinkin eri tavoilla. Jos ympäristö tarjoaa esimerkiksi oman erikoiskielen tuotteen kuvaamiseen, vaatimukset täyttävä tuote voidaan suoraan kuvata tällaisella kielellä ja tarvittava tuotekohtainen koodi voidaan generoida automaattisesti. Jos taas tuoterunko koostuu joukosta komponentteja, joiden rajapinnat muodostavat toteutusympäristön, tuotteella saattaa olla myös oma, tuotekohtainen arkkitehtuurinsa, joka joudutaan suunnittelemaan erikseen. Tyypilliset tuoterungot ovat näiden ääripäiden välillä. Jos tuotteiden varianssi on suhteellisen pientä, ne tarvitsevat vain hyvin vähän tai ei ollenkaan tuotekohtaista arkkitehtuuria. Tällöin tuotteen vaatimukset voidaan toteuttaa lähes suoraan tuoterungon pohjalle. Jos taas tuotteiden varianssi on suuri, tuoterunkoarkkitehtuuri tarjoaa
92 168 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 169 vain tuotteiden perusrakenteen, jota joudutaan täydentämään tuotekohtaisilla arkkitehtuuritason ratkaisuilla. Tällöin tuoteprosessiin kuuluu olennaisena osana arkkitehtuurisuunnittelu ja yksityiskohtainen suunnittelu. Kuvassa 7.4 esitetty prosessimalli on monessa suhteessa yksinkertaistettu. Tuoterunkoprosessi on usein asteittainen: tuoterunko voi aluksi tukea vain osaa tuotteista, jolloin tuoterungon ja siihen pohjautuvien tuotteiden kehittäminen voi tapahtua samanaikaisesti. Tuotteiden tekemisestä ja käyttämisestä saadut kokemukset vaikuttavat toisaalta tuoterungon kehittymiseen. Edellä esitetyt prosessit perustuvat erilaisiin työntekijärooleihin. Tuoterunkoarkkitehtuurin rakentajien vastuulla on tuoteperheen ja sen arkkitehtuurin suunnittelu ja toteuttaminen. Lisäksi he pitävät huolta siitä, että yhteiseen arkkitehtuuriin sijoittaminen pysyy kannattavana. Sovelluksen rakentajat puolestaan suunnittelevat ja toteuttavat yksittäiset ohjelmistotuotteet yhteisen arkkitehtuurin pohjalta. He ovat myös yhteydessä asiakkaisiin, ja pitävät huolta siitä, että ohjelmistotuotteille asetetut vaatimukset täyttyvät. Alusta pakollinen Komponentteja Tuoterunko valinnainen vaihtoehtoinen, ei tuotekohtainen Kuva 7.5: Tuoterunko ja yksittäinen tuote Alusta Tuote vaihtoehtoinen, mahdollisesti tuotekohtainen Yhteiset piirteet 7.5 Muunneltavuus tuoterungossa Tuote 2 Toiminnallisuus, joka on kaikissa tuotteissa samassa muodossa Tuoterunkoon perustuvan ohjelmistokehityksen keskeinen ongelma on muunneltavuuden (varianssin) hallinta. Tuoterunkoon kuuluu tyypillisesti ohjelmistoalusta, joka tulee sisältymään kaikkiin tuotteisiin, sekä joukko komponentteja, joista jotkin ovat valinnaisia (voidaan ottaa mukaan tuotteeseen, mutta ei välttämätön) ja jotkin vaihtoehtoisia (joku tietystä valikoimasta on otettava mukaan tuotteeseen). Usein tuotteeseen tehdään myös puhtaasti tuotekohtaisia uusia komponentteja. Kuva 7.5 havainnollistaa tilannetta. Muunneltavuuden hallinta on tärkeää tuoterunkoon pohjautuvan ohjelmistokehityksen kaikissa vaiheissa. Käymme seuraavassa läpi muunneltavuuden hallintaan liittyviä näkökulmia prosessin eri vaiheissa Muunneltavuus vaatimustasolla Kuten kaikki käyttäjävaatimukset, myös haluttu varianssi rajataan vaatimusmäärittelyssä. Jos ajatellaan kunkin tuotteen toiminnalli- Tuote 1 Tuote 3 Eroavat piirteet Sama käsitteellinen toiminnallisuus, jossa pieniä tuotekohtaisia eroja Toiminnallisuus esiintyy kahdessa tai useammassa tuotteessa (mutta ei kaikissa) Tuotekohtainen toiminnallisuus Kuva 7.6: Ohjelmistotuotteiden yhdistävät ja erottavat piirteet suutta tietyn suorakaiteen sisälle jäävänä alueena (kuva 7.6), niin sijoittamalla suorakaiteet päällekkäin tuotteita yhdistäviä ja erottavia piirteitä voidaan havainnollistaa. Tämän mukaisesti toiminnallisuus voidaan jakaa neljään kategoriaan: Tietty toiminnallisuus esiintyy täsmälleen samassa muodossa kaikissa tuotteissa (musta alue kuvassa).
93 170 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 171 Sama käsitteellinen toiminnallisuus saattaa esiintyä useassa tuotteessa, mutta ko. toiminnallisuudessa on tuotteiden välillä pieniä eroja (esimerkiksi tumman harmaalla merkitty alue kuvassa). Tietty toiminnallisuus saattaa olla osa kahta tai useampaa tuotetta, mutta ei kuitenkaan kaikkia (merkitty kuvaan vaalean harmaalla). Tietty toiminnallisuus voi olla täysin tuotekohtaista (merkitty kuvaan valkoisella). Tuotteiden yhteiset ja eroavat ominaisuudet voidaan esittää täsmällisesti nk. piirremallin (feature model) avulla. Piirremallissa kuvataan, mitä piirteitä tuotteella voi olla, mitkä niistä ovat pakollisia, mitkä valinnaisia (voivat olla mukana, mutta eivät välttämättä) ja mitkä vaihtoehtoisia (piirre voi esiintyä eri variaatioina). Lisäksi piirremalli määrittelee, milloin valinnainen tai vaihtoehtoinen piirre kiinnitetään ja millaisissa kombinaatioissa piirteet voivat tuotteessa esiintyä. Piirremallin antamiseen voidaan käyttää UML:n laajennosta (kuva 7.7). UML tarjoaa yleisesti kolme laajennosmekanismia, stereotyypit (stereotype), lisätietomääreet (tagged value) ja rajoitteet (constraint). Stereotyypin avulla voidaan jokin UML:n mallielementti (esimerkiksi luokka) erikoistaa tarkoittamaan tiettyä käsitekategoriaa. Piirremallin tapauksessa luokkasymboli stereotyypitetään pakolliseksi, valinnaiseksi tai vaihtoehtoiseksi piirteeksi. Lisätietomääreet ovat mallin elementteihin liitettäviä, ylimääräistä tietoa kyseisestä elementistä antavia nimi-arvo pareja. Piirremallissa lisätietomääreellä voidaan kertoa valinnaisten ja vaihtoehtoisten piirteiden kiinnitysaika. Rajoitteet ovat loogisia lausekkeita, joilla annetaan mallin ilmentymälle oikeellisuusehtoja. Piirremallissa rajoitteilla voidaan kertoa tuotteen sallitut piirrekonfiguraatiot. Kuvassa 7.7 on annettu kuvitteellisen PDA-laitteen piirremalli. Sen mukaan esimerkiksi näyttölaitteen suuntaus voi olla joko vertikaalinen tai horisontaalinen, mutta ei kumpaakin (xor-rajoite), ja tämä ominaisuus kiinnitetään tuotteessa kehitysaikana (lisätietomääre BindingTime = development). <<mandatory>> Network channels { BindingTime = runtime } <<alternative>> WLAN <<alternative>> GPRS <<alternative>> GSM Data <<alternative>> Vertical <<context>> Handheld Device SDK variability <<mandatory>> Display orientation { BindingTime = development } {xor} <<alternative>> Horizontal <<mandatory>> Control method { BindingTime = development } <<alternative>> Keyboard <<alternative>> Pen <<optional>> GPS support { BindingTime = development } Kuva 7.7: Muunneltavuuden kuvaus piirremallina käyttäen laajennettua UML-luokkakaavioesitystä (Myllymäki 2002) Muunneltavuus suunnittelussa ja toteutuksessa Piirrekartoituksen jälkeen tiedämme tarkemmin, missä laajuudessa tuoterunkoarkkitehtuurin on tuettava erilaisia tuotemuunnelmia. Kunkin muunneltavuusvaatimuksen kohdalla on päätettävä tapa, jolla tätä muunneltavuutta tuetaan tuoterungossa. Muunneltava piirre on eristettävä siten, että se voidaan tarvittaessa poistaa (valinnainen piirre) tai korvata (vaihtoehtoinen piirre) toisella muunnelmalla. Jos muunnelma kiinnitetään vasta ajoaikana, tuoterunkoon pitää sisällyttää mekanismi, jolla tuote voidaan konfiguroida ajoaikana. Vaikka tuoterunkoarkkitehtuurin rooli korostuu muunneltavuuden mahdollistajana, kaikkea muunneltavuutta ei välttämättä oteta huomioon vielä arkkitehtuuritasolla. Jotkut muunneltavuusvaatimukset on luontevampaa ottaa huomioon vasta yksityiskohtaisen suunnittelun tasolla ja jotkut vasta toteutustasolla. Esimerkiksi Strategia-suunnittelumallin käyttö tietyn operaation toteutuksen muunteluun voidaan kuvata vasta yksityiskohtaisen suunnittelun tasolla, ja joskus voidaan operaation rungon tai sen osan muuntelu tehdä tehokkuussyistä vasta toteutustasolla. {xor} <<mandatory>> Command buttons
94 172 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 173 Suunnittelutason muunneltavuutta tukeville ratkaisuille on vaikea antaa mitään yleisiä ohjeita. Ratkaisuissa käytetään tyypillisesti hyväksi komponenttiteknologiaa (esim. rajapintojen erilaisia toteutuksia), ohjelmointikielten geneerisiä ominaisuuksia (esim. C++:n template-piirteet), arkkitehtuurityylien muunneltavuutta tukevia ominaisuuksia ja suunnittelumalleja. Tärkeätä on, että suunnittelussa käytetyt muunneltavuutta tukevat ratkaisut pystytään liittämään tiettyihin muunneltavuusvaatimuksiin. Kutsumme muunneltavuusvaatimuksen ja sitä tukevan suunnitteluratkaisun muodostamaa kokonaisuutta tuoterungossa variaatiopisteeksi (variation point). keskuksesta. Tuoterungolle voidaan kehittää myös erillinen testaussovellus, joka pyrkii testaamaan tuoterunkoa mahdollisimman kattavasti. Toinen tapa ratkaista testauksen vaivalloisuus on pyrkiä automatisoimaan testausta mahdollisimman pitkälle. Ääritapauksessa tämä tarkoittaa uuden tuoterungon toteuttamista testausohjelmiston muuntelemista varten. On myös olemassa standardilähestymistapoja, joita käyttäen automatisointi yksinkertaistuu. Näistä tunnetuin lienee Incremental Testing Framework (Binder 2001), jossa testaus etenee iteratiivisesti pienin askelin, mahdollistaen erilaisten konfiguraatioiden yksinkertaisemman huomioinnin Muunneltavuus testauksessa Tuoterunkojen heikkoutena mainitaan usein testattavuus. Vaikka variaatiopisteiden avulla voidaan helposti määritellä ja toteuttaa uudenlaisia konfiguraatioita, on syntyvien ohjelmistojen toiminnan osoittaminen usein samantapaista kuin yksittäisen järjestelmän testaaminen. Syynä tähän on se, että tuoterunko itsessään ei välttämättä ole suoritettavissa, vaan se voi sisältää esimerkiksi ilman toteutusta olevia rajapintoja. Silloinkin kun tuoterunko on suoritettavissa, voi olla vaikeaa määritellä testitapauksia, jotka mahdollistaisivat tuoterungon testaamisen omana kokonaisuutenaan. Näin tuoterungon muunneltavuus ja sen edut eivät suoraan siirry testausvaiheeseen. Tästä syystä jokainen tuotettu ohjelmisto usein testataan ikään kuin se olisi täysin itsenäinen kokonaisuus. Tällöin jotkin ohjelmiston osat testataan jokaisessa tuotteessa, mutta varioidut osat ainoastaan niitä hyödyntävien tuotteiden yhteydessä. Kokonaisuuden testaaminen on tarpeen, sillä uudessa ympäristössä aiemmin validoidut komponentit saattavat toimia virheellisesti. Testaamisen työläys johtaa helposti joustavuuden rajoittamiseen käytännön toteutuksissa. Tällöin voidaan esimerkiksi määritellä tietyt referenssikokoonpanot, joiden toiminnan ohjelmistovalmistaja testaa itse. Halutessaan asiakas voi poiketa näistä kokoonpanoista, mutta testaaminen täytyy suorittaa erikseen. Luonnollisesti tuoterungon toteuttaja voi tarjota konsultointipalveluita sekä testauksessa että käyttöönotossa. Joskus toteuttaja voi jopa tarjota asiakaskohtaisen kokoonpanon testaamispalvelun eri korvausta vastaan, mikäli kyse on riittävän suuresta järjestelmästä, kuten esimerkiksi puhelin- 7.6 Tuoterunko ja organisaatio Jo aiemmin esitetyn Conwayn lain (1.5) mukaan ohjelmistokehitystä harjoittavan yksikön organisaatio ja kyseisen ohjelmiston arkkitehtuuri muodostuvat lopulta samanrakenteisiksi. Tämä laki soveltuu yleensä varsin hyvin myös tuoterunkoihin. Tuoterunkoa hyödyntävän yrityksen ohjelmistokehitys jakaantuu organisaatiossa tavallisesti tuoterungon kehittämisestä vastaavaan yksikköön ja yksittäisistä tuotteista vastaaviin yksiköihin kuvan 7.8 mukaisesti. Yksiköt ovat voimakkaassa vuorovaikutuksessa keskenään. Tuoterunkoryhmä toimittaa tuoteryhmille ohjelmistoalustaan kuuluvia komponentteja, niiden päivityksiä ja niihin liittyviä dokumentteja. Tuoterunkoryhmä myös informoi tuoteryhmiä alustan tarjoamista mahdollisuuksista. Tuoteryhmät puolestaan toimittavat tuoterunkoryhmälle uusia vaatimuksia sekä yksittäisen tuotteen yhteydessä kehitettyjä komponentteja, jotka voitaisiin lisätä alustaan. Erääksi ongelmaksi tuoterungon yhteydessä muodostuu se, miten huolehditaan pitkäjänteisestä arkkitehtuurin ylläpidosta ja siihen liittyvästä uudistamisesta. Arkkitehtuuriin liittyvät ylläpitotarpeet ovat usein luonteeltaan teknisiä, eivätkä tuota välittömästi tuloja yritykselle. Toisaalta kehitystyötä suunniteltaessa nämä vaatimukset kilpailevat panostuksesta potentiaalisesti myytävien asiakasvaatimusten kanssa, jolloin taloudellinen näkökohta voi muodostua merkittäväksi. Myös ohjelmistokehitysryhmien ja markkinoinnin välinen kommunkointi on tärkeätä. Yleisesti markkinointiyksiköiden tehtävänä on välittää asiakkailta peräisin olevia uusia tarpeita ja vaati-
95 174 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 175 Markkinointi Tuotteen ja tuoterungon valmiudet Asiakas Tuote Tuote Tuoterungon valmiudet Asiakkaiden tarpeet Asiakkaiden tarpeet Tuotteen valmiudet Sovellusalusta Sovellusalusta Arkkitehtuurialusta Arkkitehtuurialusta Johto Tuoteyksiköt Tuoterunkoyksikkö Tuotteet Resurssialusta Komponentit Päivitykset Vaatimukset Valmiudet Palaute Kuva 7.9: Tuoterunkoarkkitehtuurin kerrosmalli Kuva 7.8: Tuoterunkoa toiminnassaan hyödyntävä organisaatio (Bass et al. 1997) muksia ohjelmistokehitysyksiköille. Tuoterunkoryhmän tehtävänä on ilmaista tuoterungon tarjoamat mahdollisuudet markkinoinnille, jotta jälkimmäinen pystyisi näkemään, mitkä uudet ominaisuudet ovat helposti ja mitkä vaikeasti toteuttavissa alustan päälle. Tuoteryhmät puolestaan kertovat markkinoinnille, mitä ominaisuuksia tällä hetkellä on tarjolla tuotteissa. Markkinointiyksikkö kommunikoi asiakkaiden kanssa kertomalle näille tuotteiden ja alustan tarjoamista ominaisuuksista ja mahdollisista laajennoksista, ja asiakkaat puolestaan esittävät markkinointihenkilöille uusia vaatimuksia. Asiakkaat voivat myös suoraan kommunikoida ohjelmistokehitysyksiköiden kanssa ilman markkinointia, vaikka tämä on harvinaista erityisesti tuoterunkoyksikön tapauksessa. Tuoterunkoyksikkö ja tuoteyksiköt edellyttävät hieman erilaisia taitoja työntekijöiltä. Tuoterungon kehittäminen vaatii ominaisuuksia, jotka ovat tyypillisiä ohjelmistoarkkitehdeille: sovellusalueen hyvää tuntemusta, suunnittelutaitoa ja -kokemusta, yleisten standarditeknologioiden tuntemusta, hyvää yleistys- ja abstrahointikykyä, sekä hyvää kommunikointitaitoa. Tuotteen kehittäminen taas vaatii asiakkaiden tarpeiden ja tuotteiden hyvää ymmärtämistä, ko- kemusta komponenttipohjaisesta sovellusten rakentamisesta, käyttöliittymien tuntemusta, ohjelmistojen räätälöintitaitoa ja toteutusteknologioiden tuntemusta. 7.7 Tuoterunkoarkkitehtuurin kerrosmalli Kuten jo aiemmin todettiin, tuoterungon keskeinen ongelma on muunneltavuuden hallinta. Muunteluun liittyvien huolenaiheiden eriyttämiseksi tuoterunkoarkkitehtuuri voidaan jakaa neljään kerrokseen kerrosarkkitehtuurin idean mukaisesti (kuva 7.9). Nämä kerrokset ovat resurssialusta, arkkitehtuurialusta, sovellusalusta, ja tuotekerros, joista jokainen mahdollistaa eri osa-alueisiin kohdistuvaa varianssia (Myllymäki et al. 2002). Samaan tapaan kuin suunnittelumallien tai arkkitehtuurityylien yhteydessä, malli kuvastaa käytännön toteutuksia. Mallin mukaiset kerrokset voidaan usein tunnistaa tuoterungoista, vaikka mallia ei olisikaan pyritty tietoisesti noudattamaan suunnittelussa. Kerrosten tunnistaminen voi helpottaa tuoterunkoarkkitehtuurin ymmärtämistä ja eheyden ylläpitämistä.
96 176 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit Resurssialusta Mallin alin kerros, resurssialusta, tarjoaa useimmiten API-rajapinnan, jonka kautta järjestelmä voi käyttää ympäristön yleisiä peruspalveluja ja resursseja. Järjestelmästä riippuen nämä voivat olla esimerkiksi kommunikaatioon liittyviä (hajautetut järjestelmät), prosessien hallintaan liittyviä (rinnakkaiset järjestelmät), tai tiedon talletukseen liittyviä (tietokannat). Resurssialustan tarjoamat palvelut eivät riipu järjestelmän arkkitehtuurista eivätkä sovellusalueesta. Palveluja kutsutaan yksinkertaisen API:n kautta. Palvelujen kutsujan täytyy huomioida ainoastaan yksittäisten palvelujen semantiikka, mutta resurssialustan sisäistä arkkitehtuuria ei tarvitse tuntea, eikä resurssialustalla toisaalta ole merkittävää vaikutusta sen palveluja pyytävän järjestelmän arkkitehtuuriin. Resurssialustalla voi kuitenkin olla oma sisäinen tilansa, jolloin palvelujen pyytäminen ei välttämättä ole sallittua kaikissa järjestyksissä. Esimerkiksi tiedosto tai tietoliikenneyhteys pitää avata ennen kuin sitä voidaan käyttää ja semafori luoda ennen kuin sitä voi hyödyntää. Tällöin resurssialustan on joko hallitusti informoitava kutsujaa virheellisestä kutsusta (jolloin kutsuja voi reagoida siihen haluamallaan tavalla), tai sitten on varmistettava staattisesti, että kutsuja noudattaa sallittuja kutsumuotoja. Tämä voidaan tehdä esimerkiksi määrittelemällä sallitut kutsusekvenssit työkalulle, joka voi tarkistaa resurssien oikean käytön. Resurssialusta kiinnittää järjestelmät tiettyyn ulkoiseen (esim. laite-, verkko-, grafiikka- tms.) ympäristöön; se abstrahoi ympäristöltä edellytettävät toiminnalliset vaatimukset, jotka eri ympäristöt voivat toteuttaa eri tavoin. Tarvittaessa voidaan käyttää yksinkertaisia sovittimia helpottamaan ohjelmiston siirtoa ympäristöstä toiseen. Resurssialustan alapuolelle kerrosmallissa voidaan näin ajatella vielä laitekerros, joka on jätetty pois kuvasta 7.9. Resurssialusta perustuu usein yrityksestä riippumattomaan standardiin tai yleisesti saatavilla olevaan ohjelmistoon. Esimerkki resurssialustasta voisi olla vaikkapa CORBA-toteutus, tietokantapalvelut abstrahoiva kerros, sulautetun järjestelmän käyttöjärjestelmä, tai graafisen ympäristön primitiivipalvelut Arkkitehtuurialusta Resurssialustan jälkeen seuraava ohjelmistokerros on nimeltään arkkitehtuurialusta. Tämä kerros määrittelee järjestelmien yleisen arkkitehtuurityylin ja tarjoaa tämän arkkitehtuurin mukaiset kiinnityskohdat sovellusaluekohtaisille kerroksille. Arkkitehtuurialusta ei kuitenkaan vielä ota kantaa sovellusalueeseen. Arkkitehtuurialustan tarjoamaan rajapintaan voi kuulua myös API-tyyppisiä kutsurajapintoja, mutta tällöin on olennaista, mikä sovelluskohtainen ohjelmayksikkö kutsuu mitäkin palvelua: kukin sovellusaluekohtainen yksikkö sijoittuu tiettyyn rooliin arkkitehtuurissa, ja tämä määrää miten yksikkö käyttää arkkitehtuurialustan palveluja. Yksi tapa ilmaista tällainen rooli on antaa kantaluokka, jonka aliluokat ovat kantaluokan määrittelemässä roolissa. Tällöin arkkitehtuurialusta on toteutusmuodoltaan kehysohjelmisto. Arkkitehtuurialustan suunnitteluun vaikuttavat lähinnä siihen pohjautuvien sovellusten ei-toiminnalliset, laadulliset vaatimukset (esimerkiksi hajautettavuus, yleinen muunneltavuus, suorituskyky). Arkkitehtuurialusta voi olla sovelluksen tarjoaman yrityksen tekemä tai yleinen, mahdollisesti standardiin perustuva. Esimerkki arkkitehtuurialustasta voisi olla vaikkapa EJB (arkkitehtuurityylinä hajautettu proxy-pohjainen client-server arkkitehtuuri), komponenttien viestinvälitysalusta (arkkitehtuurityylinä message dispatcher -tyyppinen malli), tai graafinen käyttöliittymäalusta (arkkitehtuurityylinä esimerkiksi MVC) Sovellusalusta Sovellusalusta rakentuu arkkitehtuurialustan määrittelemälle yleiselle arkkitehtuurimallille. Sovellusalusta toteuttaa sovellusten rungon jollakin tietyllä sovellusalueella. Tähän runkoon voi sisältyä sovellusaluekohtaisia arkkitehtuuriratkaisuja, jotka kuitenkin pohjautuvat arkkitehtuurialustan määrittelemään yleiseen tyyliin. Sovellusalusta tarjoaa erikoistamisrajapinnan, jonka avulla toteutetaan sovelluksen (yleensä) toiminnallisia vaatimuksia. Sovellusalustan suunnitteluun voivat vaikuttaa sekä sovellusten laadulliset että toiminnalliset (ei-sovelluskohtaiset) vaatimukset. Eräs toteutusmuoto sovellusalustalle on sovelluskehys sekä joukko sitä täydentäviä komponentteja. Kukin tällainen komponentti sisältyy vähintään kahteen eri sovellukseen. Esimerkki sovellusalustasta
97 178 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 179 Tuotekerros Sovellusalusta Arkkitehtuurialusta Pörssikauppajärjestelmä Pankkisovellusalusta EJB CANBus CANFilter MessageDispatcherIF XMLMsg Message Dispatcher Msg type(): MsgType Component receive(msg) Resurssialusta Kuva 7.10: Nelikerrosmalliin perustuva esimerkkisovellus. voisi olla vaikkapa EJB:hen pohjautuva vakuutusjärjestelmäkehys, Swing:iin (Javan graafinen käyttöliittymäkirjasto) perustuva verkonhallintasovelluskehys tai Symbian-ympäristöön toteutettu mobiilisovelluskehys, jonka avulla voidaan toteuttaa sama sovellus useille esimerkiksi näytöiltään erilaisille mobiililaitteille. Sovellusalusta on yleensä yrityskohtainen, koska sen tavoitteena on saattaa tietyn sovellusalueen ohjelmistojen tuntemusta helposti uudelleenkäytettävään muotoon. Helppouden lisäksi tavoitellaan usein myös ilmaisuvoimaisuutta, jolloin pienellä sovellustason työllä saadaan rakennetuksi paljon ominaisuuksia tarjoava tuote. Sovellusalusta on siksi keskeinen strateginen väline tietyllä sovellusalueella toimivalle yritykselle Sovelluskerros Tietokantatuki, CORBA Sovelluskerros toteuttaa lopulta sovelluskohtaiset toiminnalliset vaatimukset. Tämän kerroksen osat kirjoitetaan vasten sovellusalustan tarjoamaa erikoistamisrajapintaa. Tämä rajapinta voi piilottaa pohjalla olevan arkkitehtuurityylin, tai paljastaa sen rajoitetusti. Joskus sovelluskerroksen osat voidaan generoida automaattisesti lähtien sovelluksen vaatimusten kuvauksesta. Sovelluskerros on aina yrityskohtainen. Esimerkkinä nelikerrosmallista voisi mainita esimerkiksi kuvan 7.10 mukaisen pörssiohjelmiston. Huomaa, että kullakin kerroksella on siinä selvästi oma roolinsa ja vastuualueensa. send(msg) register(msgtype,component) MVC BrakeViewIF update() Tarkastellaan edellisen luvun esimerkkiarkkitehtuuria, jossa esittelimme auton polttoaineenkulutuksen valvontaohjelmiston. Koska kyseinen arkkitehtuuri sisälsi jo valmiiksi joustavuutta tarjoavia suunnitteluratkaisuita, voidaan sitä ajatella myös yleisen valvontaohjelmiston tuoterungona (kuva 7.11). Tällaisen valvontasovelluksen tehtävänä on tarkkailla auton käyttäytymistä ajon aikana, ja kerätä erilaista informaatiota huoltoa ja korjaustoimenpiteitä varten tarkkailemalla auton tiedonsiirrosta huolehtivaa CAN-väylää. Valvontaohjelmistolla ei ole merkittäviä reaaliaikavaatimuksia, koska se ei suoraan osallistu auton ajoaikaiseen hallintaan. Kuvassa 7.11 on annettu esimerkkinä jarrujen tilan valvontaan liittyvät osat; vastaavia MVC-kolmikkoja voi esiintyä muille auton tarkkailua vaativille loogisille yksiköille. Järjestelmään voi liittyä myös komponentteja (SomeServices), joilla ei ole niiden tilaa heijas- Brake- View BrakeController handleevent(event) SomeServices BrakeModelIF BrakeState recordusage checkcondition register(view) getstate() setstate() GSMComp sendreport Kuva 7.11: Ajoneuvon valvontaohjelmiston tuoterunko 7.8 Esimerkki: auton valvonnan tuoterunko DB
98 180 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 181 tavaa käyttöliittymää mutta jotka käsittelevät joitakin CAN-sanomia halutuilla tavoilla, mahdollisesti lähettäen edelleen viestejä muille komponenteille. Suoraviivaisen tuoterungoksi muuttamisen jälkeen arkkitehtuuri tarjoaa seuraavanlaisia muuntelumahdollisuuksia: Uudenlaisten CAN-sanomien kuunteleminen ja niihin reagoiminen on mahdollista lisäämällä järjestelmään tarvittavat uudet XML-sanomat ja niitä ymmärtävät käsittelykomponentit, joita käyttäen on mahdollista luoda kokonaan uudenlaisia toimintoja. Tarvittaessa käsittelykomponentit saattavat olla yhteydessä ulkomaailmaan (GSMComp) tai käyttää jo aiemmin toteutettua tietokantaa. Uudenlaisten käyttäjälle tarjottavien näkymien lisääminen tapahtuu toteuttamalla sopivat View- ja Controller-komponentit, jotka voivat esimerkiksi hyödyntää erilaisten ajotietokoneiden näyttöjen ominaisuuksia. Samoin erilaiset ohjaintoiminnallisuudet (esim. kosketusnäyttö ja perinteinen autossa käytetty ns. "viiksiin" sijoitettu ohjausmekanismi) voidaan huomioida näitä komponentteja päivittämällä. Sama pätee myös eri kieliversioihin, olettaen että ajoneuvo halutaan lokalisoida. Voi tosin olla kätevämpää toteuttaa yksinkertainen varianssipiste tätä varten näkymäkomponentin sisään. CAN-väylän vaihtaminen toisenlaiseen väylään on mahdollista vaihtamalla väyläajuri ja mahdollisesti päivittämällä komponenttia CANFilter. Arkkitehtuuriin liittyy myös joitakin perustavaa laatua olevia ongelmia, kun sitä ryhdytään muuntamaan tuoterungoksi: Järjestelmässä ei ole selkeää konfiguraatiosta vastaavaa komponenttia. Tästä syystä esimerkiksi toiminto, joka automaattisesti selvittäisi itse senhetkisen toimintaympäristön (esim. saman ajoneuvon eri mallit), voi osoittautua vaikeaksi toteuttaa, vaikkei tämä yhden mallin tapauksessa ongelmallista olisikaan. Virhetilanteista toipuminen on hajautettu erikseen kaikkiin komponentteihin. Tästä syystä voi olla vaikea taata, että kaikki komponentit, joiden pitäisi tietää virheistä, ovat var- masti saaneet ilmoituksen. Tämä ongelma olisi voinut tulla vastaan jo yhdenkin version tapauksessa. Koska tämä tuoterunkoarkkitehtuuri on suoraviivaisesti johdettu aiemmin esitetystä erikoistapauksesta, lienee selvää, ettei säästöjen saamiseen tarvita kovinkaan montaa sukupolvea, ainakaan periaatteessa. Käytännössä tilannetta hankaloittaa teknisten ominaisuuksien lisäksi se, että tuoterungon käyttö vaatii tukea ohjelmistoprosessilta, jonka muuttamisen aiheuttamat kustannukset on myös otettava huomioon. Tätä tuoterunkoa voisi ylläpitää organisaation arkkitehtuuriyksikkö, joka ottaisi vastuulleen perusarkkitehtuurin ylläpidon ja jatkokehityksen, esimerkiksi edellä mainitun automaattisen konfiguraationhallinnan toteuttamisen kaikkien sovellusten käyttöön. Kyseessä olisi tällöin arkkitehtuurialustaa ylläpitävä yksikkö. Tuoteyksiköt voisivat erikoistua esimerkiksi erilaisten ajoneuvojen valvontaohjelmistojen tai eri tyyppisten valvontasovellusten toteuttamiseen. Näiden toteuttamisessa on luonnollisesti mahdollista hyödyntää tiettyä sovelluskategoriaa tukevia sovellusalustoja. 7.9 Tuoterunkojen ongelmia Tuoterunkojen käytössä on todettu myös useita ongelmia. Usein ongelmat liittyvät henkilöstöön tai muihin ohjelmiston toteuttamiseen liittyviin sidosryhmiin, eivät suoraan toteutustekniikkaan. Jos yrityksessä on suuri henkilöstön vaihtuvuus, tuoterunkolähestymistapa ei välttämättä onnistu. Tämä johtuu yhtäältä siitä, että yksittäiset henkilöt eivät ole riittävän motivoituneita työhön, jonka hedelmät korjataan vasta vuosien päästä, ja toisaalta siitä, että tuoterunkojen yhteydessä tietyt henkilöt tulevat asiantuntemuksensa takia avainhenkilöiksi, joiden lähteminen yrityksestä on huomattava liiketoimintariski. Myös johdon ja teknisen henkilökunnan välille voi muodostua konflikti, sillä johdon kannalta olisi kätevää voida panostaa johonkin yksittäiseen tuotteeseen muita tuotteita enemmän, kun taas tekniseltä kannalta jokaista yksittäistä tuotetta tärkeämpää on kehittää tuoterunkoa. Joskus tämä ongelma ratkaistaan valitsemalla ns. lead-tuote, joka on uuden tuoterunkosukupolven ensimmäinen ilmentymä, ja suuntaamalla käytettävät voimavarat tämän tuotteen toteuttamiseen.
99 182 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 183 Vaikka tämä saattaa nopeuttaa ensimmäisen tuotteen valmistumista, tuoterungon kannalta lähestymistapa voi johtaa eräänlaiseen huojumiseen, jossa jokainen uusi tuoterunkosukupolvi joutuu oikaisemaan hivenen eri ominaisuuksien kohdalla, jotta sukupolven ensimmäinen tuote saadaan toteutettua riittävän nopeasti. Luonnollisesti muut tuotteet voivat kärsiä näistä oikaisuista. Samoin esimerkiksi arkkitehtuuri voi kärsiä, sillä myytävien ominaisuuksien toteuttaminen teknisten tuoterunkoa parantavien ominaisuuksien sijaan voi lyhyellä tähtäimellä olla houkuttelevaa. Lisäksi ongelmia voi syntyä konflikteista alustan kehittäjien ja tuotekehittäjien välillä: tuotekehittäjiltä tulee toiveita kehittää sellaisia alustan ominaisuuksia, jotka auttavat kyseistä tuotetta mutta eivät välttämättä muita tuotteita. Vastaavanlaisia konflikteja voi syntyä myös asiakkaiden ja yrityksen välille: asiakkaat haluavat panostuksia omaan tuotteeseensa eivätkä ymmärrä, jos yritys haluaisikin kehittää sen sijaan alustaa, joka voi hyödyttää myös kilpailijoita. Vastaavia konflikteja voi syntyä myös teknisistä syistä, liittyen esimerkiksi saman tuotelinjan eri tuotteiden skaalautuvuuteen tai luotettavuuteen. Yleisenä periaatteena voidaankin pitää sitä, että ääripään tuotteet kärsivät lähes aina tuoterungon käytöstä, sillä tuoterunko voi helposti olla hivenen liian monimutkainen yksinkertaisimmille tuotteille ja hivenen liian yksinkertainen monimutkaisimmille tuotteille. Tuoterunkostrategian valitseminen merkitsee yleensä (ainakin) ensimmäisen tuotteen julkistuksen viivästymistä, sillä esimerkiksi nelikerrosarkkitehtuuria käytettäessä joudutaan suunnittelemaan myös kaikki välikerrokset ja niiden keskinäiset rajapinnat. Lisäksi muunneltavuuteen kiinnitetty huomio aiheuttaa suunnittelussa lisätyötä jo sinällään. Joissakin tapauksessa tämä aikataulullinen ongelma on hoidettu tuottamalla samanaikaisesti tuoterungon kanssa ensimmäinen tuote siitä riippumattomana projektina. Tällöin ensimmäisen tuotteen aikataulu ei riipu tuoterungosta. Toisaalta myös tämä tuote joudutaan jossakin myöhemmässä vaiheessa siirtämään alustan päälle, mikä aiheuttaa lisätyötä. Joskus ensimmäinen tuote otetaan juuri tästä syystä tuoterungon perustaksi, ja varsinainen tuoterunkoarkkitehtuuri luodaan jo toteutetun järjestelmän arkkitehtuuria kehittämällä. Tyypillisesti tuoterunko lisää byrokratiaa: tuoterunkolähestymistavassa syntyy joukko ohjelmistoja, jotka ovat monella tavalla läheisesti toisiinsa sidoksissa, ja tämän kokonaisuuden hallinta edel- lyttää enemmän kirjanpitoa ja hallinnollisia rutiineja kuin perinteisen ohjelmiston tapauksessa. Samasta syystä myös version- ja konfiguraationhallinta monimutkaistuu huomattavasti, sillä näissä yhteyksissä lopulta määritellään, mitä mikäkin tuote tarkkaan ottaen pitää sisällään. Tästä syystä myös käytettävän prosessin määritteleminen on olennaista. Itse asiassa tuoterunkojen käyttö ajatellaan joskus suoraksi jatkumoksi toimintatapojen kehityksestä, vaikka kyse ei olekaan suoraan toimintatavasta vaan teknisten apuvälineiden käytöstä. Kuten jo aiemmin todettiin, testaus saattaa olla ongelmallista tuoterungon tapauksessa. Tuotealustaa voi olla vaikea tai jopa mahdoton testata yksinään niin, että se toimisi kaikissa mahdollisissa konfiguraatioissa. Saattaa olla yksinkertaisempaa luopua tuotealustan järjestelmätestauksesta ja kohdistaa järjestelmätestaus vain yksittäisiin tuotteisiin. Joissakin tapauksissa tämäkin on liian työlästä. Tällöin voidaan esimerkiksi suorittaa järjestelmätestaus ainoastaan tärkeimpien asiakkaiden toimituksille vastaavia laitteistokonfiguraatioita käyttäen. Lisäksi jonkinlaisena ongelmana voidaan pitää sitä, että monessa käytännön tilanteessa tarvitaan nopeaa ja täsmällistä päätöksentekoa ilman tietoa siitä, mikä lopulta olisi paras ratkaisu kaikkien tuoteperheen jäsenten kannalta. Tällaisessa tilanteessa hyväksyttävän siis riittävän hyvän päätöksen aikaansaamiseen tarvitaan usein luotettua henkilöä, jonka harkintaan luottavat toisaalta suunnittelijat ja toisaalta esimiehet yrityksen ylintä johtoa myöten Yhteenveto Tuoterunkojen käyttö on paljon sovellettua tekniikkaa, jossa arkkitehtuurin avulla pyritään systemaattiseen koodin uudelleenkäyttöön useissa ohjelmistotuotteissa. Tuoterungon kannattavuus riippuu yhtäältä sen varaan toteutettavien tuotteiden määrästä ja toisaalta tuotekohtaisen räätälöinnin määrästä. Tuoterunkojen ydinkysymys on muunneltavuuden hallinta: miten suunnitella arkkitehtuuri niin, että se sallii tuotteille halutun muunneltavuuden, ja miten tukea tuotteen toteuttajaa tämän muunneltavuuden hyödyntämisessä.
100 184 Ohjelmistoarkkitehtuurit Tuoterunkoarkkitehtuurit 185 Tuoterunkoon perustuvassa lähestymistavassa tuoterunkoa hallitaan kokonaisuutena, ja tätä kokonaisuutta arvostetaan enemmän kuin sen yksittäisiä ilmentymiä tuotteissa. Tuoterunkoarkkitehtuurin nelikerrosmalli sisältää resurssialustan, arkkitehtuurialustan, sovellusalustan ja tuotekohtaisen kerroksen. Tuoterungon käyttö vaikuttaa usein myös organisaatiorakenteeseen ja ohjelmistotyössä käytettyihin toimintatapoihin Harjoitustehtäviä 7.1 Afrikan tähti on lautapeli, jossa pelaajat liikuttavat nappuloitaan Afrikan kartalle piirretyssä polkuverkossa. Polut muodostuvat paikoista, joista jotkin ovat tavallisia paikkoja, jotkin lappupaikkoja, jotkin lentokenttäpaikkoja ja jotkin satamapaikkoja. Lisäksi pelissä on kaksi aloituspaikkaa. Pelin alussa lappupaikoille sijoitetaan satunnaisesti väärin päin lappuja, jotka ovat joko jalokiviä, rosvoja tai hevosenkenkiä. Pelaajat lähtevät aloituspaikoista ja pyrkivät löytämään lapun, jossa on Afrikan tähti -timantti. Se, joka ensimmäiseksi löytää Afrikan tähden ja kuljettaa sen aloituspaikkaan, on voittanut. Muut jalokivilaput tuottavat rahaa, jota voidaan käyttää nopeaan matkustamiseen lentokentiltä ja satamista. Rosvo vie pelaajan kaikki rahat. Jos Afrikan tähden löytymisen jälkeen joku toinen pelaaja löytää hevosenkengän ja ehtii ensin aloituspaikkaan, hän voittaa. Oletetaan, että Afrikan tähti halutaan toteuttaa yhdessä koneessa toimivana tietokonepelinä, jossa kartta ja peliin liittyvä informaatio ja toiminnallisuus näkyvät näytöllä kauniilla grafiikalla varustettuna. Mitä arkkitehtuurityyliä (tai -tyylejä) soveltaisit? Suunnittele sovelluksen arkkitehtuuri UML:n luokkakaaviona. (Vihje: tee ensin sovelluksen käsitemalli luokkakaaviona ja ota se arkkitehtuurin lähtökohdaksi.) 7.2 Edellisen tehtävän Afrikan tähti -pelin toteuttanut yritys huomaa, että peli saa valtavan suosion ja että muillekin vastaaville lautapeleille (esim. Muumipeli) olisi menekkiä tietokonetoteutuksina. Niinpä yritys alkaa pohtia mahdollisuutta tuoterunkolähestymistapaan. Oletetaan, että lautapelisovelluksen toteut- tamisen keskihinta on e, ja sovelluksen kehittäminen kestää 6 kk. Millä edellytyksillä yrityksen kannattaa investoida tuoterunkoon, jos tuoterungon perustamiskustannukset ovat e ja toteutustyöhön kuluu 12 kk, ja tämän jälkeen pelisovelluksen hinta on vain e, ja aikaa tarvitaan vain 2 kk per peli? Tarkastele asiaa eri puolilta yrityksen johdon kannalta, ottaen huomioon mahdolliset liiketoimintatavoitteet, henkilöresurssien käyttö, reagointikyky markkinoihin ym. Kuinka monen pelin jälkeen tuoterunko maksaisi itsensä takaisin siinä tapauksessa, että ensimmäinen peli tehdään erikseen rinnan tuoterungon kanssa, ottaen huomioon, että tämän pelin siirtäminen tuoterungon päälle maksaisi e. 7.3 Edellisen tehtävän yritys päättää lähteä rakentamaan lautapelituoterunkoa ja määrittelee ensin sen vaatimukset. Mitkä ovat tällaisten pelien yhteiset, kaikilta vaadittavat ominaisuudet ja piirteet? Mitkä ovat niiden muunneltavat piirteet, missä rajoissa muunneltavuus on sallittua ja milloin muunnelma kiinnitetään (kehitysaikana, alustuksen yhteydessä vai käyttäjän toimesta)? 7.4 Kuvaa edellisen tehtävän lautapelituoterungon arkkitehtuuri UML:n komponenttikaaviona. Toteuta muunneltavuus käyttämällä rajapintoja ja niille annettavia erilaisia toteutuksia. Käy läpi muutamia edustavia muunneltavuusvaatimuksia, joita edellisessä tehtävässä annettiin, ja kerro, miten ne saadaan toteutettua arkkitehtuurissa. 7.5 Edellisessä luvussa kuvattuja arkkitehtuurityylejä voi käyttää myös tuoterunkoarkkitehtuurin pohjana. Mitkä asiat kussakin tyylissä ovat tällöin samoja kaikille sovelluksille, mitkä asiat voivat vaihdella eri sovelluksissa? Anna vastaus taulukkona, jossa riveinä ovat eri tyylit ja sarakkeilla yhteiset asiat ja vaihtelevat asiat. 7.6 Mitkä kuvassa 7.11 esitetyn ohjelmiston ominaisuudet olisi mielestäsi järkevää testata ilman sovellusta? Entä mitkä ominaisuudet väistämättä tarvitsisivat jonkinlaisen esimerkkisovelluksen? Määrittele lisäksi testisovellus, joka testaa ohjelmistoa mahdollisimman kattavasti. 7.7 Miten luvussa 2 esitetty malliperustainen arkkitehtuuri soveltuu tuoterunkoarkkitehtuurin kuvaamiseen? Mitä muutoksia
101 186 Ohjelmistoarkkitehtuurit malliperustaiseen arkkitehtuuriin tarvittaisiin tilanteen parantamiseksi? 7.8 Miksi johdon tuki on tuoterungon tapauksessa tärkeämpää kuin tavanomaisessa ohjelmistokehityksessä? Entä miten johto voi vakuuttua tuotekehityksen etenemisestä tuoterunkolähestymistapaa käytettäessä?
102 8 Ohjelmistokehykset Ohjelmistokehys on ohjelmistorunko, jota voidaan täydentää eri tavoin eri tarkoituksia varten; ohjelmistokehys on siten vaillinaisesti toteutettu ohjelmistotuote, jossa on aukkoja ennalta odotettuja täydennyksiä varten. Kehyksistä on tullut suosittu tekniikka tuoterunkoarkkitehtuurien toteuttamiseksi erityisesti oliomaailmassa. Tarkastelemme tässä luvussa ohjelmistokehyksiä olionäkökulmasta, vaikka periaatteessa ajatus ei ole sidoksissa mihinkään tiettyyn ohjelmointiparadigmaan. Oliokehykset yhdistävät yhtäältä tuoterunkoarkkitehtuurien tavoitteet ja toisaalta olio-ohjelmoinnin tekniikat, kuten periytymisen ja dynaamisen sidonnan. 8.1 Johdatus ohjelmistokehyksiin Ohjelmistokehykset ovat olioperustainen tapa toteuttaa tuoterunko. Näin kehyksillä on sama perustavoite kuin tuoterungoilla, laajamittainen ja systemaattinen ohjelmistojen uudelleenkäyttö. Kehystekniikassa ei uudelleenkäytetä ainoastaan joukkoa komponentteja, vaan myös ohjelmistojen arkkitehtuuria ja perustoiminnallisuutta. Tämä antaa mahdollisuuden tuottaa nopeasti laadukkaita ohjelmistotuotteita Mikä on ohjelmistokehys? Olioperustainen ohjelmistokehys (object-oriented framework) on luokka-, komponentti- ja/tai rajapintakokoelma, joka toteuttaa jonkin ohjelmistojoukon yhteisen arkkitehtuurin ja perustoiminnallisuuden. Tällainen ohjelmistojoukko voi olla esimerkiksi tietylle sovellusalueelle tarkoitettu samantyyppisten sovellusten joukko (esimerkiksi simulointisovellukset), tietyn perusrakenteen omaavien so-
103 1. peli 2. peli 3. peli 188 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 189 vellusten joukko (esimerkiksi hajautetut liiketoimintasovellukset), tietyntyyppisen graafisen käyttöliittymän omaavien sovellusten joukko tai jonkin komponentin eri variaatioiden joukko. Kehys voidaan ymmärtää ohjelmistorunkona, joka sisältää aukkoja. Kehys itsessään ei (yleensä) ole suorituskelpoinen ohjelmisto. Haluttu ohjelmisto saadaan ohjelmistokehyksen avulla täyttämällä aukot uudella koodilla, joka toteuttaa kyseisen ohjelmiston tarvitseman erityistoiminnallisuuden muuttamatta kehyksen tarjoamaa arkkitehtuuria. Tätä kutsutaan kehyksen erikoistamiseksi (specialization). Jos erikoistamisen tuloksena saadaan itsenäinen sovellus, kutsutaan kehystä sovelluskehykseksi (application framework). Jos erikoistamisen tuloksena saadaan komponentti, kutsutaan kehystä komponenttikehykseksi (framelet). Kuvassa 8.1 on verrattu tarvittavia työtuntimääriä tietokonepelien tapauksessa, kun pelit tehdään perinteisesti yksittäisinä sovelluksina (ylempi palkki) tai kun ne tehdään kehyksen avulla (alempi palkki). Nämä tulokset perustuvat todellisiin ohjelmistokehitysprojekteihin, joissa on tehty saman tapaisia pelejä molemmilla tekniikoilla. Tässä tapauksessa tulokset osoittavat, että kolmannesta pelistä lähtien kehyksen käyttö alkaa olla kannattavaa. Kehyksen tapauksessa alkuinvestointi on ollut huomattava (mukaanlukien kehyksen oppimiseen tarvittava työmäärä), mutta tämän jälkeen yksittäinen uusi pelisovellus vaatii varsin pienen työmäärän. Huomaa, että perinteiselläkin tavalla toteutettuna toinen peli vaatii vähemmän työtunteja kuin ensimmäinen, sillä toteuttaja tietää, miten sovellus kannattaa toteuttaa. Laajamittaisella ohjelmistojen uudelleenkäytöllä on kuitenkin hintansa: mikään yleiskäyttöinen ratkaisu ei koskaan voi olla yhtä tehokas kuin tiettyä sovellusta varten räätälöity ratkaisu. Esimerkiksi yllä kuvatussa esimerkissä kehyksen avulla tehdyt pelisovellukset ovat noin 70 % hitaampia ja 200 % enemmän tilaa vieviä kuin erikseen tehdyt sovellukset. Niinpä ei välttämättä kannata käyttää kehystä sovelluksissa, joissa suorituskyky on kriittisen tärkeää, vaikka kehitysajat lyhenisivätkin. Kehystekniikkaa on kuitenkin sovellettu menestyksellisesti monilla sovellusalueilla. Tunnetuimpia kehyksiä ovat graafisten käyttöliittymien rakentamiseen tarkoitetut kehykset (esimerkiksi Java-ympäristöön kuuluva Swing): tähän tarkoitukseen kehys sopiikin erityisen hyvin, koska interaktiivinen graafinen käyttöliittymä on luontevaa toteuttaa olioperustaisesti ja koska tapahtumapohjainen ilman kehystä henkilötyötunteja kehyksen avulla Kuva 8.1: Työtuntimäärien vertailu ilman kehystä (yllä) ja kehyksen kanssa (alla) pelisovellusten tapauksessa (Santelices ja Nussbaum 2001) käyttöliittymä muodostaa tavallisesti sovelluksen rungon. Nykyisin tuskin koskaan edes harkitaan tällaisen käyttöliittymän toteutusta muulla tavoin kuin jonkin kehyksen avulla. Muita menestyksellisiä kehystekniikan sovellusalueita ovat edellä mainitut pelisovellukset, pankkisovellukset, vakuutussovellukset, verkonhallintasovellukset, erilaiset graafiset editorit jne Erikoistamisrajapinta 1. peli 2. peli 3. peli Kehyksen rakentaminen + koulutus Edellä todettiin, että kehystä voidaan ajatella ohjelmistorunkona, jossa on aukkoja, laajennoskohtia (hot spot). Kun kehystä käytetään ohjelmistotuotteen tekemiseen, nämä kohdat täytetään tuotekohtaisella koodilla (kuva 8.2). Kutsut kulkevat molempiin suuntiin kehyksen ja tuotekohtaisen koodin välillä. Kehys määrää, mitkä vaatimukset tuotekohtaisen koodin tulee täyttää kussakin aukossa. Vaatimukset voivat liittyä tuotekohtaisen koodin rakenteeseen tai sen ajoaikaiseen toimintaan. Yleinen ongelma kehysten yhteydessä on kuitenkin, että näitä vaatimuksia ei täsmällisesti dokumentoida. Kehyksen tarjoamia laajennoskohtia ja niihin liittyviä vaatimuksia tuotekohtaiselle koodille kutsutaan kehyksen erikoistamisrajapinnaksi. Erikoistamisrajapinnan luonne riippuu paljon valitusta kehysarkkitehtuurityylistä, joita tarkastelemme jatkossa. Tyypillisesti kehyksen erikoistamisrajapinta on kuitenkin huomattavasti
104 190 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 191 erikoistamisrajapinta kehys tuotekohtainen osa kontrolli Kun suunnittelumallilla pyritään muunneltavuuteen, on luonnollista, että suunnittelumallia sovelletaan kehyksen ja tuotekohtaisen koodin rajapinnalla. Lähes kaikissa suunnittelumalleissa on helposti nähtävissä yleinen, kehykseen kuuluva osa, ja vaihtuva, tuotekohtainen osa. Näin suunnittelumallin ilmentymä määrittelee samalla osan kehyksen erikoistamisrajapintaa. Tätä on havainnollistettu kuvassa 8.3, jossa kuvataan, miten Rekursiokooste-suunnittelumallin ilmentymä tyypillisesti jakautuu kehyksen ja tuotekohtaisen koodin välillä. Tässä tapauksessa hierarkian lehtiolioiden luokka erikoistetaan tuotekohtaisesti, kun taas itse puurakenne sisältyy jo kehykseen. Kuva 8.2: Ohjelmistokehys, sen erikoistamisrajapinta ja tuotekohtaiset osat mutkikkaampi kuin komponentin palvelurajapinta. Laajennoskohdilla voi olla erilaisia suhteita keskenään: yhden laajennoskohdan täyttäminen tietyllä tavalla saattaa edellyttää, että jokin toinen laajennoskohta täytetään vastaavalla tavalla. Pikemmin kuin palvelija-palveltava-suhteena, kehyksellä ja tuotekohtaisella koodilla on mutkikas vuorovaikutussuhde, johon osallistuvat monet elementit kummaltakin puolelta tietyissä kehyksen säätelemissä rooleissa. Valitettavasti nykyinen ohjelmistotekniikka tarjoaa vain vähän tukea tällaisen suhteen kuvaamiseen. Niinpä erikoistamisrajapinta kuvataan parhaimmillaankin vain käyttöesimerkeillä ja/tai epäformaalilla tekstillä dokumenteissa Kehykset ja suunnittelumallit Suunnittelumalleilla on keskeinen asema kehysten arkkitehtuurissa. Tämä johtuu ennen muuta siitä, että useimpien suunnittelumallien tavoitteena on lisätä järjestelmän joustavuutta ja muunneltavuutta, johon myös kehysten arkkitehtuurissa pyritään. Monet suunnittelumallit onkin "löydetty" juuri kehysohjelmistoista. Usein kehyksen arkkitehtuuri voidaan pitkälle kuvata esittämällä, mitä suunnittelumalleja siinä on käytetty ja miksi. Tämä onkin monesti paras tapa selittää kehyksen tyypillisesti mutkikas arkkitehtuuri Hollywood-periaate Perinteisessä kirjaston uudelleenkäytössä sovelluksella on toiminnan pääkontrolli, ja se kutsuu tarvittaessa tietyissä kohdissa yleisiä kirjastopalveluja. Kehyksen tapauksessa tilanne on päinvastainen. Mahdollisen sovelluskohtaisen alustuksen jälkeen pääkontrolli siirtyy kehykselle, joka pitää sen koko toiminnan ajan. Tietyissä kohdissa kehys voi kuitenkin kutsua sovelluskohtaista koodia. Koska kehys ei saa suoraan tulla siitä riippuvaiseksi, käytetään tässä takaisinkutsuja. Kun perinteisen kirjaston tapauksessa sovellus kutsuu uudelleenkäytettävää koodia, kehyksen tapauksessa uudelleenkäytettävä koodi kutsuu sovelluskohtaista koodia. Tätä kehystekniikalle ominaista käänteistä kontrollin kulkua kutsutaan joskus Hollywood-periaatteeksi ("don t call us, we ll call you") Erikoistamismekanismit Kehyksen erikoistamisrajapinnassa voidaan käyttää erilaisia mekanismeja tuotekohtaisen koodin sitomiseen kehyksen koodiin. Perinteisesti tähän tarkoitukseen käytetään periytymistä: kehys tarjoaa kantaluokan, jonka tuotekohtainen luokka perii ja samalla antaa joillekin sen operaatioille uuden toteutuksen. Käyttämällä tehdasta (ks. luku 4) kehys voi luoda tuotekohtaisen aliluokan ilmentymiä tuntematta tätä luokkaa. Kun kehys kutsuu näiden olioiden operaatioita, kontrolli siirtyy dynaamisen sidonnan kautta tuotekohtaiseen operaation toteutukseen. Kun operaatio on suoritettu, kontrolli palaa jäl-
105 192 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 193 Leaf operation() Item operation() * children Composite operation() Kehykseen kuuluva osa Tuotekohtainen osa For all children c: c.operation() Kuva 8.3: Rekursiokooste-suunnittelumalli kehyksen erikoistamisrajapinnan osana leen kehykselle. Tämä on varsin suoraviivainen sovellus Hollywood-periaatteesta. Periytymisen sijasta voidaan käyttää myös rajapintojen toteutusta: jollekin kehyksen sisältämälle rajapinnalle annetaan tuotekohtainen toteutus luokkana tai komponenttina. Kehys näkee tällöin tuotekohtaisen luokan ilmentymän tai komponentin rajapinnan kautta, ja pyytäessään jotakin rajapintaan kuuluvaa palvelua tulee kutsuneeksi tuotekohtaista koodia. Sekä rajapinnan toteutusta että periytymistä käyttävässä erikoistamisessa kehyksen tulee tarjota tapa, jolla tuotekohtainen olio tai komponentti voidaan rekisteröidä kehykselle. Periytyminen ja rajapinnan toteutus ovat suhteellisen voimakkaita erikoistamismekanismeja, jotka mahdollistavat täysin uuden koodin liittämisen kehykseen. Jos haluttu muunneltavuus on rajoitetumpaa ja ennakoitavissa, voidaan näiden sijasta käyttää ohjelmointikielten tarjoamia parametrointimekanismeja. Tuotekohtainen alustuskomponentti voi tällöin käyttää esimerkiksi rakentajien parametreja luodessaan kehyksen luokista erilaisia ilmentymiä tai jotakin kehyksen luokan operaatiota kutsuessaan antaa parametrien avulla kehykselle tietoa halutusta toimintamuunnelmasta. Alustuskomponentti voi myös toteuttaa haluamansa variaation muodostamalla tietyn oliokonfiguraation kehyksen luokkien ilmentymistä. Eräät ohjelmointikielet, mukaan lukien C++ ja uusimmat Javaversiot, tarjoavat mahdollisuuden geneeristen ohjelmarakenteiden esittämiseen. Esimerkiksi geneerinen luokka on luokkakaavain, josta ei sellaisenaan voi luoda ilmentymiä vaan joka pitää ensin konkretisoida kiinnittämällä geneeriset parametrit. Tyypillisesti geneerisen luokan tapauksessa parametroidaan jokin tietotyyppi, jota luokka käsittelee. Esimerkiksi geneerinen pinoluokka voi parametroida pinossa olevien alkioiden tyypin (tai luokan). Sovellus voi konkretisoida tällaisen yleiskäyttöisen geneerisen pinoluokan kiinnittämällä alkioiden tyypiksi jonkin sovelluskohtaisen tyypin tai luokan. Geneeriset rakenteet tarjoavat ilmeisen erikoistamismekanismin kehyksille. Sovelluskohtainen koodi voi erikoistaa kehyksen tarjoamia geneerisiä luokkia kiinnittämällä näiden todellisiksi parametreiksi joitakin sovelluksen omia luokkia. Tällainen ohjelmakoodin tasolla tapahtuva staattinen erikoistaminen on hyvin erityyppistä aikaisempiin mekanismeihin verrattuna, joissa erikoistus saadaan aikaan varioimalla kehyksen käsittelemiä olioita kehyksen antamien rajojen puitteissa. Erikoistamiseen voidaan käyttää myös kielen tarjoamia refleksiivisyysominaisuuksia. Kieli on refleksiivinen, jos se sallii ohjelman tutkia ja mahdollisesti jopa muuttaa itseään. Esimerkiksi Java tarjoaa rajoitetun refleksiivisyyden, jonka avulla ohjelmoija voi esimerkiksi kysyä erilaista lähdekooditason tietoa ohjelmasta, kuten olioiden luokkien ominaisuuksia. Refleksiivisyyttä hyödyntämällä kehys voi kysyä ajoaikana tietoa sovelluskohtaisesta koodista, ja ohjata omaa toimintaansa tämän tiedon mukaisesti. Kehys voisi kysyä vaikkapa jonkin sovelluskohtaisen olion julkisten attribuuttien nimet, ja käyttää niitä hyväksi käyttöliittymässä. Tällöin kyseisen sovelluskohtaisen luokan kirjoittajan täytyy tietää kehyksen toimintaperiaate, jotta hän voi antaa koodin oikeassa muodossa. Nämä periaatteet ovat siksi osa kehyksen erikoistamisrajapintaa. Viime kädessä myös itse sovellus voi tarjota loppukäyttäjälle toimintoja, jotka voidaan tulkita sovelluksen ajoaikaiseksi erikoistamiseksi. Käyttäjä voi esimerkiksi räätälöidä käyttöliittymän mieleisekseen (esim. tekstinkäsittelysovellukset), tai kuvata erityisellä skriptikielellä tietyssä tilanteessa aktivoituvia toimintoja (esim. taulukkolaskentasovellukset). Pitkälle vietynä käytön aikainen variaatioiden kuvaus ja valinta hämärtää jossain määrin eroa sovelluksen kehittäjän ja loppukäyttäjän välillä. Käytön aikainen variaation hallinta
106 194 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 195 liittyy läheisesti järjestelmien käytettävyysominaisuuksien parantamiseen, emmekä puutu siihen tässä enempää Kehykset ohjelmistokehitysparadigmana Ohjelmistotekniikan perusongelma on järjestelmän vaatimusten ja käytettävissä olevan toteutusteknologian välisen kuilun ylittäminen. Kehykset tarjoavat tämän ongelman helpottamiseen yhden lähestymistavan: kehys nostaa toteutusvälineistön tasoa lähemmäksi tietyn sovellusalueen tai sovelluskategorian vaatimuksia. Sovelluksen kehittäjän ei tarvitse miettiä kyseisen sovellusalueen edellyttämiä perusratkaisuja (esimerkiksi arkkitehtuuria), vaan riittää, että hän tuntee kehyksen erikoistamisrajapinnan ja sen suhteen sovelluksen vaatimuksiin. Erikoistamisrajapinta tarjoaa sallitun variaation sisällä hyvin tehokkaat välineet näiden vaatimusten toteuttamiseen. Hyvin abstraktissa mielessä kehystä voidaan verrata korkean tason sovellusorientoituneeseen kieleen. Ongelmana vain on, että tämä kieli kehyksen erikoistamisrajapinta on tavallisesti varsin puutteellisesti määritelty, jos ollenkaan. Toisaalta kehyksen etuna on avoimuus: sovelluskehittäjä toimii yleisen ohjelmointikielen puitteissa ja hallitsee suoritettavan toteutuskoodin, mikä helpottaa järjestelmän testausta ja ylläpitoa. Tarvittaessa sovellusohjelmoija voi myös suhteellisen helposti laajentaa kehyksen oletettua erikoistamisrajapintaa ja toteuttaa ominaisuuksia, joita kehyksen rakentaja ei ole edes ennakoinut. Kehyksen "toteutuskieli"-rooli konkretisoituu tilanteessa, jossa käytetään sovellussuuntautunutta tekstuaalista kieltä halutun sovelluksen ominaisuuksien kuvaamiseen, ja tästä kuvauksesta tuotetaan automaattisesti kehyksen laajennoskohtiin lisättävä sovelluskohtainen koodi. Näin kehyksen erikoistamisrajapinta määritellään täsmällisesti konkreettisen kielen muodossa, mikä helpottaa erikoistamisrajapinnan ymmärtämistä ja käyttöä. 8.2 Kehyslajit Tarkastelemme seuraavaksi yleistä kehysten luokittelua sen perusteella, mikä on kehyksen pääasiallinen erikoistamismekanismi. Luokittelu ei ole tarkka siinä mielessä, että käytännössä kehykseen voidaan tavallisesti soveltaa useita erikoistamismekanismeja. Luokitte- lu auttaa kuitenkin sovelluskehittäjää ymmärtämään kehyksen erikoistamisrajapinnan perusluonteen. Esitämme seuraavassa luokittelun käyttäen kuviteltua esimerkkikehystä. Oletetaan, että eräs biologi haluaa tutkia eliöpopulaatioiden käyttäytymistä tietokonesimulaatioilla. Tätä varten hän kehittää simulointijärjestelmän, jonka avulla populaation toiminta voidaan esittää näytöllä ja siitä voidaan kerätä erilaista tilastotietoa. Jotta järjestelmällä voisi tutkia erityyppisiä populaatioita, sen käyttöliittymä mahdollistaa tiettyjen parametrien asetuksen. Biologi huomaa kuitenkin pian, että mikään parametrointi ei riitä kattamaan kaikkia eliölajeja: viime kädessä uusien eliölajien käyttäytyminen pitäisi pystyä antamaan ohjelmakoodina. Toisaalta, vaikka itse eliöiden käyttäytyminen vaihteleekin lähes mielivaltaisesti, suurin osa itse simulointijärjestelmästä ja eliöiden elinympäristöstä pysyy samana. Tämä on tyypillinen tilanne, joka johtaa kehystekniikan käyttöön: kehystä voidaan ajatella ohjelmistona, joka parametroidaan ohjelmakoodilla. Tarkastelemme seuraavassa erilaisia ratkaisuja simulointikehyksen toteuttamiseksi. Keskitymme lähinnä kehyksen (huomattavasti yksinkertaistetun) loogisen ytimen kuvaamiseen, ja sivuutamme esimerkiksi graafisen käyttöliittymän, joka tällaisessa kehyksessa on toki olennainen. Tämän kehyksen kaltaisen simulointikehyksen täydellinen Java-koodi erikoistuksineen on saatavilla osoitteesta Abstraktit kehykset Abstrakti kehys (abstract framework) ei sisällä lainkaan suoritettavaa koodia, vaan ainoastaan rajapintoja. Tällainen kehys ei siis anna simulointisovellusten yhteistä perustoiminnallisuutta, mutta se määrittelee kuitenkin rajapintojen avulla sovelluksen luokkien tai komponenttien palvelut. Esimerkkitapauksessa abstrakti kehys voisi sisältää simulointimaailman (World), eliöiden (Creature) sekä eliötehtaan (CreatureFactory) rajapinnat kuvan 8.4 mukaisesti. Kuvassa kehys on esitetty pakkauksena, joka on varustettu stereotyypillä <<framework>>. Käytämme tässä rajapinnalle luokkasymbolia, jotta rajapinnan palvelut saadaan luontevasti näkyviin. Abstrakti kehys itsessään rajoittaa sovelluskehittäjää varsin niukasti: kehys edellyttää toteutukselta ainoastaan rajapintojen nou-
107 196 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 197 <<framework>> SimulationFW <<interface>> Creature setmyworld(world) show() getx(): int gety(): int move() interact(creature) growold() die() Kuva 8.4: Abstrakti kehys dattamista. Kuvan 8.4 UML-mallissa on lisäksi annettu muuta informaatiota oletetuista sovelluksista koskien mahdollisia oliokonfiguraatiota (kertautumismääreet "*" ja "1"). Tämä informaatio yhdessä luokkien ja operaatioiden nimien (ja parametrityyppien) kanssa antaa jo vihjeitä siitä, miten toteutus on ajateltu tehtävän, mutta sovelluskehittäjälle jää edelleen suuria vapauksia, ja vastaavasti kehys tukee sovelluskehittäjää suhteellisen vähän. Abstraktia kehystä onkin luontevampaa käyttää kerroksittaisessa kehyksessä (ks. kohta 8.3) yleisimmän kerroksen rajapintana kuin sellaisenaan sovellusten pohjana. Tällöin voidaan ajatella, että abstrakti kehys erikoistetaan antamalla konkreettinen kehys, joka noudattaa abstraktin kehyksen rajapintoja. Abstrakti kehys voi tällaisessa tilanteessa toimia standardina, jonka eri konkreettiset kehykset toteuttavat. Tunnettu esimerkki tästä on EJB (Enterprise JavaBeans), joka on rajapintoina määritelty standardi. EJB-standardin toteuttavat useat tuotteet, jotka tarjoavat kehyksen hajautettujen liiketoimintasovellusten rakentamiseen Javalla Muunneltavat kehykset * <<interface>> World getsize(): int add(creature) remove(creature) show() simulate(int,creaturefactory) <<interface>> CreatureFactory 1 createcreature(): Creature Muunneltava kehys (white-box framework) tarjoaa erikoistamisrajapinnassaan luokkia, jotka toimivat kantaluokkina sovelluskohtaisille aliluokille. Aliluokat antavat toteutuksen yhdelle tai useammalle kantaluokan operaatiolle. Lisäksi sovelluskehittäjä tyypillisesti antaa sovelluskohtaisen alustuskoodin, joka luo näiden aliluokkien il- mentymiä ja rekisteröi ne kehykselle. Alustuksen jälkeen pääkontrolli siirretään kehykselle, joka kutsuu sovelluskohtaisten olioiden palveluja Hollywood-periaatteen mukaisesti. Muunneltavassa kehyksessä pääasiallisena erikoistamismekanismina on periytyminen. Kuvassa 8.5 on annettu esimerkkikehys muunneltavana versiona. Kehys koostuu eliö- ja eliötehdasrajapintojen lisäksi näiden oletustoteutuksista (kuvan luokat DefaultCreature ja DefaultCreatureFactory) sekä simulointimaailman toteuttavasta luokasta (World). Viimeksi mainittua ei ole ajateltu periytettävän, vaan ainoastaan käytettävän sellaisenaan sovelluksessa. Oletustoteutusluokat määrittelevät eliöille tietyn peruskäyttäytymisen (mm. liikkuminen ja keskinäinen vuorovaikutus). Kuvassa 8.5 on myös näkyvissä kehyspakkauksen ulkopuolella erikoistus, jossa kehystä on käytetty toisiaan syövien eliöiden muodostaman populaation simulointiin. Tätä varten sovelluskehittäjä on antanut näiden eliöiden käyttäytymistä kuvaavan luokan (EatingCreature) sekä sitä vastaavan olioita luovan tehdasluokan (EatingCreatureFactory). Syöjäeliöluokassa on annettu uusi toteutus eliöiden vuorovaikutukselle (operaatio interact), joka tässä tapauksessa sisältää syömistoiminnon. Alustuskoodissa (SimulationApp) luodaan World-olio sekä EatingCreatureFactory-olio, luodaan syöjäeliöitä ja liitetään ne maailmaan add-operaatiolla, sekä lopuksi siirretään pääkontrolli kehykselle kutsumalla maailman simulate-operaatiota. Jälkimmäisen parametrina annetaan simulointiajan lisäksi tehdasolio, jotta kehys pystyisi myös itse tarvittaessa luomaan syöjäeliöitä. Muunneltava kehys tarjoaa voimakasta tukea sovelluskehitykseen, mutta sen menestyksellinen käyttäminen edellyttää, että sovelluskehittäjä tuntee hyvin kehyksen erikoistamisrajapinnan: mitkä luokat on tarkoitettu periytettäviksi, mille operaatioille voidaan antaa tai on annettava sovelluskohtainen toteutus, ja miten se tarkkaan ottaen tulisi tehdä. Tätä on yleensä työläs selvittää pelkästään kehyksen koodia tutkimalla. Niinpä erikoistamisrajapinnan täsmällisen kuvauksen merkitys on erityisen suuri muunneltavan kehyksen tapauksessa. Esimerkkikehyksen tapauksessa tehdasolion käyttöoletus on epäsuorasti pääteltävissä rajapintojen ja oletustoteutusten perusteella. Mutkikkaan kehyksen tapauksessa tämäntyyppiset oletukset jäävät kuitenkin helposti huomaamatta sovelluskehittäjältä, ellei niitä ole selkeästi dokumentoitu.
108 198 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 199 <<framework>> SimulationFW DefaultCreature xcoord ycoord age <<interface>> Creature setmyworld(world) show() getx(): int gety(): int move() interact(creature) growold() die() setmyworld(world)... die() EatingCreature energy interact(creature) Kuva 8.5: Muunneltava kehys Hyvin suunniteltuna, dokumentoituna ja esimerkkierikoistuksilla varustettuna muunneltava kehys tarjoaa kuitenkin voimakkaan välineen sovelluskehitykseen. Varsinkin sellaisessa ympäristössä, jossa kehyksen suunnittelijat ja käyttäjät ovat samassa organisaatiossa (esimerkiksi yrityksessä), muunneltavan kehyksen edut tulevat hyvin esiin. Suurin riski muunneltavan kehyksen yhteydessä on sen kasvaminen suureksi, monoliittiseksi ohjelmistoksi, jonka erikoistamisrajapintaa kukaan ei enää hallitse Plugin-kehykset * World getsize(): int add(creature) remove(creature) show() simulate(int,creaturefactory) <<create>> SimulationApp main() <<create>> <<create>> <<create>> <<interface>> CreatureFactory 1 createcreature(): Creature DefaultCreatureFactory createcreature(): Creature EatingCreatureFactory createcreature(): Creature Plugin-kehyksen (plug-in framework) pääasiallinen erikoistamismekanismi on rajapintojen toteutus: kehys erikoistetaan toteuttamalla kehyksen sisältämiä rajapintoja sovelluskohtaisilla laajennosyksiköillä (plug-in), ja rekisteröimällä toteutukset kehykselle. Laajennosyksikkö koostuu tyypillisesti yhdestä tai useammasta luokasta tai komponentista. Kehys rekisteröi laajennosyksiköt automaattisesti lataamalla ne sovitusta hakemistosta. Näin kehys voidaan erikoistaa yksinkertaisesti sijoittamalla halutut toteutukset laajennoshakemistoon ennen järjestelmän käynnistystä. Tyypillinen esimerkki plugin-kehyksestä on Eclipse, joka on erilaisten ohjelmistokehitysympäristöjen tekemiseen tarkoitettu Java-ohjelmistoalusta. Eclipsen laajennosyksikkö voi määritellä edelleen omia laajennoskohtiaan (extension point), joita muut yksiköt laajentavat. Laajennoskohta on tavallisesti rajapinta, jonka laajentava yksikkö toteuttaa. Näin pientä ydintä lukuun ottamatta kaikki uuden ympäristön toiminnallisuus tulee määriteltyä rajapintoja toteuttavissa laajennosyksiköissä. Esimerkkikehyksemme tapauksessa yksi laajennosyksikkö voisi antaa uuden eliötyypin toteutuksen, sen tarvitseman tehdasluokan sekä uuden sovelluksen pääohjelman kuvan 8.6 mukaisesti. Tässä kehys käynnistyessään aktivoi laajennosyksiköiden lataamisen (luokka PluginLoader), jonka puolestaan oletetaan automaattisesti aktivoivan ladatun sovelluksen pääohjelman (main-operaatio SimulationApp-luokassa). Huomaa, että olemme käyttäneet UML:n pakkausta <<plugin>>-stereotyypillä varustettuna ilmaisemaan laajennosyksikköä. Plugin-kehyksellä on selkeitä etuja verrattuna perinteiseen muunneltavaan kehykseen. Plugin-kehyksen erikoistamisrajapinta on helpommin nähtävissä, koska se koostuu rajapintojen toteutuksista. Laajennosyksiköt muodostavat luonnollisen tavan strukturoida erikoistus ja liittää se kehykseen. Periaatteessa plugin-kehys sallii jopa sovelluksen ajoaikaisen erikoistamisen Koottavat kehykset Koottava kehys (black-box framework) on perinteisen uudelleenkäytettävän kirjaston ja kehyksen välimuoto: pääkontrolli on kehyksellä, mutta kehys ei (takaisin)kutsu sovelluskohtaista koodia. Tarvittava muunneltavuus saadaan aikaan pelkästään luomalla kehyksen luokkien ilmentymiä sopivilla alustusparametreilla, muodostamalla näistä ilmentymistä haluttuja konfiguraatioita ja kutsumalla näiden palveluja sopivilla parametreilla ennen kontrollin siirtämistä kehykselle. Koottavaa kehystä pidetään usein muunneltavan kehyksen evoluution lopputuloksena: kun sovellusalueella tarvittava muunnel-
109 200 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 201 <<framework>> SimulationFW <<framework>> SimulationFW <<interface>> Creature setmyworld(world) show() getx(): int gety(): int move() interact(creature) growold() die() <<plugin>> EatingApplication * World getsize(): int add(creature) remove(creature) show() simulate(int,creaturefactory) <<load>> 1 PluginLoader load() <<interface>> CreatureFactory createcreature(): Creature DefaultCreature xcoord ycoord age <<interface>> Creature setmyworld(world) show() getx(): int gety(): int move() interact(creature) growold() die() setmyworld(world)... die() * <<create>> EatingCreature EatingCreature energy interact(creature) World getsize(): int add(creature) remove(creature) show() simulate(int,creaturefactory) <<create>> <<interface>> CreatureFactory 1 createcreature(...): Creature DefaultCreatureFactory <<create>> createcreature( ): Creature EatingCreatureFactory createcreature( ): Creature EatingCreatureClass energy interact(creature) SimulationApp main() EatingCreatureFactory createcreature(): Creature SimulationApp main() <<create>> <<create>> Kuva 8.6: Plugin-kehys Kuva 8.7: Koottava kehys tavuus ymmärretään riittävästi, kehyksen luokat ovat muokkaantuneet tukemaan tätä muunneltavuutta niin hyvin, että niitä ei enää tarvitse periyttää. Näin kehyksen käyttö yksinkertaistuu huomattavasti ja tulee lähemmäksi tavanomaista kirjastoa. Koottavan kehyksen tapauksessa sovelluskehittäjän täytyy ymmärtää vain kehyksen luokkien palvelurajapinnat, mutta ei luokkien toteutustapaa, mikä taas muunneltavan kehyksen tapauksessa on usein välttämätöntä. Esimerkkikehyksestä tulee koottava kehys, jos biologi päättää, että tulevissa simulointisovelluksissa tarvitaan vain tiettyjä peruseliötyyppejä; kunkin perustyypin sisällä tarvittava muunneltavuus saadaan aikaan rakentajien ja muiden operaatioiden parametroinnilla. Niinpä biologi siirtää EatingCreature-luokan tehtaineen kehyksen puolelle yhdessä muiden vastaavien peruseliötyyppejä kuvaavien luokkien kanssa. Tehtaita kannattaa edelleen käyttää eliöiden luontiin, koska näin saadaan muu osa kehyksestä riippumattomaksi valitusta eliötyypistä. Näin saadun koottavan kehyksen erikoistuskoodiksi riittää alustus (SimulationApp-luokan main-operaatio), joka luo halutun alkutilanteen simulointia varten käyttäen suoraan kehyksen luokkia. Tämän mukainen koottava simulointikehys on esitetty kuvassa 8.7. Yleisesti koottavan kehyksen etuna on käytön helppous, mutta toisaalta koottava kehys tukee vain hyvin rajattua muunneltavuutta: alustuskoodia lukuun ottamatta soveluskehittäjällä ei ole mahdollista määritellä täysin uutta käyttäytymistä. Tämä ratkaisu ei siten toimi sovellusalueilla, joilla variaatio on ainakin yksityiskohdissaan vaikeasti ennustettavaa. Lopuksi on syytä korostaa, ettei kehys juuri koskaan kuulu pelkästään yhteen edellä mainituista kategorioista. Esimerkiksi lähes kaikissa kehyksissä on koottavan kehyksen piirteitä, vaikka niiden perusluonne olisikin jokin muu. Tämä johtuu siitä, että eri asioiden varioituvuus tietyllä sovellusalueella voi olla hyvinkin erilaajuista: joissakin asioissa varioituvuus saattaa olla rajattu muutamiin vaihtoehtoihin, kun taas toisissa asioissa pitää voida sallia eri sovelluksissa lähes mielivaltainen käyttäytyminen. Niinpä myös tarvittavat erikoistamismekanismit vaihtelevat samassakin kehyksessä. Silti edellä esitetyt kategoriat antavat yleiskuvan kehysten tavallisimmista tyypeistä.
110 202 Ohjelmistoarkkitehtuurit Ohjelmistokehykset Kehysten strukturointi Eräs merkittävä riski kehyksiin perustuvassa uudelleenkäytössä on kehyksen kasvaminen monoliittiseksi, hallitsemattomaksi kokoelmaksi luokkia, joilla on mutkikkaita keskinäisiä riippuvuuksia. Etenkin muunneltava kehys voi helposti tulla tällaiseksi, jos ongelmaa ei selvästi tiedosteta ja oteta huomioon kehyksen evoluutiossa. Kehysohjelmiston tapauksessa ei pidä unohtaa ohjelmistotekniikan keskeisiä periaatteita: ohjelmiston modularisointia (so. ohjelmiston jakamista yksiköihin, joilla on kiinteät sisäiset suhteet mutta heikot suhteet muihin yksiköihin) ja huolenaiheiden erottelua (separation of concerns) eri yksiköihin. Tarkastelemme seuraavassa, miten kehyksiä voidaan strukturoida niiden hallittavuuden parantamiseksi Kerroksittaiset kehykset Kerrosarkkitehtuurit tarjoavat luontevan tavan jakaa kehysohjelmisto osiin, jotka ovat eri abstraktiotasoilla. Kehysten tapauksessa tasot erotellaan toisistaan niiden tarjoaman tuen yleisyyden suhteen: erikoistava ohjelmistokerros vähentää aina yleisyyttä. Tavallisesti ajatellaan, että erikoistuksen tuloksena syntyy yksittäinen sovellus, jossa yleisyysaste on nolla. Näin ei kuitenkaan tarvitse välttämättä olla: erikoistuksen tuloksena voidaan saada ohjelmistotuote, joka on uusi, entistä erikoistuneempi kehys. Tämä uusi kehys tarjoaa voimakkaampaa tukea mutta kapeammalla sovellusalueella. Kun kehyksen sovellusaluetta kavennetaan asteittain peräkkäisillä erikoistuksilla, saadaan tuloksena kerroksittainen kehys, jossa alimpana kerroksena on hyvin yleinen ydinkehys, sen yläpuolella hieman erikoistuneempi kehys jne., kunnes lopulta ylimpänä kerroksena on yksittäinen sovellus. Tarkastellaan esimerkkinä kerroksittaisesta kehyksestä vakuutussovelluskehystä. Kehyksen pohjana on EJB (Enterprise Java- Beans), joka on itse abstrakti kehys. EJB:n toteuttavat monet tuotteet, kuten ei-kaupallinen JBoss. Toisaalta vakuutussovellusten käyttöliittymä perustuu Javan GUI-tukeen, joka koostuu AWT-kehyksestä (Abstract Windowing Toolkit) ja sen päälle rakennetusta kehittyneemmästä Swing-kehyksestä. Näin järjestelmän logiikkaa ja Swing AWT Henkivakuutussovellus Vakuutussovelluskehys Kuva 8.8: Kerroksittainen EJB-pohjainen kehys käyttöliittymää tuetaan erillisillä kehyksillä. Kehysohjelmiston kerrosarkkitehtuuri on esitetty kuvassa 8.8. Kerroksittaisella kehyksellä on selviä etuja. Kerrosrakenne jakaa muuten monoliittisen kehyksen helpommin ymmärrettäviin osiin, joista kullakin on oma abstraktiotasonsa, erikoistamisrajapintansa ja sovellusalueensa. Kerroksia voidaan ylläpitää omina kokonaisuuksinaan, ja tiettyä kerrosta voidaan käyttää useiden erikoistuneempien kehysten pohjana (ks. hierarkkiset kehykset alla). Kerroksittainen kehys tarjoaa useita eri abstraktiotasoilla olevia erikoistamisrajapintoja, joista sovelluskehittäjä voi valita parhaiten sopivan. Samassakin sovelluksessa voidaan näin soveltaa eri abstraktiotasoilla olevia erikoistamisrajapintoja tarpeen mukaan. Jos jokin erikoistamisrajapinta osoittautuu liian rajoittuneeksi, sovelluskehittäjä voi siirtyä seuraavaksi alemmalle (ja yleisemmälle) tasolle ja tarvittaessa edelleen alemmalle tasolle, kunnes riittävän yleinen erikoistamisrajapinta saavutetaan Hierarkkiset kehykset EJB JBoss Kerroksittaisen kehyksen ideaa voidaan suoraviivaisesti yleistää haarauttamalla erikoistamispolut: pohjalla oleva kehys voidaan erikoistaa kahdeksi tai useammaksi kapeampaa sovellusaluetta tukevaksi kehykseksi. Kun tätä ajatusta sovelletaan yleisesti, järjestelmästä muodostuu hierarkkisesti rakentunut kehys. Simulointikehyksen tapauksessa hierarkkinen kehys saadaan luontevasti eliöluokittelujen mukaan. Alimmassa kerroksessa on täl-
111 204 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 205 Lentävien hyönteisten kehys Yksittäiset simulointisovellukset Hyönteisten kehys Juoksevien hyönteisten kehys Eliökehys Jyrsijöiden kehys Yleinen simulointikehys Nisäkkäiden kehys Kuva 8.9: Hierarkkinen simulointikehys Merinisäkkäiden kehys löin eliölajeista riippumaton simulointituki, seuraavaksi ylemmässä kerroksessa kaikille eliöille yhteinen oletuskäyttäytyminen. Tämä kerros voidaan edelleen erikoistaa vaikkapa hyönteisiä ja nisäkkäitä tukeviksi kehyksiksi. Nämä voidaan edelleen erikoistaa vielä kapeaalaisemmiksi kehyksiksi hyönteisten ja nisäkkäiden alalajien mukaan. Erikoistuneita kehyksiä voi rakentaa niin pitkälle, kunnes haluttu variointi saadaan aikaan sovelluksen omassa käyttöliittymässä ilman uutta koodia. Kuvassa 8.9 on esitetty mahdollinen simulointikehyksen kerrosarkkitehtuuri. Hierarkkisen kehyksen olennainen etu kerroksittaiseen kehykseen nähden on erikoistamisrajapintojen suurempi valikoima: hierarkkinen kehys tarjoaa paitsi eri yleisyystasoilla olevia erikoistamisrajapintoja myös samalla yleisyystasolla olevia mutta eri sovellusalueen segmenteille tarkoitettuja erikoistamisrajapintoja. Näin sovelluskehittäjällä on hyvä mahdollisuus löytää juuri hänen tarpeisiinsa optimaalisesti sopiva erikoistamisrajapinta, eikä hänen tarvitse ymmärtää kuin pieni osa koko kehysjärjestelmästä. Puhdas hierarkkinen kehys on käytännössä harvinainen, koska se edellyttää, että kehyksen sovellusalue voidaan jäsentää luontevasti sekä vertikaalisessa suunnassa eri abstraktiotasoihin että horisontaalisessa suunnassa eri segmentteihin. Hierarkkista kehystä kannattaa kuitenkin käyttää yleisenä mallina laajan kehysohjelmiston organisointiin, vaikka sitä ei puhtaassa muodossa saavutettaisikaan Komponenttikehysten käyttö Tarkastelimme luvussa 3 yleisesti järjestelmän jakamista komponentteihin, jotka tarjoavat ja vaativat tietyt palvelurajapinnat. Koottavan kehyksen tapauksessa on mahdollista organisoida kehys joukoksi komponentteja ja rajapintoja siten, että haluttu variaatio saadaan aikaan yhdistelemällä kehykseen kuuluvia komponentteja sopivalla tavalla. Jos kehyksellä on selkeä komponentteihin perustuva yleisarkkitehtuuri (esimerkiksi tietovuoarkkitehtuuri), suurikin kehys pysyy vielä suhteellisen hyvin hallinnassa. Komponenttipohjaista rakennetta voidaan soveltaa myös muunneltavan kehyksen tapauksessa, mutta tällöin muunneltavuus ei voi perustua pelkästään komponenttien valitsemiseen ja yhdistelyyn. Koska komponentin tulee olla itsenäinen ohjelmayksikkö, kullakin komponentilla tulisi olla myös oma erikoistamisrajapintansa. Näin kukin komponentti voi toimia ikään kuin pienenä kehyksenä, komponenttikehyksenä (framelet). Kun tällainen komponenttikehyksistä koostuva kehys halutaan erikoistaa, erikoistetaan valitut komponenttikehykset (periyttämällä tai vaadittujen rajapintojen toteutuksella), ja näin saadut varsinaiset konkreettiset komponentit liitetään sen jälkeen yhteen niin, että ne muodostavat halutun sovelluksen. Edellä kuvattu, komponenttikehyksiin pohjautuva kehys yhdistää koottavien ja muunneltavien kehysten hyvät puolet. Vaikka tällainen kehys sallii voimakkaat erikoistamismekanismit, erikoistamisrajapinta pysyy hyvin hallittuna ja ymmärrettävänä, sillä se koskee aina vain yhtä komponenttia kerrallaan. Korkeimmalla tasolla sovellus on koottu tavalliseen tapaan komponenteista hyvien modularisointiperiaatteiden mukaisesti. Jos kehys tukee tiettyä kokonaisarkkitehtuuria (esimerkiksi tietovuoarkkitehtuuria tai viestinvälitysarkkitehtuuria), se voi huolehtia myös komponenttien koostamisesta ja kutsumisesta, kunhan vain sovelluskehittäjä on rekisteröinyt erikoistetut komponentit asianmukaisesti. Kuvassa 8.10 on esitetty komponenttikehyksiin pohjautuva kehys. Tässä on ajateltu, että sovelluskehittäjä koostaa itse sovelluksen käyttäen erikoistettuja komponentteja. Komponentit on erikoistettu sekä periyttämällä että vaadittujen rajapintojen toteutuksella.
112 206 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 207 Sovelluksen pääohjelma Kuva 8.10: Komponenttikehyksiä käyttävä kehys 8.4 Kehysten suunnittelu Koska kehykset ovat olio-ohjelmistoja, tuntuisi luonnolliselta, että niiden suunnittelussa voisi käyttää jo varsin kypsiä yleisiä oliosuunnittelumenetelmiä, joita kehiteltiin 90-luvun lopulla (esimerkiksi Rumbaugh in OMT-menetelmä ja sen johdannaiset). Valitettavasti näitä menetelmiä ei kuitenkaan voi suoraan soveltaa kehysten tapauksessa: kehyksen suunnittelu on kertaluokkaa vaikeampaa kuin yksittäisen sovelluksen suunnittelu. Oliosuunnittelumenetelmät lähtevät siitä, että on olemassa yksittäisen sovelluksen suhteellisen tarkat vaatimukset. Vaatimuksia voidaan tarkentaa myös käyttötapauksilla, joissa järjestelmää käyte- Komponenttikehys Komponenttikehys Komponenttikehys Koottava kehys Kehysohjelmistot ovat olioperustainen tapa toteuttaa tuoterunkoarkkitehtuuri ja sitä tukeva ohjelmistoalusta. Niinpä kehysohjelmistojen suunnittelussa ja käytössä voidaan soveltaa yleisiä tuoterunkoarkkitehtuureihin liittyviä malleja ja tekniikoita, joita on käsitelty lyhyesti luvussa 7. Tarkastelemme seuraavassa vielä kehysohjelmistojen (erityisesti sovelluskehysten) suunnitteluun liittyviä kysymyksiä Kehysten suunnittelu ja oliosuunnittelu tään jonkin olennaisen tavoitteen saavuttamiseksi. Tämä on jo periaatteellisessa ristiriidassa kehyksen idean kanssa: sovelluskehyksen tulisi tarjota yleinen tuki äärettömän monen sovelluksen vaatimusten toteuttamiseksi, eivätkä monet näistä sovelluksista ole etukäteen edes tiedossa. Kun tavoitteena ei ole yksittäisen konkreettisen sovelluksen tekeminen, on myös käyttötapausten antaminen vaikeampaa. Jo käyttötapauksen käsite on kehyksen tapauksessa epäselvä: onko käyttäjä sovelluksen loppukäyttäjä vai kehyksen käyttäjä? Edellisessä tapauksessa voidaan parhaimmillaankin antaa käyttötapauksia vain joillekin edustaville esimerkkisovelluksille, joita kehyksellä oletetaan saatavan. Tällainen esimerkkisovelluksen esimerkkikäyttö ei tietenkään voi samassa mielessä olla suunnittelun perustana kuin käyttötapaukset perinteisessä ohjelmistokehityksessä. Kehyksen erikoistajan tulkitseminen kehyksen käyttäjäksi on myös luontevaa. Olennainen osa kehyksen vaatimuksista koskee sillä tehtävien sovellusten varianssia: mitä erilaisia muunnelmia kehyksen on tuettava. Antamalla muunneltavuusvaatimuksia ja tarkentamalla ne "erikoistamistapauksiksi" voidaan varmistua siitä, että keskeiset muunneltavuusvaatimukset toteutetaan, samaan tapaan kuin perinteisessä ohjelmistokehityksessä varmistutaan käyttötapauksilla, että keskeiset toiminnalliset vaatimukset toteutetaan. Tarkastelemme myöhemmin, kuinka tällainen "erikoistamistapaus" voidaan esittää ns. erikoistamismallina. Toisaalta voidaan todeta, että huolellisesti tehdyn oliojärjestelmän ja kehyksen välinen ero on häilyvä. Vaikka tiettyä järjestelmää ei olekaan ajateltu uudelleenkäytettävän, muut järjestelmän vaatimukset saattavat johtaa siihen, että järjestelmään tulee kehysmäisiä piirteitä. Erityisesti ylläpidettävyys on vaatimuksena hyvin lähellä uudelleenkäytettävyyttä: kummassakin tapauksessa halutaan tuottaa uusi järjestelmä olemassaolevan pohjalta mahdollisimman vähällä työllä. Tekniset ratkaisut, jotka tukevat uudelleenkäytettävyyttä, tukevat myös ylläpidettävyyttä. Myös järjestelmän yleinen ymmärrettävyys ja hallittavuus saadaan usein paremmaksi kehysmäisillä suunnitteluratkaisuilla, koska ne tyypillisesti vähentävät järjestelmän sisäisiä riippuvuuksia. Niinpä hyvän olioarkkitehdin tunnistaakin usein siitä, että hän suunnittelee yksittäisen järjestelmän tämän järjestelmän oman kehyksen erikoistuksena.
113 208 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 209 sovelluksia yhteiset osat sovelluskehys yleistys erikoistus uusi sovellus Kuva 8.11: Kehyksen iteratiivinen rakentamisprosessi Kehyksen kehitysprosessin vaiheet Ohjelmistokehitysprosessi jakaantuu kehystekniikkaa sovellettaessa kahteen osaan: itse kehysohjelmiston tekemiseen ja sen erikoistamiseen. Nämä vastaavat edellisessä luvussa esitellyn tuoterunkoprosessin osia (alustakehitysprosessi ja tuotekehitysprosessi). Sekä kehysohjelmiston tekemisessä että sen erikoistamisessa voidaan käyttää samantyyppistä vaihejakoa kuin perinteisessä ohjelmistokehitysprosessissa, mutta kullakin vaiheella on kehyksen tapauksessa omat erityispiirteensä. Käymme seuraavassa lyhyesti läpi kehykseen perustuvan ohjelmistokehitysprosessin vaiheet ja niiden keskeisen sisällön. Tässä kuvatussa prosessimallissa sovelletaan aikaisemmin (luku 1) esitettyä yleistä, arkkitehtuurikeskeistä ohjelmistokehitysprosessia. Kehyksen suunnitteleminen Kehysten iteratiivinen rakentamisprosessi Osittain edellä kuvatuista syistä sovelluskehysten suunnittelu on ollut ja on edelleen melko huonosti ymmärretty prosessi. Sovelluskehyksen suunnittelu lähtee yleensä liikkeelle siitä, että tietyllä sovellusalueella on joukko samantyyppisiä sovelluksia. Tätä aluetta päätetään lähteä tukemaan kehyksellä, jolloin analysoidaan olemassaolevat sovellukset ja pyritään löytämään niiden yhteiset osat. Näistä muodostuu kehyksen ensimmäinen versio, jota sovelletaan seuraavan sovelluksen tekemiseen. Yleensä kehys ei siihen suoraan sovi, vaan sitä pitää yleistää. Tämä jatkuu, kunnes kehys vähitellen tulee riittävän joustavaksi ja yleiseksi. Prosessi voi olla pitkä, huonosti hallittu ja vaikeasti ennustettavissa. Prosessi on esitetty kuvassa Lisäongelmia syntyy usein siitä, että kehyksen erikoistamisrajapinta ei tue työn osittamista yhtä hyvin kuin perinteinen API, jossa jokaisen operaation voi määritellä, toteuttaa ja testata erikseen. Tämä voi omalta osaltaan johtaa uusiin iteraatiokierroksiin. Vastaavasti sovelluskehyksen käyttäjän on usein opeteltava koko kehyksen toiminta yhtenä kokonaisuutena, ei operaatio kerrallaan, kuten on mahdollista perinteisten kutsurajapintojen tapauksessa. Vaatimusanalyysin tuloksena saadaan sovellusalueen malli, sovellusten yhteiset vaatimukset ja sovellusten muunneltavuusvaatimukset. Lisäksi vaatimuksiin voidaan sisällyttää yksi tai useampi tyypillisen esimerkkisovelluksen kuvaus omine vaatimuksineen ja käyttötapauksineen. Esimerkkisovellukset olisi hyvä valita niin, että ne edustavat kehyksen tarjoaman variaation ääripäitä. Alustava arkkitehtuurisuunnittelu tehdään käyttäen hyväksi sovellusalueen mallia ja mahdollisia esimerkkisovellusten vaatimuksia. Tässä vaiheessa ei vielä kiinnitetä huomiota muunneltavuuteen, vaan pyritään löytämään kyseiselle alueelle sopiva yleinen arkkitehtuuri (mm. perusarkkitehtuurityyli). Keskeinen työskentelytapa tässä vaiheessa on yleistäminen: arkkitehdin tulisi löytää sovellusalueen olennaiset käsitteet ja mekanismit yleistämällä esimerkkisovellusten vaatimuksia ja analysoimalla sovellusalueen mallia. Jos kehys on tarkoitettu tukemaan myös käyttöliittymää, sitä koskevat vaatimukset on samoin yleistettävä sopivan käyttöliittymäarkkitehtuurin löytämiseksi. Vaiheen tuloksena saadaan kehyksen alustava arkkitehtuuri. Arkkitehtuurisuunnittelussa otetaan tarkasteltavaksi yksi muunneltavuusvaatimus kerrallaan ja analysoidaan, tarvitaanko kyseisen muunneltavuuden tukemiseen arkkitehtuu-
114 210 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 211 ritason ratkaisua vai riittääkö siihen yksityiskohtaisen suunnittelutason ratkaisu. Jos muunneltavuutta on tuettava arkkitehtuuritasolla, muokataan arkkitehtuuria halutun muunneltavuuden saavuttamiseksi. Ratkaisussa voidaan usein hyödyntää suunnittelumalleja. Ratkaisu dokumentoidaan nk. erikoistamismallina, jossa kuvataan tähän muunneltavuuskohtaan liittyvä arkkitehtuuriratkaisu sekä sen hyödyntäminen sovelluksen tekemisen yhteydessä. Selostamme erikoistamismallin tarkemmin jatkossa. Vaiheen tuloksena saadaan kehyksen arkkitehtuurikuvaus yhdessä arkkitehtuuritason erikoistamismallien kanssa sekä luettelo myöhempiin vaiheisiin siirretyistä muunneltavuusvaatimuksista. Yksityiskohtaisessa suunnittelussa käydään läpi yksityiskohtaisen suunnittelun tasolle siirretyt muunneltavuusvaatimukset ja annetaan ne toteuttavat suunnitteluratkaisut. Tässäkin voidaan ratkaisun perustana usein käyttää suunnittelumalleja. Ratkaisut sekä niiden käyttö erikoistuksen yhteydessä kuvataan erikoistamismalleina. Vaiheen tuloksena saadaan kehyksen yksityiskohtainen suunnitteludokumentti suunnittelutason erikoistamismalleineen. Arkkitehtuurin arvioinnissa analysoidaan, miten hyvin saatu arkkitehtuuri tukee annettuja muunneltavuusvaatimuksia. Tämä voidaan tehdä käyttäen yleisiä arkkitehtuurin arviointimenetelmiä (ks. luku 9) tai näyttämällä, miten vaatimuksiin sisällytetyt esimerkkisovellukset saadaan aikaan kehyksen erikoistamismalleja soveltamalla. Mikäli kehyksen joustavuudessa havaitaan puutteita, palataan aikaisempiin vaiheisiin. Tässä vaiheessa voidaan löytää myös uusia muunneltavuusvaatimuksia. Toteutuksessa kehys koodataan halutulla ohjelmointikielellä. Kehyksen tarjoamat, muunneltavuusvaatimuksissa edellytetyt valmiit muunnelmavaihtoehdot koodataan. Koodauksessa on otettava huomioon, että pienin muunneltava ohjelmayksikkö on funktio (metodi), joka voidaan uudelleenmääritellä aliluokissa. Riittävän hienojakoisen muunneltavuuden saavuttamiseksi kannattaa kaikki mahdolliset erikseen muunneltavat toiminnallisuudet koodata erillisiksi funktioiksi. Kielen tarjoamia piirteitä tulee käyt- tää erikoistamisrajapinnan rajaamiseen (esim. Javan finalmääreet). Testauksessa pyritään varmistumaan kehyskoodin virheettömyydestä. Kehyksen testaaminen on ongelmallista samasta syystä kuin tuoterunkojen testaaminen yleisesti: kehyksen tulisi toimia yhdessä kaikkien mahdollisten erikoistuksien ja muunnelmakombinaatioiden kanssa. Koska tämän testaaminen on periaatteessa mahdotonta, tyydytään usein testamaan kehys yhdessä muutamien edustavien esimerkkierikoistuksien kanssa, jotka kattavat mahdollisimman hyvin kehyksen koodin. Testaamista helpottaa, jos kehys on jaettu komponenttikehyksiksi, jolloin kukin komponenttikehys voidaan testata erikseen. Kehyksen erikoistaminen Vaatimusanalyysissä varmistetaan, että sovelluksen vaatimukset mahtuvat kehyksen tarjoaman muunneltavuuden sisään: vaatimusten on toteuduttava joko suoraan kehyksen ominaisuuksien kautta tai kehyksen tukeman muunneltavuuden kautta. Analyysi voidaan tehdä vertaamalla kehyksen vaatimuksia sovelluksen vaatimuksiin. Suunnittelussa käydään läpi ne vaatimukset, jotka edellyttävät kehyksen erikoistamisrajapinnan käyttöä, ja kuvataan erikoistamismalleja hyödyntämällä, kuinka kukin tällainen vaatimus toteutetaan erikoistamisrajapinnan avulla. Vaiheen tuloksena saadaan dokumentti, jossa erikoistamisrajapinnan käyttö tämän sovelluksen tapauksessa on kuvattu suunnittelutasolla. Tällainen kuvaus voidaan antaa esimerkiksi UML-luokkakaaviona, jossa näkyvät sovelluskohtaisten luokkien lisäksi ne kehyksen luokat, jotka liittyvät näihin (esimerkiksi kantaluokat). Toteutuksessa annetaan sovelluskohtainen koodi edellisen vaiheen suunnitteludokumentin mukaisesti. Testaus suoritetaan kuten minkä hyvänsä järjestelmän testaus. Testitapaukset tulisi suunnitella niin, että ne kattavat sovelluskohtaisen koodin. Testauksessa on tärkeätä, että myös kehyksen lähdekoodi on saatavilla, jotta virheet pystytään jäljittämään. Sovelluksen testaus voi paljastaa vikoja
115 212 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 213 myös kehyksessä. Valitettavasti kaupallisten kehysten lähdekoodi ei useinkaan ole käytettävissä. Joskus kehyksen käyttöä halutaan lisäksi tukea työkaluilla, jotka automatisoivat osan erikoistamisesta. Eräs mahdollisuus on toteuttaa oma sovellussuuntautunut kieli, jolla kuvataan halutut sovelluksen ominaisuudet korkealla abstraktiotasolla. Tällainen kieli voi olla esimerkiksi XML-pohjainen, jolloin voidaan käyttää olemassaolevia työkaluja kielen prosessointiin. Sovelluksen kuvauksesta generoidaan automaattisesti tarvittava erikoistuskoodi, jolloin sovelluskehittäjän ei tarvitse lainkaan tuntea kehystä: kieli toimii korkean tason erikoistamisrajapintana sovelluskehittäjälle. Kehyksen erikoistamistyökalujen suunnittelu ja toteutus on osa kehyksen rakentamisprosessia Kerroksittaisen kehyksen suunnittelu Kerroksittaisen (tai hierarkkisen) kehyksen idea tarjoaa mahdollisuuden inkrementaaliseen kehyksen kehitysprosessiin: sen sijaan, että pyrittäisiin löytämään oikeat abstraktiot ja ne toteuttava kehys kerralla, tehdään yleistys ja vastaava kehyskerroksen suunnittelu askeleittain. Karkeasti prosessi voi edetä silloin seuraavasti: Määritellään ensin sopivan esimerkkisovelluksen vaatimukset. Yleistetään näitä vaatimuksia korvaamalla sovelluskohtaiset käsitteet yleisemmillä. Käsitteen yleistys johtaa samalla myös uuteen muunneltavuusvaatimukseen tämän käsitteen kohdalla. Kun on saatu ensimmäisen tason yleistetty käsitemalli ja vaatimukset, jatketaan prosessia pyrkimällä yleistämään nämä käsitteet vastaavalla tavalla. Tätä toistetaan niin kauan kuin löytyy luonnollisia, yleisempiä käsitetasoja. Tuloksena syntyy joukko asteittain yleistyviä kehysvaatimuuksia. Tyypillisesti tasoja voisi olla 2 3. Yleisin vaatimusmääritely on pohjana ydinkehyksen toteutukselle. Tätä erikoistamalla saadaan toteutus seuraavan tason kehykselle jne. kunnes lopulta voidaan toteuttaa esimerkkisovelluksen vaatimukset erikoistamalla viimei- nen kehystaso. Lopputuloksena saadaan näin kerroksittainen kehys. Vaikka edellä kuvattu kerroksittaisen kehyksen rakentamisprosessi on monessa mielessä idealistinen, sitä voidaan käyttää karkeana ohjenuorana. Malli kuvaa ajatuskulkua, jota kehyksen suunnittelija joka tapauksessa jossain määrin noudattaa pyrkiessään systemaattisesti löytämään sopivan yleistystason kehyksen vaatimuksille Erikoistamismallit Erikoistamismalli sitoo muunneltavuusvaatimukset kehyksen erikoistamisrajapintaan kuvaamalla, miten tietty muunneltavuus saadaan aikaan hyödyntämällä kehyksen arkkitehtuuri- ja suunnitteluratkaisuja. Erikoistamismallin esitysmuodossa voidaan soveltaa suunnittelumallien esitysmuotoa: kummassakin tapauksessa kyse on tietyn ongelman ratkaisusta ja tämän ratkaisun "käyttöohjeista". Erikoistamismallin tapauksessa kyse on vain spesifistä, tiettyyn kehykseen liittyvästä muunneltavuusongelmasta, kun taas suunnittelumallin tapauksessa kyse on yleisestä suunnitteluongelmasta. Erikoistamismalli voi hyvin olla suunnittelumallin sovellus. Kuvassa 8.12 on annettu esimerkkikehykseen liittyvä erikoistamismalli. Mallissa ilmoitetaan se muunneltavuusvaatimus, johon malli liittyy, sekä muunnelman kiinnitysaika. Muunneltavuuteen liittyvä suunnitteluratkaisu kuvataan sen jälkeen UML:n luokkakaaviolla, jossa on varjostettu kehykseen kuuluvat osat. Ratkaisuun liittyvät elementit ja niiden rooli ratkaisussa selitetään. Jos erikoistuskoodilla on tiettyjä rajoituksia tai erityisvaatimuksia, ne kuvataan. Lopuksi voidaan antaa vielä esimerkkikoodi erikoistuksesta. 8.5 Kehysten riskejä Kehysten käytöstä on paljon positiivisia kokemuksia teollisuudessa: kehykset tarjoavat yrityksille tavan tallettaa ohjelmistojen rakentamiseen liittyvää tietotaitoaan ja uudelleenkäyttää sitä tehokkaasti. Kehykset ovatkin osoittautuneet monelle yritykselle tärkeäksi strategiseksi eduksi. Silti kehysten käyttöön liittyy myös potentiaalisia
116 214 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 215 Muunneltavuusvaatimus: M23 Muunneltavuusvaatimuksen kuvaus: eliön käyttäytyminen (liikkuminen, vuorovaikutus, vanheneminen ja kuolema) on voitava määritellä mielivaltaisesti Muunneltavuuden kiinnitysaika: käännösaika Rakenne DefaultCreature move interact die growold show <<interface>> CreatureFactory createcreature NewCreature move interact die growold show createcreature <<creates>> NewCreatureFactory Selitykset DefaultCreature: oletustoteutus eliöille NewCreature: uusi sovelluskohtainen eliötyyppi CreatureFactory: eliöiden tehdasluokan rajapinta NewCreatureFactory: sovelluskohtaisten eliöiden tehdasluokka move: yhden aikayksikön aikana tapahtuva eliön liike interact: vuorovaikutus kahden eliön välillä die: eliön kuolemiseen liittyvät toimet growold: eliön vanheneminen show: eliön ulkoasun tuottaminen näytölle Rajoitteet NewCreature: rakentajan täytyy kutsua yliluokan rakentajaa NewCreatureFactory/createCreature: täytyy palauttaa NewCreature-olio move: täytyy kutsua "show"-operaatiota, muuttaa yleensä x- ja y-koordinaatteja die: täytyy poistaa eliöolio maailmasta growold: täytyy lisätä age-attribuutin arvoa Esimerkki class NewCreature extends DefaultCreature { int energy; public NewCreature(int x, int y, int e) { super(x, y); energy = e; } public void move() { xcoord = (xcoord+1)%myworld.getsize(); show(); } public void show() {...} public void interact(abstractcreature c) { if (c!= this && c instanceof NewCreature) { if (((NewCreature)c).energy < energy) { c.die(); } } } } return new NewCreature(); Kuva 8.12: Simulointikehyksen erikoistamismalli riskejä, joista on syytä olla tietoinen. Tarkastelemme näitä seuraavassa Tekninen vaativuus ja monimutkaisuus Kehykset ovat arkkitehtuuriltaan vaativia ohjelmistoja. Kehysohjelmiston arkkitehdin on tunnettava hyvin paitsi sovellusalue myös oliotekniikat, erityisesti suunnittelumallit. Arkkitehdin on osattava myös ennakoida tulevia tarpeita sovellusalueella. Kehysten suunnittelumenetelmät ovat toistaiseksi huonosti ymmärrettyjä (tai niitä ei yksinkertaisesti ole), mistä johtuen kehysprojektit ovat usein vaikeasti ennustettavia ja siksi ongelmallisia prosessin hallinnan kannalta. Erikoistajien kannalta ongelmaksi saattaa muodostua kehyksen erikoistamisrajapinnan vaikea ymmärrettävyys etenkin muokattavien kehysten tapauksessa. Jos erikoistaja pyrkii suhteellisen yksinkertaisen sovelluksen tuottamiseen, erikoistamisrajapinnan tarjoama yleisyys aiheuttaa tarpeetonta työtä Kehysten yhdistely Kehyksen ideaan kuuluu, että sillä on päävastuu sovelluksesta, ja se kutsuu sovelluskohtaista koodia tarpeen mukaan. Tämä ajatus on ristiriidassa sellaisen tilanteen kanssa, jossa sovelluksen tekemiseen halutaan käyttää kahta tai useampaa kehystä: miten voidaan yhdistää useita kehyksiä, jotka kaikki kuvittelevat olevansa päävastuullisia? Tällainen tilanne on kuitenkin suhteellisen yleinen. Esimerkiksi sovelluksen käyttöliittymän toteutukseen voidaan käyttää yhtä kehystä, ja toista kyseisen sovellusalueen logiikan toteuttamiseen. Esimerkkinä käyttämämme simulointikehyksen tapauksessa tarkastelimme ainoastaan jälkimmäistä kehystä ja sivuutimme käyttöliittymän, joka todennäköisesti toteutettaisiin jonkin GUI-kehyksen pohjalle. Tällä kehyksellä on pääkontrolli interaktiivisen käyttöliittymän kannalta, mutta toisaalta varsinainen simulointikehys pitää hallussaan pääkontrollia simulointitoiminnan kannalta. Kahden tai useamman kehyksen yhdistäminen käy suhteellisen helposti, jos toteutuskieli tarjoaa rinnakkaiset säikeet: tällöin kukin kehys voi toimia omassa säikeessään ja pitää pääkontrollin tässä säikeessä. Jos säikeistystä ei ole mahdollista käyttää, kehysten yhdis-
117 216 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 217 täminen voi olla hyvin vaikeaa. Mahdollinen ratkaisu on valita yksi kehys pääkontrollin omaavaksi kehykseksi (esim. käyttöliittymäkehys) ja muokata muut kehykset palveluja tarjoaviksi passiivisiksi komponenttikehyksiksi, joilla ei ole pääkontrollia Monoliittisuus Ehkä suurin riski erityisesti muunneltavan kehyksen tapauksessa on kehyksen kasvaminen vaikeasti hallittavaksi, monoliittiseksi ohjelmistoksi, jonka ylläpito ja uudelleenkäyttö on ongelmallista. Kehyksen rakentamisessa unohtuu helposti ohjelmistojen modularisoinnin tärkeys. Suurikin kehysohjelmisto saattaa toisaalta olla hyvin hallittavissa, jos sen arkkitehtuuri jakaa ohjelmiston osiin, joilla on selkeät omat vastuunsa. Tarkastelimme edellä (8.3) erilaisia yleisiä tapoja kehyksen strukturointiin Laadullinen varianssi Kehyksen muunneltavuusvaatimukset koskevat tavallisesti toiminnallisuutta pikemmin kuin ohjelmiston laadullisia ominaisuuksia, ja toiminnallisen varianssin tukemiseen on tarjolla erilaisia tekniikoita. Joskus voi kuitenkin olla tarpeen tuottaa samasta kehyksestä laadullisilta ominaisuuksiltaan erilaisia sovelluksia. Laadullisten ominaisuuksien variointi on kuitenkin teknisesti vaikeaa, koska laadulliset ominaisuudet seuraavat tavallisesti arkkitehtuurin perusratkaisuista, jotka eivät ole muutettavissa. Jos kehyksen avulla halutaan saada esimerkiksi suorituskykyinen mutta paljon pysyväismuistia kuluttava tuote ja toisaalta muistin kulutuksen suhteen optimaalinen mutta vähemmän suorituskykyinen tuote, voi tällaisen muunneltavuuden tukeminen olla vaikeaa Dokumentointi Samoin kuin kehysohjelmistojen suunnittelutekniikat myös niiden dokumentointitekniikat hallitaan yleensä huonosti. Kehyksen arkkitehtuuri voidaan kuvata samalla tavalla kuin minkä hyvänsä ohjelmiston arkkitehtuuri, mutta kehyksen tapauksessa tällainen kuvaus ei suoranaisesti palvele kehyksen varsinaista tarkoitusta, toteutusvälineenä toimimista. Kehyksen tapauksessa olennaista on erikoistamisrajapinnan kuvaus, mutta koko erikoistamisrajapinta voi olla huonosti jäsentynyt jopa kehyksen suunnittelijalle. Lisäksi erikoistamisrajapinnalle ei ole olemassa mitään yleisesti tunnettua kuvaustapaa. Tässä kirjassa esitetty erikoistamismallin käsite tarjoaa yhden tekniikan erikoistamisrajapinnan dokumentointiin. 8.6 Yhteenveto Ohjelmistokehykset ovat olioperustainen tuoterunkojen toteutustekniikka. Kehys erikoistetaan ohjelmistotuotteeksi täydentämällä siinä olevat aukot tuotekohtaisella koodilla. Kehysten avulla ohjelmoija uudelleenkäyttää jo toteutettua perusarkkitehtuuria kokonaisuutena. Kehyksiä on useita eri tyyppejä perustuen siihen, mitä erikoistamisen tuloksena syntyy ja mitä mekanismeja käytetään kehyksen erikoistamisessa. Kehysohjelmisto voidaan strukturoida kerrosten tai muunneltavien komponenttien avulla. Kehysten erikoistamisrajapinta voidaan kuvata erikoistamismalleilla, jotka liittävät muunneltavuusvaatimukset suunnitteluratkaisuihin. Kehysten käytön ongelmia ovat niiden suunnittelun ja käytön tekninen vaativuus ja heikko menetelmätuki. 8.7 Harjoitustehtäviä 8.1 Tee C++:lla tai Javalla sovellus, joka tunnistaa eläimien ääniä: kun sovellukselle annetaan esimerkiksi merkkijono "hauhau", se vastaa ilmoittamalla eläimen nimen ("koira"). Oletetaan, että kukin eläinlaji kuvataan omalla aliluokallaan, joka ilmaisee eläimen käyttäytymisen, mukaan lukien sen ääntä kuvaavan merkkijonon, jonka palauttaa operaatio getsound(). Eläinaliluokalla (ja sen yliluokalla) voi tämän lisäksi olla muita operaatioita, joilla ei ole tässä merkitystä. Pyri sellaiseen arkkitehtuuriin, jossa sovelluksessa tarvittavat muutokset ovat
118 218 Ohjelmistoarkkitehtuurit Ohjelmistokehykset 219 mahdollisimman pieniä, kun uusi eläinaliluokka lisätään siihen. Onko mahdollista tehdä ratkaisua, jossa olemassaolevaan sovelluskoodiin ei tarvitse lainkaan kajota? 8.2 Anna Javalla pieni kehys (framelet), jonka avulla voidaan toteuttaa olioeditori mielivaltaiselle sovelluskohtaiselle oliolle: editoria käytetään dialogi-ikkunassa, jossa on rivi jokaista olion perusluokan julkista merkkijono- tai kokonaislukukenttää kohden; rivillä on kentän nimi sekä tekstikenttä, jossa näkyy kentän nykyinen arvo. Tekstikenttää voidaan editoida, ja OKnappulaa painettaessa uudet arvot tallentuvat kenttiin. (Vihje: käytä Javan refleksiivisyyspiirteitä.) 8.3 Suunnittele sovelluskehys, jota voidaan käyttää erilaisten vuokrausjärjestelmien (videofilmit, autot jne.) toteutukseen. Sovelluskehys olettaa, että järjestelmässä voidaan vuokrata erilaisia hyödykkeitä asiakkaille tietyksi ajaksi. Asiakas voi olla henkilö, yritys tms. Asiakkailla voi olla asiakaskohtaisia alennuksia. Vuokra voidaan laskea vuokrausajan mukaan millä hyvänsä vuokrattavasta hyödykkeestä ja asiakkaan alennuksesta riippuvalla kaavalla. Vuokrattava hyödyke voi olla kehyksen kannalta periaatteessa mikä hyvänsä; yksittäisen sovelluksen on kuitenkin tuettava tietyntyyppisiä tuotteita. Vuokrausjärjestelmä huolehtii vuokrausaikojen, vuokrattavien hyödykkeiden varaston ja asiakkaiden kirjanpidosta sekä vuokrauslaskujen ja erilaisten yhteenvetotaulukoiden tuottamisesta. Esitä järjestelmän arkkitehtuuri UML:n luokkakaaviona. Riittää, että tarkastelet järjestelmän loogista puolta, käyttöliittymän voi jättää suunnitelman ulkopuolelle. Näytä, miten kehys erikoistetaan autonvuokrausjärjestelmäksi. Anna esimerkki erikoistamismallista, joka kuvaa jonkin kehyksen laajennoskohdan. 8.4 Suunnittele sovelluskehys, jota voidaan käyttää erilaisten kalenteriohjelmien toteuttamiseen pöytäkoneeseen. Kun kalenteriohjelma käynnistetään, tulee näytölle kuluvan viikon esitys, jossa on viikonpäivät ja niitä vastaavat kuukauden päivät sekä kuukauden nimi. Kunkin päivän kohdalle voidaan laittaa muistutuksia, joihin liittyy kellonaika. Erilaisia muistutustyyppejä ja niihin liittyviä varoitustoimintoja on voitava helposti lisätä sovelluksiin. Ohjelma tarjoaa mm. seuraavat toiminnot: kalenterin selaus: näytä seuraava/edellinen viikko muistutuksen lisäys/poisto tietyn päivän muistutusten näyttö (esim. yksi kerrallaan) Riittää, että tarkastelet toimintalogiikkaa; käyttöliittymä voidaan jättää ulkopuolelle. Esitä kehyksen arkkitehtuuri UML:n luokkakaaviona. Näytä, miten kehys erikoistetaan seuraaville muistutustyypeille: tapaaminen, kokous, opetustapahtuma niihin liittyvine tietoineen. 8.5 Suunnittele sovelluskehys, jonka avulla voidaan toteuttaa palettityökaluja. Palettityökalu koostuu paletista ja piirrosalueesta. Käyttäjä voi valita paletista kuvion, joka tulee näkyviin piirrosalueelle. Kuvio voi olla esimerkiksi geometrinen kuvio tai jokin muu kuvio; kehyksen on sallittava periaatteessa mielivaltaiset kuviot. Näytä, miten kehys erikoistetaan työkaluksi, jolla voidaan piirtää ympyröitä ja neliöitä. Esitä kehyksen ja sen erikoistuksen arkkitehtuuri UML:n luokkakaaviona. 8.6 Aktiivinen teksti tarkoittaa tekstiä, joka sisältää paitsi merkkijonoja myös erilaisia aktiivisia komponentteja, so. komponentteja, jotka reagoivat hiiren klikkaukseen erilaisin tavoin ja joilla on oma ulkoasunsa näytöllä. Aktiivista tekstiä voidaan käyttää esimerkiksi kehittyneiden tekstikäyttöliittymien ja hypertekstijärjestelmien toteutukseen. Aktiivisen tekstin komponentit (mm. merkkijonot ja aktiiviset tekstialkiot) sijaitsevat teksti-ikkunan näytöllä riveillä, joiden keskinäinen etäisyys on sama kuin rivin korkein alkio (aktiiviset tekstialkiot voivat olla erikorkuisia). Suunnittele sovelluskehys, joka toteuttaa aktiivisen tekstin käsitteen. Erikoista sovelluskehys määrittelemällä merkkijonokomponentin lisäksi painonappikomponentti, so. komponentti, jonka ulkoasu on painonappi ja joka reagoi hiiren klikkaukseen näyttämällä uudessa ikkunassa painonappia edeltävään sanaan liittyvän selityksen. Sanojen selitykset on talletettu edeltäkäsin sopivaan rakenteeseen. Esitä kehyksen ja sen erikoistuksen karkea arkkitehtuuri UML:n luokkakaaviona. 8.7 Oletetaan, että eräällä sovellusalueella halutaan kuvata sovelluskohtaisia toimintoja omalla sovellusaluekohtaisella kielellä, jota järjestelmä tulkitsee (suorittaa). Tätä varten järjestelmässä on kääntäjä, joka muuntaa tekstinä annetun ohjelman sisäiseksi olioesitykseksi. Luokan Interpreter operaatio run kutsuu koko ohjelmaa edustavan Program-olion execute-ope-
119 220 Ohjelmistoarkkitehtuurit raatiota, joka aikaansaa koko ohjelman suorituksen. Järjestelmää ja erityisesti sen tukemaa sovelluskohtaista kieltä halutaan kuitenkin pystyä erikoistamaan eri ympäristöihin, joissa mm. kieleen sisältyvät perusoperaatiot saattavat olla erilaisia, samoin tarvittavat kielen kontrollirakenteet (esim. ehto ja toisto) voivat olla erilaisia. Ohjelmien sisäisen olioesityksen (Program) odotetaan koostuvan perusoperaatioista (Action), joita voidaan puolestaan koota yhteen kootuksi toiminnoksi (esim. toisto-operaation toistettava osa). Ohjelman olioesityksessä esiintyy myös lausekkeita (Expression), jotka koostuvat kokonaisluvuista ja muuttujien nimistä sekä perusoperaatioista +, -, *, /. Operaatio-olioilla on "execute"-operaatio, joka suorittaa ko. kielen operaation toiminnon. Lausekeolioilla (ja niiden osia edustavilla olioilla) on "evaluate"-funktio, joka palauttaa lausekkeen tai sen osan arvon. Anna ohjelmien sisäisen olioesityksen toteuttava kehys UML:n luokkakaaviona yhdessä erikoistuksen kanssa, jossa kieleen kuuluu muuttujan sijoitus (sijoitettava arvo annetaan lausekkeena), sovelluskohtaisen (parametrittoman) toiminnon kutsu (toiminnon nimi) sekä toisto-operaatio, jossa toistoehto annetaan lausekkeena (toistetaan kunnes lausekkeen arvo on 0) ja toistettava osa koottuna operaatiojonona. Anna operaatioiden execute ja evaluate pseudokoodi kommenttilaatikoissa ao. luokille. Kielen ulkoisella syntaksilla ei ole tässä tehtävässä merkitystä: kääntäjän oletetaan pystyvän tuottamaan aina sisäisen olioesityksen.
120 9 Arkkitehtuurien arviointi Arkkitehtuurin arviointi eroaa monien muiden teknisten vaihetuotteiden katselmoinnista sikäli, että sen laatu ei perustu puhtaasti suunnitteluaikaisiin teknisiin ansioihin, vaan myös sen kykyyn täyttää sille määritellyt pidemmän tähtäyksen tavoitteet. Tällaisia ovat esimerkiksi laajennettavuus, muunneltavuus ja skaalautuvuus ilman että suorituskyky tai muistinkulutus kärsivät kohtuuttomasti. Koska arkkitehtuuria suunnitellessa muita teknisiä dokumentteja ei yleensä ole käytössä, suunnittelupäätökset perustuvat usein esimerkiksi yrityksen uuteen järjestelmään liittyviin liiketoimintatavoitteisiin, joita voidaan siksi käyttää arkkitehtuurin arvioinnin perustana. Tarkastelemme tässä luvussa aluksi yleisiä arkkitehtuurien arviointiin liittyviä näkökulmia ja seikkoja, joita voidaan käyttää arvioinnin perustana. Tämän jälkeen keskitymme tällä hetkellä kehittyneimmän arviointimenetelmän, ATAM:n (Architecture Trade-off Analysis Method) kuvaukseen. Lopuksi havainnollistamme menetelmän soveltamista esimerkillä. 9.1 Johdatus arkkitehtuurin arviointiin Palataan ensin arkkitehtuurin määritelmään, jotta ymmärrämme, mitä haluamme arvioida. Arkkitehtuuri käsittelee ohjelmiston järjestäytymistä osiinsa korkealla abstraktiotasolla, sekä näiden osien välisiä suhteita. Arkkitehtuuri ei käsittele näiden osien (esim. komponenttien tai alijärjestelmien) sisäistä rakennetta. Vastaavasti arkkitehtuurin arviointi perustuu komponenttien ja alijärjestelmien suhteisiin ja suhteiden ominaisuuksiin. Jotta arviointi onnistuisi, on sen kohdentaminen olennaista.
121 222 Ohjelmistoarkkitehtuurit Arkkitehtuurien arviointi Arvioinnin kohdentaminen Arkkitehtuurista puhuttaessa on sovittava, mikä on se järjestelmä, jonka arkkitehtuuria tarkastellaan. Jos tarkasteltavana on jokin alijärjestelmä, sen arkkitehtuuri käsittelee vuorostaan sen osia. Arkkitehtuuri ei kuitenkaan yleensä mene sellaiselle tasolle, jossa tarkastellaan yksittäisiä tietorakenteita, algoritmeja tai rajapintojen yksityiskohtia (esim. parametreja). Edellä esitetystä määritelmästä voidaan löytää helposti poikkeuksia: esimerkiksi algoritmien ja tietorakenteiden valinnalla voi olla olennaisia vaikutuksia vaikkapa suorituskykyyn. On kuitenkin selkeästi erotettava arkkitehtuurin arvioinnin tavoitteet esimerkiksi algoritmien vaativuusanalyysin tavoitteista: jälkimmäisessä pyritään johtamaan riippuvuus ongelman ratkaisutavan kuluttamille resursseille (aika, tila) suhteessa syötteen kokoon, kun taas edellisessä pyritään löytämään riippuvuus järjestelmän rakenteen ja sen laadullisten ominaisuuksien välille. Jos tarkasteltava laadullinen ominaisuus liittyy resurssien käyttöön, algoritmianalyysi voi antaa informaatiota arkkitehtuuriarvioinnin pohjaksi. Itse asiassa joskus jopa se, että järjestelmän arkkitehti pitää jotakin asiaa arkkitehtoonisesti olennaisena, riittää tämän ominaisuuden sisällyttämiseen arkkitehtuuriin. Arkkitehtuurin arviointia voidaan pitää systemaattisena, kattavana arkkitehtuurin katselmointina. Monia arviointimenetelmiin liittyviä tekniikoita (esim. skenaarioita) voi soveltaa katselmoinnissa sellaisinaan Arvioinnin perusteet Kuten jo aiemmin esitettiin, arkkitehtuuri ratkaisee yleensä sen, kuinka hyvin ohjelmisto täyttää laadulliset vaatimukset, ja arkkitehtuuri suunnitellaan nimenomaan laadullisten vaatimusten ehdoilla. Niinpä arkkitehtuurien arvioinnissa on päähuomio laadullisten pikemmin kuin toiminnallisten vaatimusten tarkastelussa. Jotta ohjelmiston laadulliset ominaisuudet saataisiin arvioitua kattavasti, on tärkeätä, että arkkitehtuuri sisältää kaikki (tai ainakin olennaisilta osin) laadullisiin ominaisuuksiin vaikuttavat ratkaisut. Tätä voidaan pitää arkkitehtuurin täydellisyyden kriteerinä: jos jota- kin laadullista ominaisuutta ei kyetä arkkitehtuurin perusteella arvioimaan, arkkitehtuuri on tältä osin puutteellinen. Toisaalta tämäkään ei aina tarkkaan ottaen päde, koska esimerkiksi käyttöliittymän yksityiskohdat vaikuttavat usein olennaisesti järjestelmän käytettävyyteen, joka on laadullinen ominaisuus, mutta ei näkyvissä arkkitehtuuritasolla. Vastaavasti arkkitehtuurin toteutustapa voi vaikuttaa paljonkin järjestelmän tehokkuuteen. Olennaista on kuitenkin, että arkkitehtuuri mahdollistaa laatuvaatimusten täyttymisen tunnettujen toteutustapojen puitteissa. Yleisiä laatuvaatimuksia ovat esimerkiksi: suorituskyky; järjestelmän kuluttamat resurssit tietyn data-, tapahtuma- tai käyttäjämäärän käsittelemiseen luotettavuus; järjestelmän kyky pysyä toimintakelpoisena saatavuus; järjestelmän pystyssäoloajan suhteellinen osuus turvallisuus; järjestelmän kyky torjua oikeudettomat käyttäjät aiheuttamatta haittaa laillisille käyttäjille muunneltavuus; muutoksien tekemisen helppous siirrettävyys; kuinka hyvin järjestelmä tukee siirtoaan eri resurssiympäristöihin varioitavuus; kuinka hyvin järjestelmässä on otettu huomioon tiettyjen vaatimusten vaihtelu. Käytännössä edellä mainittuja ominaisuuksia voidaan yhdistellä, jolloin tuloksena saadaan monimutkaisempia vaatimuksia ainakin silloin, kun vaatimukset ovat keskenään osin ristiriitaisia toteutustekniikan kannalta. On huomattava, että edellä luetellut laatuvaatimukset eivät ole sellaisenaan yksiselitteisiä. Mitä esimerkiksi tarkoittaa täsmällisemmin muunneltavuus? Tällaisia yleisiä laatuvaatimuksia ei pitäisi esiintyä järjestelmän vaatimuksissa ilman täsmennyksiä. Esimerkiksi muunneltavuuden kohdalla on erikseen määriteltävä, minkä asioiden suhteen järjestelmän tulisi olla muunneltava. Myös toiminnallisia vaatimuksia voidaan arvioida arkkitehtuurin pohjalta. Tällöin tarkastellaan sitä, kyetäänkö kyseisellä arkkitehtuurilla saavuttamaan tietyt toiminnot, ts. ovatko nämä toiminnot toteutettavissa arkkitehtuuriin kuuluvien komponenttien välisellä vuorovaikutuksella. Tällainen arviointi on kuitenkin yleensä melko suoraviivaista: siihen riittää osoittaa (esim. sekvenssikaaviolla), että tiettyyn toimintoon tarvittava tuki löytyy arkkitehtuurista, olet-
122 224 Ohjelmistoarkkitehtuurit Arkkitehtuurien arviointi 225 taen että tietyt komponentit ovat olemassa. Lisäksi uusien ominaisuuksien yhteydessä arkkitehtuurin arviointi voi toimia suoraviivaisena tapana esitellä uudet arkkitehtuuriin mahdollisesti vaikuttavat vaatimukset tekniselle henkilöstölle. Arkkitehtuurin arviointi auttaa suunnittelijoita ymmärtämään järjestelmää paremmin ja näkemään sen ongelmakohtia. Arkkitehtuurin arviointi pakottaa suunnittelijat käymään systemaattisesti läpi järjestelmän eri puolia, ja miettimään asioita, joita he muuten eivät tulisi ajatelleeksi. Tässä mielessä arkkitehtuurin arviointi on hyödyllistä myös jälkikäteen tehtynä. Arkkitehtuurin arviointi auttaa myös ennustamaan järjestelmän myöhempää evoluutiota ja esimerkiksi ylläpitokustannuksia. Nämä tiedot voivat olla tärkeitä johdon kannalta mm. resurssoinnin suhteen Arvioinnin sidosryhmiä Conwayn lain mukaisesti arkkitehtuuri ei vaikuta ainoastaan järjestelmän perusrakenteeseen, vaan sillä on myös usein olennainen merkitys prosessi- ja organisaationäkökulmien kannalta. Arkkitehtuuri vaikuttaa työnjakoon, työntekijöiden rooleihin, työntekijöiden pätevyysvaatimuksiin, dokumentteihin ja vaihetulosten laatuun. Arkkitehtuuri määrää usein myös organisaation rakenteen. Siksi arkkitehtuurien arviointiin liittyy myös muita kuin suoraan välittömän ohjelmiston toteutuksen kanssa tekemisissä olevia sidosryhmiä, joita ovat esimerkiksi arkkitehti (tai arkkitehdit), toteuttajat, testaajat ja ylläpitäjät, joilla kaikilla on omanlaisiaan odotuksia järjestelmään liittyen. Esittelemme seuraavassa joitakin keskeisiä sidosryhmiä, jotka kuitenkaan eivät välttämättä osallistu toteutustyöhön. Koska laadulliset ominaisuudet ovat tärkeitä arkkitehtuurivaatimuksia, myös esimerkiksi markkinointi voi olla kiinnostunut arvioinnista. Samoin esimerkiksi tuotepäälliköt ovat asemassa, jossa järjestelmän laadulliset ominaisuudet ovat tärkeitä. Tieto esimerkiksi helposti muunneltavista ominaisuuksista helpottaa neuvottelua asiakkaiden kanssa, ja toisaalta katselmoinneissa opittu terminologia helpottaa kommunikointia teknisen henkilökunnan kanssa. Erityisesti tilanteessa, jossa arkkitehtuuri ymmärretään investoinniksi, kuten tuoterunkoarkkitehtuurit, myös liiketoimintajohto on arkkitehtuurin arvioinnin sidosryhmä, ainakin sikäli, että johdon tulisi valvoa sijoituksen arvoa ja sen kehitystä. Ymmärrys arkkiteh- tuurin tilasta helpottaa tätä arviointia. Lisäksi teknisen henkilökunnan suhtautuminen arkkitehtuuriin on usein merkityksellistä, sillä vaikka arkkitehtuuri periaatteessa olisikin hyvin määritelty ja toteutettu, voi toteuttajien asenne paljastaa piileviä ongelmia arkkitehtuurin käytössä. Myös yrityksen tutkimus- ja työkalukehitysorganisaatiot sekä muut samantyyppistä järjestelmää toteuttavat projektit voivat osoittautua sidosryhmäksi. Nämä sidosryhmät ovat kiinnostuneita ainakin hyödynnetyistä tekniikoista. Lisäksi osallistujia voi yleensä käyttää riippumattomana teknisenä osapuolena esimerkiksi eri suunnittelupäätösten vaikutuksia arvioidessa. Yksi sidosryhmä voisi lisäksi olla henkilöstöhallinto, joka puolestaan voi olla kiinnostunut kuulemaan, minkälaisia tekniikoita on tarpeen tuntea, kun tuotekehitystä vahvistetaan lisätyövoimalla tai koulutuksella. Vastaavasti asiakkaat ovat kiinnostuneet järjestelmästä, joten heidätkin voidaan laskea mukaan jopa useaksi eri sidosryhmäksi erilaisten odotusten mukaisesti. Muita ulkopuolisia sidosryhmiä saattavat olla esimerkiksi lainsäädännölliset asiantuntijat, mikäli toteutetaan ohjelmistoa, jonka ominaisuuksia säännellään. Näin pitkälle sidosryhmiä ei kuitenkaan aina käytännössä kartoiteta, vaan esimerkiksi markkinoinnin voidaan ajatella edustavan asiakkaan vaatimuksia ja odotuksia. 9.2 Arkkitehtuurin arviointi osana ohjelmistoprosessia Arkkitehtuurin arvioinnin tulisi olla rutiininomainen osa normaalia ohjelmistokehitysprosessia. Tämä on tärkeätä, koska arkkitehtuuridokumentti on järjestelmän ensimmäinen arvioinnin mahdollistava kuvaus, ja sen arvioinnissa havaittavat puutteet pystytään korjaamaan aikaisimmassa mahdollisessa vaiheessa suhteellisen vähillä kustannuksilla. Toisaalta arkkitehtuuri sisältää kaikkein kriittisimmät järjestelmän suunnitteluratkaisut, joten sen arviointi on erityisen tärkeää. Arkkitehtuurin arviointia voidaan tässä mielessä pitää järjestelmän perusratkaisujen testauksena ennen yksityiskohtaisen suunnittelun aloittamista. Toinen, huomattavasti kalliimpi tapa testata järjestelmän perusratkaisuja on rakentaa prototyyppi, jota ei ole tarkoituskaan kehittää tuotteeksi asti. Usein prototyyppilähestymistapa ei kuitenkaan ole mahdollinen aikataulu- tai kustannussyistä.
123 226 Ohjelmistoarkkitehtuurit Arkkitehtuurien arviointi 227 Kaikkein kallein tapa testata arkkitehtuuri on tehdä se normaalin järjestelmätestauksen yhteydessä. Arkkitehtuuria voidaan periaatteessa arvioida minkä tahansa arkkitehtuurikuvauksen perusteella, mutta arvioinnin tulokset ovat luonnollisesti aina suhteessa tähän kuvaukseen. Jos kuvaus on epätarkka, myös arvioinnin tulokset ovat epätarkkoja. Silti ne voivat olla hyödyllisiä. Ainakin seuraavat kolme erityyppistä arviointia lienevät hyödyltään kiistattomia. Arkkitehtuuri voidaan arvioida heti, kun ensimmäinen arkkitehtuurin luonnos on olemassa. Tällöin arviointi kannattaa suorittaa suhteellisen pienimuotoisena: vielä suhteellisen epätarkan ja luonnosmaisen arkkitehtuurin arviointiin ei kannata uhrata huomattavasti resursseja. Tavoitteena on ennen muuta analysoida vaatimuksia arkkitehtuurin kannalta: voiko ylipäänsä mikään arkkitehtuuri täyttää vaatimuksia. Kun arviointi tehdään ennen kuin vaatimukset on lopullisesti jäädytetty, voidaan mahdottomista vaatimuksista vielä luopua. Arvioinnin tuloksena saadaan koottua realistiset vaatimukset ja rakennettua alustava arkkitehtuuriratkaisu näiden täyttämiseen. Jos kaikkia vaatimuksia ei voida täyttää, mutta niistä ei haluta vielä tässä vaiheessa kokonaan luopua, voidaan vaatimukset priorisoida. Alustavassa arkkitehtuurin arvioinnissa tulisi olla läsnä henkilöitä, joilla on valtuus määrätä vaatimuksista (esim. asiakkaan edustaja). Täydellisempi arkkitehtuurin arviointi tehdään, kun arkkitehtuurin suunnittelu on saatu suoritettua ja arkkitehtuurin kuvaava dokumentti on valmiina. Tähän arviointiin kannattaa käyttää suhteellisen paljonkin resursseja. Tämän tulisi tapahtua ennen kuin toteutus aloitetaan, koska arkkitehtuuri saattaa muuttua arvioinnin tuloksena. Näin arviointi tulisi tehdä ennen kuin toteuttajat alkavat tehdä arkkitehtuurista riippuvia ratkaisuja. Ongelmana on kuitenkin usein se, että toteuttaminen on resurssien varaamisen ja aikataulun takia aloitettava jo arkkitehtuurisuunnittelun aikana. Arkkitehtuuria voidaan arvioida myös valmiista järjestelmästä. Tämä voi olla järkevää, jos halutaan esimerkiksi lisätä organisaation ymmärrystä järjestelmästä erityisesti laatuattribuuttien suhteen (esimerkiksi onko järjestelmä siirrettävissä uuteen ympäristöön). Jos järjestelmällä ei ole ajan tasalla olevaa arkkitehtuurikuvausta, sellainen voidaan joissain rajoissa tuottaa (osittain) automaattisesti takaisinmallinnustyökalujen avulla. 9.3 Arkkitehtuurin arviointimenetelmät Arkkitehtuurin arviointimenetelmät tuottavat yleensä vastauksen seuraavanlaisiin kysymyksiin: Sopiiko suunniteltu arkkitehtuuri järjestelmälle? Mikä vaihtoehtoisista arkkitehtuureista soveltuu parhaiten järjestelmälle, ja miksi? Miten hyvä tulee olemaan järjestelmän jokin tietty laadullinen ominaisuus, olettaen että järjestelmä toteutetaan järkevästi? Ensimmäinen kysymys koskee järjestelmän vaatimusten täyttymistä tietyllä arkkitehtuurilla. Yleensä ollaan kiinnostuneita erityisesti laatuvaatimuksista. Jos arkkitehtuuri ei sovellu järjestelmälle, pitäisi myös pystyä kertomaan miksi. Toinen kysymys liittyy tilanteeseen, jossa arkkitehtuurille (tai sen osalle) on esitetty useita vaihtoehtoja. Vastaus kertoo, mikä näistä parhaiten sopii järjestelmään, ja miksi. Kolmas kysymystyyppi eroaa kahdesta edellisestä siinä, että siinä pyritään löytämään määrällisiä arvioita jostakin laatuominaisuudesta. Vastaus voi esimerkiksi kertoa, kuinka kalliiksi järjestelmän ylläpito todennäköisesti tulee. Keskeinen ongelma arkkitehtuurien arvioinnissa on se, miten saadaan poimittua arkkitehtuurin kuvauksesta jotain sellaista informaatiota, joka kuvaa tulevan järjestelmän laatuominaisuuksia. Useat arkkitehtuurien arviointimenetelmät perustuvat skenaariotekniikkaan: arvioinnissa pyritään esittämään konkreettisia esimerkkitilanteita, joissa tietyt laatuominaisuudet tulevat esiin. Skenaarion luonne riippuu sitä, mitä laatuominaisuutta halutaan tarkastella. Jos tarkastellaan esimerkiksi muutettavuutta, skenaario käsittelee jotakin muutostarvetta järjestelmän evoluutiossa. Jos tarkastellaan suorituskykyä, skenaario käsittelee jotakin suorituskykykriittistä tilannetta järjestelmän käytössä (tämä on lähellä käyttötapauksen käsitettä). Jos tarkastellaan turvallisuutta, skenaario käsittelee jotakin uhkatilannetta järjestelmän käytössä. Jos tarkastellaan uudelleenkäytettävyyttä, skenaario käsittelee jonkin uuden sovelluksen tekemistä uudelleenkäyttämällä järjestelmää jne. Tämän jälkeen tutkitaan, miten arkkitehtuuri sopii kyseiseen skenaarioon. Skenaarioiden etuna on niiden suhteellisen helppo löytäminen, konkreettisuus ja ymmärrettävyys.
124 228 Ohjelmistoarkkitehtuurit Arkkitehtuurien arviointi 229 Skenaarioiden lisäksi voidaan arvioinnin pohjana käyttää esimerkiksi erilaisia tarkistuslistoja ja kysymyslomakkeita, joissa esitetään arkkitehtuuriin tai arkkitehtuurin suunnitteluprosessiin liittyviä kysymyksiä (esimerkiksi "onko käyttöliittymä erotettu sovelluslogiikasta?"). Kysymykset voivat olla yleisiä, kaikkiin arkkitehtuureihin liittyviä, tai ne voivat olla suunnattuja tietyntyyppisille (sovellusaluekohtaisille) arkkitehtuureille. Näitä voidaan myös yhdistää skenaariopohjaisiin menetelmiin. Informaation saantimenetelmänä voidaan käyttää myös erilaisia mittaustekniikoita, joilla pyritään saamaan täsmällisiä, jotakin laatuominaisuutta kuvaavia lukuarvoja (esim. järjestelmän suorituskykyä tai rakenteen selkeyttä). Tämä edellyttää, että on olemassa jokin järjestelmän formaali malli, jota mitata. Mallina voi legacy-järjestelmän tapauksessa olla itse lähdekoodi, jolloin voidaan käyttää ohjelmistoille kehitettyjä erilaisia metriikoita. Muuten mallina voi olla järjestelmän arkkitehtuurin (joidenkin aspektien) esitys tunnettua täsmällisen merkityksen omaavaa menetelmää käyttäen, esimerkiksi Petri-verkko tai UML-malli. Tällaista mallia analysoimalla (tai joskus jopa ajamalla ts. simuloimalla järjestelmää formaalin mallin avulla) voidaan johtaa erilaisia järjestelmän hyvyyttä kuvaavia arvoja tai tunnuslukuja. Useimmiten arkkitehtuurin kuvaus annetaan jonkun sellaisen asiantuntijan arvioitavaksi, jolla on kokemusta järjestelmien suunnittelusta. Tulos riippuu tietysti täysin kyseisen henkilön ominaisuuksista. Tämän subjektiivisen rajoitteen vuoksi järjestelmällisiä arviointeja varten onkin esitelty menetelmiä, jotka ainakin periaatteessa pyrkivät yksilön mielipidettä objektiivisempaan arviointiin. Tarkastelemme seuraavassa tarkemmin skenaariopohjaisia menetelmiä, joiden käytöstä on enemmän käytännöllistä kokemusta. Varhaisimpia skenaariopohjaisia menetelmiä on SAAM, joka on SEI:ssä (Software Engineering Institute, Carnegie-Mellon University) kehitetty menetelmä. Menetelmä on tarkoitettu lähinnä muunneltavuuteen liittyvien laatuominaisuuksien arviointiin, mutta sillä voidaan arvioida myös toiminnallisuutta. ATAM (Clements et al. 2002), johon tutustumme seuraavaksi, on SAAM-menetelmästä edelleen SEI:ssä johdettu yleisempi menetelmä, jota voidaan käyttää muidenkin laatuominaisuuksien arviointiin. Muista arviointimenetelmistä voi mainita MPM:n (Maintenance Prediction Method), joka on Jan Boschin (Bosch 2000) kehittämä erityisesti ylläpidettävyyden arviointiin tarkoitettu menetelmä. 9.4 ATAM Arkkitehtuurin arviointiin tarkoitettu ATAM-menetelmä (Architecture Tradeoff Analysis Method) jakaantuu neljään osioon, jotka ovat esittely, analyysi, testaus ja raportointi (Clements et al. 2002). Esittelyosiossa esitellään arviointiin osallistuville ATAM-menetelmä, järjestelmän arkkitehtuuri sekä liiketoimintatavoitteet. Analyysiosiossa etsitään laatuominaisuuksiin vaikuttavat arkkitehtuuriratkaisut, analysoidaan järjestelmän laatuvaatimukset ja kuvataan niihin liittyvät skenaariot, sekä identifioidaan niiden avulla arkkitehtuurin kriittiset kohdat. Esittely- ja analyysiosiot muodostavat menetelmän ensimmäisen vaiheen, johon osallistuvat arviointiryhmän lisäksi tuoteprojektin vastuuhenkilöt. Testausosiossa täydennetään skenaarioita järjestelmän käyttäjien näkökulmasta ja analysoidaan arkkitehtuuriratkaisut vasten uutta skenaariojoukkoa. Lopuksi raportointiosiossa esitetään arvioinnin tulokset. Testaus- ja raportointiosio muodostavat arvioinnin toisen vaiheen, johon osallistuu ensimmäisen vaiheen osallistujien lisäksi muiden sidosryhmien edustajia (esim. asiakkaan). Koko prosessi kestää noin kolme päivää; toisen vaiheen aluksi käytetään päivä ensimmäisen vaiheen tulosten kertaamiseen uusille osallistujille Esittelyosio Esittelyosio koostuu kolmesta askeleesta. Ensimmäiseksi arvioinnin johtaja kuvaa ATAM-menetelmän kaikille arviointiin osallistuville. Tavoitteena on kuvata arviointiprosessi, kunkin rooli siinä, käytettävät tekniikat sekä tulosten muoto. Tärkeätä on, että kaikilla on realistiset odotukset prosessista. Toiseksi projektipäällikkö tai asiakkaan edustaja kertoo, mitkä ovat järjestelmän olennaiset vaatimukset ja tavoitteet liiketoiminnan kannalta, sekä mitkä ovat järjestelmälle ylhäältäpäin asetetut rajoitteet (esim. tekniset, taloudelliset, poliittiset tms.). Tavoitteena on, että kaikki ymmärtävät järjestelmän toimintaympäristön. Kolmanneksi järjestelmän pääarkkitehti kuvaa arkkitehtuurin, sen teknisen toimintaympäristön sekä sen rajapinnan muihin mahdollisiin järjestelmiin. Arkkitehtuurin kuvauksessa voidaan käyttää
125 230 Ohjelmistoarkkitehtuurit Arkkitehtuurien arviointi 231 näkymiä (esim. jo aiemmin esitetty 4+1-malli); olennaista on, että sellaiset näkökulmat järjestelmään tulevat esitetyiksi, jotka ovat olleet keskeisiä arkkitehtuurin suunnittelussa Analyysiosio Analyysiosio koostuu kolmesta askeleesta. Ensimmäisessä askeleessa tunnistetaan olennaiset arkkitehtuuriratkaisut, joilla on pyritty tiettyjen laadullisten vaatimusten täyttämiseen. Tyypillisesti nämä ovat erilaisten arkkitehtuurityylien ja suunnittelumallien soveltamisia. Kuhunkin ratkaisuun liitetään selvitys siitä, millä tavoin ratkaisu tukee laatuominaisuutta; mikä ratkaisussa on olennaista laatuominaisuuden kannalta ja mitkä ovat olennaisia kysymyksiä ratkaisua sovellettaessa. Näitä kysymyksiä voi löytyä esimerkiksi kyseisten suunnittelumallien tai tyylien selostuksista. Toisessa askeleessa laaditaan laatupuu (utility tree) (kuva 9.1), jossa täsmennetään laatuominaisuuksia sekä liitetään laatuominaisuuksiin esimerkkitilanteita (skenaarioita), joissa ko. laatuominaisuus tulee esille. Kukin skenaario painotetaan kahdella parametrilla: kuinka tärkeä ko. skenaario on järjestelmän kannalta, ja kuinka vaikea skenaario on toteuttaa. Jälkimmäinen parametri on suuri, jos järjestelmän rakentaminen sellaiseksi, että skenaario on mahdollinen, vie huomattavasti resursseja. Yleensä riittää käyttää skaalaa (low, medium, high). Nyt on siis olemassa painotettu laatupuu skenaarioineen sekä arkkitehtuuriratkaisujen kuvaukset. Kolmannessa askeleessa nämä liitetään toisiinsa: kuhunkin (ainakin korkean prioriteetin) skenaarioon liitetään sitä tukevat arkkitehtuuriratkaisut (kuva 9.1). Puun perusteella on mahdollista identifioida riskit, turvalliset ratkaisut, herkkyyskohdat ja tasapainokohdat. Riski muodostuu arkkitehtuuriin liittyvästä suunnittelupäätöksestä tai arkkitehtuurin piirteest, joka mahdollisesti voi johtaa jonkin laatuominaisuuden huononemiseen (tarkastelemme tässä vain arkkitehtuurista johtuvia riskejä, projektille voi aiheutua riskejä myös muista lähteistä). Riski voidaan kuvata kertomalla kyseinen päätös/ominaisuus, siitä mahdollisesti aiheutuva ongelma laatuominaisuuden kannalta sekä syy, josta ongelma aiheutuu. Quality Performance Modifiability Availability Security quality attributes Kuva 9.1: Laatupuu Transaction throughput Response time Change UI Change OS Hardware failure Server crash Data confidentiality refinements scenarios (examples) Maximize throughput on authentication server (H,M) Authentication response in less than 1 sec. (H,M) Change to Web UI in 1 month (M,H) Change to Linux in 6 months (L,H) Restart after disk failure in 5 minutes (L,H) Restart after auth server crash in 5 minutes (M,M) Credit card transaction secure % (H,L) Turvallinen ratkaisu on sellainen arkkitehtuuriin liittyvä ilmeisen hyvä päätös, joka parantaa järjestelmän laatuominaisuuksia, ja joka perustuu olettamuksiin, joiden voidaan olettaa olevan voimassa koko arkkitehtuurissa. Jos nämä olettamukset muuttuvat, ratkaisu ei luonnollisestikaan pysy turvallisena. Turvallinen ratkaisu voidaan dokumentoida kuvaamalla taustalla olevat olettamukset, suunnittelupäätös, siitä seuraavat edut laatuominaisuuksille, sekä syyt näille seurauksille. Herkkyyskohta on sellainen arkkitehtuurin piirre tai suunnittelupäätös, joka on kriittinen jonkin laatuominaisuuden kannalta. Jos tätä ominaisuutta muutetaan tai päätöksestä luovutaan, jokin laatuominaisuus on vaarassa heikentyä. Herkkyyskohdat kertovat näin, miksi järjestelmä saavuttaa jonkin laatuominaisuuden. Herkkyyskohta kuvataan kertomalla kyseinen suunnittelupäätös ja siitä riippuva laatuominaisuus. Tasapainokohta on sellainen herkkyyskohta, joka vaikuttaa useampaan kuin yhteen laatuominaisuuteen. Usein tasapainokohdan ratkaisu vaikuttaa edullisesti johonkin laatuominaisuuteen ja epäedullisesti johonkin toiseen. Tyypillinen
126 232 Ohjelmistoarkkitehtuurit Arkkitehtuurien arviointi 233 tasapainokohta voisi olla jonkin suunnittelumallin soveltaminen, jolloin suunnittelumallin kuvauksesta yleensä jo ilmenee, mihin laatuominaisuuksiin se vaikuttaa. Usein suunnittelumalli lisää muunneltavuutta mutta heikentää suorituskykyä. Tasapainokohta kuvataan selostamalla kyseinen suunnittelupäätös ja dokumentoimalla miten se vaikuttaa laatuominaisuuksiin. Laatupuussa tarkennetaan järjestelmän laatu ensin tärkeimpiin yleisiin laatuominaisuuksiin, sitten jaetaan nämä tarkempiin kategorioihin järjestelmän kannalta tarvittaessa useampaan tasoon, ja lopulta kiinnitetään puun lehdiksi skenaarioita (esimerkkitilanteita), joissa ko. laatuominaisuus tulee esille. Kuhunkin skenaarioon liitetään sen tärkeyttä ja saavuttamisen vaikeutta kuvaava kirjain (kuvassa käytetyt kirjaimet ovat L = low, M = medium, H = high). Laatupuu on siis järjestelmäkohtainen, vaikka samantyyppisillä järjestelmillä on myös samantyyppiset laatupuut. Yleiset laatuominaisuudet ovat usein samoja eri järjestelmillä, mutta eri järjestelmissä voidaan tarkastella näiden eri osajoukkoja tai hieman eri tavalla painottuneita laatuominaisuuksia (esim. muutettavuus vs. ylläpidettävyys vs. siirrettävyys) Testausosio Analyysivaiheessa arkkitehtuuriratkaisuja arvioitiin lähinnä arkkitehdin ja arviointiryhmän näkemysten pohjalta. Tällöin lähdettiin laatuvaatimuksista ja päädyttiin skenaarioihin, jotka liittyvät näihin. Testausosion tarkoituksena on täydentää ja testata analyysin tuloksia käyttäen skenaarioita, joita tuottavat muut sidosryhmät (esim. testaajat, ylläpitäjät, asiakkaat, johto). Tällöin lähdetään skenaarioista, joita sidosryhmien on helppo tuottaa omien intressiensä pohjalta, ja päädytään laatuominaisuuksiin. Tavoitteena on herättää keskustelua eri sidosryhmien välille ja saavuttaa yhteinen näkemys siitä, mikä järjestelmässä on tärkeää ja mihin laatuominaisuuksiin on kiinnitettävä huomiota. Testausosion ensimmäisessä askeleessa sidosryhmät ideoivat skenaarioita omista lähtökohdistaan. Skenaariot ovat joko käyttötapauksia (use case), järjestelmän evoluutioon liittyviä odotettavissa olevia muutos-, laajennos-, ylläpito- ym. skenaarioita, tai radikaalisti uusiin vaatimuksiin pohjautuvia rasitusskenaarioita. Jälkimmäisten tarkoituksena on hakea arkkitehtuurin rajat, ja myös löytää uusia herkkyyskohtia arkkitehtuurista. Löydetyt skenaariot priorisoidaan esimerkiksi äänestysmenettelyllä. Tämän jälkeen skenaarioita verrataan laatupuussa jo oleviin skenaarioihin. Jos nämä täsmäävät, kaikki on hyvin. Jos skenaariota ei löydy puusta, mutta sille on puussa sopiva laatuominaisuushaara, siitä tulee uusi lehti puuhun. Jos skenaario liittyy useaan laatuattribuuttiin, se tulee (mahdollisesti hieman uudelleenmuotoiltuna korostamaan kyseistä laatuominaisuutta) useaan kohtaan lehdeksi. Jos skenaariolle ei ole sopivaa haaraa puussa, se joko ei liity lainkaan laatuominaisuuksiin, tai se liittyy uuteen, vielä tunnistamattomaan laatuominaisuuteen. Jälkimmäisessä tapauksessa puuhun lisätään kyseinen laatuominaisuus uutena haarana, ja skenaario liitetään siihen lehdeksi. Tämä on kiinnostava tilanne, koska ilmeisesti arkkitehti ei ole tiedostanut jotain laatuvaatimusta, ja arkkitehtuurin tarkastaminen tätä skenaariota vasten todennäköisesti tulee paljastamaan uusia riskejä. Testausosion toisessa askeleessa tehdään samanlainen analyysi kuin edellä esitettiin, mutta tällä kertaa kaikkien sidosryhmien toimesta ja koskien edellisen askeleen tuloksena saatua täydennettyä tärkeiden skenaarioiden luetteloa. Arkkitehti kertoo, miten kukin tällainen skenaario toteutuu arkkitehtuurin kannalta, ts. mitkä arkkitehtuuriratkaisut mahdollistavat skenaarion tai tukevat sitä. Jos mukana on uusia skenaarioita, tämä saattaa tuoda esiin myös uusia olennaisia arkkitehtuuriratkaisuja, joita arkkitehti ei ole huomannut kertoa aikaisemmin. Testausosion viimeinen vaihe on luonteeltaan aikaisemman analyysin testausta siinä mielessä, että jos uusia skenaarioita ei löytynyt edellisessä askeleessa, analyysissa käydään uudestaan läpi aikaisemmat skenaariot eri osanottajajoukolla. Toivon mukaan uutta informaatiota on vähän Raportointiosio ATAM-prosessin tulokset esitetään arvioinnin lopuksi koko arviointiin osallistuneelle ryhmälle. Tulokset esitetään myös arviointiraporttina, joka laaditaan varsinaisen arviointiprosessin jälkeen. ATAM-menetelmän keskeinen tavoite on identifioida arkkitehtuurin
127 234 Ohjelmistoarkkitehtuurit Arkkitehtuurien arviointi 235 olennaiset, laatuominaisuuksiin vaikuttavat ratkaisut käyttäen apuna skenaarioita ja analysoida ratkaisut. Arvioinnin tuloksena saadaan tietoa, joka auttaa ymmärtämään arkkitehtuuria ja hallitsemaan sen riskejä: tietoa siitä, miten arkkitehtuuri liittyy laatuominaisuuksiin. Tuloksena voi joskus tulla myös välittömästi parannuksia arkkitehtuuriin, vaikka tämä ei missään tapauksessa ole ATAMin tavoite: arkkitehtuurin korjaaminen ei kuulu ATAMin tehtäväalueeseen. Analyysin tulokset voidaan koota skenaarioittain (koskien priorisoituja skenaarioita) siten, että kuhunkin skenaarioon liitetään sitä tukevat arkkitehtuuriratkaisut sekä näihin ratkaisuihin liittyvät riskikohdat, turvalliset ratkaisut, herkkyyskohdat ja tasapainokohdat, jotka on erikseen listattu ja numeroitu. Lisäksi skenaarioon liitetään ne laatuominaisuudet, joita skenaario testaa, sekä kuvaus olosuhteista, joissa skenaario tapahtuu. ATAMin kehittäjät suosittelevat, että arvioinnin lopuksi arviointiryhmä kokoaa myös nk. riskiteemoja. Nämä ovat riskien ryhmiä, joiden taustalla on sama yleinen syy. Joukko riskejä voi esimerkiksi liittyä erilaisiin tiedon häviämisiin laitteisto- tai ohjelmistohäiriöissä, jolloin riskiteemana voisi olla riittämätön huomion kiinnittäminen tiedon varmistukseen. Riskiteemojen tarkoituksena on liittää riskit paremmin korkeamman tason liiketoimintatavoitteisiin ja tehdä ne ymmärrettävämmiksi johdolle. 9.5 Esimerkki: auton kunnonvalvontaohjelmiston arkkitehtuurin arviointi Tarkastelemme esimerkkinä auton tietokonejärjestelmään liitettävää monitorointialijärjestelmää (kuva 9.2), jonka tehtävänä on tarkkailla ja kerätä tietoja auton toiminnasta huoltoa varten. Esitämme osia arkkitehtuurin arvioinnista ATAM-menetelmällä; jätämme täydellisen arvioinnin harjoitustehtäväksi. Analyysivaiheen aluksi identifioimme arkkitehtuurissa käytetyt, tiedostetut ratkaisut tiettyjen laatuominaisuuksien saavuttamiseksi. Tällainen on esimerkiksi viestinvälitysarkkitehtuurityyli, jonka varaan järjestelmä on rakennettu. Tiedämme jo ko. tyylin yleisestä kuvauksesta, että tämä ratkaisu tekee järjestelmästä hyvin laajennettavan (vieläpä ajoaikaisesti), koska uuden toiminnallisen komponentin lisääminen ei vaadi uusia rajapintoja. Muita käytettyjä arkki- CANBus SourceHandler View update() <<creates>> XMLMsg accepts MessageDispatcher send(msg) register(msgtype,component) CarStateComp attach(controller) getdata() service() Controller handleevent(event) <<creates>> <<interface>> Msg type(): MsgType interpretes <<interface>> Component receive(msg) tehtuuriratkaisuja ovat MVC-mallin, XML:n ja yleisen abstraktin viestirajapinnan (Msg) käyttö. Seuraavaksi rakennamme laatupuun. Esimerkiksi muunneltavuus voidaan tässä jakaa kolmeen kategoriaan, tietoesitysten muunneltavuuteen, toiminnallisuuden muunneltavuuteen ja ympäristön (laitteiston) muunneltavuuteen. Esimerkkejä muunneltavuudesta ovat seuraavat skenaariot: Viestien XML-muotoinen esitys vaihdetaan binäärimuotoiseen kahdessa henkilötyökuukaudessa. Tietokanta vaihdetaan toisen valmistajan tietokannaksi yhdessä henkilötyökuukaudessa. Alunperin mustavalkonäyttöä varten toteutettu näkymä (View) vaihdetaan värinäyttöä tukevaksi puolessa henkilötyökuukaudessa välttävästi. Täydellinen tuki vaatii kahden henkilötyökuukauden työn. Ensimmäinen skenaario on tärkeä, koska XML:n prosessoinnin tehokkuudesta ei ole varmuutta ja koska uudet toiminnallisuudet regs SomeServices service1() service2() Kuva 9.2: Auton kunnonvalvonnan arkkitehtuuri DB GSMComp
128 236 Ohjelmistoarkkitehtuurit Arkkitehtuurien arviointi 237 Scenario: S1: Change from XML to binary in 2 months Quality attribute: Modifiability Stimulus: Need to make message handling faster Response: Processing time reduced by order of magnitude Architectural solutions: Risk Nrisk Sens Troff Implicit invocation style RP1 SP1 TP1 Abstract interface for messages NP1 SP2 Type-based message delivery NP2 SP3 Kuva 9.3: Yksinkertainen skenaario ja sen analyysi voivat vaatia lähes reaaliaikaisuutta; sen toteuttaminen katsotaan suhteellisen vaikeaksi, koska jokainen komponentti tulkitsee oman viestinsä itse ja siksi jokaista komponenttia joudutaan muuttamaan. Toisaalta koska arkkitehtuurissa viesteillä on abstrakti rajapinta, jonka taakse viestien esitys piilotetaan, viestienvälitysmekanismia ei tarvitse muuttaa. Toinen skenaario on myös tärkeä, koska tietokanta voidaan eri syistä todella joutua vaihtamaan nopealla aikataululla; toisaalta sen toteuttaminen ei ole aivan helppoa, koska näyttää siltä, että arkkitehtuurissa ei ole abstrahoitu tietokantarajapintaa, ja useat komponentit käyttävät suoraan tietokantaa. Viimeiseksi esitetty skenaario on puolestaan tärkeä sikäli, että se mahdollistaa erilaisten konfiguraatioiden rakentamisen riippuen auton ostajan mieltymyksistä. Skenaario mittaa siis joustavuutta ympäristön muutosten osalta, ainakin jollakin tasolla. Analysoimme arkkitehtuurin kutakin skenaariota vasten laatimalla skenaariokohtaisen selvityksen, jossa tutkitaan kuhunkin skenaarioon liittyvät arkkitehtuuriratkaisut ja luokitellaan ne riskeiksi, turvallisiksi ratkaisuiksi, herkkyyskohdiksi tai tasapainokohdiksi. Esimerkki tällaisesta selvityksestä on esitetty kuvassa 9.3, jossa aiemmin esitetty muutos XML-muotoisesta tiedon esitysmuodosta binääriseen kuvataan tarkemmin. Selvityksessä viitataan tunnuksiin (RP1, NP1 jne.), joihin liitetään selitys asianomaisen luokittelun syistä. Esimerkiksi tässä tapauksessa viestinvälitysarkkitehtuuri (implicit invocation style) on tulkittu yleisesti riskiksi, koska binääriset viestit voivat olla vaikeampia käsitellä viestinvälittäjän kannalta. Scenario: S27: Change CAN-bus to proprietary information source Quality attribute: Modifiability Stimulus: CAN-bus not any more used Response: The same functionality using proprietary information source Architectural solutions: Risk Nrisk Sens Troff Separate source handler NP14 SP12 Abstract interface for messages NP1 SP2 Kuva 9.4: Uusi skenaario ja sen analyysi Testausvaiheessa sidosryhmät kehittelevät ensin yhdessä käyttö-, evoluutio- ja rasitusskenaarioita, jotka asetetaan äänestämällä tärkeysjärjestykseen. Esimerkissämme eräs johtaja tietää, että yhtiö on pitkällä tähtäyksellä luopumassa CAN-standardista, ja liiketoimintaan liittyvistä syistä kehittää oman tiedonsiirtoväylän autoihinsa. Tästä syystä järjestelmän tulisi olla muunnettavissa niin, että se saa tietoja muustakin lähteestä kuin CAN-väylältä. Eräs insinööri puolestaan huomaa, että auton käyttäytymiseen vaikuttavat ulkoiset olosuhteet, ja nämä tulisi myös voida rekisteröidä, koska muuten huoltotietoja voi olla vaikea tulkita oikein. Autoihin tullaankin tulevaisuudessa asentamaan ulkolämpötilan anturit, joten järjestelmän pitäisi pystyä lukemaan myös niiltä (ja muilta antureilta) tietoa suoraan ohi CAN-väylän. Nämä ovat molemmat evoluutioskenaarioita, jotka edellyttävät samantyyppistä muunneltavuutta järjestelmältä. Skenaariot äänestetään tärkeiksi, ja ne analysoidaan tarkemmin. Lisäksi skenaariot lisätään laatupuuhun solmun "Change data collecting" alle. Esimerkkinä analysoimme uuden skenaarion, joka koskee CAN-väylän korvaamista yrityksen omalla tiedonsiirtoväylällä. Tähän skenaarioon liittyvä dokumentaatio on esitetty kuvassa 9.4. Skenaarion toteuttamisen kannalta oleelliset ratkaisut arkkitehtuurissa ovat erillisen informaatiolähteen lukijan käyttö (SourceHandler) sekä viestien rajapinnan käyttö. Kummatkin tarvitaan, jotta järjestelmän muu osa olisi riippumaton CAN-väylästä. Edellinen ratkaisu on siis myös herkkyyskohta muutettavuudelle. Lopuksi arvioinnin tulokset raportoidaan. Emme kuitenkaan puutu varsinaiseen raportointiin tässä esimerkissä. Varsinaisen loppuraportin lisäksi arvioinnin tulokseksi voidaan laskea myös sen sivutuotteet, joita ovat ainakin arkkitehtuuritietämyksen lisääntymi-
129 238 Ohjelmistoarkkitehtuurit Arkkitehtuurien arviointi 239 nen yrityksessä sekä parantunut käsitys eri osapuolten arkkitehtuuriin liittyvistä odotuksista. 9.6 Arvioinnin ongelmia Arkkitehtuurin arviointi ATAM-menetelmää käyttäen voi osoittautua raskaaksi tavaksi varmistaa arkkitehtuurin laatu, ainakin jos kyseessä on pieni järjestelmä. Tällöin voidaan laatia kevyempi, esimerkiksi yrityskohtainen katselmointitapa, jossa resurssit saadaan kohdennettua paremmin tuottavaan työhön. Esimerkiksi tässä teoksessa esimerkkinä käytetty ajoneuvon kunnossapitojärjestelmä on arvioitu käyttäen eräänlaista ketterää (agile) arkkitehtuurin arviointimenetelmää, jossa osallistujien työmäärä yritettiin pitää mahdollisimman vähäisenä. Lisäksi jo tarkastuslista, johon on kerätty tärkeimmät arkkitehtuurilta vaadittavat ominaisuudet, voidaan ajatella yksinkertaisena arkkitehtuurin arviointimenetelmänä. Arkkitehtuurin arvioinnin käytännön ongelmaksi voi myös muodostua sopivan vaiheen löytäminen. Aikaisessa vaiheessa arviointi voi tuntua tarpeettomalta, sillä järjestelmästä tiedetään suhteellisen vähän. Toteutusvaiheen alkaessa arviointi voidaan nähdä uhkana järjestelmän toteutusaikataululle, sillä mikäli arkkitehtuurista löytyy suuria puutteita, joudutaan sitä luonnollisesti korjaamaan. Toteutuksen jälkeistä arviointia voidaan puolestaan pitää turhana siksi, että järjestelmä on jo toteutettu. Toisaalta mikäli katselmointia ei suoriteta lainkaan, voi arkkitehtuuri rämettyä vakavasti ennen kuin ongelmat konkretisoituvat. Tästä syystä arviointi tulisi kiinnittää ainakin johonkin ohjelmistoprosessin etappiin. Tätä arviointia voi luonnollisesti hyödyntää myös opetustilaisuutena kutsumalla arviointiin mukaan myös ohjelmistosuunnittelijat, jotka lopulta toteuttavat arkkitehtuurin mukaisen järjestelmän. Usein kuullun huomion mukaan arkkitehdit tietävät arkkitehtuurin ongelmista jo etukäteen, ja siksi arviointi voi vaikuttaa turhalta. Tällöin korostuu arvioinnin rooli tiedonvälityksen työkaluna. Koska arkkitehtuurin tila vaikuttaa suoraan sen varaan toteutettavien ohjelmistojen laatuun, olisi tieto mahdollisista ongelmista ja vahvuuksista saatettava kaikkien sidosryhmien tietoon, jotta suunnitelmat jatkotoiminnasta olisivat realistisia. 9.7 Yhteenveto Arkkitehtuurin arviointi on perusteellinen tapa suorittaa arkkitehtuurin katselmointi aikaisessa vaiheessa. Arviointi pakottaa eri osapuolet ja sidosryhmät keskustelemaan kattavasti arkkitehtuurin näkökulmista tietyn, ennalta määrätyn prosessin puitteissa ja perustelemaan näkemyksiään siitä, millainen arkkitehtuurin tulisi olla. Arviontimenetelmille on tyypillistä, että ne ovat suhteellisen väljiä ja että niitä voidaan soveltaa ja räätälöidä tietyntyyppisille yrityksille tai järjestelmille. Jotkut arviointimenetelmät, varsinkin ATAM, vaativat melko huomattavan määrän eri sidosryhmien resursseja, joita ei välttämättä ole mahdollista saada tiukkojen määräaikojen kanssa kamppailevissa yrityksissä. Tarvittaessa voi olla kannattavaa tehdä riisuttu versio jostain arviointimenetelmästä oman yrityksen käyttöön. 9.8 Harjoitustehtäviä 9.1 Arvioi tuoterunkoluvun lautapelituoterungon arkkitehtuuri ATAM-menetelmää käyttäen. 9.2 Mitkä arkkitehtuuridokumentit (luku 2) ovat mielestäsi hedelmällisimpiä kohteita ATAM-menetelmän mukaiselle katselmoinnille? 9.3 ATAM-arvioinnit ovat käytännössä usein kovin raskaita toteuttaa täydessä laajuudessaan. Miten menetelmää voisi keventää, jotta sitä voitaisiin käyttää ketterässä (agile) ohjelmistokehityksessä? 9.4 Miten ATAM-menetelmää tulisi muuttaa, mikäli haluttaisiin tuottaa parempia arvioita työmääristä ja kustannuksista? 9.5 Miten tuoterungon arkkitehtuurin arviointi eroaa tuotekohtaisesta arvioinnista?
130 240 Ohjelmistoarkkitehtuurit
131 Kirjallisuusviitteet Bass et al Bass, L., Clements, P. ja Kazman, R.: Software Architecture in Practice. Addison-Wesley, Binder 2001 Binder, R.V.: Testing Object-Oriented Systems: Models, Patterns, and Tools. Addison-Wesley, Boehm 1988 Boehm, B.: A spiral model of software development and enhancement. IEEE Computer 21, Bosch 2000 Bosch J.: Design and Use of Software Architectures. Addison-Wesley, Brown et al Brown, W.J., Malvcau, C.R., McCormick, H.W.S., ja Mowbray, T.J.: AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis. Wiley, Buschmann et al Buschmann, F., Meunier, R., Rohnert, H., Sommerland, P., ja Stal, M.: Pattern-Oriented Software Architecture, Volume 1, A System of Patterns. Wiley, Clements et al Clements, P., Kazman, R., Klein, M.: Evaluating Software Architectures. SEI Series in Software Engineering, Addison-Wesley, Clements ja Northrop 2002 Clements, P. ja Northrop, L.: Software Product Lines Practices and Patterns. SEI Series in Software Engineering, Addison-Wesley, 2002.
132 242 Ohjelmistoarkkitehtuurit Kirjallisuusviitteet 243 Clements et al Clements, P., Bachmann, F., Bass, L., Garlan, D., Ivers, J., Little, R., Nord, R. ja Stafford, J.: Documenting Software Architectures. SEI Series in Software Engineering, Addison-Wesley, Conway 1968 Conway, M.: How do committees invent? Datamation, 14(4), 28 31, April Coplien 1997 Coplien, J.: Idioms and patterns as architectural literature. IEEE Software, 14(1), 36 42, January Dijkstra 1976 Dijkstra, E.W.: A Discipline of Programming. Prentice- Hall, D Souza ja Wills 1998 D Souza, D.F. ja Wills, A.C.: Objects, Components, and Frameworks with UML: The Catalysis Approach. Addison Wesley, Extensible Markup Language 2003 World Wide Web Consortium: Extensible Markup Language (XML). Saatavilla osoitteesta Viitattu Fayad et al Fayad, M., Schmidt D.C., Johnson R.: Building Application Frameworks, Wiley Filman et al Filman, R.E., Elrad, T., Clarke, S. ja Aksit, M.: Aspect- Oriented Software Development, Addison Wesley, Fowler 1997 Fowler M.:. Analysis Patterns: Reusable Object Models. Addison-Wesley, Reading MA, Gamma et al Gamma, E., Helm, R., Johnson, R., and Vlissides J. Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley, Reading, MA, Gamma et al Gamma, E., Helm, R., Johnson, R., and Vlissides J. Design Patterns: Olio-ohjelmointi - Suunnittelumallit. IT Press, IBM 2004 Eclipse home page, IBM, Viitattu IEEE 2000 IEEE Recommended Practice for Architectural Description of Software-Intensive Systems. IEEE Standard , Jaaksi et al Jaaksi, A., Aalto, J-M., Aalto, A., and Vättö, K.: Tried & True Object Development - Industry-Proven Approaches with UML. Cambridge University Press, Jazayeri et al Jazayeri M., Ran, A. ja Van der Linden, F.: Software Architecture for Product Families - Principles and Practice. Addison-Wesley, Jansen et al Jansen, A.G.I., Smedinga, R., Van Gurp, J. ja Bosch, J.: First class feature abstractions for product derivation. IEE Proc.-Softw. 151(4), August Kruchten 1995 Kruchten, P.: The 4+1 View Model of Architecture. IEEE Software, 12(6), 42-50, November Marinescu 2002 Marinescu, F.: EJB Design Patterns. John Wiley & Sons 2002, 288 pages. Mitchell ja McKim 2002 Mitchell R., McKim J.: Design by Contract by Example. Addison-Wesley Myllymäki 2002 Myllymäki, T.: Variability management in software product lines. Report 30, Institute of Software Systems, Tampere University of Technology, 2002.
133 244 Ohjelmistoarkkitehtuurit Kirjallisuusviitteet 245 Myllymäki et al Myllymäki, T., Koskimies, K. ja Mikkonen, T.: Structuring product lines: A layered architectural style. Proceedings of the 8th International Conference on Object-Oriented Information Systems, , LNCS 2425, Springer, Noble ja Weir 2001 Noble, J. ja Weir, C.: Small Memory Software, Patterns for systems with limited memory. Addison Wesley, Object Management Group 2003 Object Management Group: MDA guide version OMG Document Number omg/ , June Parnas 1972 Parnas, D. L.: On the criteria to be used in decomposing systems into modules, Communications of the ACM 15:2, , December Parnas et al Parnas, D.L., Clements, P.C. ja Weiss, D.M.: The modular structure of complex systems. IEEE Transactions on Software Engineering, SE-11(3), , March Santelices ja Nussbaum 2001 Santelices, R. and Nussbaum M.: A framework for the development of videogames. Software Practice and Experience 31 (2001), Schmidt D. et al Schmidt D. et al.: Pattern-Oriented Software Architecture, Volume 2, Patterns for Concurrent and Networked Objects, Wiley Shaw ja Garlan 1996 Shaw, M. ja Garlan, D. Software Architecture. Perspectives of an Emerging Discipline. Prentice Hall, Smith ja Williams 2001 Smith C. ja Williams L.: Performance Solutions. Addison-Wesley 2001, 544 pages. Szyperski 1998 Szyperski C.: Component Software - Beyond Object- Oriented Programming, Addison-Wesley Unified Modeling Language 2004 Unified Modeling Language (UML). Saatavilla osoitteesta Viitattu Weiss ja Lai 1999 Weiss D.M., Lai C.T.R.: Software Product-Line Engineering. Addison-Wesley, Yoder ja Barcalow 1997 Yoder, J. ja Barcalow, J.: Architectural patterns for enabling application security. The 4th Pattern Languages of Programming Conference, Monticelo, Illinois, USA. September 1997.
134 246 Ohjelmistoarkkitehtuurit
135 Hakemisto 247 Hakemisto.NET 53, 73 A Abstract framework 195 Abstrakti kehys 195 Abstrakti tehdas 103, 120 Afrikan tähti 184 Alexander, Cristopher 102 Alijärjestelmäarkkitehtuuridokumentti 43 Aliohjelma 16 Alustakehitysprosessi 164, 165 Alustava arkkitehtuuridokumentti 42 Analyysimalli 104 Antisuunnittelumalli 107 Application engineering 164 Application engineering environment 166 Application framework 188 Architectural pattern 104 Arkkitehtuurialusta 177 Arkkitehtuuridokumentti 42 Arkkitehtuurimalli 104 Arkkitehtuurin arviointi 210 Arkkitehtuurin kuvauskieli 34 Arkkitehtuuripainotteinen ohjelmistokehitysprosessi 158 Arkkitehtuurityyli 104, 166 Asiakas-palvelin-arkkitehtuuri 136 Aspect-Oriented Design 57 Aspektiperustainen ohjelmointi 20 Aspektiperustainen suunnittelu 57 ATAM 221, 229 AWT 202 B Beck, Kent 102 Black-box framework 199 Blob class 108 Builder 120 Buschmann 142 C C# 59 C++ 59 Callback 85 CAN 179 Catalysis 19 Class invariant 62 Composite 109, 113, 121 Concern 56 Constraint 170 Controller 142 Conwayn laki 24, 57, 173 Coplien, Jim 102 Copy and paste 108 CORBA 138, 176 COTS-komponentti 56 D Dekompositio 19 Design-by-contract 62 Domain engineering 164 Domain model 165 DSL 159 Dynaaminen kuvaus 38 E Eclipse 199 Edustaja 83, 103
136 248 Ohjelmistoarkkitehtuurit EJB 55, 138, 177, 178, 196, 202 Erikoistaminen 188 Erikoistamismalli 213, 214, 217 Erikoistamisrajapinta 189 Erikoistamisrajapinta 189, 190, 217 Esimerkkikuvaus 38 Esitutkimusvaihe 164 Evoluutio 163 Extension point 199 F Feasibility study 164 Feature model 170 Filter 132 Force of gravitation 164 Forwarding 81 Fragile base class problem 70 Framelet 188, 205, 218 Fyysinen näkymä 36 G Gamma, Erich 102 Gang of Four 103 Geneeriset rakenteet 193 GoF 103, 104, 117 H Herkkyyskohta (ATAM) 231 Hierarkkinen kehys 203, 204 Hollywood-periaate 191 Huolenaihe 31, 56 I Idiomi 104 Invariantti 62 Iteratiivinen rakentamisprosessi 208 J J2EE 53, 73, 103 Java 59 JBoss 202 Järjestelmäarkkitehtuuridokumentti 42 K Kehitysnäkymä 36 Kerroksittainen kehys 202 Kerrosarkkitehtuuri 126 Komponentti 16, 53 Komponenttikehys 188, 205 Komponenttiteknologia 54 Konkreettinen arkkitehtuuri 34 Koottava kehys 199 Kutsumuoto 58 Käsitemalli 165 Käyttäytymiskuvaus 38 Käyttötapaus 21 L Laatupuu 230 Lisätietomääre 170 Looginen näkymä 36 Luokkainvariantti 62 Lähde 87 M Malli 32 Malli (MVC) 142 Malli-näkymä-ohjainarkkitehtuuri 142 Malliperustainen arkkitehtuuri 26, 45 Mediator 79 Meta-arkkitehtuuri 33 Model 142 Model-view-controller 142 Modularisointi 202 Muunneltavuusvaatimukset 165 Muunneltava kehys 196, 197 Muuntelunäkymä 37 MVC 142, 177, 179 Määrittelykuvaus 38 N Näkökulma 24, 31, 35 Näkymä 24, 31, 35 Näkymä (MVC) 142 O Object-oriented framework 187 Observer 142 OCL 151 Ohjain (MVC) 142 Ohjelmistoalusta 16 Ohjelmistoarkkitehtuurien kuvaus 29
137 Hakemisto 249 Ohjelmistokehitysprosessi 164 Ohjelmistokehys 187 Ohjelmointirajapinta 44 Olioperustainen ohjelmistokehys 187 OMT 206 Organisaatio 173 P Parametrointimekanismit 192 Periytyminen 191 Perustelu 32 Piirremalli 170 Pipe 132 Pipes-and-filters architecture 132 Plug-in framework 198 Plugin-arkkitehtuuri 56 Plugin-kehys 198 Poikkeus 65 Pre/post condition 62 Product family 160 Product platform 160 Product-line architecture, PLA 160 Profiili 151 Profiili, UML 33 Prosessinäkymä 36 Provided interface 59 Proxy 83 R Rajapinta 58, 76 Rajapintadokumentti 44 Rajapintojen toteutus 192 Rajoite 170 Rakennekuvaus 38 Rakentaja 120 Rakenteinen tietotyyppi 16 Referenssiarkkitehtuuri 33 Refleksiivisyys 193 Rekursiokooste 101, 109, 120, 121, 191 Required interface 54, 59 Resurssialusta 176 Riski (ATAM) 230 Riskiteema 234 Roolirajapinta 77 Rumbaugh 206 S Sidosryhmä 31 Signature 58 Siirtäminen 81 Skenaarionäkymä 36 Smart pointer 84 Sopimuspohjainen suunnittelu 62 Sovellusalusta 177 Sovelluskehys 16, 188 Sovelluskerros 178 Sovellusperhe 16 Sovellussuuntautunut kieli 159 Sovitin 103 Specialization 188 SQL 146 Staattinen kuvaus 38 Stakeholder 24 Stereotyyppi 170, 195, 199 Suunnittelumalli 39, 101, 190, 213 Swing 178, 203 Symbian 178 Särkyvän yliluokan ongelma 70 T Tagged value 170 Takaisinkutsu 85 Takaisinmallinnus 29 Tarjottu rajapinta 59 Tarkkailija 87, 103 Tarkkailija-suunnittelumalli 142 Tasapainokohta (ATAM) 231 Testaus 211 Tietoabstraktio 16 Tietokonepeli 188 Tietovarastoarkkitehtuuri 145 Tietovuoarkkitehtuuri 132 Tila 103 Toteutus 210 Toteutusympäristö 166 Tulkkiperustainen arkkitehtuuri 147 Tulo- ja jättöehdot 62 Tuotealusta 160 Tuotearkkitehtuuri 34 Tuotearkkitehtuuridokumentti 43 Tuotekehitysprosessi 164, 167 Tuoteperhe 16, 160
138 250 Ohjelmistoarkkitehtuurit Tuoterungon painovoima 164 Tuoterunko 160 Tuoterunkoarkkitehtuuri 34, 160 Tuoterunkoarkkitehtuuridokumentti 43 Tuoterunkoarkkitehtuurin kerrosmalli 175 Turvallinen ratkaisu (ATAM) 231 Turvallinen tietoalkio 135 U UML 55, 113, 118, 119, 120, 151, 170, 185, 199, 213, 218, 219, 220 Utility tree 230 V Vaadittu rajapinta 54, 59 Vaatimusanalyysi 209, 211 Valvontaohjelmisto 179 Vastasuunnittelumalli 107 Viestinvälitysarkkitehtuuri 139 View 142 View (architectural) 24 Viewpoint 24 Virheestätoipumistekniikka 135 Välittäjä 79, 103 Väylä 132 W Web service 30 Web-palvelu 30 White-box framework 196 X XML 135, 147, 180 Y Yhteiset vaatimukset 165 Ä Älykkäät osoittimet 84
2 Ohjelmistoarkkitehtuurien kuvaus
2 Ohjelmistoarkkitehtuurien kuvaus 2.1 Arkkitehtuurikuvauksen merkityksestä 2.2 Arkkitehtuurin kuvaukseen liittyvät käsitteet 2.3 Arkkitehtuurikuvaukset eri tasoilla 2.4 Arkkitehtuurinäkymät ja kuvaustyypit
Ohjelmistojen mallintaminen, mallintaminen ja UML
582104 Ohjelmistojen mallintaminen, mallintaminen ja UML 1 Mallintaminen ja UML Ohjelmistojen mallintamisesta ja kuvaamisesta Oliomallinnus ja UML Käyttötapauskaaviot Luokkakaaviot Sekvenssikaaviot 2 Yleisesti
1 Johdanto. TTY Ohjelmistotekniikka. Ohjelmistoarkkitehtuurit Syksy 2008
1 Johdanto 1.1 Mikä on ohjelmistoarkkitehtuuri? 1.2 Katsaus ohjelmistotuotannon kehittymiseen 1.3 Epäonnistuneen ohjelmistoarkkitehtuurin seurauksia 1.4 Ohjelmistoarkkitehtuuri ja ohjelmistokehitysprosessi
Ohjelmistojen mallintaminen
Ohjelmistojen mallintaminen - Mallit - Ohjelmiston kuvaaminen malleilla 31.10.2008 Harri Laine 1 Malli: abstraktio jostain kohteesta Abstrahointi: asian ilmaiseminen tavalla, joka tuo esiin tietystä näkökulmasta
Ohjelmistoarkkitehtuurit Syksy 2009 TTY Ohjelmistotekniikka 1
2 Ohjelmistoarkkitehtuurien kuvaus 2.1 Arkkitehtuurin kuvaukseen liittyvät käsitteet 2.2 Arkkitehtuurikuvaukset eri tasoilla 2.3 Arkkitehtuurinäkökulmat ja kuvaustyypit 2.4 Arkkitehtuuriviipaleiden kuvaus
Ohjelmistojen suunnittelu
Ohjelmistojen suunnittelu 581259 Ohjelmistotuotanto 154 Ohjelmistojen suunnittelu Software design is a creative activity in which you identify software components and their relationships, based on a customer
Ohjelmistoarkkitehtuurit, syksy
Ohjelmistoarkkitehtuurit Tuoteperheet Tuoterunkoarkkitehtuurit Perinteisessä ohjelmistotuotannossa on keskitytty uusien ohjelmistojen laadukkaaseen tuottamiseen Erikoistuneista ainutlaatuisista vaatimuksista
Ohjelmistokehykset ohjelmistorunkoja uudelleenkäyttö olioperustaisista ohjelmistorunko
Ohjelmistokehykset Määritelmä & tavoitteet, taustaa & peruskäsitteitä, kehykset vs. suunnittelumallit, erikoistamisrajapinnat & kontrollinkulku, kehystyypit, kehysten rakenne ja evoluutio, esimerkki: JHotDraw,
Ohjelmistoarkkitehtuurit Syksy 2009 TTY Ohjelmistotekniikka 1
Ohjelmistoarkkitehtuurit Syksy 2009 Kai Koskimies Ohjelmistoarkkitehtuurit Syksy 2009 TTY Ohjelmistotekniikka 1 Tervetuloa Kuopion yliopisto, Oulun yliopisto (Kajaani), Tampereen yliopisto, Turun yliopisto,
Ohjelmistoarkkitehtuurit. Syksy 2010
Ohjelmistoarkkitehtuurit Syksy 2010 Kai Koskimies Tervetuloa Oulun yliopisto, Tampereen yliopisto, Turun yliopisto, Tampereen teknillinen yliopisto, Vaasan yliopisto Kurssin tavoitteet Arkkitehtuurin roolin
Ohjelmistoarkkitehtuurit. Syksy 2008
Ohjelmistoarkkitehtuurit Syksy 2008 Kai Koskimies 1 Tervetuloa Kuopion yliopisto, Oulun yliopisto, Tampereen yliopisto, Teknillinen korkeakoulu, Turun yliopisto, Vaasan yliopisto, Tampereen teknillinen
Luento 8. Ohjelmistokehykset Tuoteperheet CSM14101 Ohjelmistoarkkitehtuurit
Ohjelmistoarkkitehtuurit Luento 8 Ohjelmistokehykset Tuoteperheet 19.10.2017 CSM14101 Ohjelmistoarkkitehtuurit 1 OHJELMISTOKEHYKSET 19.10.2017 CSM14101 Ohjelmistoarkkitehtuurit 2 Ohjelmistokehykset (software
Ohjelmistoarkkitehtuurit. Kevät
Ohjelmistoarkkitehtuurit Kevät 2012-2013 Johannes Koskinen http://www.cs.tut.fi/~ohar/ Tervetuloa Oulun yliopisto, Tampereen yliopisto, Turun yliopisto, Tampereen teknillinen yliopisto 2 Kurssin tavoitteet
2 Ohjelmistoarkkitehtuurien kuvaus
2 Ohjelmistoarkkitehtuurien kuvaus 2.1 Arkkitehtuurikuvauksen merkityksestä 2.2 Arkkitehtuurin kuvaukseen liittyvät käsitteet 2.3 Arkkitehtuurikuvaukset eri tasoilla 2.4 Arkkitehtuurinäkymät ja kuvaustyypit
Ylläpito. Ylläpito. Ylläpidon lajeja Ohjelmistotuotanto, syksy 1998 Ylläpito
Kaikki ohjelmistoon sen julkistamisen jälkeen kohdistuvat muutostoimenpiteet jopa 70-80% ohjelmiston elinkaarenaikaisista kehityskustannuksista Ylläpidon lajeja korjaava ylläpito (corrective) testausvaiheessa
1 Johdanto. Ohjelmistoarkkitehtuurit Syksy 2010 TTY Ohjelmistotekniikka 1
1 Johdanto 1.1 Mikä on ohjelmistoarkkitehtuuri? 1.2 Ohjelmistoarkkitehtuuri ja laatuvaatimukset 1.3 Katsaus ohjelmistotuotannon kehittymiseen 1.4 Miksi ohjelmistoarkkitehtuuri on tärkeä 1.5 Ohjelmistoarkkitehtuuri
Ohjelmistoarkkitehtuurit, syksy 2012 4.9.2010
Ohjelmistotutkimuksen painopisteitä Ohjelmistoarkkitehtuurit Johdanto ja peruskäsitteitä 2000 1995 1990 1985 1980 1970 Tuoteperhearkkitehtuurit, MDA, väliohjelmistot, aspektit CASE-välineet: uudelleenkäyttö,
Malliperustainen ohjelmistokehitys - MDE Pasi Lehtimäki
Malliperustainen ohjelmistokehitys - MDE 25.9.2007 Pasi Lehtimäki MDE Miksi MDE? Mitä on MDE? MDA, mallit, mallimuunnokset Ohjelmistoja Eclipse, MetaCase Mitä jatkossa? Akronyymiviidakko MDE, MDA, MDD,
Sisällys. Ratkaisumallien historia. Ratkaisumalli. Ratkaisumalli [2] Esimerkki: Composite [2] Esimerkki: Composite. Jaakko Vuolasto 25.1.
Sisällys Ratkaisumallien historia Jaakko Vuolasto 25.1.2001! Ratkaisumalli! Christopher Alexander! Ohjelmistotuotannosta arkkitehtuuriin! Henkilöhistoriaa! Ensimmäisiä käyttökokemuksia! Yhteenveto 25.1.2001
Ohjelmistoarkkitehtuurit Syksy 2009 TTY Ohjelmistotekniikka 1
3. Komponentit ja rajapinnat 3.1 Komponenttien idea: ohjelmistotuotannon rationalisointi 3.2 Mikä on ohjelmistokomponentti? 3.3 Komponentit ohjelmistoyksikköinä 3.4 Rajapinnat 3.6 Komponenttien räätälöinti
Ylläpito. Ylläpidon lajeja
Ylläpito Kaikki ohjelmistoon sen julkistamisen jälkeen kohdistuvat muutostoimenpiteet jopa 70-80% ohjelmiston elinkaarenaikaisista kehityskustannuksista Ylläpidon lajeja korjaava ylläpito (corrective)
Enterprise SOA. Nyt. Systeemi-integraattorin näkökulma
Enterprise SOA. Nyt. Systeemi-integraattorin näkökulma 12.11.2007 Janne J. Korhonen 12.11.2007 Agenda 1. Prosessit ja palvelut, BPM ja SOA 2. BPM-projekteista yleensä 3. Prosessin elinkaarimalli 4. Kokemuksia
Harjoitustehtävät ja ratkaisut viikolle 48
Harjoitustehtävät ja ratkaisut viikolle 48 1. Tehtävä on jatkoa aiemmalle tehtävälle viikolta 42, missä piti suunnitella älykodin arkkitehtuuri käyttäen vain ennalta annettua joukkoa ratkaisuja. Tämäkin
Ohjelmistokehykset (software frameworks)
Ohjelmistoarkkitehtuurit 1 (software frameworks) Osittain abstraktiksi jätettyjä ohjelmistorunkoja, joita eri tavoin täydentämällä saadaan rakennettua kokonaisia uusia sovelluksia tai sovelluksen osia
Projektityö
Projektityö 21.10.2005 Projektisuunnitelma Työn ositus Projektisuunnitelman sisältö Kurssin luennoitsija ja projektiryhmien ohjaaja: Timo Poranen (email: [email protected], työhuone: B1042) Kurssin kotisivut:
Ohjelmistoarkkitehtuuri
Ohjelmistoarkkitehtuurien ylläpito Arkkitehtuurityylejä ja laatuvaatimuksia Arkkitehtuurin uudistaminen Arkkitehtuurin uudistamisen malleja Arkkitehtuurin arviointi TTY Ohjelmistotekniikka 1 Ohjelmistoarkkitehtuuri
1 Johdanto. Pieni motivointikalvo. 1.1 Mikä on ohjelmistoarkkitehtuuri?
1 Johdanto 1.1 Mikä on ohjelmistoarkkitehtuuri? 1.2 Ohjelmistoarkkitehtuuri ja laatuvaatimukset 1.3 Katsaus ohjelmistotuotannon kehittymiseen 1.4 Miksi ohjelmistoarkkitehtuuri on tärkeä 1.5 Ohjelmistoarkkitehtuuri
Käyttäjien tunnistaminen ja käyttöoikeuksien hallinta hajautetussa ympäristössä
www.niksula.cs.hut.fi/~jjkankaa// Demosovelluksen tekninen määrittely v. 0.6 Päivitetty 11.12.2000 klo 20:26 Mickey Shroff 2 (12) Dokumentin versiohistoria Versio Päivämäärä Tekijä / muutoksen tekijä Selite
10. Tuoterunkoarkkitehtuurit
10. Tuoterunkoarkkitehtuurit Johdanto Näkökulmat tuoterunkoihin perustuvaan ohjelmistokehitykseen: liiketoiminta, organisaatio, prosessi, tekninen Tuoterunkojen etuja ja ongelmia 1 Uudelleenkäytt yttö
Ohjelmistoarkkitehtuurit. Syksy 2007
Ohjelmistoarkkitehtuurit Syksy 2007 Kai Koskimies 1 Tervetuloa Tampereen yliopisto, Teknillinen korkeakoulu, Turun yliopisto 2 Kurssin tavoitteet Arkkitehtuuritason peruskäsitteiden ymmärtäminen Arkkitehtuurien
Ohjelmistokehykset (software frameworks)
Ohjelmistoarkkitehtuurit 1 (software frameworks) Osittain abstraktiksi jätettyjä ohjelmistorunkoja, joita eri tavoin täydentämällä saadaan rakennettua kokonaisia uusia sovelluksia tai sovelluksen osia
Tenttikysymykset. + UML- kaavioiden mallintamistehtävät
Tenttikysymykset 1. Selitä mitä asioita kuuluu tietojärjestelmän käsitteeseen. 2. Selitä kapseloinnin ja tiedon suojauksen periaatteet oliolähestymistavassa ja mitä hyötyä näistä periaatteista on. 3. Selitä
ohjelman arkkitehtuurista.
1 Legacy-järjestelmällä tarkoitetaan (mahdollisesti) vanhaa, olemassa olevaa ja käyttökelpoista ohjelmistoa, joka on toteutettu käyttäen vanhoja menetelmiä ja/tai ohjelmointikieliä, joiden tuntemus yrityksessä
Tämän lisäksi listataan ranskalaisin viivoin järjestelmän tarjoama toiminnallisuus:
Dokumentaatio, osa 1 Tehtävämäärittely Kirjoitetaan lyhyt kuvaus toteutettavasta ohjelmasta. Kuvaus tarkentuu myöhemmin, aluksi dokumentoidaan vain ideat, joiden pohjalta työtä lähdetään tekemään. Kuvaus
Ohjelmistotekniikan menetelmät, kesä 2008
582101 - Ohjelmistotekniikan menetelmät, kesä 2008 1 Ohjelmistotekniikan menetelmät Methods for Software Engineering Perusopintojen pakollinen opintojakso, 4 op Esitietoina edellytetään oliokäsitteistön
7. Tuoterunkoarkkitehtuurit
7. Tuoterunkoarkkitehtuurit Johdanto Näkökulmat tuoterunkoihin perustuvaan ohjelmistokehitykseen Kerrostyyli tuoterunkoarkkitehtuureille Tuoterunkojen etuja ja ongelmia 1 Uudelleenkäytt yttö opportunistinen:
Tenttikysymykset. + UML- kaavioiden mallintamistehtävät
Tenttikysymykset 1. Selitä mitä asioita kuuluu tietojärjestelmän käsitteeseen. 2. Selitä kapseloinnin ja tiedon suojauksen periaatteet oliolähestymistavassa ja mitä hyötyä näistä periaatteista on. 3. Selitä
Sisäänrakennettu tietosuoja ja ohjelmistokehitys
Sisäänrakennettu tietosuoja ja ohjelmistokehitys Petri Strandén 14. kesäkuuta, 2018 Petri Strandén Manager Cyber Security Services Application Technologies [email protected] Petri vastaa KPMG:n Technology
Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo
Concurrency - Rinnakkaisuus Group: 9 Joni Laine Juho Vähätalo Sisällysluettelo 1. Johdanto... 3 2. C++ thread... 4 3. Python multiprocessing... 6 4. Java ExecutorService... 8 5. Yhteenveto... 9 6. Lähteet...
Copyright by Haikala. Ohjelmistotuotannon osa-alueet
Copyright by Haikala Ohjelmistotuotannon osa-alueet Ohjelmiston elinkaari 1. Esitutkimus, tarvekartoitus, kokonaissuunnittelu, järjestelmäsuunnittelu (feasibility study, requirement study, preliminary
Helsingin yliopisto/tktl DO Tietokantojen perusteet, s 2000 Johdanto & yleistä Harri Laine 1. Tietokanta. Tiedosto
Tietokanta Tiedosto Tietokanta (database) jotakin käyttötarkoitusta varten laadittu kokoelma toisiinsa liittyviä säilytettäviä tietoja Ohjelmointikielissä apumuistiin tallennettuja tietoja käsitellään
Ohjelmistoarkkitehtuurit Kevät käytäntöjä
Ohjelmistoarkkitehtuurit Kevät 2014 -käytäntöjä Samuel Lahtinen http://www.cs.tut.fi/~ohar/ 8.1.2014 1 Tervetuloa Oulun yliopisto, Tampereen yliopisto, Turun yliopisto, Tampereen teknillinen yliopisto
Suunnitteluvaihe prosessissa
Suunnittelu Suunnitteluvaihe prosessissa Silta analyysin ja toteutuksen välillä (raja usein hämärä kumpaankin suuntaan) Asteittain tarkentuva Analyysi -Korkea abstraktiotaso -Sovellusläheiset käsitteet
Ohjelmiston testaus ja laatu. Ohjelmistotekniikka elinkaarimallit
Ohjelmiston testaus ja laatu Ohjelmistotekniikka elinkaarimallit Vesiputousmalli - 1 Esitutkimus Määrittely mikä on ongelma, onko valmista ratkaisua, kustannukset, reunaehdot millainen järjestelmä täyttää
Arkkitehtuurikuvaus. Ratkaisu ohjelmistotuotelinjan monikielisyyden hallintaan Innofactor Oy. Ryhmä 14
Arkkitehtuurikuvaus Ratkaisu ohjelmistotuotelinjan monikielisyyden hallintaan Innofactor Oy Ryhmä 14 Muutoshistoria Versio Pvm Päivittäjä Muutos 0.4 1.11.2007 Matti Eerola 0.3 18.10.2007 Matti Eerola 0.2
FiSMA 1.1 Toiminnallisen laajuuden mittausmenetelmä Ohje monikerrosarkkitehtuurin mittaamiseen
FiSMA 1.1 Monikerrosarkkitehtuuri 1 (7) FiSMA 1.1 Toiminnallisen laajuuden mittausmenetelmä Ohje monikerrosarkkitehtuurin mittaamiseen 1. Yleiset periaatteet FiSMA 1.1 -menetelmässä mitataan sovellusperiaatteen
Testaajan eettiset periaatteet
Testaajan eettiset periaatteet Eettiset periaatteet ovat nousseet esille monien ammattiryhmien toiminnan yhteydessä. Tämä kalvosarja esittelee 2010-luvun testaajan työssä sovellettavia eettisiä periaatteita.
Ohjelmistoarkkitehtuurit, syksy
Ohjelmistoarkkitehtuurit Ohjelmistoarkkitehtuurin kuvaaminen on arkkitehtuuritason suunnitteluratkaisujen kuvaamista Arkkitehtuuritasoisuus Aihe, ongelma tai ohjelmistoelementti on arkkitehtuuritasolla,
Ohjelmiston toteutussuunnitelma
Ohjelmiston toteutussuunnitelma Ryhmän nimi: Tekijä: Toimeksiantaja: Toimeksiantajan edustaja: Muutospäivämäärä: Versio: Katselmoitu (pvm.): 1 1 Johdanto Tämä luku antaa yleiskuvan koko suunnitteludokumentista,
Osittavat arkkitehtuurityylit. Palveluihin perustuvat arkkitehtuurityylit. Erikoisarkkitehtuurityylit
6. Arkkitehtuurityylit Osittavat arkkitehtuurityylit Kerrosarkkitehtuurit Tietovuoarkkitehtuurit Palveluihin perustuvat arkkitehtuurityylit Asiakas-palvelin arkkitehtuurit Viestinvälitysarkkitehtuurit
Ohjelmistojen mallintaminen. Luento 11, 7.12.
Ohjelmistojen mallintaminen Luento 11, 7.12. Viime viikolla... Oliosuunnittelun yleiset periaatteet Single responsibility eli luokilla vain yksi vastuu Program to an interface, not to concrete implementation,
Muutamia peruskäsitteitä
Muutamia peruskäsitteitä Huom. 1: nämä peruskäsitteet eivät muodosta hyvin määriteltyä keskenään yhteensopivien käsitteiden joukkoa, vaan käsitteet ovat osittain päällekkäisiä ja eri yhteyksissä niillä
11. Kehysarkkitehtuurit
11. Kehysarkkitehtuurit Johdanto Kehystyypit Kehykset ja arkkitehtuuri Kehykset ja suunnittelumallit Kehyspohjainen ohjelmistokehitys Esimerkkikehys Kehysten toteutuksesta Kehysten etuja ja ongelmia Yhteenvetoa
Arkkitehtuurien tutkimus Outi Räihä. OHJ-3200 Ohjelmistoarkkitehtuurit. Darwin-projekti. Johdanto
OHJ-3200 Ohjelmistoarkkitehtuurit 1 Arkkitehtuurien tutkimus Outi Räihä 2 Darwin-projekti Darwin-projekti: Akatemian rahoitus 2009-2011 Arkkitehtuurisuunnittelu etsintäongelmana Geneettiset algoritmit
Uutisjärjestelmä. Vaatimusmäärittely. Web-palvelujen kehittäminen. Versio 1.3
Uutisjärjestelmä Vaatimusmäärittely Versio 1.3 Sisällys 1 Muutoshistoria... 4 2 Viitteet... 4 3 Sanasto... 4 3.1 Lyhenteet... 4 3.2 Määritelmät... 4 4 Johdanto...5 4.1 Järjestelmän yleiskuvaus... 5 4.2
Tenttikysymykset. + UML-kaavioiden mallintamistehtävät
Tenttikysymykset 1. Selitä mitä asioita kuuluu tietojärjestelmän käsitteeseen. 2. Selitä kapseloinnin ja tiedon suojauksen periaatteet oliolähestymistavassa ja mitä hyötyä näistä periaatteista on. 3. Selitä
Kurssin aihepiiri: ohjelmistotuotannon alkeita
Kurssin aihepiiri: ohjelmistotuotannon alkeita [wikipedia]: Ohjelmistotuotanto on yhteisnimitys niille työnteon ja työnjohdon menetelmille, joita käytetään, kun tuotetaan tietokoneohjelmia sekä monista
17/20: Keittokirja IV
Ohjelmointi 1 / syksy 2007 17/20: Keittokirja IV Paavo Nieminen [email protected] Tietotekniikan laitos Informaatioteknologian tiedekunta Jyväskylän yliopisto Ohjelmointi 1 / syksy 2007 p.1/10 Tavoitteita
Ohjelmistotuotanto vs. muut insinööritieteet. (Usein näennäinen) luotettavuus ja edullisuus
Yhteenveto Ohjelmistotuotanto vs. muut insinööritieteet Monimutkaisuus Näkymättömyys (Usein näennäinen) luotettavuus ja edullisuus Muunnettavuus Epäjatkuvuus virhetilanteissa Skaalautumattomuus Copyright
9. Muunneltavuuden hallinta
9. Muunneltavuuden hallinta Muunneltavuuden hallinta (Variability management): Tekniikat ja työtavat, jotka auttavat kuvaamaan, toteuttamaan ja hyödyntämään tuoterungon mahdollistamaa ohjelmistotuotteiden
Ohjelmistotekniikan menetelmät, Ohjelmistotuotannon työkaluista
582101 - Ohjelmistotekniikan menetelmät, Ohjelmistotuotannon työkaluista 1 Ohjelmistotuotannon työkaluuista Projektinhallintatyökalut (ei käsitellä tällä kurssilla) CASE- ja mallinnustyökalut (esim. Poseidon)
2. Olio-ohjelmoinnin perusteita 2.1
2. Olio-ohjelmoinnin perusteita 2.1 Sisällys Esitellään peruskäsitteitä yleisellä tasolla: Luokat ja oliot. Käsitteet, luokat ja oliot. Attribuutit, olion tila ja identiteetti. Metodit ja viestit. Olioperustainen
Ohjelmistotekniikan menetelmät, UML
582101 - Ohjelmistotekniikan menetelmät, UML 1 Sisältö DFD- ja sidosryhmäkaavioiden kertaus Oliomallinnus UML:än kaaviotyypit 2 Tietovuokaaviot Data flow diagrams, DFD Historiallisesti käytetyin kuvaustekniikka
Web-palvelu voidaan ajatella jaettavaksi kahteen erilliseen kokonaisuuteen: itse palvelun toiminnallisuuden toteuttava osa ja osa, joka mahdollistaa k
1 Web-palvelu voidaan ajatella jaettavaksi kahteen erilliseen kokonaisuuteen: itse palvelun toiminnallisuuden toteuttava osa ja osa, joka mahdollistaa ko. toiminnallisuuden hyödyntämisen Web-palveluna.
Ohjelmistojen mallintaminen, kesä 2010
582104 Ohjelmistojen mallintaminen, kesä 2010 1 Ohjelmistojen mallintaminen Software Modeling Perusopintojen pakollinen opintojakso, 4 op Esitietoina edellytetään oliokäsitteistön tuntemus Ohjelmoinnin
Ohjelmistoarkkitehtuurit. Kevät 2014
Ohjelmistoarkkitehtuurit Kevät 2014 Samuel Lahtinen (Johannes Koskinen) http://www.cs.tut.fi/~ohar/ 1 Yleisiä asioita Luennot keskiviikkoisin 10:15- Viikkoharjoitukset jatkuvat taas 8.4. Arviointien paikat
Ohjelmistoarkkitehtuurit 2016. Kevät 2016 -käytäntöjä
Ohjelmistoarkkitehtuurit Kevät 2016 -käytäntöjä Samuel Lahtinen http://www.cs.tut.fi/~ohar/ 13.1.2016 1 Tervetuloa Tampereen teknillinen yliopisto, Oulun yliopisto, Turun yliopisto 13.1.2016 2 Tiedonvälitys
Ohjelmistojen mallintaminen, kesä 2009
582104 Ohjelmistojen mallintaminen, kesä 2009 1 Ohjelmistojen mallintaminen Software Modeling Perusopintojen pakollinen opintojakso, 4 op Esitietoina edellytetään oliokäsitteistön tuntemus Ohjelmoinnin
Ohjelmistoarkkitehtuurit, syksy
Ohjelmistoarkkitehtuurit 2 Rajapinnat 24.9.2012 1 Arkkitehtonisen näkymän esittäminen Luonnollinen kieli + vapaamuotoinen grafiikka taustalla on oltava joku malli, jonka mukaisia asioita kuvaukseen otetaan
3. Käsiteanalyysi ja käsitekaavio
3. Käsiteanalyysi ja käsitekaavio lehtori Pasi Ranne Metropolia ammattikorkeakoulu E-mail: [email protected] sivu 1 Käsiteanalyysi Selvitetään mitä tietokantaan pitää tallentaa Lähtökohtana käyttäjien
Ohjelmistotekniikka - Luento 2
Ohjelmistotekniikka - Luento 2 Luku 2: Prosessimallit - miten spiraalimalliin päädyttiin - spiraalimallista (R)UP malliin - oman ammattitaidon kehittäminen; PSP ja TSP mallit 1 Luento 2: Prosessimallit
Tietorakenteet ja algoritmit - syksy 2015 1
Tietorakenteet ja algoritmit - syksy 2015 1 Tietorakenteet ja algoritmit - syksy 2015 2 Tietorakenteet ja algoritmit Johdanto Ari Korhonen Tietorakenteet ja algoritmit - syksy 2015 1. JOHDANTO 1.1 Määritelmiä
TIE-20200 Samuel Lahtinen. Lyhyt UML-opas. UML -pikaesittely
Lyhyt UML-opas UML -pikaesittely UML, Unified Modeling Language Standardoitu, yleiskäyttöinen mallinnuskieli, jota ylläpitää/hallitsee (Object Management Group) OMG Historiaa: 90-luvulla oli paljon kilpailevia
Sisällys. Mitä on periytyminen? Yksittäis- ja moniperiytyminen. Oliot ja perityt luokat. Periytymisen käyttö. 8.2
8. Periytyminen 8.1 Sisällys Mitä on periytyminen? Yksittäis- ja moniperiytyminen. Oliot ja perityt luokat. Periytymisen käyttö. 8.2 Mitä on periytyminen? Periytyminen (inheritance) tarkoittaa luokan piirteiden
Alkuraportti. LAPPEENRANNAN TEKNILLINEN YLIOPISTO TIETOJENKÄSITTELYN LAITOS CT10A4000 - Kandidaatintyö ja seminaari
LAPPEENRANNAN TEKNILLINEN YLIOPISTO TIETOJENKÄSITTELYN LAITOS CT10A4000 - Kandidaatintyö ja seminaari Alkuraportti Avoimen lähdekoodin käyttö WWW-sovelluspalvelujen toteutuksessa Lappeenranta, 30.3.2008,
4.2 Yhteensopivuus roolimalleihin perustuvassa palvelussa
4. Roolimallipalvelu 4.1 Tiedot palvelusta Palvelun nimi: Palvelun versio 01.01.00 Toteuttaa palvelun yksilöllistä palvelua (kts. M14.4.42) Roolimallipalvelu (Model role service) MYJ:lle, jotka toteuttavat
Ohjelmointitekniikka lyhyesti Survival Kit 1 Evtek KA ELINKAARIMALLEISTA
Ohjelmointitekniikka lyhyesti Survival Kit. Vesiputousmalli ELINKAARIMALLEISTA. Ohjelmiston elinkaari Ohjelmiston elinkaarella (life cycle) tarkoitetaan aikaa, joka kuluu ohjelmiston kehittämisen aloittamisesta
Vaatimusmäärittely Ohjelma-ajanvälitys komponentti
Teknillinen korkeakoulu 51 Vaatimusmäärittely Ohjelma-ajanvälitys komponentti Versio Päiväys Tekijä Kuvaus 0.1 21.11.01 Oskari Pirttikoski Ensimmäinen versio 0.2 27.11.01 Oskari Pirttikoski Lisätty termit
11.4. Context-free kielet 1 / 17
11.4. Context-free kielet 1 / 17 Määritelmä Tyypin 2 kielioppi (lauseyhteysvapaa, context free): jos jokainenp :n sääntö on muotoa A w, missäa V \V T jaw V. Context-free kielet ja kieliopit ovat tärkeitä
19/20: Ikkuna olio-ohjelmoinnin maailmaan
Ohjelmointi 1 / syksy 2007 19/20: Ikkuna olio-ohjelmoinnin maailmaan Paavo Nieminen [email protected] Tietotekniikan laitos Informaatioteknologian tiedekunta Jyväskylän yliopisto Ohjelmointi 1 / syksy 2007
Tietokanta (database)
Tietokanta Tietokanta (database) jotakin käyttötarkoitusta varten laadittu kokoelma toisiinsa liittyviä säilytettäviä tietoja 1 Tiedosto Ohjelmointikielissä apumuistiin tallennettuja tietoja käsitellään
UML:n yleiskatsaus. UML:n osat:
UML:n yleiskatsaus - voidaan hyödyntää hyvin laajasti. - sopii liiketoimintamallinnukseen, ohjelmistomallinnukseen sen jokaiseen vaiheeseen tai minkä tahansa pysyviä ja muuttuvia ominaisuuksia sisältävän
Koodimalli Code Model
Koodimalli Code Model Luento 6 10.10.2017 CSM14101 Ohjelmistoarkkitehtuurit 1 Oppimistavoitteet Koodimalli Arkkitehtuurisuunnittelun ja implementaation välinen kuilu ja sen hallitseminen Arkkitehtuuria
Arkkitehtuuri muutosagenttina
Arkkitehtuuri muutosagenttina Smarter Processes, Development & Integration Hannu Salminen CTO OP-Pohjola 2013 IBM Corporation Taustaa Nykyinen IT-arkkitehtuuri ja liiketoimintatarpeet eivät kohtaa OP-Pohjolan
Ohjelmistoarkkitehtuurit Kevät 2016 Johdantoa
Ohjelmistoarkkitehtuurit Kevät 2016 Johdantoa Samuel Lahtinen http://www.cs.tut.fi/~ohar/ 8.1.2014 1 1 Johdanto 1.1 Mikä on ohjelmistoarkkitehtuuri? 1.2 Ohjelmistoarkkitehtuuri ja laatuvaatimukset 1.3
Luokka- ja oliokaaviot
Luokka- ja oliokaaviot - tärkeimmät mallinnuselementit : luokat, oliot ja niiden väliset suhteet - luokat ja oliot mallintavat kuvattavan järjestelmän sisältöä ja niiden väliset suhteet näyttävät, kuinka
IT-OSAAJA, TIETOJENKÄSITTELYN ERIKOISTUMISOPINNOT
IT-OSAAJA, TIETOJENKÄSITTELYN ERIKOISTUMISOPINNOT KOULUTUKSEN KOHDERYHMÄ SISÄLTÖ Koulutuksen tavoitteena on antaa opiskelijalle valmiudet uusien tietoteknisten menetelmien ja välineiden hyödyntämiseen.
