Loskomen van de monoliet

En hoe we evolueerden met Microservices

(19 juli 2018)

A Monolith rule code apen

Bij Conio begon het allemaal met wat gewoonlijk een “monoliet” wordt genoemd. Dat wil zeggen, een enkele codebase die alles van de volledige applicatie bevat: de frontend-componenten, backend-componenten, API-services, achtergrondtaken; hel, zelfs devops-scripts. En het werkte in het begin heel goed. Slechts een paar software-engineers, die aan afzonderlijke delen van de codebase werken (dus zeer kleine kans op codewijzigingsconflicten), eenvoudig te implementeren. Volledige focus op het schrijven van toepassingsfunctionaliteiten, zonder je over veel andere zorgen te hoeven maken. Hoe we de implementatie benaderden? Omdat slechts een paar bètaklanten op de hoogte waren van de voortdurende vooruitgang, was het geen echt probleem om services een tijdje stil te leggen, de volledige codebase uit te rollen (ongeacht hoe klein of groot de algehele veranderingen waren en of ze ook databasemigraties omvatten), en dan weer de diensten aanbieden.

Het was absoluut bevredigend om te zien hoe een product vanaf het begin vorm kreeg en waardering kreeg van eindklanten. We wisten echter heel goed dat deze benadering niet past bij een modern Fintech-bedrijf.

Wat dan?

Met de meeste softwaretoepassingen zijn klanten vrij tolerant. Ja, Whatsapp werkt mogelijk niet meer en kan een paar uur uitval hebben: zeker hinderlijk, maar geen waargenomen probleem. Hetzelfde geldt voor Pokemon Go of je favoriete game-app. dat is echter niet het geval als het om geld gaat : stemmingswisselingen als u niet kunt inloggen op uw bankrekening of niet in staat bent om handelsactiviteiten uitvoeren. Dit is nog erger in het geval van cryptocurrency-applicaties: de meeste mensen herinneren zich beruchte blunders uit het verleden, en wanneer ze zelfs voor een korte tijd geen toegang hebben tot hun cryptocurrency-fondsen, ontstaan ​​er speculaties. Dat is eerlijk. Het is uw geld, en u zou weinig of geen problemen moeten hebben wanneer u het wilt gebruiken.

De bovenstaande Monolith is niet geschikt voor een dergelijk scenario: elke wijziging aan de codebase in productie zou een volledige implementatie vereisen, met bijbehorende downtime. Elke dag werken we aan het verbeteren van onze services door bugs te verhelpen, onze interface nog vriendelijker te maken, oude functionaliteiten te verwijderen en nieuwe toe te voegen die beter kunnen worden gebruikt. We brengen deze updates vaak dagelijks uit, zodat onze klanten onmiddellijk kunnen profiteren, en we streven ernaar om geen enkele invloed te hebben op de ervaring van de klant. Dat wil zeggen, welke formule we ook achter de schermen bedenken, moet onzichtbaar zijn voor de buitenwereld (althans, meestal). Dus zijn we weggegaan van de Monolith en hebben we gekozen voor wat gewoonlijk Microservices-architectuur wordt genoemd.

Evolutie via Microservices

De enorme, stevig gelijmde enkele codebasis is nu opgesplitst in kleinere delen, die elk een bepaalde service vertegenwoordigen. Telkens wanneer ze worden uitgevoerd, communiceren services synchroon met elkaar via het standaard HTTP-protocol en asynchroon via wachtrijen (afgehandeld door bijvoorbeeld RabbitMQ en Apache Kafka).

Interacties in een Microservices-architectuur

Het is een behoorlijke uitdaging om de monoliet in kleinere componenten op te splitsen, maar het is zeer de moeite waard de moeite. In militaire termen lijkt het sterk op wat Julius Caesar deed om gestaag over het grote grondgebied van Gallia te regeren: “ verdeel en heers “.

1) Product kan continu worden ingezet. Een code-update is nu alleen van toepassing op een microservice: in de meeste gevallen kan deze onmiddellijk worden ingezet voor productie en worden vrijgegeven zonder impact voor de klant.

2) Code is gemakkelijker te beheren. Vanuit het perspectief van een bedrijfsorganisatie veranderen dingen wanneer een team van 2 software-engineers een team van 10 software-engineers wordt. Het is effectiever en met minder codeconflicten wanneer elk teamlid verantwoordelijk is voor zijn / haar eigen microservice.

3) Code is gemakkelijker te onderhouden. Een Microservices-architectuur vereist van nature de definitie van een interface om te communiceren met de externe wereld (of het nu de frontend-app of een andere backend-service is) en is volledig geïsoleerd van elk ander gezichtspunt. Dit maakt het mogelijk om afzonderlijke componenten van de applicatie opnieuw te bekijken, opnieuw te ontwerpen of zelfs helemaal opnieuw te schrijven (zelfs in verschillende talen indien dit uitkomt) zonder de rest te beïnvloeden.

4) De prestaties kunnen worden verbeterd. Elke microservice kan nu de meest geschikte taal gebruiken. Zware cryptografische rekencomponenten kunnen bijvoorbeeld worden geoptimaliseerd in C, terwijl API-services in Python en langlopende taken in Go.

5) Verbeterde code-isolatie en beveiliging. Elke microservice kan in zijn eigen Docker-container worden uitgevoerd, waardoor privilege-isolatie, netwerk- en gegevensscheiding wordt geboden en, van het allergrootste belang voor een groeifase, een enorm schaalbaarheidspotentieel.

Zijn Microservices dan het antwoord?

Natuurlijk bestaat er niet zoiets als een gratis lunch. Een Microservices-architectuur heeft ook zijn eigen reeks moeilijke uitdagingen:

1) Operationele complexiteit. DevOps-technici zijn absoluut nodig om de fijne kneepjes van het nieuwe implementatieproces glad te strijken.

2) Hardware bloat. Microservices worden vaak uitgevoerd in Docker-containers; zodra het aantal microservices toeneemt, wordt het steeds uitdagender om de volledige applicatie op dezelfde hardware te draaien als voorheen.

3) Intercommunicatie-overhead: elk verzoek moet mogelijk communiceren met een of meer verschillende microservices via het netwerk. Dit kan een verhoogde latentie veroorzaken en mogelijk onderhevig zijn aan tijdelijke storingen. Om veerkrachtige services te implementeren en de schaalbaarheid van het hele systeem te verbeteren, is het nodig om interacties te verplaatsen naar asynchrone berichtenuitwisseling (bijv. Met Apache Kafka en / of RabbitMQ).

4) Eventuele consistentie. Dit is waarschijnlijk de moeilijkste uitdaging van een Microservices-architectuur. Gegeven een enkele microservice is het mogelijk om RDBMS-transacties binnen zijn grenzen te creëren . Helaas is een veelvoorkomend probleem bij gedistribueerde architecturen het omgaan met meerdere transacties die niet binnen dezelfde grenzen vallen. Als gevolg hiervan kan het systeem in een illegale en onherstelbare staat terechtkomen. Om dergelijke problemen te verminderen, past Conio verschillende strategieën toe:

  1. Volg de praktijken van Domain Driven Design, ontleed de domeinen op een hoger niveau in subdomeinen en beperk ze tot individueel begrensde contexten ; elke begrensde context wordt geïmplementeerd als een microservice, waarbij transactiegrenzen worden toegepast. Dit lost de mogelijkheid op om inconsistenties te hebben voor specifieke subdomeinen.
  2. Implementeer idempotente asynchrone interacties, die vroeg of laat inconsistenties oplossen.
  3. Vermijd waar mogelijk elke actie waarbij meerdere subdomeinen betrokken kunnen zijn.

5) Complexe rapportage. Aangezien elk subdomein binnen een specifieke begrensde context leeft, kan het nodig zijn dat complexe rapporten die meerdere subdomeinen omvatten, gegevens uit meerdere gegevensbronnen moeten opvragen: dit kan zowel de expressiviteit van de domeinen als de schaalbaarheid van het systeem negatief beïnvloeden. Hier in Conio hebben we een CQRS-architectuur aangenomen om backoffice-activiteiten en bedrijfsanalyserapporten te ondersteunen.

6 ) Logboeksysteem. Elk element in een gedistribueerd systeem draagt ​​bij aan het creëren van het logboek van het hele systeem. Het is echter noodzakelijk om tools te implementeren die de benodigde verbindingen tussen al dergelijke gescheiden logboeken kunnen maken om een ​​uniform logboek te hebben voor elke interactie. Hier in Conio gebruiken we ELK (ElasticSearch, Logstash, Kibana) stack om logboekgegevens op te slaan en op te vragen: elk logboek is verrijkt met de nodige correlatie-ids die het bovengenoemde uniforme logboek mogelijk maken.

Stop nooit de evolutie

Onze mening? Het opsplitsen van de eerste enkele codebase moet worden gezien als een langetermijntaak , met voortdurende verfijningen. Bij Conio kostte het ons een paar jaar, waarin we stap voor stap van 1 enorme codebase naar meer dan 100 microservices gingen. We zijn op een punt aangekomen waarop we trots zijn op de resultaten, maar tegelijkertijd blijven we onderzoeken. Er zijn meerdere mogelijke nieuwe optimalisaties: verhuizen van Docker Swarm naar Kubernetes? Migratie van backend-voor-frontend-services naar serverloze lambda-functies? Overschakelen naar een volledige stroom voor continue implementatie? De mogelijkheden zijn eindeloos.

We hebben hier een aantal onderwerpen en technologieën aangeroerd. In de volgende artikelen zullen we meer details over onze bevindingen en voortgang delen. Als je wilt, kun je commentaar geven en ons je ervaringen vertellen.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *