Ohjelmistotuotannon ohjelmointihaasteita 1

Ohjelmistotuotannon ohjelmointihaasteita 1 on 14 ohjelmointi- ja konfigurointitehtävää sisältävä 2 opintopisteen laajuinen itseopiskelukurssi. Kurssin tehtävien aihepiirinä ovat nykyaikaiset ohjelmistotuotannon työkalut ja ratkaisut. Kurssin aikana tutustutaan mm. mavenia modernimpaan konfiguraatiohallintatyökalun Gradleen, jatkuvan integraation pilvipalveluun Travisiin ja viime aikoina muotiin nousseisiin mikropalveluarkkitehtuureihin.

Kurssin esitietovaatimuksena ovat kurssin Ohjelmistotuotanto suoritus, sujuva ohjelmointitaito ja valmius omatoimiseen tiedonhakuun.

Kurssi suoritetaan tekemällä su 17.1. klo 23:59 mennessä alla olevat tehtävät. Tehtyäsi tehtävät lähetä sähköpostia osoitteeseen mluukkai@cs.helsinki.fi

Kurssi ei sisällä kontaktiopetusta, mutta saat tarvittaessa vihjeitä tehtävien tekoon IRCnetistä kanavalla #ohtu2015

Valmistelut

Forkkaa Haastavaohtu -repo. Joissakin tehtävissä pyydetään palauttamaan tehtäviä forkkiin.

1. Travis

Travis on nopeasti suureen suosioon noussut pilvipalveluna toimiva Continuous Integration -järjestelmä. Kirjaudu sisään Github-tunnuksillasi osoitteessa https://travis-ci.org/.

Käytämme esimerkkinä Uutislukija -mavenprojektia, jolla on oma github-repositorio. UutisLukija on käyttöliittymä Päivän Uutiset -rajapinnan toteuttavien uutishakuohjelmien uutisten lukemiseen. UutisLukija käyttää maven-riippuvuutenaan Päivän Uutiset -rajapinnan toteuttavaa HackerNewsUutiset-repositoriota, jolla taas on maven-riippuvuutena Päivän Uutiset -rajapinnan repositorio .

Forkkaa https://github.com/hy-ohtu/UutisLukija githubissa. Ota selvää miten repositorion lisääminen githubista travisiin tapahtuu ja triggeröi ensimmäinen buildi.

Huomasit varmaankin, että buildi ei mennyt läpi ja muuttui punaiseksi. Virheilmoitusta lukemalla saat selville, mistä se johtuu. Ratkaise tilanne käyttämällä .travis.yml-tiedoston before_install asetuksia niin, että lopulta olet asentanut travisissa etukäteen riippuvuuksina tarvittavat repositoriot.

2. Git cherrypick & NetBeans Refactor

Tee Pull Request refaktorointimuutoksesta. Kaikki repoon liian paljon konflikteja aiheuttamattomat, aikaisemmin tekemättömät refaktoroinnit hyväksytään.

3 Versionhallinnan sisäinen toiminta

Ohjelmistokehittäjän yleissivistykseen kuuluu myös tietää, miten git toimii sisäiseltä rakenteeltaan. Lue https://git-scm.com/book/en/v2/Git-Internals-Git-Objects. Git on melko monimutkainen joten emme ota suoraan mallia siitä, vaan koodaamme oman yksinkertaisemman OhtuVersionhallinta -ohjelman. Ohjelmointikieli on vapaa ja speksit ovat: Lisää ohjelmasi koodit haastavaohtu -repoosi.

4 Coveralls

Coveralls on raportointityökalu joka on yhteensopiva Travisin kanssa. Tämä tehtävä vaatii että sinulla on forkattu uutislukija ja sille pystytetty Travis-CI, tee siis tehtävä 1 jos ei ole. Nyt konffaamme Uutislukijalle Coveralls -raporttityökalun. Kirjaudu sisään github-tunnuksillasi. Seuraa coverallsin ohjeita niin, että lopulta Uutislukijan sivusi näyttää tältä mutta hieman tyhjemmältä. Älä siis jätä sivua "There have been no builds for this repo." -tilaan. Konfiguroi Uutislukijan README näyttämään coverallsin README -badge, markdown-tägi löytyy edellisen kuvan oikealta laidalta.

5 Johdantoa lisensseihin

Open Source Initiative on voittoa tuottamaton organisaatio, joka perustettiin vuonna 1998 muinaisen Netscape Communicatorin lähdekoodin julkaisun innoittamana viralliseksi avoimen lähdekoodin asioita ajavaksi organisaatioksi. OSI on aktiivisesti osallistunut avoimen lähdekoodin yhteisön rakennukseen, julkiseen näkyvyyteen sekä koulutukseen. Sekä vastaa kysymykseen: kun olet löytänyt sopivan lisenssin, mitä teet kun haluat julkaista koodisi sillä? Lisää tiedosto forkattuun haastavaohtu -repoosi.

6 Lisenssit

Tmc-Netbeans on epäyhteensopiva eclipsen kanssa. Miksi? Kirjoita syy tiedostoon ja lisää se haastavaohtu -repoosi.

7 Wiremockitus

Wiremock on työkalu nettiä käyttävien softakomponenttien mockitukseen. Tässä tehtävässä testaamme ja mockitamme Hacker News Uutiset niin, ettei testaus tarvitse enää nettiyhteyttä.

8 Webissä toimiva uutislukija ja Continuous deployment pipeline

Tehtävänä on toteuttaa Spark-sovelluskehyksen avulla Uutislukijasta webissä toimiva versio. Ohjelma voi näyttää esimerkiksi seuraavalta https://spark-testi.herokuapp.com/ Ei haittaa vaikka et olisi tehnyt elämäsi aikana vielä yhtään web-sovellusta, Spark on hyvin yksinkertainen ja itse ohjelma ei vaadi kovin montaa riviä koodia. Ohjelmoinnin sijaan pääpaino tehtävässä on konfiguroinnilla. Tehtävässä tulee toteuttaa continuous deployment pipeline, jonka avulla jokainen testit läpäisevä githubiin pushattu versio deployataan automaattisesti herokuun Automatisoidun testauksen tulee olla kattavaa, rivikattavuudeltaan mielellään vähintään 90%. Pelkkä automatisoitu yksikkötestaaminen ei riitä, ohjelmaa tulee testata myös selaimen tasolla (katso vihjeitä testaamiseen täältä). Ohjelman testiraportin pitää olla nähtävissä Coverallsissa. Travis- ja Coveralls-badget tulee olla näkyvillä projektin repositorion readmessa. Readmesta tulee myös löytyä linkki ohjelman herokuun deployattuun versioon. Tehtävä voi olla osin haastava, kysy apua jos juutut. Muista kuitenkin lukea ensin sparkjava documentation ja tutorial.

Vihjeitä:

9 Hello Maven!

Selvitä itsellesi mitä Maven-pluginilla tarkoitetaan: http://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-programming-maven.html#writing-plugins-what-is-plugin Tehtyäsi tämän tehtävän olet tehnyt yksinkertaisen Maven pluginin, joka tulostaa syöttämäsi merkkijonon info-lokiin. Tehtävä tarjoaa perustan myös paljon haastavamman pluginin kirjoitukseen. Plugin Descriptor määrittelee mm. Maven pluginin Mojot, niiden käyttäytymisen ja paljon muuta. Emme kirjoita tässä tehtävässä omaa descriptoria vaan annamme Mavenin generoida descriptorin puolestamme. Tutustu silti päälipuolisesti seuraavan sivun sisältöön: http://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-plugin-descriptor.html Maven -plugin koostuu joukosta Mojoja, joista jokainen on oma javaluokkansa. Mojoa vastaava javaluokka sisältää joukon annotaatioita, jotka kertovat Mavenille kuinka pluginia vastaava descriptori luodaan. Ennen kuin voimme aloittaa Mojojen koodauksen on luotava uusi Maven projekti. Maven Archetype pluginin generate -goal luo tyhjän pluginille sopivan projektin:
mvn archetype:generate \ -DgroupId=GROUPID \ -DartifactId=PLUGININ_NIMI-maven-plugin \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-mojo
Jos nimeämiskäytännöt ovat sinulle vielä epäselviä niin lue tämä: https://maven.apache.org/guides/mini/guide-naming-conventions.html Huom! ArtifactId tulee määritellä muodossa: PLUGININ_NIMI-maven-plugin, tai maven-PLUGININ_NIMI-plugin Uusi projektisi sisältää nyt POM:in ja esimekki-Mojon. Tärkeimpänä huomiona POM:n elementti <packaging>, joka määrittelee pakkauksen olevan muotoa maven-plugin. Elementti muuttaa Mavenin lifecycleä siten, että Plugin Descriptor luodaan automaattisesti ajamalla mvn install projektin kansiossa. Lue lisää aiheesta täältä: http://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-custom-plugin.html#writing-plugins-sect-creating-plugin-project Kirjoitetaan seuraavaksi pluginimme ensimmäinen Mojo! Luo samaan kansioon esimerkki-Mojon kanssa luokka ViestiMojo.java:
public class ViestiMojo { //... }
Jokaisen pluginin on toteutettava rajapinta org.apache.maven.plugin.Mojo. Me toteutamme rajapinnan perimällä luokan org.apache.maven.plugin.AbstractMojo.
public class ViestiMojo extends AbstractMojo { //... }
Selvitä itsellesi, mitkä tärkeät metodit AbstractMojo tarjoaa: http://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-custom-plugin.html#writing-plugins-simple-java-mojo Ja ota selvää loggauksen eri tasoista: http://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-custom-plugin.html#writing-plugins-sect-logging Lisätään Mojoon attribuutti viesti ja metodi, jolla viesti lähetetään info-lokiin.
//... private Object viesti = "Hello maven!; public void execute() throws MojoExecutionException, MojoFailureException { getLog().info(viesti.toString()); } //...
Mojostamme puuttuu vielä annotaatio, jonka perusteella Maven generoi plugin descriptorin. Maven-pluginit annototoidaan hieman erikoisella tavalla kommenttien sisään. Tehdään annotaatiot, jotka määrittelevät Mojollemme goalin sekä sen, että goalin suoritus ei vaadi projektia:
//... /** * Määritellään mojon goaliksi lahetys: * * @goal lahetys * @requiresProject false * */ public class ViestiMojo //...
Mene plugin projektisi juureen ja aja:
$ mvn install
Plugin on nyt valmiina suoritettavaksi! Voit nyt suorittaa pluginisi seuraavalla komennolla:
$ mvn GROUPID:ARTIFACTID:VERSIO:lahetys
Voimme suorittaa pluginin missä tahansa hakemistossa, myös sellaisissa joissa ei ole pom.xml-tiedostoa, koska määrittelimme, että lahetys-goal ei tarvitse projektia toimiakseen. Miksi plugin toimii nyt yhtäkkiä koneesi jokaisessa hakemistossa? Kun asennat pluginin komennolla mvn install tallentuu pluginista generoitu jar-tiedosto koneesi hakemistossa .m2-sijaitsevaan lokaaliin maven-repositorioon. Kun suoritat pluginin, maven etsii pluginin koodia ensisijaisesti lokaalista repositoriosta.

10 Pluginin parametrit

Useat Maven pluginit vaativat käyttäjältä parametreja. Voimme määritellä annotaatioden avulla, että pluginimme goal laheta ottaa parametrin nimeltään viesti, jonka oletusarvo on "Hello Mojo!". Huomaa että parametrillä on sama nimi kuin aiemmin määrittelemällämme attribuutilla.
public class ViestiMojo // /** * Olio, joka tulostetaan logiin. * * @parameter * expression="${lahetys.viesti}" * default-value="I'm the default Hello maven" */ //... }
Voit suorittaa parametrin ottavan maven goalin muutamalla eri tavalla. Parametrin arvo komentoriviltä määritellään seuraavasti:
$ mvn GROUPID:ARTIFACTID:VERSIO:lahetys -Dlahetys.viesti="Hello Maven!"
Jos olet liittänyt pluginisi johonkin projektiisi voit muokata parametrin arvoa esimerkiksi seuraavilla tavoilla:
... Hello Maven
...tai määrittelemällä suoraan pluginin konfiguraation kautta:
... GROUPID PLUGINISI_NIMI-maven-plugin VERSIO Hello Maven! ...
Selvitä kuinka voit lyhentää komennon muotoon: $ mvn PLUGININ_NIMI:lahetys http://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-custom-plugin.html#writing-plugins-sect-plugin-prefix Eli jos pluginisi artifactId on viesti-maven-plugin tulisi pluginin toimia komennolla:
$ mvn viesti:lahetys -Dlahetys.viesti="Hello Maven!"
Laajenna pluginiasi nyt siten, että sille voi antaa kaksi parametria, joilla molemmilla on default-arvo. Laajennuksen jälkeen pluginisi tulisi toimia seuraavasti (epäoleellinen poistettu tulostuksesta):
mluukkai$ mvn viesti:lahetys [INFO] Hello maven mluukkai$ mvn viesti:lahetys -Dlahetys.tervehdys="Hei" [INFO] Hei maven mluukkai$ mvn viesti:lahetys -Dlahetys.tervehdys="Auf wiedersehen" -Dlahetys.viesti="plugin" [INFO] Auf wiedersehen plugin

11 code-plugin

Tehdään plugin, jonka avulla käyttäjän on mahdollista tehdä erilaisia kyselyjä projektin lähdekoodiin. Pluginistasi siis tulee tehdä sellainen, että se toimii ainoastaan projektin yhteydessä. Tee ensin goal tree, joka näyttää unixin tree-komennon tavoin mitä koodi- ja testitiedostoja ohjelman src-hakemiston alta löytyy:
mluukkai$  mvn code:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building OhtuVarasto 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO]   ohtu
[INFO]       ohtuvarasto
[INFO]           Main.java
[INFO]           Varasto.java
[INFO]
[INFO]   ohtu
[INFO]       ohtuvarasto
[INFO]           VarastoTest.java
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
Tee seuraavaksi goal todo, joka etsii projektin lähdekoodista kaikki paikat, joissa lukee // TODO: ja näyttää rivin jälkeiset 5 riviä allaolevan esimerkin mukaan:
mluukkai$ mvn code:todo
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building OhtuVarasto 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] Main.java
[INFO]
[INFO]   6          // TODO: kokeile paremmilla syötteillä
[INFO]   7          Varasto mehua = new Varasto(100.0);
[INFO]   8          Varasto olutta = new Varasto(100.0, 20.2);
[INFO]   9
[INFO]  10          System.out.println("Luonnin jälkeen:");
[INFO]  11          System.out.println("Mehuvarasto: " + mehua);
[INFO]
[INFO]
[INFO] Varasto.java
[INFO]
[INFO]   9      // TODO: kutsu täältä 2 parametrista konstruktoria
[INFO]  10
[INFO]  11      public Varasto(double tilavuus) {
[INFO]  12          if (tilavuus > 0.0) {
[INFO]  13              this.tilavuus = tilavuus;
[INFO]  14          } else {
[INFO]
[INFO]  75      // TODO: tulostus englanniksi
[INFO]  76
[INFO]  77      public String toString() {
[INFO]  78          return ("saldo = " + saldo + ", vielä tilaa " + paljonkoMahtuu());
[INFO]  79      }
[INFO]  80  }
[INFO]
[INFO]
[INFO] ------------------------------------------------------------------------
Tee todo-goalissa näytettävien rivien määrä konfiguroitavaksi:
mluukkai$ mvn code:todo -Dtodo.rows=2
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building OhtuVarasto 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] Main.java
[INFO]
[INFO]   6          // TODO: kokeile paremmilla syötteillä
[INFO]   7          Varasto mehua = new Varasto(100.0);
[INFO]   8          Varasto olutta = new Varasto(100.0, 20.2);
[INFO]
[INFO]
[INFO] Varasto.java
[INFO]
[INFO]   9      // TODO: kutsu täältä 2 parametrista konstruktoria
[INFO]  10
[INFO]  11      public Varasto(double tilavuus) {
[INFO]
[INFO]  75      // TODO: tulostus englanniksi
[INFO]  76
[INFO]  77      public String toString() {
[INFO]
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

12 Hello gradle

Gradle on mavenia uudempi, erityisesti Android-maailmassa suosittu projektinhallinta/buildi-järjestelmä. Tutustu gradleen ja muuta Ohtuvarasto gradle-muotoon. Selvitä mitä kaikkia gradle-taskeja java-pluginilla on. Kokeile ainakin seuraavia: Jos olet NetBeansin käyttäjä, asenna NB:hen gradle-plugin.

13 Spark with gradle

Tee tehtävän 8 uutislukijasta gradlella buildattava versio. Riittää, että pystyt kääntämään koodin, ajamaan testit ja generoimaan kaikki riippuvuudet sisältävät jar-tiedoston, jonka voi suorittaa seruaavaan tapaan:
mluukkai$ java -jar spark_gradle-all.jar

14 REST-api ja mikropalveluarkkitehtuuri

Tehtävässä on osat 1-7. Yksi viime aikoijen hypetermeistä ohjelmistokehityksessä on ollut microservices eli suomeksi mikropalvelut. Wikipedian määritelmä: > In computing, microservices is a software architecture style in which complex applications are composed of small, independent processes communicating with each other using language-agnostic APIs. These services are small, highly decoupled and focus on doing a small task, facilitating a modular approach to system-building. Jos haluat tietää lisää mikropalveluista, lue http://martinfowler.com/articles/microservices.html. Teemme tässä tehtävässä ohjelman tai ohjelmiston, joka koostuu muutamasta pieneen asiaan keskittyvästä palvelusta, eli mikropalvelusta sekä näiden kanssa kommunikoivasta asiakasohjelmasta. Palvelut pyörivät webissä ja kommunikointi niiden kanssa tapahtuu HTTP-protokollaa käyttäen, seuraten RESTful-periaatteita. Palvelumme ovat siis REST-apeja. Emme mene nyt sen tarkemmin siihen mitä kaikkea termi REST pitää sisällään (googlaa jos kiinnostaa), tarkastelemme ainoastaan yhtä tapaa tehdä HTTP REST -apeja. Apimme toimivat seuraavien periaatteiden mukaan Hieman yksinkertaistaen, kun mennään normaalisti katsomaan web-sivun sisältöä, tehdään GET-pyyntö johonkin urliin. Esim HTTP GET osoitteeseen https://ohtustats-s2015.herokuapp.com/submissions/public.json näyttää kurssin tehtävien palautukset json-muodossa. Myös linkkien klikkaus selaimessa aiheuttaa GET-pyynnön. Pyynnön vastauksena siis lähetetään yleensä jotain dataa, eli html-koodi selaimelle näytettäväksi tai json-muotoista "raakadataa". Palautettua dataa sanotaan pyynnön vastauksen (engl. response) bodyksi. Bodyn mukana lähetetään myös pyynnön statuskoodi. Tuttuja statuskoodeja ovat 200, eli onnistunut pyyntö ja 404 eli document not found. Myös POST-pyyntö kohdistuu tiettyyn url:iin. Pyynnön mukana lähtee viesti eli body, joka voi sisältää jollakin tavoin enkoodattua tietoa. Selain lähettää websivuilla olevien formien tiedot HTTP POST:illa. Lisää HTTP:n pyyntötyypeistä esim. osoitteessa http://www.w3schools.com/tags/ref_httpmethods.asp.

osa 1: warmup ja workflow

Kloonaa Person-palvelun sisältämä repositorio Palvelumme persistoivat oliot MongoDB-tietokantaan. Mongo on ns. dokumenttitietokanta. Hoidamme olioiden käsittelyn Morphia-kirjaston avulla, ja mongosta tehtävässä ei tarvitse tietää oikeastaan mitään. Jos et halua asentaa Mongoa koneellesi, valitse itsellesi vapaa tietokantainstanssi täältä. Person-palvelu on toteutettu Spark-frameworkilla. Palvelu (ks. tiedosto App.java) on rekisteröitynyt kuuntelemaan seuraavia urleja eli "routeja": Sovellus käyttää siis tokeneihin perustuvaa autentikaatiota, jossa periaatteena on se, että operaatiot jotka eivät ole kaikille sallittuja on suojattu siten, että pyyntöihin on lisättävä mukaan validi autentikaatiotoken. Token välitetään pyynnön Authorization-headerin arvona. Tokenin saa haltuunsa kirjautumalla, eli sovelluksessamme lähettämällä käyttäjätunnuksen ja salasanan POST-pyynnöllä osoitteeseen '/session'. Tokenautentikaatio on nimenomaan se tapa, millä REST-apien kanssa tapahtuva kommunikaatio tapahtuu. Sovelluksessamme toteutus on suoraviivainen ja noudattaa melkien OAuth 2.0 Resource Owner Password Credentials Grant-flowta. Person-palvelu tallettaa generoidut validit tokenit muistiin, eli token säilyy niin kauan validina kuin palvelu on päällä, uudelleenkäynnistyksen jälkeen vanhat tokenit eivät ole enää voimassa. Käynnistä sovellus. Käynnistyksen helpottamista varten projektin juuressa on skripti run_person.sh, joka suorittaa pari tuttua maven-komentoa. Sovellus käynnistyy porttiin 4567 Kokeile eri urleja selaimella, curlilla ja postmanilla Selain on lähes käyttökelvoton apien testailemiseen. Ehkä tarkoitukseen paras työkalu on postman. Tee seuraavat: luo käyttäjä, pyynnön mukana menevän jsonin oletetaan olevan muotoa
{
    "name": "pihla",
    "username": "pihla",
    "password": "salainen",
    "address": "mannerheimintie"
}
* kirjaudu järjestelmään * kokeile listata käyttäjät ilman autentikaatiotokenia * listaa käyttäjät siten että autentikaatiotoken on määritelty * tee koodiin esifiltteri, jonka avulla jokaisesta pyynnöstä tulostuu alla olevan esimerkin tapaan _metodi, headerit ja body_ eli pyyntöön liittyvä data
---------------------
POST
Accept = */*
Accept-Encoding = gzip, deflate
Accept-Language = en-US,en;q=0.8,fi;q=0.6
Authorization = 39defbca-f5d3-472b-b2d9-c87d8c36c2ad
CSP = active
Cache-Control = no-cache
Connection = keep-alive
Content-Length = 110
Content-Type = text/plain;charset=UTF-8
Host = localhost:4567
Origin = chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop
Postman-Token = 25767dce-6cd2-50d8-eac0-74bd17cbdb58
User-Agent = Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.132 Safari/537.36
{
    "name": "pihla",
    "username": "pihla",
    "password": "salainen",
    "address": "mannerheimintie"
}
Pidä koko ajan myös silmällä mitä konsolissa tapahtuu. HUOM jos/kun teet muutoksia koodiin, palvelu täytyy uudelleenkäynnistää, jotta muutokset saadaan voimaan.

osa 2: client

Kloonaa repositoriossa https://github.com/mluukkai/ApiClient oleva Person-sovellusta käyttävä komentoriviohjelma itsellesi. Ohjelmassa on tällä hetkellä toiminnallisuus valmiina sisäänkirjautumiseen ja kaikkien käyttäjien listaamiseen. Toteuta ohjelmaan uuden käyttäjän rekisteröinti. Ohjelma kommunikoi Person-palvelun kanssa käyttäen Apache HTTPClientin uutta hienoa fluent-apia Huomaa, että projektin juuressa on jälleen ohjelman suoritusta helpottava skripti.

osa 3: kirjaston eriytys

Molemmissa projekteissa, ApiClient ja PersonService on copypastettuna suuri määrä samoja luokkia. Eriytä yhteiset luokat projektiiksi nimeltään esim. _DomainLib_ jonka liität ApiClientin ja PersonServicen riippuvuudeksi.

osa 4: Product-palvelu

Ohjelmistomme on osa verkkokauppaa (Amazon on kuuluisa ja varhainen esimerkki mikroarkkitehtuurin soveltamisesta). Tee nyt palvelu, joka vastaa myytävien tuotteiden listauksesta, anna palvelulle nimi ProductService. Palvelu toimii seuraavasti
{
    "name": "Kevytmaito",
    "producer": "Valio",
    "price": 3,
    "inStock": 240
}
{
    "token": "tokenvaluehere",
    "valid": true
}
Laajenna ApiClientiä siten, että voit käyttä sen avulla uutta palvelua. Muista testauksen yhteydessä postman ja debugtulosteiden tekeminen konsoliin

osa 5: cleaner code

Huomaat että erityisesti HTTP-pyyntöjä tekevässä koodissa on paljon toisteisuutta, ja sama toisteisuus on levinnyt useaan projektiin. Eriytä toisteisuus kirjastoon nimeltään esim. RestLib, jonka käyttö voi näyttää esim. seuraavalta:
// create the object that encapsulates communication with api endpoints
Http http = new Http();

// the library object has methods to get the urls used in app
String personService = http.endpointForPersons();

// use the method get (or post) to make the request
ApiResponse response = http.get(http.endpointForPersons());

// the response object has easy accessors for status and the returned message body
if (response.ok()) {
   for (Person person : response.body(new Person[0])) {
      System.out.println(person);
   }
} else {
   System.out.println(response.error());
}
Esimerkissä Http ja ApiResponse ovat siis omatekemiä kirjastoluokkia, jotka piilottavat alemman tason apien käytön. Muuta clientiä ja Product-palvelua siten että ne käyttävät uutta kirjastoa.

osa 6: konfiguraatiopalvelu

Palveluiden ja asiakkaan, tai kirjaston koodissa on kovakoodattuna urleja, palvelun oma porttinumero ja tietokantakonfiguraatioita. Toteuta erillinen palvelu, ConfigurationService, jota käyttäen kaikki muut ohjelman komponentit saavat kaikki konfiguraatiotietonsa. Konfuguraatiopalvelin palauttaa ohjelmiston konfiguraatiot jsonina esim. seuraavaan tapaan:
{
  endpoint: {
    persons: "http://localhost:4567/persons",
    products: "http://localhost:4568/products",
    session: "http://localhost:4567/session",
    token: "http://localhost:4567/tokens"
  },
  mongoUrl: "mongodb://ohtu:ohtu@ds055842.mongolab.com:55842/kanta1",
  mongoDb: "kanta1",
  productsPort: 4568,
  personsPort: 4567
}
Voit kovakoodata konfiguraatiot ConfigurationServiceen, tai lukea ne esim. tiedostosta, tai tietokannasta. Edes konfiguraatiopalvelimen osoitetta ei saa kovakoodata muihin komponentteihin, vaan ne saavat osoitteen _ympäristömuuttujan_ (environment variable) kautta.

osa 7: Tokenin itsenäinen validointi

Nyt jokainen pyyntö ProductServiceen, joka edellyttää tokenin käyttöä pyynnön autentikointiin saa aikaan sen, että ProductServicen on varmistettava tokenin voimassaolo PersonServiceltä. Jos sovelluksessa olevien palveluiden määrä kasvaisi suureksi, olisi se vaara, että tokenien validointi aiheuttaisi suorituskyvyn pullonkaulan. Muuta autentikoinnin toteutusta siten, jokaisen palvelun on itse mahdollista varmistaa onko autentikaatiotokeni voimassa. Tämä onnistuu esim. liittämällä tokeniin tieto sen voimassaoloajasta. Kun näin tehdään, tokenia ei kuitenkaan voi lähettää sellaisenaan, muutenhan asiakasohjelma voisi väärentää voimassaoloajan. Token, tai ainakin sen voimassaoloaika on siis salattava siten, että ainoastaan palvelimet voivat purkaa salauksen. Voit toteuttaa tokenin salaamisen esim. Jasypt-kirjaston, metodin StandardPBEStringEncryptor avulla. Älä kovakoodaa salausavainta palveluiden koodiin vaan välitä se esim. ympäristömuuttujan avulla.

osa 8: testaus

Sovellukselle olisi kannattanut ehkä jo tehdä jonkinlaiset testit. Testejä voitaisiin tehdä monelle tasolle: yksikkötestejä luokille ja metodeille, HTTP API:n kautta suoritettavia testejä yksittäiselle palvelulle tai koko sovelluksen tasolla tapahtuvaa, asiakasohjelman toimintaa simuloivia, kaikkia palveluita käyttäviä testejä. Yksikkötestejä kannattaa mirkopalveluihin tehdä ehkä vain siinä tapauksessa, jos palvelu sisältää monimutkaista toimintalogiikkaa. Palvelumme ovat triviaaleja, joten yksikkötestaus ei kannata. Yhden palvelun tasolla tapahtuva, API:n kautta suoritettava testaus on usein järkevää, jätämme kuitenkin senkin testaustason tällä kertaa välistä ja keskitymme koko sovelluksen testaamiseen. Tee testejä varten oma projekti, esim. ApiIntegrationTest, ja toteuta sinne testit, joissa kommunikoit palvelujen kanssa samalla tavalla kuin kommunikointi ApiClientin ja palveluiden välillä tapahtuu. Emme tee nyt täydellisiä testejä, riittää että testaat esim. seuraavaat: Testatessa emme halua käyttää "tuotantokäytössä" olevaa tietokantaa. Muuta ConfigurationServiceä siten, että se tarjoaa sekä tuotanto- että testausympäristön konfiguraatiot. Tarkoitus on ajaa testit siten, että kaikki palvelut on käynnistetty testausmoodiin (eli niille on annettu konfiguraatioapin testikonfiguraatiot tarjoava url). Käytetään testausta varten tietokantaa, jonka mongodb-url on mongodb://ohtu:ohtu@ds035664.mongolab.com:35664/tests. Testien käyttämä tietokanta pitäisi pystyä tyhjentämään testiajojen välissä. Voit hoitaa tyhjentämisen seuraavalla koodilla:
	String mongoUrl = "mongodb://ohtu:ohtu@ds035664.mongolab.com:35664/tests";
	MongoClientURI connectionString = new MongoClientURI(mongoUrl);
	MongoClient mongoClient = new MongoClient(connectionString);

	MongoDatabase database = mongoClient.getDatabase("tests");

	for (String collectionName : database.listCollectionNames()) {
	if ( collectionName.equals("system.indexes")) continue;

	MongoCollection collection = database.getCollection(collectionName);
	collection.drop();
	}
	
Koodi edellyttää että projektilla on riippuvuutena MongoDB Java-driver.