Artikel uit Java magazine 4 – 2021
WebAssembly (Wasm) is een veelbelovende nieuwe techniek die veel potentie biedt voor zowel frontend als backend oplossingen. Wasm deelt een belangrijk speerpunt met Java, namelijk: Write Once Run Anywhere. Hoe verschillen Java en Wasm hierin? En is het mogelijk beide technieken te combineren?
{ WAT is WASM }
WAT staat voor WebAssembly Text Format, dus ja WAT is WASM!
Wasm is een ‘general purpose language’, aanvankelijk ontwikkeld om betere performance en security naar de browser te brengen. Ik zeg aanvankelijk, omdat al snel bleek dat Wasm ook buiten de browser veel potentie biedt. In december 2019 heeft W3C Wasm aanbevolen als de vierde taal in de browser. Het wordt inmiddels op steeds grotere schaal gebruikt, voornamelijk voor zwaardere webapplicaties, zoals Google Earth, Autocad en Unity. Op het gebruik van Wasm buiten de browser kom ik later nog terug.
Wasm code is binary en niet bedoeld om zelf te schrijven. Het is een intermediate language waar andere talen naartoe kunnen compileren. Zo kun je als developer je favoriete taal gebruiken (naast JavaScript) om web-functionaliteit te schrijven. Daarnaast kan bestaande code makkelijk geporteerd worden naar Wasm en in je browser draaien. Denk daarbij aan libraries om images te bewerken (zoals bij squoosh.app), een complete geluidsbewerkingstudio (zoals bij Soundation), of een Unity game zoals AngryBots. Het opent allerlei nieuwe deuren, waaronder ook multi-threading in de browser!
‘WebAssembly is the first fast, language agnostic, retargetable, and safe IR that we agreed upon as a community.’ Tyler McMullen (CTO @ Fastly).
{ Write once run anywhere (WORA) }
WORA is een van de speerpunten van Java. Andere ‘niet-Wora-talen’ compileren naar een specifieke machine architectuur. Java compileert naar bytecode die vervolgens door een JVM geinterpreteerd wordt. Door deze abstractie kan dezelfde code overal waar een JVM draait runnen. Java heeft ook in de browser gedraaid via Java-applets, alhoewel het niet echt in de browser draaide, maar de browser een JVM-process startte in een andere thread. Java-applets worden niet meer ondersteund sinds 2017.
Wasm is ontwikkeld om in een host omgeving te draaien, of dit nou de browser is, of een virtual machine. Net als bij Java bytecode is Wasm bytecode een intermediate representation die door een virtual machine wordt vertaald naar machinecode. De twee bytecodes verschillen wel van elkaar, in de zin dat Java bytecode meer high-level is en Wasm low-level zoals assembly, vandaar ook de naam WebAssembly.
{ Docker }
Veel nieuwe talen maken geen gebruik van een virtual machine, maar produceren native images, zoals Go, Swift en Rust. Het nadeel dat deze images niet op elke architectuur kunnen draaien, is geminimaliseerd door het gebruik van Docker. Het levert ook schonere containers op, omdat er geen runtime geïnstalleerd hoeft te worden. Waarom kiest Wasm dan wel voor een virtual machine?
Doordat Wasm bij default secure en gesandboxed is, is de containerbehoefte weg. De dunne schil die Docker biedt om machine resources af te bakenen, wordt overgenomen door de Wasm VM. Dit leidt tot een efficiënter gebruik van resources.
{ WASM buiten de browser }
Een van de hoofddoelen van Wasm is het enabelen van high-performance applicaties in de browser. Maar buiten de browser biedt Wasm ook veel potentie. Voor Wasm buiten de browser is een specificatie opgesteld genaamd WASI (WebAssembly System Interface). WASI wordt ondersteund door verschillende Wasm runtimes, elk gericht op een specifieke use case. We zullen op een aantal use cases inzoomen.
Serverless
Door goede performance en security leent Wasm zich uitstekend voor een serverless oplossing. Op het security gedeelte komen we straks nog terug. Fastly [1], een Edge Cloud Computing Platform, heeft een eigen Wasm runtime ontwikkeld genaamd Lucet. Lucet kan een Wasm-module instantiëren in 50 microseconden. Zo kan er per HTTP-request een schone module geïnstantieerd worden zonder cold start problemen en geheel secure.
Kubernetes
Om een Wasm-module als workload in Kubernetes te draaien, hoeft het niet containerized te zijn. Ook heeft het geen eigen OS nodig. Krustlet is een open source project van DeisLabs [2] gemaakt om Wasm-workloads efficiënt in Kubernetes te kunnen draaien. Krustlet is een Kubelet geschreven in Rust. In plaats van containers te maken, runt deze Wasm-modules direct op de hardware. Dit is mogelijk doordat Wasm-modules geïsoleerd leven en er dus geen beveiligingsrisico’s zijn.
Service mesh
Istio is een service mesh gebruikt in Kubernetes. Via de Envoy Proxy creëert Istio een programmeerbare omgeving waarin onder andere verkeer, metrieken en security geregeld kunnen worden. Deze Envoy Proxy is uit te breiden met Wasm-modules [3]. Een groot voordeel is dat extensies geschreven kunnen worden in de voorkeurstaal van de developer zelf. Er is zelfs een speciale hub opgezet om dergelijke extensies te delen: https://webassemblyhub.io/.
Er zijn nog tal van andere use cases waar Wasm veel potentie biedt en er lopen ook veel initiatieven om hier gebruik van te maken. Om bij te blijven met de nieuwe ontwikkelingen kun je je aanmelden voor de Wasm-nieuwsbrief: https://wasmweekly.news/.
{ Security }
Een ander speerpunt van Wasm is security. Zo zijn een aantal use cases voor Wasm mede mogelijk gemaakt door het sterke securitymodel. Een Wasm-module draait in een geïsoleerde sandbox, met een geïsoleerd stuk memory toegewezen. Het kan geen syscalls uitvoeren op het host systeem, behalve als het daarvoor toestemming heeft gekregen. Een Wasm-module kan bijvoorbeeld geen bestanden lezen of schrijven, behalve als (en waar) jij het daar toestemming voor geeft.
Een gemiddelde codebase bestaat voor 20% uit zelfgeschreven code en voor 80% uit third party libraries. In een Java proces hebben deze libraries (libs) dezelfde rechten als het main proces. Zo zou een lib zomaar een network socket kunnen openen en wat bestanden van het filesystem kunnen versturen. In een Wasm-module wordt elke lib in een nanoproces (geïsoleerd) gedraaid en krijgt het alleen toestemming waar het expliciet om vraagt. Dit zorgt dus voor een veel fijnmaziger securitymodel.
{ Java en Wasm }
Met Wasm hebben we een mooie nieuwe tool in de toolbox. Maar hoe kunnen wij Javanen hier gebruik van maken?
{ Kotlin }
Een groeiend aantal talen biedt ondersteuning voor Wasm als compilation target, waaronder Kotlin. Dit wordt mogelijk gemaakt door het LLVM-project dat de portabiliteit van Wasm ondersteunt. LLVM begon als ‘Low Level Virtual Machine’, maar is uitgegroeid tot een overkoepelende noemer voor meerdere compilertechnologie gerelateerde projecten. Kotlin maakte al gebruik van LLVM om naar native te compileren en kan dus ook naar Wasm compileren. Daarmee kunnen we dus Kotlin code in de browser draaien, of in andere Wasm ondersteunende hosts.
Om Kotlin te compileren naar Wasm hoef je alleen de buildtarget op wasm32 te zetten. Het resultaat is een Wasm-module en een .wasm.js file met de JavaScript API die aangeroepen kan worden vanuit Wasm. Deze twee kunnen vervolgens met een script tag in een HTML-pagina ingeladen worden:
<script wasm=”hello.wasm” src=”hello.wasm.js”></script>
{ JWebAssembly }
Voor JVM-talen bestaat er ook de JWebAssembly compiler [4]. Deze compileert Java bytecode naar Wasm. Zo kun je naast Java je favoriete JVM-taal gebruiken, zoals Clojure, Groovy of Scala en de resulterende bytecode omtoveren naar Wasm.
JWebAssembly biedt een gradle plugin waarmee je kan configureren welke SourceSets je wil compileren en de naam van de output. Door de flag debugNames op true te zetten, worden methode- en parameternamen behouden in de Wasm bytecode. Daarnaast genereert het ook een sourcemap, waardoor debuggen mogelijk is in de browser.
Hier is een voorbeeld van een Java methode die gecompileerd kan worden naar Wasm:
@Export
public static void main() {
Document document = Window.document();
HTMLElement div = document.createElement(“div”);
Text text = document.createTextNode(“Hello World, this text come from WebAssembly.”);
div.appendChild( text );
document.body().appendChild( div );
}
De ‘Export’ annotatie geeft aan dat deze methode vanuit JavaScript beschikbaar is. Vervolgens wordt er vanuit de Java code geïnteracteerd met de DOM en een div element toegevoegd met inhoud. Dit is een basaal voorbeeld, maar het biedt veel nieuwe mogelijkheden.
{ Consuming Wasm met GraalVM }
LLVM ondersteunt niet alleen Kotlin met compilertechnologie, maar ook GraalVM maakt gebruik van LLVM. Ondersteunt GraalVM dan ook Wasm? Het antwoord is: ja. GraalVM biedt een experimentele module genaamd GraalWasm [5], waarmee Wasm-modules gerund kunnen worden. Dat draagt bij aan het doel van GraalVM om een universeel platform voor code executie te worden.
Naast het runnen van Wasm-modules met GraalWasm kent GraalVM ook de Polyglot API. Deze API maakt het mogelijk om andere talen te embedden in Java code. Ook wordt Wasm ondersteund en kun je dus vanuit je Java applicatie een Wasm-module gebruiken. GraalVM biedt zo verschillende mogelijkheden om Java en Wasm te combineren.
{ Conclusie }
WebAssembly heeft een aantal overeenkomsten met Java. Zo is het een bytecode definitie die architectuur agnostisch overal kan draaien. Zoals er verschillende talen Java bytecode kunnen produceren, zijn er ook veel talen die naar Wasm kunnen compileren, waaronder Kotlin. Mede door het sterke securitydesign van Wasm, biedt Wasm veel potentie voor verschillende use cases, zoals serverless en in een microservice landschap.
Het is moeilijk voor een nieuwe techniek om op te boksen tegen bestaande technieken waar al decennia aan ontwikkeld is. Wasm is geen vervanger voor Java, net zo min het een vervanger is voor JavaScript. Het is een nieuwe tool binnen het ecosysteem met veel potentie waar we zeker meer van gaan zien. Qua ‘developer gemak’ is Java niet te evenaren met al zijn frameworks, libraries en tools. Maar voor low-level code, waar security en performance belangrijk zijn, zie ik voor Wasm een florissante toekomst.
Referenties:
- Fastly: https://www.fastlylabs.com/
Lees ook hun blog over de introductie van Lucet: https://www.fastly.com/blog/announcing-lucet-fastly-native-webassembly-compiler-runtime - Krustlet van DeisLabs: https://krustlet.dev/
Lees ook hun blog over de introductie van Krustlet:
https://deislabs.io/posts/introducing-krustlet/ - De Wasm extensibility voor Istio’s Envoy Proxy:
https://istio.io/latest/docs/concepts/wasm/ - JWebAssembly:
https://github.com/i-net-software/JWebAssembly - GraalWasm:
https://www.graalvm.org/reference-manual/wasm/
BIO
Sjoerd During is software engineer bij JDriven en gaat geen uitdaging uit de weg. Hij is geïntrigeerd door nieuwe technieken en mogelijkheden in het Java ecosysteem.