Nanoservices: microservices, maar dan kleiner
Als je een *big ball of mud* refactort krijg je een *monoliet*. Splits die monoliet vervolgens op in modules en je krijgt een *SOA* (Service Oriented Architecture). Geef elke service één verantwoordelijkheid en je hebt *microservices*. Maar het kan nóg kleiner (Ruby Garage, 2021).
In het kader van de minor DevOps heb ik het in dit blog over hoe je met nanoservices een systeem opdeelt in hele kleine services met elk één taak. Ik leg uit wat precies het verschil is tussen microservices en nanoservices en waarom je voor nanoservices moet kiezen (of juist niet). Met behulp van het Serverless framework schrijf ik zelf een nanoservice, natuurlijk op een DevOps manier.
There's always a smaller fish
We kennen microservices als modulaire, onafhankelijke applicaties die op zichzelf deploybaar en schaalbaar zijn. Microservices kunnen een oplossing zijn voor bedrijven die tegen problemen aan lopen zodra hun applicatie begint te schalen. Nieuwe features ontwikkelen blijkt een stuk complexer te zijn zodra je met 1000 developers aan dezelfde codebase moet werken (Kim et al., 2016, pp. 182–183).
Een nanoservice is een stuk code met één publieke methode die net zoals een microservice autonoom is. Microservices hebben één verantwoordelijkheid, en nanoservices hebben één taak. Een microservice zou dus verantwoordelijk kunnen zijn voor het afrekenen van een bestelling, maar een nanoservice kan zo klein zijn dat het alleen de taak heeft om een bevestigingsmail naar de klant te sturen (Ebi, 2020).
Serverless
Een belangrijke eigenschap van nanoservices is dat ze vaak serverless zijn. Dit betekent niet dat het niet op een server draait, maar dat een cloud provider verantwoordelijk is voor het beheren en bereikbaar houden van de infrastructuur. Als developer hoef je je dan alleen nog zorgen te maken over in welke runtime je nanoservice draait (RedHat, 2017). Dit gaat dus nog een stapje verder dan microservices. Microservices deploy je meestal in een container met een besturingssysteem. Bij nanoservices is zelfs het besturingssysteem niet meer relevant.
Replicas: ∞
Bij een serverless architectuur draait je applicatie on-demand. Zodra je een request stuurt naar de nanoservice (of er treedt een event op), zorgt de cloud provider (bijvoorbeeld AWS) dat er een nieuwe instantie van je nanoservice opgestart wordt. Zodra de request of event afgehandeld is, wordt de nanoservice weer uitgezet (RedHat, 2017).
Pitstop uitbreiden met een nanoservice
Nu we weten wat het verschil is tussen een microservice en een nanoservice, gaan we een nanoservice schrijven. Ik gebruik voor dit voorbeeld de Pitstop applicatie van Edwin van Wijk (2017). Dit is een werkplaats management systeem voor een fictieve auto-garage. Deze applicatie wordt als voorbeed gebruikt voor verschillende software-architectuur concepten zoals microservices, Domain Driven Design en CQRS.
Wat je nodig hebt
- NodeJS
- Optioneel: een AWS account (Free Tier)
Wat we gaan maken
Om in Pitstop een reparatie te plannen moet je eerst je voertuig registeren. Hiervoor vul je het kentekennummer, merk en type van je auto in. Het merk en type van een auto kun je in Nederland eigenlijk afleiden uit het kentekennummer. Hiervoor kunnen we een API van de RDW gebruiken, maar in het kader van Ubiquitous Language willen we onze Engelse codebase niet mengen met Nederlandse termen. Ook heb je voor deze API een secret key nodig, en deze willen we natuurlijk niet openbaar maken in onze front-end. Laten we hier een nanoservice voor gaan schrijven met behulp van de Serverless documentatie.
Yet Another YAML file
Om te voorkomen dat je voor elke wijziging in je serverless function door 100 AWS menu's moet klikken, maken we gebruik van een serverless application framework. Dit maakt het mogelijk om je nanoservice ook lokaal te testen en voorkomt dat je vast komt te zitten aan één cloud provider. Er zijn meerdere serverless application frameworks, maar hier gebruiken we Serverless Framework omdat deze open-source is en het makkelijker maakt om voor meerdere cloudplatformen te ontwikkelen (Microsoft, z.d.).
Serverless project opzetten
# Serverless draait zelf op NodeJS, maar je kunt je nanoservices in andere talen programmeren
$ npm install -g serverless
# Serverless heeft standaard templates voor veelgebruikte talen, zoals NodeJS op AWS
$ sls create --template aws-nodejs --path get-car-brand
De aws-nodejs
template maakt twee bestanden aan. In serverless.yml
staat de configuratie van je nanoservice, zoals de runtime, hoeveelheid geheugen en het platform waarop je wil deployen. In handler.js
staat de entrypoint van je nanoservice. Deze methode wordt uitgevoerd zodra je de nanoservice aanroept. Deze heeft een event parameter. In het geval van een REST API zou dit dus de request kunnen zijn.
Serverless functie schrijven
Onze nanoservice zal niet veel meer doen dan een request sturen naar de RDW API en het resultaat vertalen naar het Engels.
# Om HTTP requests te sturen kunnen we in NodeJS de axios library gebruiken
$ npm i axios
'use strict';
const axios = require('axios');
const SECRET_KEY = process.env.RDW_API_KEY;
module.exports.getCarBrandAndType = async (event) => {
const voertuigGegevens = await axios
.get(`https://opendata.rdw.nl/resource/m9d7-ebf2.json?kenteken=${event.licensePlate}`,
{ headers: { 'X-App-Token': SECRET_KEY }});
// Onze nanoservice werkt als een HTTP API en geeft dus een response terug.
// Je zou hier eventueel ook een event kunnen publishen naar een event queue.
return {
statusCode: 200,
body: JSON.stringify(
{
brand: voertuigGegevens.data[0].merk,
type: voertuigGegevens.data[0].handelsbenaming
}
),
};
};
Omdat we de nanoservice niet lokaal aanroepen, maar via HTTP, moeten we een event definïeren. Ook heb ik de handler methode een andere naam gegeven, dus pas ik dit aan in de serverless.yml
.
functions:
getCarBrandAndType:
handler: handler.getCarBrandAndType
events:
- http: GET getCarBrandAndType
We kunnen onze nanoservice nu met één command deployen naar onze gewenste cloud provider. Ook dit definieer je in je serverless.yml
.
provider:
name: aws
runtime: nodejs16.x
lambdaHashingVersion: 20201221
The DevOps way
Laten we nu kijken hoe we nanoservices volgens een DevOps methode kunnen ontwikkelen volgens de DevOps best practices van Atlassian (z.d.).
Shift Left
Het is onhandig om onze nanoservice telkens naar de cloud te moeten deployen om het te kunnen testen. Unit tests kun je natuurlijk gewoon in je project zetten zoals je dat normaal doet, maar voor integratietests gebruiken we Serverless local (Skierkowski, 2019). Onze integration tests schrijven we in een serverless.test.yml
bestandje.
- name: gets the brand and type by license plate
endpoint:
method: GET
path: /carBrandAndType/58GGG8
response:
body:
brand: RENAULT
type: CLIO
$ sls test
passed - GET /carBrandAndType/58GGG8 - gets the brand and type by license plate
Silver Bullet
Ik hoor je nu al denken: "Al deze moeite voor één functie?". Zoals bijna elke architecturale stijl zijn nanoservices natuurlijk geen silver bullet.
Voor- en nadelen van nanoservices
Fowler (2015) beschrijft op zijn blog een aantal voor- en nadelen van microservices die min of meer ook gelden voor nanoservices.
Ten eerste kun je met microservices en nanoservices verschillende programmeertalen mixen. Ik vind dit persoonlijk een goede use case voor nanoservices. Stel dat je een library hebt gevonden die heel veel werk voor je wegneemt, maar deze is niet in jouw programmeertaal geschreven. Deze zou je dan in een nanoservice kunnen gebruiken en aanroepen vanuit je bestaande applicatie.
Daar tegenover staat dat het aanroepen van een nanoservice kosten met zich meebrengt. Het aanroepen van een nanoservice verhoogt de latency. Daarnaast is de foutafhandeling voor een remote call ingewikkelder dan voor een lokale functie-aanroep.
Om met al deze complexiteit om te gaan heb je ook weer nieuwe vaardigheden en tooling nodig die niet iedereen heeft. Fowler (2015) denkt dat het invoeren van een DevOps cultuur ook helpt bij deze complexiteit.
Autonomie
Een reden voor veel DevOps organisaties om over te stappen op microservices is zodat development teams de vrijheid hebben om onafhankelijk van elkaar te ontwikkelen (InfoQ, 2015). Een nanoservice is zo klein dat je er met één persoon al aan kan werken. Zo creëer je dus eigenlijk een cultuur waarin samenwerking moeilijker wordt.
Conclusie
Het opsplitsen van je applicatie kan een oplossing zijn wanneer je erachter komt dat de onderdelen van je applicatie te sterk aan elkaar gekoppeld zijn. Maar zoals het KISS-principe voorschrijft is het ook belangrijk om je applicatie zo simpel mogelijk te houden. De overhead die nanoservices met zich meebrengen is niet iets wat elke organisatie kan gebruiken.
Misschien zorgt de verdere ontwikkeling van tooling (zoals Serverless) er wel voor dat het makkelijk wordt om meerdere nanoservices te beheren zonder de onnodige overhead.
Bronnen
- Atlassian. (z.d.). DevOps Best Practices. Geraadpleegd op 7 oktober 2021 van https://www.atlassian.com/devops/what-is-devops/devops-best-practices
- D., A. (2021, 27 juli). Best Architecture for an MVP: Monolith, SOA, Microservices, or Serverless? RubyGarage. https://rubygarage.org/blog/monolith-soa-microservices-serverless
- Ebi, C. (2020, 25 februari). The rise of nanoservices. Increment. https://increment.com/software-architecture/the-rise-of-nanoservices
- Fowler, M. (2015, 1 juli). Microservice Trade-Offs. martinfowler.com. https://martinfowler.com/articles/microservice-trade-offs.html
- Kim, G., Debois, P., Willis, J., Humble, J., & Allspaw, J. (2016). The DevOps Handbook. Amsterdam University Press.
- Microsoft. (z.d.). Oplossingen met meerdere cloudomgevingen met Serverless Framework - Azure Example Scenarios. Microsoft Docs. Geraadpleegd op 6 oktober 2021 van https://docs.microsoft.com/nl-nl/azure/architecture/example-scenario/serverless/serverless-multicloud
- RedHat. (2017, 31 oktober). What is serverless? https://www.redhat.com/en/topics/cloud-native-apps/what-is-serverless
- Retter, M. (2021, 24 februari). Netflix AWS: The Netflix Serverless Case Study. Dashbird. https://dashbird.io/blog/serverless-case-study-netflix/
- Skierkowski, M. (2019, 10 juli). Basic Integration Testing with Serverless Framework. Serverless. https://www.serverless.com/blog/serverless-test-framework
- Van Wijk, E. (2017, 26 september). GitHub - EdwinVW/pitstop. GitHub. https://github.com/EdwinVW/pitstop