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….

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.

Spring 4 + Spring Boot + Jax-rs + Jersey + Jackson + JsonObject toimimaan

Sain hiukan takkua yrittäessäni konvertoida Java EE projektia toimimaan Springin päällä standardin Jax-RS rajapinnan avulla sensijaan että olisin käyttänyt Springin omia.

Tässä toimivaksi testattu dependencies-osa pom.xml tiedostosta, jossa riippuvuudet paikallaan. Pohjalla on Spring boot, joka on jo otettu parent-osion avulla mukaan (kuten aiemmassa blogissani näytin):

Huomaa että riippuvuudet joissa ei ole versionumeroa tulevat Spring boot perinnässä dependencyManagement osan kautta, esim. log4j, mysql, jne. Tässä on myös muutama extra riippuvuus JAX-RS:n ohella sekä mysql ajurit.

jersey.version property on asetettu arvoon 2.8 ylempänä properties-osiossa, jota tässä ei ole näytetty.

<dependencies>

  <!-- Let's get this started with Spring Boot for web apps -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
 
  <!-- Do not include tomcat server libs, as we package .war -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
  </dependency> 
 
  <!-- Include Spring Data, jpa, and derby for database testing -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>


  <!-- Log4j support so we can get some log output -->
  <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
  </dependency> 

  <!-- Mysql database -->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>

  <!-- Jersey JAX-RS 2.0 support -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-jersey</artifactId>
    <version>1.0.2.BUILD-SNAPSHOT</version>
  </dependency> 

  <dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.json</artifactId>
    <version>1.0.4</version>
    <scope>runtime</scope>
  </dependency>

  <dependency>
    <groupId>javax.json</groupId>
    <artifactId>javax.json-api</artifactId>
    <version>1.0</version>
  </dependency>

  <dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-server</artifactId>
    <version>${jersey.version}</version>
  </dependency> 

  <dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-common</artifactId>
    <version>${jersey.version}</version>
  </dependency> 

  <dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>${jersey.version}</version>
  </dependency> 
 
 <dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-processing</artifactId>
    <version>${jersey.version}</version>
  </dependency> 
 
  <dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>${jersey.version}</version>
  </dependency> 

  <dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-spring3</artifactId>
    <version>${jersey.version}</version>
  </dependency> 

  <!-- JAXB-to-JSON Serialization support -->
  <dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>${jersey.version}</version>
  </dependency> 
 
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.4.0-rc3</version>
  </dependency> 

  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.4.0-rc3</version>
  </dependency> 
 
  <!-- Using restassured for API testing -->
  <dependency>
    <groupId>com.jayway.restassured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>2.3.1</version>
    <scope>test</scope>
  </dependency>
 </dependencies>

Ylläolevassa on tuotu myös jersey-kirjastoja uudestaan vaikka osa niistä tuleekin spring-jersey ja spring-boot-jersey mukana. Mutta näin saadaan tuoreimmat versiot käyttöön ja versio helposti säädetyksi kerralla. Tässä voisi myös käytellä dependencyManagement-osiota versioiden hallintaan.

 

 

 

Maven Elastic Beanstalk Plugin

Hiljattain kirjoittelin Spring Boot projektista, jolla on helppo saada aikaan web sovellus joka tukee Java EE teknologioita mutta pyörii jopa Tomcat serverillä. Yksi hyöty mikä tästä saavutetaan on helppo mahdollisuus tuikata paketti vaikka Elastic Beanstalk-pilveen.

Tässä on Maven plug-in joka hoitaa homman (lisää build-osioon pom.xml:ssä):

<plugin>
 <groupId>br.com.ingenieux</groupId>
 <artifactId>beanstalk-maven-plugin</artifactId>
 <version>1.3.1</version>
 <configuration>
 <s3Bucket>elasticbeanstalk-eu-west-1-11111111111</s3Bucket>
 <s3Key>${project.artifactId}/${project.build.finalName}-${maven.build.timestamp}.war</s3Key>
 <region>eu-west-1</region>
 <!-- will save as s3://[bucket name]/[artifactId]/artifactId-version-TIMESTAMP.war -->
 <autoCreateApplication>true</autoCreateApplication>
 <applicationName>${project.artifactId}App</applicationName>
 <environmentName>${project.artifactId}Env</environmentName>
 <cnamePrefix>${project.artifactId}app</cnamePrefix>
 <versionLabel>${project.version}-${buildNumber}</versionLabel>
 </configuration>
 </plugin>

Luonnollisesti s3 bucket id:t ja regionit kuntoon tuossa. Ja näin aukeaa uusia komentoja:

mvn beanstalk:upload-source-bundle beanstalk:create-application-version beanstalk:create-environment

Ylläoleva luo beanstalkin avulla Amazon AWS klusteriin uuden web serverin pyörittämään softaa.

Tämä taas päivittää softan uuteen versioon, mukava yhdistää esim. mvn clean install käskyihin:

mvn beanstalk:upload-source-bundle beanstalk:create-application-version beanstalk:update-environment

Huom! Nämä kaverit kaipaavat myös autentikointia, se taas onnistuu kun tekee settings.xml tiedostoon .m2 kansioon tällaista:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
 http://maven.apache.org/xsd/settings-1.0.0.xsd">
   <servers>
     <server>
     <id>aws.amazon.com</id>
       <username>AATAJABEETÄJACEETÄ</username>
       <password>HÄRPÄTIHÄRPÄTI</password>
     </server>
   </servers>
 </settings>

Nämä tiedot tietysti löytyvät Amazon AWS alta, ja luonnollisesti salasanasta on parasta käyttää kryptattua muotoa, mitä Maven tukeekin mukavasti. Huom! Tietysti myös ylläolevassa plugin-asetuksessa syytä asettää tuo s3 bucket id oikein.

Lisäksi, jos lisäät tämän plugin-osion configurationin jälkeen, deploy tapahtuu automaattisesti esim. mvn clean install komennolla:

<executions>
  <execution>
    <phase>install</phase>
    <goals>
      <goal>upload-source-bundle</goal>
      <goal>create-application-version</goal>
      <goal>update-environment</goal>
    </goals>
  </execution>
</executions>

No niin, tuossa pluginissa on kuitenkin metka bugi, ainakin nykyversiossa. Jostain syystä create-vaiheessa se vaatii cnamePrefix:in parametrina (ja luo tomcat ympäristön joka ei tue edes Java 7:aa vaan on Java 6 tasossa – kamalaa! – mutta ei vielä se bugi) – mutta update vaiheessa taas tuo cnamePrefix kylmästi ohitetaan ja sen sijaan on jotenkin kovakoodattu artifactId:n käyttö.

Toisin sanoen, fiksuinta on tehdä beanstalkkiin ensin sopiva projekti jossa on oikeasti Java 7 tuki (ja saisi se 8:kin tippua sinne jossain vaiheessa..) – ja pitää huolta että cnamePrefix ja projektin artifactId ovat prikulleen, case-sensitiivisesti samat. Silloin piisaa konfiguraatioksi:

<plugin>
  <groupId>br.com.ingenieux</groupId>
  <artifactId>beanstalk-maven-plugin</artifactId>
  <version>1.3.1</version>
  <configuration>
    <applicationName>NewFacebookApp</applicationName>
     <s3Key>${project.artifactId}/${project.build.finalName}-${maven.build.timestamp}.war</s3Key>
     <s3Bucket>elasticbeanstalk-eu-west-1-1112223333</s3Bucket>
     <region>eu-west-1</region>
     <environmentName>NewFacebookEnvironment</environmentName>
  </configuration> 
</plugin>

Ja tarkista siis, että projektin alussa määritelty artifactId = beanstalkissa oleva url prefix osa, eli se mitä on ennen elasticbeanstalk.com osaa. Sitten toimii.

Eli ei ihan vakaata kamaa tämä plugin. Mutta toimiessaan ihmeellisen ihana.

(kirosanat editoitu pois konfiguraation kommenteista)

 

 

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