Node.js
Node.js

Follow

Feb 24, 2017 – 14 min read

Dit artikel is afkomstig van Tomislav Capan, technisch consultant en Node.js liefhebber. Tomislav publiceerde dit oorspronkelijk in augustus 2013 op de Toptal-blog – u vindt de oorspronkelijke post hier; de blog is enigszins bijgewerkt. Het volgende onderwerp is gebaseerd op de mening en ervaringen van deze auteur.

JavaScript’s stijgende populariteit heeft veel veranderingen met zich meegebracht, en het gezicht van webontwikkeling is vandaag de dag dramatisch anders. De dingen die we tegenwoordig op het web kunnen doen met JavaScript dat zowel op de server als in de browser draait, waren slechts enkele jaren geleden moeilijk voor te stellen, of werden ingekapseld binnen sandboxed omgevingen zoals Flash of Java Applets.

Voordat je je verdiept in Node.js, wil je misschien eerst wat meer lezen over de voordelen van het gebruik van JavaScript over de hele stack, dat de taal en het dataformaat (JSON) verenigt, waardoor je optimaal ontwikkelaarshulpmiddelen kunt hergebruiken. Omdat dit meer een voordeel is van JavaScript dan van Node.js in het bijzonder, zullen we het hier niet veel bespreken. Maar het is een belangrijk voordeel om Node.js in je stack op te nemen.

Node.js is een JavaScript runtime-omgeving die is gebouwd op de V8 JavaScript-engine van Chrome. Het is vermeldenswaard dat Ryan Dahl, de bedenker van Node.js, zich richtte op het maken van real-time websites met push-mogelijkheden, “geïnspireerd door toepassingen zoals Gmail”. Met Node.js gaf hij ontwikkelaars een tool om te werken in het non-blocking, event-driven I/O paradigma.

In één zin: Node.js schittert in real-time webapplicaties die gebruik maken van push-technologie via websockets. Wat is daar zo revolutionair aan? Nou, na meer dan 20 jaar stateless-web gebaseerd op het stateless request-response paradigma, hebben we eindelijk webtoepassingen met real-time, bidirectionele verbindingen, waarbij zowel de client als de server de communicatie kunnen initiëren, waardoor ze vrijelijk gegevens kunnen uitwisselen.

Dit staat in schril contrast met het typische web response paradigma, waarbij de client altijd de communicatie initieert. Bovendien is het allemaal gebaseerd op de open web stack (HTML, CSS en JS) die via de standaardpoort 80 draait.

Men zou kunnen aanvoeren dat we dit al jaren hebben in de vorm van Flash en Java Applets – maar in werkelijkheid waren dat slechts sandboxed omgevingen die het web gebruikten als een transportprotocol om aan de client te worden geleverd. Plus, ze werden uitgevoerd in isolatie en werkten vaak over niet-standaard poorten, die extra machtigingen en dergelijke kunnen hebben vereist.

Met al zijn voordelen, Node.js speelt nu een cruciale rol in de technologie stack van veel high-profile bedrijven die afhankelijk zijn van zijn unieke voordelen. De Node.js Foundation heeft al het beste denken rond waarom bedrijven Node.js zouden moeten overwegen geconsolideerd in een korte presentatie die kan worden gevonden op de Node.js Foundation’s Case Studies page.

In deze post zal ik niet alleen bespreken hoe deze voordelen worden bereikt, maar ook waarom je Node.js zou willen gebruiken – en waarom niet – met behulp van enkele van de klassieke webapplicatiemodellen als voorbeelden.

Hoe werkt het?

Het belangrijkste idee van Node.js: gebruik non-blocking, event-driven I/O om lichtgewicht en efficiënt te blijven in het gezicht van data-intensieve real-time applicaties die over gedistribueerde apparaten draaien.

Dat is een mond vol.

Wat het echt betekent is dat Node.js geen silver-bullet nieuw platform is dat de webontwikkelingswereld zal domineren. In plaats daarvan is het een platform dat een bepaalde behoefte vervult. En dit begrijpen is absoluut essentieel. Je wilt Node.js zeker niet gebruiken voor CPU-intensieve operaties; in feite zal het gebruik ervan voor zware berekeningen bijna al zijn voordelen teniet doen. Waar Node.js echt in uitblinkt, is in het bouwen van snelle, schaalbare netwerktoepassingen, omdat het in staat is om een enorm aantal gelijktijdige verbindingen te verwerken met een hoge doorvoer, wat gelijk staat aan een hoge schaalbaarheid.

Hoe het onder de motorkap werkt, is behoorlijk interessant. Vergeleken met traditionele web-serving technieken waar elke verbinding (verzoek) spawnt een nieuwe thread, neemt het systeem RAM en uiteindelijk maxing-out op de hoeveelheid RAM beschikbaar, Node.js werken op een enkele thread, met behulp van niet-blokkerende I/O-aanroepen, waardoor het tienduizenden gelijktijdige verbindingen kan ondersteunen (vastgehouden in de event-lus).

*Image afkomstig uit oorspronkelijke blogpost.

Een snelle berekening: ervan uitgaande dat elke thread potentieel 2 MB geheugen bij zich heeft, brengt draaien op een systeem met 8 GB RAM ons op een theoretisch maximum van 4000 gelijktijdige verbindingen (berekeningen ontleend aan Michael Abernethy’s artikel “Just what is Node.js?”, gepubliceerd op IBM developerWorks in 2011; helaas is het artikel niet meer beschikbaar), plus de kosten van context-switching tussen threads. Dat is het scenario waar je typisch mee te maken hebt bij traditionele web-serving technieken. Door dat allemaal te vermijden, haalt Node.js schaalbaarheidsniveaus van meer dan 1M gelijktijdige verbindingen, en meer dan 600k gelijktijdige websockets verbindingen.

Er is natuurlijk de kwestie van het delen van een enkele thread tussen alle client verzoeken, en het is een potentiële valkuil van het schrijven van Node.js toepassingen. Ten eerste kunnen zware berekeningen Node’s enkele thread verstikken en problemen veroorzaken voor alle clients (meer hierover later), omdat inkomende verzoeken worden geblokkeerd totdat de berekening is voltooid. Ten tweede moeten ontwikkelaars heel voorzichtig zijn om geen exceptie te laten opborrelen naar de core (bovenste) Node.js event loop, die ervoor zal zorgen dat de Node.js instantie wordt beëindigd (effectief crashen van het programma).

De techniek die wordt gebruikt om te voorkomen dat excepties opborrelen naar de oppervlakte is het doorgeven van fouten terug naar de aanroeper als callback parameters (in plaats van ze te gooien, zoals in andere omgevingen). Zelfs als er een onbehandelde uitzondering naar boven borrelt, zijn er hulpmiddelen ontwikkeld om het Node.js proces te monitoren en het noodzakelijke herstel van een gecrashte instantie uit te voeren (hoewel je waarschijnlijk niet in staat zult zijn om de huidige status van de gebruikerssessie te herstellen), de meest voorkomende is de Forever module, of het gebruik van een andere benadering met externe systeemhulpmiddelen upstart en monit, of zelfs alleen upstart.

npm: The Node Package Manager

Wanneer we Node.js bespreken, is er één ding dat zeker niet mag worden weggelaten en dat is ingebouwde ondersteuning voor pakketbeheer met behulp van het npm-gereedschap dat standaard bij elke Node.js-installatie wordt geleverd. Het idee van npm-modules is vrij vergelijkbaar met dat van Ruby Gems: een set openbaar beschikbare, herbruikbare componenten, beschikbaar via eenvoudige installatie via een online repository, met versie- en afhankelijkheidsbeheer.

Een volledige lijst van verpakte modules is te vinden op de npm-website, of toegankelijk met behulp van de npm CLI-tool die automatisch wordt geïnstalleerd met Node.js. Het module-ecosysteem is open voor iedereen, en iedereen kan zijn eigen module publiceren die dan in de npm repository wordt opgenomen. Een korte inleiding tot npm kan worden gevonden in een Beginnershandleiding, en details over het publiceren van modules in de npm Publishing Tutorial.

Enkele van de meest nuttige npm modules vandaag de dag zijn:

  • express – Express.js, een Sinatra-geïnspireerde web development framework voor Node.js, en de de-facto standaard voor de meerderheid van Node.js toepassingen die er vandaag zijn.
  • hapi – een zeer modulaire en eenvoudig te gebruiken configuratie-centric kader voor het bouwen van web-en service-toepassingen
  • connect – Connect is een uitbreidbaar HTTP-server framework voor Node.js, het verstrekken van een verzameling van hoge prestaties “plugins” bekend als middleware; dient als een basis fundament voor Express.
  • socket.io en sockjs – Server-side component van de twee meest voorkomende websockets componenten die er zijn vandaag.
  • pug (voorheen Jade) – Een van de populaire templating engines, geïnspireerd door HAML, een standaard in Express.js.
  • mongodb en mongojs – MongoDB wrappers om de API voor MongoDB object databases in Node.js te bieden.
  • redis – Redis client library.
  • lodash (underscore, lazy.js) – De JavaScript utility belt. Underscore begon het spel, maar werd omvergeworpen door een van zijn twee tegenhangers, voornamelijk vanwege betere prestaties en modulaire implementatie.
  • forever – Waarschijnlijk het meest voorkomende hulpprogramma om ervoor te zorgen dat een bepaald node script continu draait. Houdt je Node.js proces in productie in het gezicht van eventuele onverwachte storingen.
  • bluebird – Een volledig uitgeruste Promises/A+ implementatie met uitzonderlijk goede prestaties
  • moment – Een lichtgewicht JavaScript datum bibliotheek voor het parseren, valideren, manipuleren en formatteren van datums.

De lijst gaat maar door. Er zijn tonnen echt nuttige pakketten die er zijn, beschikbaar voor iedereen (geen aanstoot aan degenen die ik hier heb weggelaten).

Waar Node.js zou moeten worden gebruikt

Chat is de meest typische real-time, multi-user applicatie. Van IRC (vroeger), via vele proprietary en open protocollen die op niet-standaard poorten draaien, naar de mogelijkheid om alles vandaag de dag in Node.js te implementeren met websockets die over de standaard poort 80 draaien.

De chat applicatie is echt het sweet-spot voorbeeld voor Node.js: het is een lichtgewicht, high traffic, data-intensieve (maar lage processing/computation) applicatie die over gedistribueerde apparaten draait. Het is ook een geweldige use-case om te leren, want het is eenvoudig, maar toch dekt het de meeste paradigma’s die je ooit zult gebruiken in een typische Node.js-toepassing.

Laten we proberen weer te geven hoe het werkt.

In het eenvoudigste scenario, hebben we een enkele chatroom op onze website waar mensen komen en berichten kunnen uitwisselen op een één-op-veel (eigenlijk alle) manier. Bijvoorbeeld, stel dat we drie mensen op de website hebben die allemaal zijn aangesloten op ons message board.

Op de server-side, hebben we een eenvoudige Express.js applicatie die twee dingen implementeert: 1) een GET ‘/’ request handler die de webpagina serveert met zowel een message board als een ‘Send’ knop om nieuwe berichtinvoer te initialiseren, en 2) een websockets server die luistert naar nieuwe berichten uitgezonden door websocket clients.

Op de client-kant, hebben we een HTML-pagina met een paar handlers opgezet, een voor de ‘Send’ knop klik event, die het invoerbericht ophaalt en stuurt het naar beneden de websocket, en een ander die luistert naar nieuwe inkomende berichten op de websockets client (dat wil zeggen, berichten van andere gebruikers, die de server nu wil dat de client weergeeft).

Wanneer een van de clients een bericht plaatst, gebeurt het volgende:

  • Browser vangt de ‘Send’ knop klik op via een JavaScript handler, haalt de waarde uit het invoerveld (d.w.z., de berichttekst), en zendt een websocket-bericht met behulp van de websocket-client die is verbonden met onze server (geïnitialiseerd bij de initialisatie van de webpagina).
  • Server-side component van de websocket-verbinding ontvangt het bericht en stuurt het door naar alle andere verbonden clients met behulp van de broadcast-methode.
  • Alle clients ontvangen het nieuwe bericht als een push-bericht via een websockets client-side component die draait binnen de webpagina. Ze pikken vervolgens de inhoud van het bericht op en werken de webpagina ter plekke bij door het nieuwe bericht aan het bord toe te voegen.

Afbeelding afkomstig van de oorspronkelijke blog.

Dit is het eenvoudigste voorbeeld. Voor een robuustere oplossing zou je een eenvoudige cache kunnen gebruiken op basis van de Redis store. Of in een nog geavanceerdere oplossing, een berichtenwachtrij om de routering van berichten naar clients af te handelen en een robuuster afleveringsmechanisme dat tijdelijke verbindingsverliezen kan opvangen of berichten kan opslaan voor geregistreerde clients terwijl ze offline zijn. Maar ongeacht de verbeteringen die je maakt, Node.js zal nog steeds werken onder dezelfde basisprincipes: reageren op gebeurtenissen, het omgaan met veel gelijktijdige verbindingen, en het handhaven van vloeibaarheid in de gebruikerservaring.

API ON TOP OF AN OBJECT DB

Hoewel Node.js echt schittert met real-time applicaties, is het een natuurlijke pasvorm voor het blootstellen van de gegevens van object DBs (bijv. MongoDB). JSON opgeslagen gegevens kunnen Node.js functioneren zonder de impedantie mismatch en data conversie.

Bijvoorbeeld, als je Rails gebruikt, zou je converteren van JSON naar binaire modellen, dan weer bloot te stellen als JSON via de HTTP wanneer de gegevens worden verbruikt door React.js, Angular.js, enz., of zelfs gewoon jQuery AJAX calls. Met Node.js, kunt u eenvoudig uw JSON objecten blootstellen met een REST API voor de client om te consumeren. Bovendien hoeft u zich geen zorgen te maken over het omzetten tussen JSON en wat anders bij het lezen of schrijven van uw database (als je MongoDB gebruikt). Kortom, kunt u voorkomen dat de noodzaak voor meerdere conversies door het gebruik van een uniforme data serialisatie formaat over de client, server en database.

QUEUED INPUTS

Als je het ontvangen van een grote hoeveelheid gelijktijdige gegevens, kan uw database een knelpunt worden. Zoals hierboven afgebeeld, kan Node.js de gelijktijdige verbindingen gemakkelijk zelf afhandelen. Maar omdat databasetoegang een blokkeringsoperatie is (in dit geval), komen we in de problemen. De oplossing is om het gedrag van de client te bevestigen voordat de gegevens werkelijk naar de database worden geschreven.

Met die aanpak behoudt het systeem zijn responsiviteit onder een zware belasting, wat vooral handig is wanneer de client geen harde bevestiging nodig heeft van een succesvolle gegevens schrijven. Typische voorbeelden zijn: het loggen of schrijven van gegevens over het volgen van gebruikers, die in batches worden verwerkt en pas later worden gebruikt; evenals bewerkingen die niet onmiddellijk hoeven te worden weergegeven (zoals het bijwerken van een aantal “Likes” op Facebook) waarbij uiteindelijke consistentie (zo vaak gebruikt in de NoSQL-wereld) acceptabel is.

Gegevens worden in een wachtrij geplaatst via een soort caching of message queuing (MQ) infrastructuur (bijv, RabbitMQ, ZeroMQ) en verteerd door een aparte database batch-write proces, of rekenintensieve verwerking backend diensten, geschreven in een beter presterende platform voor dergelijke taken. Vergelijkbaar gedrag kan worden geïmplementeerd met andere talen/frameworks, maar niet op dezelfde hardware, met dezelfde hoge, gehandhaafde doorvoer.

Afbeelding overgenomen uit origineel artikel.

In het kort: met Node kun je de schrijfacties van de database naar de zijkant schuiven en ze later afhandelen, alsof ze geslaagd zijn.

DATA STREAMING

In traditionelere webplatforms worden HTTP-verzoeken en -reacties behandeld als geïsoleerde gebeurtenissen; in feite zijn het eigenlijk streams. Deze observatie kan worden gebruikt in Node.js om een aantal coole functies te bouwen. Het is bijvoorbeeld mogelijk om bestanden te verwerken terwijl ze nog worden geupload, omdat de data binnenkomt via een stream en we ze op een online manier kunnen verwerken. Dit kan worden gedaan voor real-time audio of video encoding, en proxying tussen verschillende databronnen (zie volgende sectie).

PROXY

Node.js kan gemakkelijk worden gebruikt als een server-side proxy waar het een groot aantal gelijktijdige verbindingen kan verwerken op een non-blocking manier. Het is vooral handig voor het proxen van verschillende diensten met verschillende responstijden, of het verzamelen van gegevens van meerdere bronpunten.

Een voorbeeld: denk aan een server-side applicatie die communiceert met bronnen van derden, gegevens uit verschillende bronnen binnenhaalt, of activa zoals afbeeldingen en video’s opslaat naar cloud-diensten van derden.

Hoewel er speciale proxyservers bestaan, kan het gebruik van Node in plaats daarvan nuttig zijn als je proxying-infrastructuur onbestaand is of als je een oplossing nodig hebt voor lokale ontwikkeling. Hiermee bedoel ik dat je een client-side app zou kunnen bouwen met een Node.js ontwikkelingsserver voor assets en proxying/stubbing API-verzoeken, terwijl je in productie dergelijke interacties zou afhandelen met een speciale proxy-service (nginx, HAProxy, enz.).

BROKERAGE – STOCK TRADER’S DASHBOARD

Laten we teruggaan naar het toepassingsniveau. Een ander voorbeeld waar desktopsoftware domineert, maar gemakkelijk zou kunnen worden vervangen door een real-time weboplossing, is de handelssoftware van makelaars, die wordt gebruikt om aandelenkoersen te volgen, berekeningen/technische analyses uit te voeren en grafieken/grafieken te maken.

Door over te schakelen op een real-time webgebaseerde oplossing zouden makelaars gemakkelijk van werkstation of werkplek kunnen veranderen. Binnenkort zien we ze misschien op het strand in Florida… of Ibiza… of Bali.

APPLICATIE MONITORING DASHBOARD

Een andere veel voorkomende use-case waarin Node-met-web-sockets perfect past: het volgen van websitebezoekers en het visualiseren van hun interacties in real-time. U zou real-time statistieken van uw gebruiker kunnen verzamelen, of het zelfs naar een hoger niveau kunnen tillen door gerichte interacties met uw bezoekers te introduceren door een communicatiekanaal te openen wanneer zij een specifiek punt in uw trechter bereiken – een voorbeeld hiervan is te vinden met CANDDi.

Stelt u zich eens voor hoe u uw bedrijf zou kunnen verbeteren als u wist wat uw bezoekers in real-time aan het doen waren – als u hun interacties zou kunnen visualiseren. Met de real-time, bidirectionele sockets van Node.js, kunt u dat nu.

SYSTEM MONITORING DASHBOARD

Nu, laten we eens naar de infrastructuurkant van de dingen gaan. Stel je bijvoorbeeld een SaaS provider voor die zijn gebruikers een service-monitoring pagina wil aanbieden (bijvoorbeeld de GitHub Status pagina). Met de Node.js event loop, kunnen we een krachtig web-gebaseerd dashboard maken dat de status van de diensten op een asynchrone manier controleert en gegevens naar clients pusht met behulp van websockets.

Zowel interne (intra-company) als openbare statussen van diensten kunnen live en in real-time worden gerapporteerd met behulp van deze technologie. Duw dat idee een beetje verder en probeer je een Network Operations Center (NOC) voor te stellen dat toezicht houdt op toepassingen in een telecommunicatie-exploitant, cloud/netwerk/hosting provider, of een financiële instelling, allemaal draaiend op de open web stack ondersteund door Node.js en websockets in plaats van Java en/of Java Applets.

Note: Probeer niet om harde real-time systemen in Node.js te bouwen (dat wil zeggen, systemen die consistente responstijden vereisen). Erlang is waarschijnlijk een betere keuze voor die klasse van toepassingen.

SERVER-SIDE WEB APPLICATIES

Node.js met Express.js kan ook worden gebruikt om klassieke webapplicaties aan de server-side te maken. Echter, hoewel mogelijk, is dit verzoek-antwoord paradigma waarin Node.js gerenderde HTML zou ronddragen niet de meest typische use-case. Er zijn argumenten voor en tegen deze aanpak aan te voeren. Hier zijn enkele feiten om te overwegen:

Pros:

  • Als uw toepassing geen CPU-intensieve berekeningen heeft, kunt u het bouwen in Javascript van boven naar beneden, zelfs tot aan de database-niveau als u JSON opslag Object DB zoals MongoDB gebruikt. Dit vergemakkelijkt de ontwikkeling (inclusief het inhuren) aanzienlijk.
  • Crawlers ontvangen een volledig gerenderde HTML-respons, die veel SEO-vriendelijker is dan, laten we zeggen, een Single Page Application of een websockets-app die bovenop Node.js wordt uitgevoerd.

Cons:

  • Elke CPU-intensieve berekening zal de responsiviteit van Node.js blokkeren, dus een threaded platform is een betere aanpak. Als alternatief zou je kunnen proberen de berekeningen te schalen(*).
  • Het gebruik van Node.js met een relationele database is nog steeds een hele klus (zie hieronder voor meer details). Doe jezelf een plezier en pak een andere omgeving zoals Rails, Django, of ASP.Net MVC als je probeert om relationele bewerkingen uit te voeren.

(*) Een alternatief voor CPU-intensieve berekeningen is het creëren van een zeer schaalbare MQ-gebaseerde omgeving met back-end verwerking om Node te houden als een front-facing ‘bediende’ om client verzoeken asynchroon af te handelen.

SERVER-SIDE WEB APPLICATIE MET EEN RELATIONELE DATABASE BEHIND

In vergelijking van Node.js met Express.js tegen Ruby on Rails, bijvoorbeeld, is er een zuivere beslissing in het voordeel van de laatste als het gaat om relationele toegang tot gegevens.

Relationele DB-tools voor Node.js zijn nog steeds vrij onderontwikkeld, in vergelijking met de concurrentie. Aan de andere kant, Rails biedt automatisch datatoegang setup recht uit de doos, samen met DB-schema migraties ondersteuning tools en andere juweeltjes (woordspeling bedoeld). Rails en zijn collega-frameworks hebben volwassen en bewezen Active Record of Data Mapper data toegang laag implementaties, die je zult missen als je probeert om ze te repliceren in pure JavaScript.(*)

Toch, als je echt geneigd om JS all-the-way te blijven, kijk op Sequelize en Node ORM2.

(*) Het is mogelijk en niet ongebruikelijk om Node.js alleen als een publieke façade te gebruiken, terwijl je je Rails back-end en zijn gemakkelijke toegang tot een relationele DB behoudt.

Zware SERVER-SIDE COMPUTATIE/PROCESSING

Wanneer het op zware berekeningen aankomt, is Node.js niet het beste platform dat er is. Nee, je wilt zeker geen Fibonacci berekeningsserver bouwen in Node.js. In het algemeen annuleert elke CPU-intensieve bewerking alle doorvoervoordelen die Node biedt met zijn event-driven, non-blocking I/O-model, omdat alle inkomende verzoeken zullen worden geblokkeerd terwijl de thread bezig is met je number-crunching.

Zoals eerder gezegd, Node.js is single-threaded en gebruikt slechts een enkele CPU-kern. Als het gaat om het toevoegen van concurrency op een multi-core server, is er wat werk wordt gedaan door het Node core team in de vorm van een cluster module. Je kunt ook vrij eenvoudig meerdere Node.js server instances draaien achter een reverse proxy via nginx.

Met clustering, zou je nog steeds alle zware berekeningen moeten offloaden naar achtergrond processen die geschreven zijn in een meer geschikte omgeving daarvoor, en ze laten communiceren via een bericht wachtrij server zoals RabbitMQ.

Zelfs al zou je achtergrond verwerking in eerste instantie op dezelfde server draaien, een dergelijke aanpak heeft het potentieel voor zeer hoge schaalbaarheid. Die achtergrondverwerking diensten kunnen gemakkelijk worden gedistribueerd naar aparte worker servers zonder de noodzaak om de lasten van front-facing web servers configureren.

Natuurlijk, zou je dezelfde aanpak te gebruiken op andere platforms ook, maar met Node.js krijg je die hoge reqs/sec doorvoer waar we het over hebben gehad, omdat elk verzoek een kleine taak is die heel snel en efficiënt wordt afgehandeld.

Conclusie

We hebben Node.js van theorie tot praktijk besproken, beginnend met zijn doelen en ambities, en eindigend met zijn sweet spots en valkuilen. Wanneer mensen tegen problemen met Node aanlopen, komt het bijna altijd neer op het feit dat blokkeringsoperaties de wortel van alle kwaad zijn – 99% van het Node-misbruik komt als een direct gevolg.

Houd in gedachten: Node.js is nooit gemaakt om het compute schalingsprobleem op te lossen. Het is gemaakt om het I/O-schalingsprobleem op te lossen, en dat doet het heel goed.