Docker + Java Trixx

Sattuneesta syystä Docker työkalupakin käyttö on itsellä lisääntynyt suorastaan räjähdysmäisesti viime aikoina. Se ei ole aina helppoa, mutta on kyllä palkitsevaa. Kun ensi kertaa saa hallittua kunnon könttiä palveluita parilla komentorivikomennolla, sensijaan että aiemmin seikkaili siellä ja täällä ja saastutti konettaan X kappaleella erilaisia asennuksia… Ja kun aiemmin Vagrant-koneet haukkasivat suurimman osan muistista, IntelliJ loput, ja nyt docker-kontteja voi läiskiä samaan tilaan tusinan.. Niin olen myyty.

Miten kontit juttelevat?

Pari niksiä on tullut opittua – tai oikeastaan luettua manuaalia tarkemmin. Niksi yksi oli, miten saada docker-compose alla docker-palvelut näkemään toisensa? Ratkaisu oli häkellyttävän yksinkertainen: Käytä docker-compose.yml versiota 2.0, tähän tapaan:

version: '2'
 services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

That’s all you need. Versiossa yksi piti linkitellä palveluita, mutta versiossa kaksi oletuksena saman docker-composen osat ovat samassa virtuaaliverkossa. Se tarkoittaa että ne näkevät toisensa suoraan imagen nimen mukaan, esim. jos web haluaa viitata redis-palveluun http protokollalla porttiin 6379, homma hoituu:

http://redis:6379

Tietysti voi olla että ajat palveluita välillä dockerissa, välillä ei. Itse olen havainnut käteväksi Spring Boot sovelluksissa käyttää tähän profiileja, docker-profiili ylikirjoittaa tarvittavat url viitteet tai vastaavat palvelinviitteet näillä image nameilla, ja perusprofiilissa voi olla että viitataan vielä localhostiin, testiympäristössä voidaan viitata taas ihan muualle.

Dockerin verkkoja voi hallita myös manuaalisesti, ja niistä voi koostaa haluamiaan kokoonpanoja. Kuitenkin, oletuksena siis kontit näkevät toisensa kunhan ovat samassa verkossa. Ei tarvitse avata mitään portteja, ellet sitten halua niihin ulkoapäin viitata.

Miten kontti viittaa hostiin?

Nogh, kaikkea ei saa konttiin vieläkään. Omassa OSX koneessa esim. Windows SQL server ei konttina pyöri, vaikka konttina löytyykin. Docker for Mac antaa vain herjaa, ympäristön pitäisi olla Docker for Windows. Joten joudun tekemään vähemmän ideaalin ratkaisun: Viittaamaan kontista ulospäin.

Onneksi homma hoituu, pitää vain välittää Dockerille tieto siitä ip-osoitteesta jossa host toimii. Se onnistuu esim. näin (OSX kone):

export DOCKERHOST=$(ifconfig | grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" | grep -v 127.0.0.1 | awk '{ print $2 }' | cut -f2 -d: | head -n1)

Dockerfilessa ei voi valitettavasti viedä ihan helpolla sisään env muuttujia, ja oletuksena kontit eivät näe ympäröivien hostien muuttujia. Vaan eipä hätää. Olen itse siirtynyt enenevässä määrin käyttämään docker-composea, ja siellä homma hoituu. Määritellään env muuttuja uudestaan docker-composessa, sitten käytetään sitä java-komentorivillä, viedään todellinen url sisään joka siis viittaa env muuttujan mukaiseen ip-osoitteeseen.

mah_service:
  environment: 
    - HOST_ADDRESS="localhost"
  build: ./mah_service
  command: echo "HOST ADDRESS FOR SQL SERVER $HOST_ADDRESS"
  command: java -Dspring.profiles.active=docker -Dspring.datasource.url="jdbc:jtds:sqlserver://${DOCKERHOST}:1433;DatabaseName=demo" -jar mah_app.jar
  ports:
    - "8080:8080"

Tah-dah, magic happens. Joka tapauksessa, tämä ei ole nätti ratkaisu, vain workaround. Windows dockesterijat eivät tarvitse tätä, vaan käyttävät mieluiten sql serveriä kontissa. Se on aina paras vaihtoehto. Lienee ihan reilua että MacOS puolellakin saa joskus kärvistellä. MS SQL Server for Linuxia ootellessa…

No, siinä tällä kertaa havainnot. Postailen tänne itselleni muistiin jatkossakin niksejä, ettei unohdu. Pian tulee Docker 1.3 ja lisää kivaa… 😉

 

Spring Boot Audit Logging

Jotain backendimpää taas vaihteeksi: Projekteissa tulee melkolailla tiheään vaadetta saada aikaan audit loggausta. Vaikkei tulisikaan, se antaa pitkän elinkaaren projekteissa itsellekin mielenrauhaa, että kykenee vastaamaan kysymykseen kuka teki mitä teki milloin teki (miksi teki ei vielä onnistu mutta ehkä IoT avulla sekin ratkaistavissa).

Audit loggausta voi tehdä villistikin eri tavoin ja eri vaatimuksilla. Joissain projekteissa on tultu nähtyä yksinkertainen audit service jota kutsutaan aina tarvittaessa, halutuista paikoista. Tässä on huonoa se, että pitää muistaa kutsua sitä, eli ei ole taattua että suuremmassa projektissa joka koodaaja on laittanut auditit paikalleen, lisäksi se rikkoo DRY periaatetta aika rumasti. Toisaalta on mahdollista tehdä monellakin tapaa filter/interceptor, joka tulee aina väliin ja loggaa vaikka kaiken. Mutta tässä mallissa voi olla ongelmana suuri hälyn määrä, eli voi olla että logi täyttyy tapahtumista jotka eivät ole oikeasti kiinnostavia mutta joita on paljon.

Kirjoittelen tätä blogia koska löysin mielestäni fiksun ratkaisun Spring Frameworkin puolelta, vieläpä Spring Boot yhteensopivana, eli ei xml:ää vaativana. Ratkaisu on fiksu koska se on mukava kompromissi kahdesta mainitusta ääripään tavasta – sisältäen tavallaan molempien huonoja ja hyviä puolia. Mutta ennenkaikkea se on melko kaunis, esteettinen, eikä riko yhtälailla ikävästi DRY periaatetta. Kirjaan näitä ylös myös ennenkaikkea itselleni muistiin, vähentää kivasti tarvittavaa aikaa soveltaa uudelleen, kun on tiedossa testattua luotettavaa ja (tällä hetkellä) ajantasaista tietoa.

Se mitä halusin on oikeastaan mahdollisuus auditoida metoditasolla on-demand, missä haluan. Ei täysautomaattisesti kaikkea, mutta ei myöskään samaa koodia copy-pasteillen joka paikkaan. Lisäksi halusin että voin halutessani määrittää audit eventille nimen, ja/tai kategorian, ja/tai koodin, pelkän metodi/luokannimen sijasta.

Homma lähtee liikkeelle ihan perinteisistä Spring AOP annotaatioista. Eli tarvitaan ensin Spring Boot projekti. Niistä olen kirjaillut jo aiemmin eli en lähde ihan sillä tasolla asiaa avaamaan tällä kertaa. Mutta sen päälle tarvitaan AOP dependency, näin:

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
 <version>${spring.boot.version}</version>
 </dependency>

Ja nyt ollaan jo aika pitkällä 😉 Hyvä huomata että Spring Boot on aika herkkä sille mitä kaikkea automatiikkaa olet kytkenyt päälle, itse olen saanut AOP featuret vahingossa joskus pois päältä esim. väärillä annotaatiolla Application/Configuration-luokassa. Mutta yleisin syy silti AOP toimimattomuuteen on rikkinäiset pointcutit. Joten testataanpa ensin iisisti mahdollisimman lavealla interceptorilla:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AuditAOP {
@After("execution(* *.*(..))")
 public void logServiceAccess(JoinPoint joinPoint) {
 System.out.println("AuditAOP: Completed : " + joinPoint);
 }
}

Jep, tuossa on AspectJ joinpoint joka tarraa kiinni ihan kaikkeen, niin kauan kuin mennään Springin läpi eli kohteena on Spring-manageroitu komponentti.Tässä kohtaa vain logataan joinpoint. Hyvä katsoa toimiiko, loggaako. Jos loggaa, erinomaista. Tarvittaessa Joinpointilta voidaan louhia lisääkin tietoja:

@After("execution(* *.*(..))")
public void logServiceAccess(JoinPoint joinPoint) {
  System.out.println("AuditAOP: Completed : " + joinPoint);
  Signature signature = joinPoint.getSignature();
  String methodName = signature.getName();
  String arguments = Arrays.toString(joinPoint.getArgs());
  System.out.println("Method: " + methodName + " with arguments "
    + arguments +  " has just been called");
}

Toimiiko tämäkin? Loistavaa. Nyt on sitten aika siirtyä itse pihviin. Voit nimittäin tehdä tästä annotaatiovetoista, annotaatiota voi käyttää halusi mukaan joko kääntämään auditin pois päältä, tai päälle. Itse tykkäisin että on annotaatio audit, jolla voin valita auditoitavan eventin nimen. Sen käyttö tapahtuisi näin:

@Component
class JokuRandomiSpringService {
  @Audit("ACCOUNT_DELETE")
  public void poistaPirunTarkeeTili() {
    // Jotain ihan järkyn fiksua koodia tähän kohtaan
  }
}

Jeah, aika mukava? Joten tehdään tämmöinen:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Audit {
  String value() default "";
}

Sitten siihen todelliseen taikuuteen. Eli miten aop interceptor aktivoituu vain annotaation havaitessaan? Näin:

@Before("execution(* *.*(..)) && @annotation(audit)")
public void logServiceAccess(JoinPoint joinPoint, Audit audit) {
  String event = audit.value();
  if ("".equals(event)) {
    event = joinPoint.getSignature().getName();
  }
  Principal user = (Principal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  String remoteAddress = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
    .getRequest().getRemoteAddr();
  auditEventService.createEvent(new AuditEventEntity(user.getName(), event, remoteAddress));
}

Huomaa myös että annotaation mäpätään joinpointtiin muuttujanimellä, ja tulee parametriksi interceptorille. Annotaation sisältä voidaan kaivaa halutut parametrit, tässä tapauksessa value, joka olisi audit eventin nimi.

Tämän esimerkin koodi menee vähän pidemmälle. Jos nimeä ei ole annettu, oletusnimi on kutsuttavan metodin nimi, eli value on valinnainen. Lisäksi kaivellaan käyttäjän identiteetti security contextista, ja ip-osoite request contextista. Huom! Esitetty malli ei ole yksikkötestiystävällisintä, voi olla että on elegantimpiakin tapoja injektoida nämä contextit.

Mitäs vielä? Tuossa koodissa oleva auditEventService on ihan tavallinen Spring komponentti/service, jossa on yksi rivi koodia jolla talletetaan audit eventti kantaan, sopivaan tauluun, jossa on halutut sarakkeet. Samoin auditevententity on yksinkertaisesti Entity Object, jossa on kentät username, event, remoteaddress – id ja aikaleima ovat autogeneroituja. Lisätään tietoa sen mukaan mikä on paranoian taso.

Joskus tuli tehtyä sellaistakin järjestelmää jossa haluttiin mahdollisimman iisi tietoturva – yleinen tietoturvan sääntö kun on, että mitä tiukemmin kiristää käyttäjille näkyvää tietoturvaa, ja vaikeuttaa arkea, sitä luovemmin opitaan kiertämään se tietoturva, luoden usein jopa turvattomampi ratkaisu kuin alunperin (salasanoja muistilapuilla, sama salasana kaikkialla, kulunvalvottujen ovien availu kohteliaisuudesta, jne). Hyviä tietoturvaratkaisuja ovat eritoten ne systeemit joissa tietoturva ei hankaloita käyttäjän arkea. (Tämän takia salasanat ovat helvetistä)

Esim. tarkka auditointi tarkkojen roolilokeroiden sijasta, kaikki saavat tehdä lähes kaikkea mutta kaikesta jää jäljet. Tai jos haluaa niin molemmat päälle. Riippuu ympäristöstä mikä on fiksua, tarpeellista tai lainsäädännön sanelemaa.

Hyvä huomata että tämän tason auditointi ei loggaa virheitä jotka johtivat keskeytymiseen jo aiemmin ketjussa, eli jos haluat vielä laveammalla siveltimellä, voit täydentää esim. servlet tason filttereillä ja virhekäsittelijöillä.

 

JSON serialisointi: Pois Circular Reference – manalasta

En tiedä onko tuttu tilanne, mutta itselleni harmillisen usein tavattu. Otetaan oliorakenne, esim. Order -> OrderItem, eli tilaus ja tilausrivejä. Rakenne menisi näin:

class Order {

  List<OrderItem> orderItems;

}

class OrderItem {}

Tähän asti kaikki loistavasti. Nyt kuitenkin on useita syitä miksi haluaisimme myös linkin OrderItemistä Order-luokkaan, esim. jos item-riveissä on assosiaatioita muuallekin ja niistä pitäisi näppärästi päästä header-tietoihin kiinni. Tai jos käyttää serialisointiin JPA-tekniikkaa eikä halua tehdä turhia välitauluja (One-to-Many assosiaatiossa tieto assosiaatiosta on many-päässä eli OrderItem luokassa)

Pysyitkö mukana? Hyvä, muutamme siis rakenteen tällaiseksi:

class Order {

  long id;
  List<OrderItem> orderItems;
}

class OrderItem {
  long id;
  Order order;
}

Ja tästä päästääkin syklisten referenssien helvettiin. Tämä on oliorakenteena ihan kelvollinen ja mahdollistaa juuri edellämainitun navigoinnin molempiin suuntiin (bidirectional one-to-many association). Tähän voisi iloisesti läpsäyttää JPA annotaatiot ja antaa sen valua kantaan ja kannasta triviaalilla koodilla.

Ongelmia tulee siinä vaiheessa kun haluttaisiin serialisoida tätä rakennetta johonkin hierarkiseen puurakenteeseen, esim. XML tai JSON. Ongelma johtuu siitä että dynaaminen sarjallistaja, esim. JAXB tai Jackson, käy läpi olion ominaisuudet yksi kerrallaan, ja kutsuu gettereitä, kerää tiedot, ja muuttaa ne tekstimuotoiseksi siirtokelpoiseksi dataksi. Siinä käy siis näin:

  1. Tallennetaan order, hienoa. Order on oliorakenne, joka sisältää orderItems listan, käydään se läpi
  2. Käsitellään jokainen orderItem vuorollaan. OrderItem on oliorakenne, joka sisältää viittauksen Order olioon
  3. Käsitellään jokainen viitattu Order vuorollaan. Order on oliorakenne joka sisältää OrderItems listan

Ja niin edelleen. Ikiliikkuja on keksitty. Tästähän saa palkakseen yleensä jonkun hienon kaatumisen ja cyclic/circlar reference errorin. Tai jos hauskasti käy, kone puuskuttaa hetken ja antaa stack overflow errorin tai out of memory errorin.

Mitä sitten on tehtävissä? Tämä artikkeli koskee JSON vaihtoehtoa, jos olet vielä XML parissa, olet pysyvästi helvetissä vailla poispääsyä, pahoittelen.

Jos käytät tätä esim. Jacksonin puitteissa, vaikkapa REST-rajapinnassa, ratkaisutapoja on muutama (tosiasiassa osa näistä sopii XML hommiinkin, jos edellisestä kohdasta tuli paha mieli):

  1. Katkaise syklinen referenssiketju merkkaamalla jommassakummassa päässä referenssi ei-serialisoitavaksi. Tapoja tähän on monia, Jackson taitaa tukea esim. Javan transient avainsanaa, @JsonIgnore annotaatiota, ja luokkatasolla voi myös listata ohitettavat kentät @JsonIgnoreProperties-annotaatiolla
  2. On myös mahdollista merkitä master-dependant suhde Jackson annotaatioilla @JsonManagedReference ja @JsonBackReference
  3. Tehdään aina value/transfer object johon normalisoidaan kulloinkin tarvittavat tiedot
  4. Myös voi merkitä identity-kentät Jackson annotaatioilla, @JsonIdentityInfo kertoo mikä kenttä on uniikki avain, jonka jälkeen serialisoinnissa voidaan viitata vain id arvoon, ei käydä läpi koko sisältöä.
  5. @JsonView annotaation käyttö näkymien muodostamiseen

Kahdessa ensimmäisessä kohdassa on yksi ongelma: Ne eivät salli talsimista edestakaisin, vaan vain yhteen suuntaan. Mutta ne ratkaisevat syklisen referenssipulman katkaisemalla rekursioketjun, eli sopivat moneen tilanteeseen. Kolmas kohta sisältää potentiaalisesti hurjan paljon virhealtista käsityötä ja myöhemmin ylläptoa ja en ole ollut koskaan kummankaan suuri fani. Neljäs kohta generoi kauheaa huttua serialisoinnista, ja en ole vielä löytänyt sille hyötykäyttöä. Neljäs kohta on näistä oma suosikkini. Se voisi olla vielä parempikin mutta sillä ainakin pääsee alkuun. Ja uusin Spring, Spring Boot, ja JAX-RS yhdistelmä tukee näitä ihanasti.

Homma toimi näin: Merkataan @JsonView annotaatiolla ne kentät, joita halutaan ehdollisesti serialisoida tai olla serialisoitamatta. Parametrina tulee tyypin nimi, joka on yleensä Java rajapinta. Esim. näin:

class View {

interface GimmeOrderRows {}

interface GimmeOrderHeader {}

}

Nyt voidaan muokata aiempia koodeja näin:

class Order {
  
  long id;
  
  @JsonView(View.GimmeOrderRows.class)
  List<OrderItem> orderItems;

}

class OrderItem {
  long id;

  @JsonView(View.GimmeOrderHeader.class)
  Order order;

}

Nyt pystyt hakemaan assosiaatiot on-demand periaatteisesti, eli voit navigoida kummasta päästä vaan. Jos et anna JsonView-annotaatiota, oletuksena saat kaiken. Heti jos annat yhdenkin @JsonView annotaation, saat kaikki kentät joihin se täsmää tai joita ei ole millään JsonView annotaatiolla varustettu. Eli jos meillä olisi tämän näköinen jax-rs palvelu…

@GET
@Path("order")
@JsonView(View.GimmeOrderRows.class)
public Order fetchOrderWithItems(long id) {
  return orderRepository.getOne(id);
}

… niin syklisen referenssin peikko pysyisi piilossa. Koska Jackson serialisoisi Orderin, ja sen sisältämät OrderItemit id-arvoineen, mutta ei seuraisi enää polkua niiden sisältämiin Order-instansseihin.

Vastaavasti nyt voisi huoletta hakea vaikkapa yhden OrderItem instanssin OrderHeadereineen ilman syklisiä referenssejä:

@GET
@Path("orderitem")
@JsonView(View.GimmeOrderHeader.class)
public OrderItem fetchOrderItemWithOrder(long id) {
  return orderItemRepository.getOne(id);
}

Samalla tekniikalla voi noutaa ehdollisesti esim. salasanatietoja, tai binäärisisältöä, tai muuten vain pitkiä kenttiä. Yhdessä kohtaa voi olla vain yksi JsonView-parametri, mutta koska niillä voi olla perintähierarkioita jotka tunnistetaan, rajoitus ei ole paha. On myös mahdollista säätää sellainen oletus, että mitään kenttää ei palauteta elleivät jsonviewt täsmää – ei myöskään niitä joista annotaatio puuttuu kokonaan.

Nyt kun vielä saisi Javaan luontevan suorastaan sisäänrakennetun JSON rajapinnan….

Mockito – ja testataan ihan mitä vain isolaatiossa

Olen joskus aiemmin kirjaillut juttuja siitä miten esim. EJB komponentteja voidaan yksikkötestata – ja käytellyt välineenä EasyMockia. Testaus isolaatiossa, eristyksissä muusta on yksi tavoiteltava asia koska näin tehdyt testit voidaan ajaa salamannopeasti ja jatkuvasti, saaden palautetta muutoksista koko ajan.

Mockito-JUnit

Sanoinko testit? Köhköh, tarkoitin tiestysti speksit. Isolaatiotestit mahdollistavat TDD ja BDD disipliinien soveltamisen haltuessaan, tai itseni suosima Spec-While-Writing tapa joka on oikeastaan ihan samaa kuin em mutta rennommin rantein. Fanaattiset isolaatiotestithän eivät nimenomaan ole testeinä kovinkaan hyviä usein, mutta spekseinä ne ovat aivan verrattomia, ja monimutkaiselle logiikalle lähes ilmaiseksi sivuvaikutuksena syntyvät testitkään eivät ole ihan huono asia.

Sanoinko ilmaiseksi? Hups. Kyllähän testien kirjoittamiseen aikaa kuluu, etenkin jos niitä säveltää etupainotteisesti ennen koodipyrähdyksiä. Etenkin isolaation saavuttaminen on aika ajoin hankalaa. Viime aikoina olen löytänyt uutta apua tälle saralle: Kuten otsikko jo spoilasi, Mockito framework on varsin kiva, etenkin Spring Bootin kanssa. Vastaavaan tapaan Javascript-puolella käyttelen nykyään Jasmine mockeja, mutta tämä ei ole artikkeli niistä vaan Mockitosta. Tässä siis vähän maistiaisia siitä miltä maistuu yksikkötestaus mockitolla ja mitä hyöytyä siitä saa.

Mockito testi voi olla tavallinen JUnit tai TestNG testi, eli aloitellaan tähän tapaan:

public class UserResourceTest {

  @Test
  public void changePasswordShouldSucceedWithValidParameters() 
    throws Exception {
      fail("Not implemented yet");
  }

}

Testatessa on tietysti mukavaa olla se mitä testataan. Perinteisesti sen voi vaikka instansioida nimellä sut – system under test, Mockitolla homma hoituu näin:

@InjectMocks
private UserResource sut;

Jotta tuo tekee jotain on tietysti syytä ajaa tämä Mockiton kera. Lisätäänpä siis testiluokan alkuun:

@RunWith(MockitoJUnitRunner.class)

Nyt tapahtuu jo jotain. Tässä testattavassa luokassa on kuitenkin paljon riippuvuuksia, jäsenmuuttujissa on yhtä ja toista jota Spring injektoi ajon aikana, mutta jotka aiheuttavat nullpointer exceptioneitä ellei niitä ole alustettu. Olen joskus injektoinut näitä riippuvuuksia reflectionin avulla, joskus setXXX-metodeita tekemällä. Mockitolla homma sujuu kokonaisuudessaan näin:

@RunWith(MockitoJUnitRunner.class)
public class UserResourceTest {

    @Mock
    private Utils utils;

    @Mock
    private UserDetailsService userDetailsServiceMock;

    @InjectMocks
    private UserResource sut;

Eli, kun alan kirjoittelemaan omaa testiäni, kaikki kolme edellämainittua on instansioitu Mockiton toimesta, mock objekteille on mock toteutukset, jotka esim. palauttavat null/eivät tee mitään. Ja mikä parasta, mock objektit on injektoitu sut objektin sisään, privaatteihin jäsenmuuttuja-kenttiin. Ensin yritetään samalla tyypillä, sitten samalla nimellä.

Mockiton oma dokumentaatio InjectMocks-metodista sanoo näin:

Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order and as described below. If any of the following strategy fail, then Mockito won’t report failure; i.e. you will have to provide dependencies yourself.

Eli tarkkana tämän kanssa.

Mitä muuta hienoa? No Mockitossa on verrattain helppoa opettaa mockit palauttamaan sopivia arvoja, esim:

UserEntity currentUser = new UserEntity();
UserAuthentication userAuthentication = new UserAuthentication(currentUser);
when(utils.getAuthentication()).thenReturn(userAuthentication);

Ja sitten vain paukuttamaan omaa suttia, joka taas kutsuu tuota utils.getAuthentication() metodia. Jos haluat tarkistaa, kutsuiko, sen voi tarkistaa verify-kutsulla:

verify(utils).getAuthentication();

Eli, Mockito vaikuttaisi hyvältä. Pääidea on tuoda testauksen hintaa alas, ja näin saada siitä yhtä luontevaa kuin hengittäminen. Tai luontevampaa.

 

Spring Boot, Java 8, AngularJS, ja Heroku pilvipalvelu

Tuli ajankohtaiseksi puskea Spring Boot sovellus Herokun alle tarjoiltavaksi. Heroku on siitä vänskä palveluntarjoaja että sieltä saa ilmaisella jäsenyystasolla jo jonkun verran prosessoriaikaa, ja resursseja – samoin kuin Google App Enginestä (ainakin aikoinaan).

Homma ei mennyt ihan heittämällä, joten kirjailen taas vähän kokemuksia ja säätöjä tähän. Omassa tapauksessani muutama kiemura johtui siitä että olin yhdistänyt sekä Javaa että Angularia samaan pakettiin – ja Heroku parka meni sekaisin sen suhteen mitä pitäisi buildata ja miten. Muuten Spring Boot + Heroku on taivaassa tehty liitos, mahtava yhdistelmä!

Mitä tarvitaan/esivalmistelut:

– Yksi Spring Boot sovellus, jossa esim. In-Memory tietokanta ja AngularJS UI sisään paketoituna – ei niin välttämätöntä, mikä hyvänsä Spring Boot käy lopulta, mutta kannat vaativat toki esim extratyötä

– Projekti pitäisi olla git versioitu, se tekee heroku käytön suorastaan naurettavan helpoksi

– Tietty Heroku free tier tunnukset tarvitaan, ja ne tulisi olla tiedossa

– Heroku Toolbelt asennettuna omalle käyttöjärjestelmällesi

– Pientä lisäjännää: Haluat ehkä lisätä bower_components tyyppiset vendor javascript kirjastot gittiin, koska jos buildaat projektin Java builderillä, mitään automatiikkaa javascript/angular puolelle ei ole, eli sen mitä lähetät Herokuun tulisi olla joko Mavenin kautta buildattua automaattisesti, tai sitten esirouskuteltua staattista sisältöä. bower_components pusku gittiin on muutenkin paras käytäntö koska ulkomaan repositoryt tai interweb eivät noin muutenkaan ole aina buildatessa saatavana, joten näillä mennään!

Ja sitten touhuamaan. Aluksi kannattaa lisätä projektiin muutama tiedosto, ellei niitä jo ennestään löydy. Tarvitset settings.properties tiedoston, Procfile tiedoston, ja application.properties tiedoston. Näistä kaksi ensimmäistä liittyy Heroku-alustaan, ja viimeinen on Spring Boot standarditiedosto – joka on luontevaa tallettaa /config kansioon.

Tässä niiden sisältö:

  • /settings.properties (asettaa JDK versioksi 8)
    • system.properties=1.8
  • /Procfile (kertoo mikä sovellus ja mistä käynnistyy)
    • web: java $JAVA_OPTS -jar target/*.jar
  • /config/application.properties (määrittää käyttäämän serveriporttina PORT ympäristömuuttujaa, tai 8080 jos sitä ei löydy)
    • server.port: ${port:8080}

Näistä tosiaan application.properties saattaa olla jo tehtynä, ja voi olla muussa kansiossakin, eli ole tarkkana. Se on Spring Boot perustiedostoja.

Nyt voidaan alkaa touhuamaan. Lisää kaikki edellämainitut git repositoryyn, commitoi, ja jatka näillä Heroku komennoilla:

  • heroku login (tänne sitten tunnareita ja salasanaa Herokun mukaan)
  • heroku create (luo sovelluksen, ja käytännössä määrittelee uuden remote repositoryn nimeltä heroku)
  • heroku buildpack:set https://github.com/heroku/heroku-buildpack-java (asettaa builderiksi juuri Java, eikä esim. NodeJS tms)
  • heroku ps:scale web=1 (asettaa käyttöön yhden dynon, ellei ole jo – nyt on resursseja ajamaan sovellusta)

Voiton puolella jo! Nyt voidaan puskea sovellus herokuun, tähän tapaan:

git push heroku master

Seuraa tarkkaan ilmoituksia mitä näkyy – etenkin jos virheilmoituksia ilmenee. Jos kaikki sujui hyvin, voit nyt avata sovelluksen:

heroku open

Toimiko? Onnittelut! Jos ei pelannut, tässä muutama debugging niksi:

  • Tarkista Heroku logit komennolla: heroku logs –tail
  • Tarkista web dynojen status komennolla: heroku ps

Huomioi että jos dynoja on vain yksi käynnissä, Heroku nukuttaa sen jos tuntiin ei ole ollut käyttöä. Näin ollen se käynnistyy hitaanlaisesti kun sitä taas tarvitaan. Tämän voi kiertää laittamalla kaksi dynoa – tai enemmän:

heroku ps:scale web=2

Huomaa että free tier antaa vain 750 dyno-tuntia, joten kahden dynon voimalla ei piisaa tehoja kuin puoleen kuukauteen ilmaistasolla. Muista myös sammutella dynot kun et niitä enää tarvitse, komennolla:

heroku ps:scale web=0

Näin! Spring Boottia voi siis tunkea Raspberry Pi kakkoseen, tai pilveen. Vaikuttaa aika toimivalta alustalta nykymuodossaan.

Tässä hyvä linkki myös artikkeliin jossa käsitellään Heroku multi-buildpack strategiaa – eli miten voit buildata sekä Java että JavaScript puolen erikseen sillä suunnalla.

https://devcenter.heroku.com/articles/using-grunt-with-java-and-maven-to-automate-javascript-tasks

Itse käytin joskus aikanaan Eirslett Maven-Grunt pluginia, joka osaa imaista node, npm, bower jne työkalustot paikallisiksi ja käyttää niitä projektin alta – mutta se oli aika monimutkainen ja kömpelökin ratkaisu. Monella on tarpeista riippuen ollut menestystä myös ihan exec-maven pluginin kanssa javascript prosessoinnissa, mutta sen ongelmana on että node pitäisi olla koneeseen asennettuna etukäteen.

Ei ole isoisäsi Spring myöskään enää!

Jep, Spring Framework on tosiaan uudistunut rajusti. Ajattelin jatkaa sepustusta siitä mitä Spring Boot projektilla voi tehdä. Tein jo aiemmin perus web projektin jossa on RESTful web service. Tällä kertaa sukelletaan Spring data pakettiin.

Lisää projektiisi riippuvuudet:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>org.hsqldb</groupId>
  <artifactId>hsqldb</artifactId>
  <scope>runtime</scope>
</dependency>

Jep, tässä tuli siis Hibernate, Spring Data JPA, ja Spring ORMS paketit mukana. Ja testikannaksi embedded hsqldb – voi tietysti olla myös derby, tai ihan tuotantokantoja tms. Mutta jostan hyvä aloitella.

Lisätään Entity Object. Spring ei vaadi persistence.xml tiedostoa, vaan oletuksena kaikki paketit pääkonfiguraatiotiedostos alla skannaillaan (Se jossa on @EnableAutoCofiguration) – huomaa tässä sudenkuoppa – jos pistät entityt esim. tuon paketin rinnalle tai yläpuolelle, niitä ei huomioida. Tästä koodista on jätetty paketti pois:

 

@Entity
public class City implements Serializable {

  @Id
  @GeneratedValue
  private Long id;

  @Column(nullable = false)
  private String name;

  @Column(nullable = false)
  private String country;

  protected City() {
    // no-args constructor required by JPA spec
    // this one is protected since it shouldn't be used directly
  }
  public City(String name, String country) {
    this.name = name;
    this.country = country;
  }
  public String getName() {
    return this.name;
  }
  public String getCountry() {
    return this.country;
  }
}

Jep, tätä ei tarvitse rekisteröidä mihinkään. Jos siinä on @MappedSuperclass, @Entity, tai @Embeddable, se huomioidaan automaattisesti. Tätähän pitäisi pystyä vähän manipuloimaan, tallettelemaan kantaan jne. Tehdään rajapinta joka perii CrudRepository kantaluokan:

public interface CityRepository extends CrudRepository<City, Long> {
}

Jep, tosiaan rajapinta. Spring taikoo sen sisään crud metodit automaattisesti. Näin ollen muutetaan meidän Example-luokan REST service esim. tällaiseksi:

@RestController
public class Example {
  @Autowired CityRepository cityRepo;
  @RequestMapping("/")
  String home() {
    City c = new City("Helsinki","Finland");
    cityRepo.save(c);
    return "Hello World2! " + cityRepo.findAll();
  }
}

 

Tuossa kutsutaan cityRepo-luokan (dynaamisesti generoitu rajapinnan pohjalta) save() ja findAll() funktioita jotka ovat automaattisesti olemassa.

No niin, nyt pitäisi vielä luoda tietokanta, datasource, tunnukset, salasanat, sun muut. Paitsi että ei. Yllätyitkö? Spring Boot olettaa että näihin muistinvaraisiin temp kantoihin ei tarvitse luoda datasourceja, tai määritellä sen enempiä tietoja. Riittää että määrittelit riippuvuuden aiemmin. Toisin sanoen voit heti testata sovellusta kunhan olet pudottanut sen sopivaan tomcat serveriin. REST palvelun pitäisi nyt joka kerralla luoda uusi City-olio ja listata se ruudulla.

Aika ketterää. Paljon olisi taas mitä voisi nyt säätää kun alkuun päästiin:

Spring Data antaa mahdollisuuden määritellä lisää kyselyitä suoraan rajapintaan, näin:

public interface CityRepository extends Repository<City, Long> {

    Page<City> findAll(Pageable pageable);

    City findByNameAndCountryAllIgnoringCase(String name, String country);

}

Kun haluat siirtyä testikannasta pysyvämpään testikantaan tai tuotantoon, voit määritellä sen datasource-määritykset application.properties tiedostossa, esim. projektin juuressa:

spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driverClassName=com.mysql.jdbc.Driver

Muista toki myös tuoda se dependencynä pom.xml tiedostossa – jotta saat ajuritkin mukaan.

Kanta luodaan automaattisesti vain H2, HSQL, ja Derby kannalle. Jos haluat sen muillekin, aseta em application.properties tiedostossa ominaisuus päälle:

spring.jpa.hibernate.ddl-auto=create-drop

Muutenkin voit säätää näitä jpa-määrityksiä samaan tapaan:

spring.jpa.database: H2
spring.jpa.show-sql: true

Lisäksi jos pistät luokkapolun juureen tiedoston nimeltä import.sql, sen sisältämät lauseet suoritetaan sovelluksen käynnistyessä – loistava tapa ladata init/testidata sisään. Sen ohella Spring imaisee tiedostot schema.sql, data.sql, ja esim. schema-${platform}.sql ja data-${platform}.sql, ottaen arvonsa spring.datasource.platform muuttujasta.

Spring Boot + Maven = Anna valaistunutta kenkää!

Oh dear, mahtaako tuo sanaleikki aueta kenellekään? Ainakin syytä tarkistaa wikipedia 😉

Kaksi lempivimpaintani, Spring ja Maven, ovat monessa projektissa pohjana. Spring on menossa suuntaan jossa pyritään helpottamaan sen aloitusta ja projekti on nimeltään Spring boot. Pistän tähän taas muistiinpanoja kokeiluistani.

Disclaimer: Vaikka Spring on monen mielestä legacymoskaa ja puhdas Java EE vaihteeksi pop, se tarjoaa edelleenkin muutaman houkuttavan argumentin ketterän projektin kannalta – kuten kyky asentaa softa vaikka tomcat palvelimille (joista löytyy esim. Amazon klusterista helpot valmisasennukset) – kyky ymmärtää ja hallita mekanismeja eri tavalla kuin sovelluspalvelinten mystiset automatiikat toimivat – ja ennen kaikkea mahtava tuki hollywood principlelle ja aop:lle – joka taas tarjoaa hienosti isolaatiota ja sitä myöden yksikkötestattavuutta, sitä myöden nopeita testejä, sitä myöden kehittäjätestejä ja testikattavuutta – sitä myöden speksejä ja toimintavarmuutta – sitä myöden business arvoa. Eli jeah, ei Springin hautajaisia vielä ole pidetty.

Vaihe 1: Tarvitaan kansio projektille, ja pom.xml. Tämmöinen:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
<groupId>com.whatever</groupId>
 <artifactId>myproject</artifactId>
 <version>0.0.1-SNAPSHOT</version>
<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>1.1.0.BUILD-SNAPSHOT</version>
 </parent>
 <!-- (you don't need this if you are using a .RELEASE version) -->
<repositories>
 <repository>
 <id>spring-snapshots</id>
 <url>http://repo.spring.io/snapshot</url>
 <snapshots>
 <enabled>true</enabled>
 </snapshots>
 </repository>
 <repository>
 <id>spring-milestones</id>
 <url>http://repo.spring.io/milestone</url>
 </repository>
 </repositories>
 <pluginRepositories>
 <pluginRepository>
 <id>spring-snapshots</id>
 <url>http://repo.spring.io/snapshot</url>
 </pluginRepository>
 <pluginRepository>
 <id>spring-milestones</id>
 <url>http://repo.spring.io/milestone</url>
 </pluginRepository>
 </pluginRepositories>
</project>

 

Jep, ja vertavuotavalla terällä tässä mennään. Eli bootin epävirallisella snapshot versiolla joka on liikkeessä. Mutta mielummin terällä kuin terän alla.

Päräytetäänpä tässä vaiheessa mvn package. Hmm tottakai esivaatimuksena Java asennettuna (8 tottakai), ja Maven, ja molemmat komentopolussa. Tai voit avata tämän projektin vaikka Netbeans Open toiminnolla, jossa pom tiedostoa onkin mukavampi editoida.

Tämä ei vielä paljoa tee. Lisätään web dependency:

<dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 </dependencies>

Aja uudelleen mvn package. Nyt paukkuu vähän enemmän latauksia – verkosta ladataan perus-spring-web projektin tarvittavat riippuvuudet. Niitä on paljon (Spring jar helvetti). Ja hups, tässä tuli mukana myös Tomcat.

Päräytetään REST palvelu paikalleen. Naputa tai kopioi tämä maven lähdekoodikansioon (src/main/java – joudut varmaan luomaan tämän kansiorakenteen, koska boot ei sitä automaattisesti tee. Ja Mavenhän vaatii Javan olevan juuri tuon polun alla. Saa tehdä paketin jos erityisesti kutkuttaa.):

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@RestController
@EnableAutoConfiguration
public class Example {

    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Example.class, args);
    }

}

Nyt sitten nopeaan testaukseen. Tarkista ensin ettei ole käynnissä palvelimia portissa 8080 (sammuta Glassfish 😉

Aja seuraava komento:

mvn spring-boot:run

Tämä potkaisee käyntiin tomcatin ja voit kokeilla sitä selaimella osoitteessa http://localhost:8080/

Toimiiko? Mahtavaa, en olisi uskonut. REST servicehän se sieltä kurkkii.

Serverin voi ajaa alas Ctrl+C

Paketointi

Nyt voisi paukauttaa koko roskan-tomcatteineen kaikkineen boot .jar pakettiin. Lisää dependencies-osan alle tällainen plugin:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Ja aja komentoriviltä:

maven package

Tadaa! target-kansioon syntyy uusi .jar joka sisältää tarvittavat riippuvuudet, voit ajaa sen komentoriviltä näin:

java -jar target/myproject-0.0.1-SNAPSHOT.jar

Kun kyllästyt leikkimään, Ctrl+C lopettaa taas.

Spring Boot Java taso on oletuksena 6. Voisi olla kiva käytellä Java 8 uusia piirteitä. Se onnistuu näin pom.xml:ssä:

<properties>
    <java.version>1.8</java.version>
</properties>

Ja jos haluat käyttää myös Tomcat 8 versiota 7:n sijasta:

 <properties>
 <java.version>1.8</java.version>
 <tomcat.version>8.0.3</tomcat.version>
 </properties>

Yksi suositeltava parannus on jakaa initialisointi ja toiminnot eri tiedostoihin, voit tehdä esim. Application-tiedoston sovelluksesi pakettihierarkiaan tähän tapaan:

package com.myorg.myproj;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

}

Ja näin tuo Example-luokka (joka olisi hyvä olla pakettihierarkiassa em. Application luokan alla) näyttäisi tältä:

package com.myorg.myproj.hello;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@RestController
public class Example {

  @RequestMapping("/")
   String home() {
    return "Hello World2!";
  }

}

Näin on helpompaa lisäillä uusia palveluita.

 

Yhden .jar paketin sovellus on melko kutkuttava, mutta käytännössä on tilanteita joissa olisi myös kiva saada .war ulos, tässä siihen vinkki:

Lisää pom.xml tiedostoon:

<packaging>war</packaging>

Nyt on syytä samantien muuttaa tomcat riippuvuuden scope moodiin ’provided’ – muuten se paketoidaan myös .war paketin sisään toiseen kertaan. Jos sinulla ei ole tätä riippuvuutta vielä, sen voi silti lisätä provided-scopella dependencies-kohtaan:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

Tässä kohtaa voisit tehdä clean&build ja tiputella tomcat webapps-kansioon, mutta eipä toimi vielä. Alunperäisen ohjeen luokka nimittäin käytti main-metodia initialisointiin, joka sopii kyllä .jar pakettiin hienosti – mutta jota ei enää kukaan kutsu.

Joten lisäämme Servlet-luokan joka perii SpringBootServletInitializer-luokan, ja nykäisee Application-luokan taas käyntiin:

package com.whatever;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

public class ApplicationInitializer extends SpringBootServletInitializer {

  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(Application.class);
  }
}

Ja sitten vain mvn clean package, ja target-kansioon putkahtaa geneerinen .war jonka voi asentaa eri palvelimille.

Aika rouheita lisäosia ovat mm. JPA, Spring Data, MongoDB moduulit..

Ja enkunkielinen lähde – kannattaa ehdottomasti lukea esim. pykälä 13.2 koskien pakettien ja luokkien järjestelyn parhaita käytäntöjä.

http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#getting-started-installing-spring-boot