Iată cam tot ce ați putea dori să știți despre tehnicile dvs. preferate de ambalare pentru Java - JAR-uri subțiri, JAR-uri grase/uber, JAR-uri subțiri și JAR-uri goale.

Alăturați-vă comunității DZone și obțineți experiența completă a membrilor.

Recent mă jucam cu diverse tehnici de ambalare a microserviciilor Java și rulam pe OpenShift folosind diferite runtime și cadre pentru a ilustra diferențele lor (WildFly Swarm vs. WildFly, Spring Boot vs. lumea etc.). Cam în același timp în care făceam acest lucru, un fir intern de listă de e-mail s-a aprins discutând unele dintre diferențe și folosind termeni precum Uber JARs, Thin WARs, Skinny WARs și câțiva alții. Unii oameni au evidențiat avantajele și dezavantajele fiecăruia, în special beneficiile abordării WAR subțiri atunci când sunt combinate cu straturi de imagine docker.

Și m-am gândit în sinea mea: nu asta face deja toată lumea? Chiar trebuie să se gândească dezvoltatorii la aceste lucruri? Dar, înainte de a intra în asta, vreau să definesc diferiții termeni pe care i-am auzit și măcar să-i înțeleg direct în propriul meu cap.

Calificativele (în ordine crescătoare a dimensiunii logice):

  • Slab - Conține NUMAI biții pe care îi introduceți literalmente în editorul de cod și NIMIC altceva.
  • Subţire - Conține toate cele de mai sus PLUS dependențele directe ale aplicației de aplicația dvs. (drivere db, biblioteci de utilități etc.).
  • Gol - Inversul Thin - Conține doar biții necesari pentru a rula aplicația, dar NU conține aplicația în sine. Practic, un „server de aplicații” preambalat pe care vă puteți implementa ulterior aplicația, în același stil ca serverele tradiționale de aplicații Java EE, dar cu diferențe importante, vom ajunge mai târziu.
  • Grăsime/Uber - Conține bitul pe care îl scrii literalmente PLUS dependențele directe ale aplicației tale PLUS biții necesari pentru a rula aplicația „pe cont propriu”.

thin

Acum să definim modul în care calificativele se mapează în lumea aplicațiilor Java și a tipurilor de pachete (JAR, WAR etc.).

GRAS/Uber JAR

Maven și în special Spring Boot au popularizat această abordare binecunoscută a ambalajului, care include tot ce este necesar pentru a rula întreaga aplicație într-un mediu standard Java Runtime (adică pentru a putea rula aplicația cu java -jar myapp.jar). Cantitatea de elemente suplimentare de runtime incluse în Uberjar (și dimensiunea fișierului său) depinde de caracteristicile cadrului și de runtime pe care le folosește aplicația dvs.

Război subțire

Dacă sunteți dezvoltator Java EE, este posibil să faceți deja acest lucru. Este ceea ce faceți de mai bine de un deceniu, așa că felicitări sunteți încă cool! Un WAR subțire este o aplicație web Java EE care conține doar conținutul web și logica de afaceri pe care ați scris-o, împreună cu dependențe terțe. Nu conține nimic furnizat de runtime-ul Java EE, prin urmare este „subțire”, dar nu poate rula „singur” - trebuie să fie implementat pe un server de aplicații Java EE sau un container Servlet care conține „ultima milă” de biți necesari pentru a rula aplicația pe JVM.

JAR subțire

La fel ca un WAR WAR, cu excepția utilizării formatului de ambalare JAR. De obicei, aceasta este utilizată de aplicații specializate/arhitecturi de pluginuri care utilizează formatul de ambalare JAR pentru plugin-ul construit special sau pentru artefacte de execuție. De exemplu, formatul .kjar din Drools.

Skinny WAR

Deși mai puțin cunoscut decât frații săi, un Război slab este mai subțire decât un Război subțire, deoarece nu include niciuna dintre bibliotecile terțe de care depinde aplicația. Acesta conține NUMAI codul (octet) pe care, în calitate de dezvoltator, îl introduceți literalmente în editorul dvs. Acest lucru are mult sens în lumea docker a imaginilor de containere stratificate, unde dimensiunea stratului este importantă pentru sănătatea DevOps, iar Adam Bien a făcut o treabă minunată demonstrând și explicând acest lucru. Mai multe despre asta mai târziu.

Skinny JAR

La fel ca un Skinny WAR, cu excepția utilizării ambalajelor JAR și a cadrelor construite în jurul său, cum ar fi WildFly Swarm și Spring Boot. Acest lucru are, de asemenea, un ton de sens pentru sănătatea CI/CD (și factura dvs. AWS) - întrebați doar Hubspot. Aici luați un Război subțire și eliminați toate dependențele terță parte. Ați rămas cu cea mai mică unitate atomică de aplicație (încercarea de a reda sunete mai mici ca o idee teribilă, dar cu Java 9/JPMS este posibilă) și trebuie să fie implementată într-un timp de execuție care o așteaptă ȘI are toate alți biți necesari pentru a rula aplicația (cum ar fi un HAR JAR)

Hollow JAR

Acesta este un runtime al aplicației Java care conține un server de aplicații „suficient” pentru a rula aplicații, dar nu conține nicio aplicație în sine. Poate rula singur, dar nu este atât de util atunci când rulează singur, deoarece nu conține aplicații și nu va face altceva decât să se inițializeze singur. Unele proiecte precum WildFly Swarm vă permit să personalizați cât este „suficient”, în timp ce altele (cum ar fi Paraya Micro sau TomEE) oferă distribuții pre-construite ale combinațiilor populare de componente runtime, cum ar fi cele definite de Eclipse MicroProfile.

Celelalte combinații

  • Hollow WAR - În teorie, ați putea împacheta un fel de aplicație pentru a rula într-un alt server de aplicații și apoi a implementa aplicații în acel strat interior. Mult noroc cu asta!
  • Fat/Uber WAR - Nu are sens cu ideea generală că Fat/Ubers poate fi rulat cu java-jar.
  • Fișiere EAR - prin definiție, un fișier EAR nu poate fi gol, gras sau slab, deci tot ce puteți crea este un EAR subțire (care este ceea ce este deja, prin definiție). Deplasați-vă, nimic de văzut aici, cu excepția faptului că fișierele EAR ar putea fi vehiculul care transportă dependențe pentru WAR-uri slabe din EAR.

De ce sa te deranjezi?

Creșterea computerului închiriat și popularitatea proceselor DevOps, a containerelor Linux și a arhitecților de microservicii au făcut din nou importanța aplicației (numărul de octeți care alcătuiesc aplicația dvs.). Atunci când implementați în medii de dezvoltare, testare și producție de mai multe ori pe zi (uneori sute pe oră sau chiar de 2 miliarde de ori pe săptămână), minimizarea dimensiunii aplicației dvs. poate avea un impact uriaș asupra eficienței generale a DevOps și asupra funcționalității dvs. sănătate psihică. Nu trebuie să minimizați liniile de cod din aplicația dvs., dar ar trebui să reduceți de câte ori aplicația dvs. și dependențele acesteia trebuie să treacă printr-o rețea, să se deplaseze pe sau pe un disc sau să fie procesate de un program . Asta înseamnă să împărțiți aplicația în diferite părți ambalate, astfel încât să poată fi separate în mod corespunzător și tratate ca atare (și chiar versionate, dacă doriți).

Cu microserviciile native în cloud, aceasta înseamnă utilizarea imaginilor de containere Linux stratificate. Dacă puteți separa componentele aplicației dvs. în diferite straturi, punând cele mai frecvent schimbătoare părți ale aplicației pe „partea de sus” și cele mai puțin schimbate părți din „partea de jos”, atunci de fiecare dată când reconstruiți o nouă versiune a aplicației dvs., în realitate ating doar cele mai înalte niveluri. Economisiți timp peste tot pentru stocare, transport și procesare a noii versiuni a aplicației dvs.

Grozav. Spune-mi doar pe care să îl folosesc!

Depinde. Argumente pro/contra fiecăruia au fost discutate de alții (aici, aici și aici). JAR-urile Fat/Uber sunt atractive datorită portabilității lor, ușurinței de execuție în IDE și caracteristicii sale de tipul all-you-need-is-a-JRE. Dar cu cât puteți separa mai mult straturile (în slab/subțire), cu atât veți economisi mai mult pe rețea/disc/CPU mai jos. Există, de asemenea, avantajul suplimentar de a putea corela toate straturile în mod independent, de exemplu, pentru a vă abona și a distribui rapid remedierile pentru vulnerabilitățile de securitate către toate aplicațiile dvs. care rulează). Deci, trebuie să decideți printre compromisuri.

Exemplu: WildFly Swarm

WildFly Swarm este un mecanism pentru ambalarea aplicațiilor Java care conțin funcționalitate „suficientă” pentru a rula aplicația. Are o abstracție numită Fracțiune, fiecare dintre acestea întruchipând unele funcționalități de care au nevoie aplicațiile. Puteți selecta fracțiile de care aveți nevoie și puteți împacheta numai acele fracțiuni împreună cu aplicația dvs. pentru a produce o imagine minimă și specializată care poate fi rulată pentru aplicația dvs.

WildFly Swarm are capacitatea de a crea multe dintre tipurile de aplicații ambalate de mai sus. Să vedem ce poate face și dimensiunile rezultate ale diferitelor opțiuni de ambalare și cum se aplică în lumea imaginilor de containere Linux stratificate. Vom începe cu Fat/Uber JAR și vom merge mai departe. Luați codul și urmați:

JAR-uri de grăsime/Uber

Aplicația eșantion este un punct final JAX-RS simplu care returnează un mesaj. Are o dependență directă de Joda Time, pe care o vom folosi mai târziu. Deocamdată, să facem un Fat JAR (care este modul implicit al WildFly Swarm) folosind codul din folderul fat-thin /:

Puteți să-l testați cu java -jar target/weight-1.0-swarm.jar și apoi curl http: // localhost: 8080/api/hello într-o fereastră terminală separată.

Nu este rău - dimensiunea de 45 de milioane pentru un borcan FAT complet autonom. Ați putea încărca acest lucru într-un strat de imagine docker de fiecare dată când reconstruiți, dar acel 45M va crește pe măsură ce implementați de fapt o aplicație din lumea reală. Să vedem dacă putem face mai bine.

Războaiele subțiri

Ca parte a construcției de mai sus a Fat JAR, WildFly Swarm a creat, de asemenea, un Război subțire, astfel încât nu este necesară o reconstruire. Aruncați o privire la Războiul subțire:

Acum ajungem undeva. Acest 512K Thin WAR ar putea fi implementat pe orice server de aplicații (cum ar fi WildFly în sine). Deci, dacă introduceți acest fișier mult mai mic în straturile dvs. de andocare superioare, în timp ce serverul dvs. de aplicații rar schimbat trăiește într-un strat inferior (așa cum demonstrează Adam Bien de mai multe ori), veți economisi destul de mult timp și energie în timpul mai multor versiuni. Sa continuam.

Războaiele slabe

Este posibil să fi observat în aplicația demonstrativă că este foarte simplă - doar câteva linii de cod. Deci, de ce este nevoie de 512K pentru a-l ține? Majoritatea celor 512M din Thin WAR sunt preluați de dependențele noastre directe - în acest caz, biblioteca Joda Time:

Dacă continuăm să adăugăm dependențe directe (cum ar fi driverele bazei de date și alte lucruri pe care practic orice aplicație de producție va avea nevoie), Thin WAR va crește și va crește în timp. În timp ce 512K nu este rău, putem face mult mai bine.

Skinny WAR: eliminarea dependențelor directe

Cu Maven, puteți elimina dependența directă din Thin WAR rezultată declarând că trebuie furnizată, ceea ce înseamnă că timpul de execuție (în acest caz, WildFly) este de așteptat să furnizeze această dependență. Datorită naturii serverelor de aplicații și a încărcătorului modular de clase WildFly, pentru ca WildFly să ofere această dependență atunci când aplicația dvs. este implementată pe acesta, va trebui să creați o definiție personalizată a modulului JBoss în configurația Wildfly și să declarați o dependență de acel modul în aplicația dvs.

WildFly Swarm oferă o modalitate mai bună, care nu necesită deloc atingerea biților serverului de aplicații. Putem crea un modul personalizat numit Fracțiune. Fracțiunile sunt cetățeni de primă clasă în WildFly Swarm și are o logică specială care leagă codul din interiorul Fracțiunilor de codul aplicației în timpul rulării. Procedând astfel, aplicația noastră nu are într-adevăr dependențe și devine un RĂZBOI Skinny.

Am creat o fracțiune pentru a găzdui biblioteca Joda Time în joda-fraction/folder din sursa aplicației de exemplu. Să-l construim și să-l copiem în depozitul nostru Maven pentru referințe ulterioare:

Acum că fracția este construită, reconstruiți versiunea slabă a aplicației (în dosarul skinny /):

Acum ajungem undeva! Războiul nostru Skinny este 2243 octeți. Este la fel de mic pe cât devine, deoarece conține literalmente doar codul nostru:

Cu toate acestea, este complet inutilizabil. Necesită nu numai un server de aplicații cu suport JAX-RS, ci și un server care poate oferi dependența noastră directă Joda Time. WildFly Swarm oferă o altă facilitate pentru a crea un astfel de timp de rulare a serverului, care va conține doar suficient server de aplicații PLUS dependența noastră separată de Joda Time. Se numește Hollow JAR.

JAR-uri goale

Pentru a construi un JAR gol potrivit pentru rularea aplicației noastre de probă, putem folosi o proprietate WildFly Swarm pentru a instrui pluginul Maven să o construiască:

Există serverul gol care cântărește 44M și WAR-ul nostru slab la 2243 bytes (utilitarul Linux raportează spațiul pe disc alocat în unități de dimensiunea blocului sistemului de fișiere, care este 4K pe sistemul meu, dar fiți siguri că WAR slab este într-adevăr 2243 bytes și atunci când sunt transferate printr-o rețea vor fi trimise doar 2243 de octeți).

Acum puteți înfunda JAR-ul gol într-un strat de container Linux și apoi înfășurați WAR-ul subțire deasupra. Când vă reconstruiți proiectul cu un cod nou, presupunând că nu adăugați mai multe dependențe la serverul de aplicații, va fi reconstruit doar războiul dvs. slab, economisind timp, spațiu pe disc, copaci și sănătatea dvs. în timpul celor 2 miliarde de reconstruiri de containere pe care le faceți fiecare săptămână.

Pentru a rula aplicația subțire și a verifica dacă funcționează în continuare împreună cu serverul gol pe care tocmai l-ați creat:

Și într-un alt terminal:

Dar cizma de primăvară?

Setările implicite Spring Boot sunt, de asemenea, pentru a construi un borcan de grăsime, așa că să aruncăm o privire la exemplul aplicației Boot:

Mult mai bun decât 45M de la WildFly Swarm. Dar unde mergem de aici? Este încă 14 milioane pentru o aplicație Hello World.

Deși este posibil să separați codul aplicației de codul de primăvară și să produceți un efect asemănător cu duo-ul gol JAR/WAR skinny WAR de la WildFly Swarm, vă cere să încălcați al patrulea perete și să cunoașteți internele Spring Boot Uberjar și să scrieți o operație cu script borcanul FAT rezultat pentru a-și împărți conținutul de-a lungul limitei aplicației definite de Boot. Rezultatul este o aplicație și un server de aplicații foarte strâns cuplate și neportabile, practic fără speranță de actualizare și fără posibilitatea de a-i implementa altceva decât aplicația din care a plecat. JAR-urile goale rulabile nu sunt considerate un cetățean de primă clasă în lumea primăverii.

rezumat

Ne-am micșorat aplicația de la 45M → 512K → 2243 octeți folosind WildFly Swarm. Cu această abordare, putem separa aplicația noastră de dependențele sale de rulare și le putem pune în straturi de imagine de container Linux separate. Acest lucru va face ca conductele dvs. CI/CD să fie mai rapide, dar, de asemenea, să vă facă dezvoltatorii mai rapizi la editare/construire/testare și să vă ofere asigurarea că testați cu aceiași biți care vor ateriza în producție. Câștig-câștig-câștig în cartea mea.

Publicat la DZone cu permisiunea lui James Falkner, DZone MVB. Vezi articolul original aici.

Opiniile exprimate de colaboratorii DZone sunt ale lor.