28 december 2022 • • 8 min leestijd januari 6, 2023 at 4:02 pm

Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt

In deze blogpost ligt een case toe waar ik recent aan gewerkt hebt. Het is een technische blog waarin ik patronen bespreek en laat zien hoe we dit oplossen bij een groot geschaald systeem waar meerdere losstaande microservice-websites los van elkaar informatie verwerken en meerdere services domein overstijgend samenwerken. Het voorbeeld dat ik in deze blogpost gebruik is de case van een registratie proces binnen een microservice architectuur. In het eerste stuk van het artikel ligt ik de randvoorwaarden toe en daarna geef ik een praktisch voorbeeld.

Microservices

Microservices zijn een softwarearchitectuur waarbij een applicatie wordt opgebouwd uit kleine, zelfstandige services die elk een specifiek doel vervullen. Deze services communiceren met elkaar via een gestandaardiseerd interface, zoals een API. Dit stelt ontwikkelaars in staat om snel nieuwe functionaliteiten toe te voegen of bestaande te wijzigen, omdat elke service afzonderlijk kan worden ontwikkeld, getest en gedepolyed. Microservices bieden ook flexibiliteit en schaalbaarheid, omdat elke service afzonderlijk kan worden opgeschaald of bijgewerkt zonder de hele applicatie te hoeven aanpassen. Het gebruik van microservices is vooral geschikt voor grote, complexe systemen met veel veranderende vereisten.

Het antipatroon: Chatty microservices of monolithic microservices

Een antipatroon zijn chatty microservices. Dit zijn microservices die frequent communiceren met elkaar door middel van het sturen van veel kleine berichten, zoals HTTP API-aanvragen. Dit kan leiden tot vertragingen en inefficiënties, omdat elke aanvraag tijd kost om te verzenden en te verwerken, daarnaast loopt het proces vast als een service niet direct antwoord geeft. Bovendien kan het ook leiden tot extra foutgevoeligheid, omdat elke aanvraag een potentieel punt is waar iets mis kan gaan. In plaats van chatty microservices is het beter om microservices te ontwerpen die berichten uitwisselen via een servicebus. Daarnaast houden we een outbox-tabel bij om te voorkomen dat een service een bericht niet kan uitvoeren. Wat dit inhoud ga ik in deze blog uitleggen.

Wat is SAGA?

SAGA is een architectuur patroon dat wordt gebruikt om complexe transacties te behandelen die betrekking hebben op verschillende domeinen of systemen. Het patroon is gebaseerd op het idee dat elke transactie kan worden opgedeeld in een aantal kleinere stappen, waarbij elke stap een “compensatie” -stap heeft die kan worden uitgevoerd op het moment dat de transactie niet succesvol is. Dit maakt het mogelijk om transacties te verwerken die over verschillende domeinen of systemen gaan, zonder dat er consistentieproblemen optreden. SAGA is vooral nuttig voor grote, complexe systemen waarbij verschillende componenten uit het systeem benodigd zijn om de transactie te voltooien.

Wat is een servicebus?

Een servicebus is een softwarecomponent die wordt gebruikt om te communiceren tussen verschillende applicaties of systemen. Het stelt deze systemen in staat om berichten naar elkaar te sturen via een gestandaardiseerd protocol, zoals AMQP (Advanced Message Queuing Protocol). Een servicebus kan worden gebruikt om asynchrone communicatie tussen systemen te ondersteunen, wat kan helpen om de prestaties en schaalbaarheid van het systeem te verbeteren. Een bekend voorbeeld van een servicebus is RabbitMQ, een open source implementatie van AMQP. RabbitMQ wordt vaak gebruikt in microservice-architecturen om communicatie tussen verschillende microservices mogelijk te maken.

Docker en Microsoft .NET 6

Docker is een open source containerplatform dat het mogelijk maakt om applicaties te bundelen en te distribueren als containerimages. Deze containerimages kunnen vervolgens op elk systeem met Docker geïnstalleerd worden, wat ervoor zorgt dat de applicatie op elk systeem op dezelfde manier uitgevoerd kan worden. Dit zorgt ervoor dat de kwaliteit omhoog gaat. In technische termen wordt dit: “environment parity” genoemd. .NET is een softwareframework van Microsoft dat wordt gebruikt voor het ontwikkelen van Windows-applicaties. .NET kan echter ook gebruikt worden om webapplicaties te ontwikkelen en is beschikbaar voor verschillende besturingssystemen. Door het gebruik van Docker kunnen .NET-applicaties op een consistente manier worden uitgevoerd op elk systeem met Docker geïnstalleerd, wat het makkelijker maakt om deze applicaties te distribueren en te implementeren.

In wat voor case zet je SAGA bijvoorbeeld in?

Een goede case om SAGA in te zetten is een registratieproces, op basis van dit voorbeeld lig ik de case concreet toe.

Dit registratieproces begint in de microservice voor een registratie. In deze registratieservice worden niet afgemaakte registraties bijgehouden. In ons geval moet een admin gebruiker toestemming (approve) geven voor de gebruiker om in het platform te komen. Pas dan wordt er in het identity systeem een gebruiker opgevoerd en wordt er een terugkoppeling gedaan aan de registratieservice dat de registratie is afgerond.

Hoe ziet de flow eruit:

In deze alinea leg ik het in hoofdlijnen uit, dan heb je een beeld van hoe het proces eruit ziet. In de volgende alinea heb ik een uitgebreidere toelichting van de stappen. De code die te zien is daarnaast ook beschikbaar op Github

Het begint allemaal met een klant die een registratie opvoert

In de onderstaande afbeelding is dit te zien via swagger, een front-end maakt dit natuurlijk helemaal af



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; Registration Create ; SAGA, .NET, Microsoft, Postman

Als je deze Execute dan krijg je een correlationId, dit is een waarde om de registratie aan elkaar te koppelen tussen verschillende losstaande services.



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; registration create output ; SAGA, .NET, Microsoft, Postman

Na dat een registratie is aangemaakt heb je dezelfde CorrelationId nodig om deze openstaande registratie goed te keuren.

Vervolgens wordt er een bericht op de service bus gezet en wordt er door de Identity service een gebruiker aangemaakt. Als je dezelfde correlationId of email adres nog een keer invoert dan wordt het proces gestopt omdat deze gebruiker dan al bestaat.

Het rechterscherm met de Notifications luistert op de servicebus en geeft de registraties realtime weer.



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; Registration Approve ; SAGA, .NET, Microsoft, Postman

Hoe ziet dit proces eruit in meer detail

Om een beter overzicht te geven van de flow heb ik flowchart gemaakt die alle stappen beschrijven. De code die te zien is daarnaast ook beschikbaar op Github



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; Registrations flow ; SAGA, .NET, Microsoft, architectuur

Http request to /create endpoint

Bij de stap “User Submit” wordt er vanuit de controller een event op de servicebus gezet. Dit doen we om alle Sagas bij elkaar te houden en in de Saga service een registratie aan te maken. In het onderstaande screen zie je de controller die een queueing service aanroept. Qua naamgeving houden we hier een command aan, dit is een intern bericht dat niet mis kan gaan. Een command blijft altijd binnen dezelfde service. Dat bekent dus dat een event met command in de naam niet gedeeld wordt tussen verschillende services. Een Event is een servicebus-bericht waar wel een mislukte variant van zou kunnen bestaan en die gedeeld kan worden tussen verschillende services.



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; Create endpoint ; SAGA, .NET, Microsoft, Rider, c#

Registration SAGA listens to RegistrationSubmittedEvent

The Registration Saga is gelinkt aan een onderliggende service, dit is gedaan in de startup door het volgende stuk code uit te voeren: services.RegisterSaga();
De Registration Service is afhankelijk van ISagaStateRepository, dit is een interface die ervoor zorgt dat de Saga service altijd op een gelijke manier een create of update kan uitvoeren.



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; Add Registration ; SAGA, .NET, Microsoft, Rider, c#

De InstanciateAsync zorgt ervoor dat er een registratie wordt aangemaakt met de Status “Created”

Stap: Http request to /approve endpoint

Als er vanuit Swagger een POST request wordt gedaan naar het approve endpoint dan wordt er op een gelijke manier een command servicebus event uitgevoerd zodat de SAGA service deze kan oppakken.



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; Registration Approve ; SAGA, .NET, Microsoft, Postman

Stap: Add RabbitMQ Event to service bus: _RegistrationApprovedEvent

In de onderstaande code wordt een registratie op approved gezet, mocht dat mislukken vanwege een Database error dan wordt er een NackException gegooid. Nack betekent Not Acknowledged. Een NackException is om RabbitMQ te laten weten dat die het opnieuw te proberen op een later moment. Als het een andere fout is dan wordt de registratie als mislukt opgegeven.



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; Approve Registration Command ; SAGA, .NET, Microsoft, Rider, c#

Stap: Create User

In de Identity service wordt hetzelfde patroon toegepast. De InstanciateAsync zorgt ervoor dat er een gebruiker wordt opgevoerd. In de onderliggende service UserService wordt een gebruiker aangemaakt in de database.



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; Create User ; SAGA, .NET, Microsoft, Rider, c#

In de Registratie Service wordt er aan het einde de status bijgewerkt zodat er een duidelijk overzicht is van succesvolle registraties



Hoe zet je microservices zo in dat altijd elk bericht minimaal één keer aankomt ; User Created Event ; SAGA, .NET, Microsoft, Rider, c#

Conclusie

Het SAGA architectuur patroon zorgt ervoor dat elk bericht minimaal één keer aankomt. Het is een complex patroon dat mogelijk relevant kan zijn binnen een complexe architectuur. Ik hoop dat ik doormiddel van deze praktische case een aantal handvaten heb gegeven.

Dit bericht is geschreven door: Dion

Tags: ,

Gecategoriseerd in:

22 december 2022 • • 6 min leestijd maart 26, 2024 at 4:10 pm

De 7 dingen die ik miste bij het beheren van mijn foto collectie

Ik kan erg van genieten om op pad te gaan en te fotograferen. Maar als ik dan thuiskom wordt het tijd om al die foto’s goed te organiseren. Dat taakje is typisch iets dat we uitstellen, maar het is wel leuk om de foto’s en ervaringen te delen – bijvoorbeeld op deze blog en verschillende sociale mediakanalen waar ik regelmatig iets publiceer. Het was dus de hoogste tijd om het tijdrovende proces van fotobeheer te optimaliseren. Ik dit artikel behandel ik aan de hand van mijn eigen ervaringen de oplossingen die ik gevonden voor de 7 meest voorkomende problemen bij het effectief beheren van een foto collectie.

1. Snel zoeken, snel vinden

Die ene foto waar je naar op zoek bent terugvinden kan best moeilijk zijn. Daarom is snel en gemakkelijk vinden waarnaar je op zoek was een geweldige manier om veel meer te kunnen doen in een kortere tijd. Zo werkt bijvoorbeeld Spotlight (zoeken) op Mac OS uitstekend, al zit dit niet geïntegreerd in de mobiele versie. Wanneer je in een afbeelding het meta-dataveld een aantal steekwoorden plaatst kun je hier later op zoeken. Maar ook op datum, omschrijving en informatie die in de camera wordt opgeslagen.

Snel zoeken, snel vinden

2. Wisselen van desktop naar mobiele weergave

Naadloos wisselen tussen je laptop, iPad en telefoon levert nog vaak problemen op.
Je moet lang wachten, die ene map is laad niet of is niet bereikbaar.
Snel even iets laten zien aan iemand is nog een heel gedoe.
Daarom heb ik nagedacht over de beste manier om te zorgen dat je snel en gemakkelijk kan wisselen tussen de desktop en mobiele applicatie,
zonder essentiële functionaliteiten in te moeten leveren.
Even opzoeken wanneer iets geweest is of de foto’s van die geweldige vakantie tevoorschijn toveren wordt zo een stuk gemakkelijker.



De 7 dingen die ik miste bij het beheren van mijn foto collectie ; Demo site op mobiel ; photo mangement, mobiel, applicatie, foto beheren

3. Klaar maken voor publicatie en het toevoegen van een watermerk

Voor het maken van een blog- of social media post maken we eerst een selectie van de foto’s die we gemaakt hebben. Maar hoe krijg je ze dan in het juiste format? Dat is vaak een taakje waarmee je moet wachten tot je thuis bent en een computer met de juiste software nodig hebt.

In mijn eigen beheerprogramma geef ik verschillende foto’s die bij elkaar horen een selectiekleur. Zo kan ik gemakkelijk een selectie maken van de foto’s die ik wil publiceren.

Op basis van deze selecties doe ik een automatische publicatie actie. Deze plaatst een watermerk in de afbeeldingen. Zelf gebruik ik een aantal opties die ik van tevoren door middel van instellingen heb gedefinieerd. Door middel van configuratie is het ook mogelijk dat er html wordt gegeneerd waarbij meta informatie ook bijgevoegd zodat zoekmachines deze inhoud sneller kunnen vinden. Een andere optie is om bijvoorbeeld een aantal verkleinde afbeeldingen te maken voor bijvoorbeeld Instagram.

Publicatie tool

4. Slim aanvullen van geolocatie data

Mobiele telefoons houden de locatie van een foto nauwkeurig bij.
Alleen doen veel camera’s dit nog niet.
Om te voorkomen dat ik de locatie gegevens handmatig moet toevoegen aan elke foto gebruik ik een sport-app op mijn telefoon.
Die houdt mijn locatie bij. Zorg ervoor dat de camera op tijd staat.
Vervolgens exporteer ik deze informatie uit de sport app waarna de photo management applicatie het geheel automatisch bij elkaar brengt.
Dan plaats ik het gpx-bestand dat alle locaties en tijden bevat in dezelfde map en draai een script dat deze integratie automatiseert.
Op deze manier kan je de verschillende informatiebronnen aan elkaar te koppelen.
Hierbij is het wel belangrijk dat de camera exact op tijd staat zodat de locatie op die tijd overeenkomt met de locatie in de sport-app.

GPX aanvullen met data, met 1 klik locatie data toevoegen

5. Foto’s en video’s importeren met minimale handmatige acties

De foto’s en video’s van je camera naar je fotobibliotheek overplaatsen is niet ingewikkeld, maar vaak een handmatig taakje.
Toch is dit eenvoudig te automatiseren.
Je sluit je SD-kaart of USB-kabel aan en de rest van het proces doet de computer zelfstandig.
Als jouw fotobibliotheek meer structuur heeft, bijvoorbeeld door de foto’s op datum te ordenen kan je dit ook configureren in de instellingen.
Voor nu gebruik daar een achtergrondscript voor die automatisch wordt getriggerd als er een sd-kaart wordt aangesloten
en dan worden de afbeeldingen die op de geheugenkaart staan automatisch op de juiste plek gezet.

Foto's en video's importeren met minimale handmatige acties

6. Zonder connectie met het internet

Op sommige plekken is de connectie nog niet zo goed om gigabytes aan informatie over de lijn te sturen.
Een clouddienst op zo’n moment niet de meest handige oplossing.



De 7 dingen die ik miste bij het beheren van mijn foto collectie ; Raspberry Pi met de applicatie draaiend ; Raspberry pi, Tafel, computer

7. Baas over eigen data en zonder problemen wisselen naar andere software

Data is het nieuwe goud en momenteel profiteren vooral de grote techbedrijven daarvan.
De oplossing zit in een transparanter systeem dat de macht over data terugbrengt bij de gebruiker.
In elke foto zitten namelijk verborgen velden waar informatie kan worden opgeslagen.
Alle informatie over een specifieke foto die je de photo management tool invoer komt in de foto terecht.
Dat is dus inclusief labels, omschrijving en locatie informatie. Bij het publiceren kun je ervoor kiezen om deze informatie niet mee te nemen in de kopie.
De tool maakt gebruik van standaarden, alle meta informatie die in de afbeelding staat wordt ook in de afbeelding zelf weggeschreven.
Je kunt dus elk moment switchen, alle data die je ingevoegd hebt blijft bewaard in de foto.



De 7 dingen die ik miste bij het beheren van mijn foto collectie ; Meta gegevens in sync ; exiftool, meta, gegevens, afbeelding, management

Conclusie

Als oplossing voor deze 7 problemen heb ik een server en desktopapplicatie ontwikkeld.
Mijn doel was om het tijdrovende organisatie proces van foto’s te optimaliseren – en bovendien heb ik veel bijgeleerd bij het maken van deze applicatie.
De app is nog in een bèta-stadium. De broncode van het project is gratis online beschikbaar op GitHub
en er is een demo omgeving zodat dit project een bijdrage kan leveren om ook jouw leven nét iets makkelijker te maken.
Er is ook een versie beschikbaar die je direct kan uitvoeren.
Voor Mac OS gebruikers zal de applicatie voor een foutmelding zorgen aangezien de app geen officiële Apple certificaten heeft.
In de instellingen van je computer kan je handmatig deze toegang verlenen. Door rechtermuis knop openen te klikken, voorbij de waarschuwingen te klikken

Zelf uitproberen op een demo omgeving? Dat kan! Ga naar demo.qdraw.nl om de app te testen

Dit bericht is geschreven door: Dion

Tags: , ,

Gecategoriseerd in:

4 maart 2022 • • 13 min leestijd november 14, 2022 at 2:28 pm

Een eigen NPM private feed maken met Azure Devops

In deze blogpost ga ik het hebben over een technische oplossing waarbij je software onderdelen kunt delen tussen verschillende applicaties. Dit is alleen relevant als je meerdere applicaties hebt en hier onderdelen tussen wil delen.

There is an English version of this blog post

TL;DR; (Samenvatting)

In deze blog koppelen we een private NPM-feed (Node Package Manager) om deze te gebruiken tijdens het ontwikkelen, in een container en in de Continuous Integration (CI) pipelines. Dit doe ik om versies te hebben van componenten. Ik gebruik hier de tools Azure DevOps, NPM, Nodejs en Docker. Om er voor te zorgen dat de authenticatie gebruiken we een Oath2 flow waarbij vlak voor de installatie (preinstall) tokens ophalen. Er is een Github repository aanwezig waar een werkend voorbeeld wordt gegeven

Project wensen

Voor een project waar ik aan werk bestaat de wens naar losse NPM registries. NPM staat voor Node Package Manager. NPM wordt gebruikt om javascript gebaseerde projecten online te her gebruiken. Hiervoor wordt de publieke npmjs.com-feed gebruikt. Als je code wil delen tussen een beperkte set gebruikers of teams kun je private user (project) of organisation-scoped packages publiceren naar het npm registry. User (project) scoped betekent dat deze alleen binnen hetzelfde Azure DevOps project wordt gedeeld en als je organisation-scoped kiest wordt deze binnen de hele organisatie gedeeld. Deze private NPM registries hebben als voordeel dat front-end applicaties eigen ontwikkelde software onderdelen kunnen uitwisselen.

Wat is Azure DevOps

Azure DevOps is een tool voor ontwikkelaars services waarmee teams werk kunnen plannen, samenwerken aan code-ontwikkeling en toepassingen kunnen bouwen en implementeren. Azure DevOps ondersteunt een samenwerkingscultuur en een reeks processen die ontwikkelaars, projectmanagers en bijdragers samenbrengen om software te ontwikkelen. Wij gebruiken de pipeline functionaliteiten het meeste. Dat is het centraal compileren van software en het releasen naar omgevingen.

Azure DevOps Artifacts

Naast de pipelines bestaan er ook Artifacts. Met Azure Artifacts kunnen ontwikkelaars pakketten uit verschillende feeds en openbare registers delen en gebruiken. Pakketten kunnen worden gedeeld binnen hetzelfde team, dezelfde organisatie en zelfs openbaar. Azure Artifacts ondersteunt meerdere pakkettypen, zoals NuGet, npm, Python, Maven en Azure Universal Packages.

Zo ziet een feed er bijvoorbeeld uit:



Een eigen NPM private feed maken met Azure Devops ; Overzicht van een private npm feed ; Azure Devops, private feed

Docker containers

Voor het project gebruiken we Docker containers om de software te draaien.
Een container is een manier om software en alle dependencies van software bij elkaar te houden en makkelijk uit te wisselen tussen verschillende omgevingen. Een Docker-containerimage is een lichtgewicht, op zichzelf staand, uitvoerbaar softwarepakket dat alles bevat wat nodig is om een applicatie uit te voeren: code, runtime, systeemtools, systeembibliotheken en instellingen.

Azure’s Windows only oplossing

De officiële tool om de authenticatie naar je private NPM feed te regelen is vsts-npm-auth alleen deze werkt alleen op Windows. Aangezien we in ons project een combinatie van Mac, Windows en Docker werken is dit geen werkbare oplossing in onze situatie.

Personal access tokens (PAT’s)

Helaas werken PAT’s niet goed in combinatie met het ophalen van NPM-packages uit de Azure Artifacts feed. Ik krijg hier 401 errors op en kom er niet verder mee.

Een alternatief: better-vsts-npm-auth

Ik ben verder gaan kijken en kwam het project better-vsts-npm-auth tegen. Deze tool doet een oauth2 authenticatie richting Azure DevOps en zorgt ervoor dat je als gebruiker een valide token hebt om npm packages mee op te halen

In de praktijk: Stappen die vooraf nodig zijn

De volgende stappen heb je nodig om zelf een refresh token te generen.

  1. Voordat je begint: Zorg ervoor dat op je ontwikkelmachine een recente versie van nodejs is geïnstalleerd
  2. Zorg ervoor dat je toegang hebt tot het Azure DevOps Artifacts gedeelte: https://dev.azure.com/YOUR_ORG/_packaging

 

Ik heb onderstaande code snippets verzameld en in een losse repository gezet. Deze is op Github te vinden en daar te downloaden.

 

Maak een nieuwe feed aan in Azure Devops

Binnen Azure DevOps valt dit onder Artifacts klik dan op “+ Create Feed”

Je kunt hier kiezen uit twee verschillende scopes

  1. Project, dus alleen het huidige project
  2. Organization: binnen de huidige Azure DevOps organization

 



Een eigen NPM private feed maken met Azure Devops ; Voeg een nieuwe feed toe ; Azure Devops, Add new feed

Installeer de global package better-vsts-npm-auth

Op je lokale machine installeer je better-vsts-npm-auth als globaal pakket zodat deze vanaf verschillende locaties aangeroepen kan worden

Doe dit middels het volgende commando:


npm i -g better-vsts-npm-auth

 

Setup voor front-end library

Ga naar je front-end library folder en zorg ervoor dat er twee dingen minimaal in staan.

  1. project .npmrc file
    De project npmrc bevat de locaties van je eigen private feeds met een prefix van een pakketnaam. Hieronder geef ik een voorbeeld van hoe ik dat toepas.
  2. package.json bestand
    Dit bevat de namen van alle dependencies die worden gebruikt.

 

1. Library Project .npmrc file

 


@qdraw-components:registry=https://pkgs.dev.azure.com/qdraw/_packaging/demo/npm/registry/
always-auth=true

 

2. Library Project package.json file

 


{
    "name": "@qdraw-components/components",
    "version": "0.0.1-rc.1",
    "description": "Test front-end components",
    "scripts": {
    },
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "typescript": "^4.5.5"
    },
    "dependencies": {
    },
    "main": "dist/cjs/index.js",
    "module": "dist/esm/index.js",
    "files": [
        "dist"
    ],
    "types": "dist/index.d.ts"
}

 

Authenticatie vanaf je ontwikkelmachine

De authenticatie verloopt vervolgens via de Azure DevOps aan de hand van de recent geïnstalleerde tool. Dit doe ik binnen de front-end library folder waar ook het bovenstaande package.json en npmrc bestand staan.


better-vsts-npm-auth



Een eigen NPM private feed maken met Azure Devops ; better-vsts-npm-auth ; Azure Devops, better-vsts-npm-auth, npmrc

 

Als ik bovenstaande url kopieer in de browser krijg ik het volgende scherm te zien. Druk op Accept om verder te gaan



Een eigen NPM private feed maken met Azure Devops ; Stateless VSTS NPM OAuth ; Azure Devops

 

Wanneer je op Accept klikt krijg je het Succes scherm te zien. Op dit scherm staat een command met een unieke refresh token.

 



Een eigen NPM private feed maken met Azure Devops ; Copy this command and run it in your terminal ; Azure Devops, terminal

 

We doen twee dingen met het token: Het command uitvoeren:


better-vsts-npm-auth config set refresh_token eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im9PdmN6NU1fN3AtSGpJS2xGWHo5M3VfVjBabyJ9.eyJuYW1laW

Kopieer het token gedeelte zonder: better-vsts-npm-auth config set refresh_token. Zet dit als environment variable
DEMO_NPM_REFRESH_TOKEN, hoe je dat doet staat hieronder beschreven. Deze variable hebben we later nodig in Docker.

 

1. Op Mac OS voeg je het volgende toe aan je .zprofile of .zshrc:


nano ~/.zprofile

export DEMO_NPM_REFRESH_TOKEN="LongTokenHere"

Deze instellingen zijn pas actief na het herstarten van je terminalvenster

 

2. Op Windows voeg je het volgende toe:

  • Druk de volgende toetscombinatie in: Windows + R
  • Type: `sysdm.cpl`
  • Click op de Advanced tab
  • Iets boven OK & Cancel click op Environment Variables…
  • Onder System Variable: Press New
  • DEMO_NPM_REFRESH_TOKEN
  • En de token (without the better-vsts prefix)
  • Druk Ok om te bevestigen

Deze instellingen zijn pas actief na het herstarten van je Powershell venster

 

Controleer of je in de juiste omgeving bent ingelogd

Controleer of de app: “Stateless VSTS NPM OAuth” aanwezig is op: https://dev.azure.com/YOUR_ORG/_usersSettings/authorizations

Staat de app daar niet, dan ben je waarschijnlijk verbonden met de verkeerde organisatie. Als dit het geval is, probeer dan de url te kopiëren en te plakken in een browservenster dat zich in de privé- of incognito modus bevindt.



Een eigen NPM private feed maken met Azure Devops ; Controleer of de app in de lijst staat ; Azure Devops, Authorized Oauth Apps

Bij authenticatie problemen

Mochten er problemen zijn met de authenticatie verwijder dan de volgende bestanden uit je home folder. Op Mac OS zou een voorbeeld dit /Users/dion kunnen zijn en op Windows C:\Users\Dion.

  1. “.vstsnpmauthrc”
  2. “.npmrc”
  3. De environment variable “DEMO_NPM_REFRESH_TOKEN”

Beide bestanden bevatten inloggegevens en kunnen veilig worden verwijderd. Probeer het daarna opnieuw. Hieronder toon ik dat de twee bestanden aanwezig zijn



Een eigen NPM private feed maken met Azure Devops ; Je home folder, deze bestanden kun je verwijderen om opnieuw te ; Azure Devops, home folder

 

NPM Publish vanuit front-end library folder

Vanuit de front-end library folder doen we een publish naar Azure DevOps. Zorg ervoor dat bij het publiceren elke keer je in de package.json een uniek oplopend versienummer hebt.



Een eigen NPM private feed maken met Azure Devops ; NPM Publish ; Azure Devops, npm, node package manager, NPM Publish

 

Nu staat deze package in de private npm feed en dat ziet er als volgt uit.



Een eigen NPM private feed maken met Azure Devops ; Nieuw aangemaakte feed ; Azure Devops, private feed

Unieke prefix wanneer je meerdere private feeds hebt

In ons geval gebruiken we meerdere npm feeds voor verschillende onderdelen. In het bovenstaande voorbeeld gebruik ik de prefix: “@qdraw-components” en een slash om deze prefix af te sluiten. Het is belangrijk om de naam op deze manier op te bouwen, dan kun je dit namelijk aangeven in het project npmrc bestand. Mocht je er een feed naast hebben voor icons dan kan deze niet dezelfde prefix hebben. De prefix moet dus uniek zijn en beginnen met een apenstaartje.

 

Docker Applicatie setup

In de applicatie waar de front-end library wordt gebruikt doen we een volgende setup: Hetzelfde. npmrc bestand staat in de applicatiemap.

De project .npmrc file


@qdraw-components:registry=https://pkgs.dev.azure.com/qdraw/_packaging/demo/npm/registry/
always-auth=true

Daarnaast heeft dit project een eigen package.json

package.json


{
  "name": "use_app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "preinstall": "node scripts/preinstall.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@qdraw-components/components": "^0.0.1-rc.1"
  }
}

In de preinstall wordt op basis van een refresh token een access token opgehaald zodat je private packages opgehaald en geïnstalleerd kunnen worden

Het bestand scripts/preinstall.js is te vinden op Github

In de Dockerfile wordt het bestand scripts/cleanauth.js aangeroepen om de inloggegevens uit de container te verwijderen.

scripts/cleanauth.js


#!/usr/bin/env node
 
// remove tokens from docker image
 
const fs = require('fs')
const path = require('path');
 
function UserProfileFolder() {
    let userProfileFolder = "~";
    if (process.env.CSIDL_PROFILE) {
        userProfileFolder = process.env.CSIDL_PROFILE;
    }
    if (process.env.HOME) {
      userProfileFolder = process.env.HOME;
    }
    return userProfileFolder;
}
 
const userProfileFolder = UserProfileFolder();
 
const vstsNpmauthRcFilePath = path.join(userProfileFolder, '.vstsnpmauthrc');
if (fs.existsSync(vstsNpmauthRcFilePath)) {
    fs.unlinkSync(vstsNpmauthRcFilePath)
}
 
const userNpmRcFilePath = path.join(userProfileFolder, '.npmrc');
if (fs.existsSync(userNpmRcFilePath)) {
    fs.unlinkSync(userNpmRcFilePath)
}

Dit project heeft de volgende Dockerfile

Dockerfile


# Install dependencies only when needed
FROM node:16-alpine AS deps
ARG YOURPROJECT_NPM_REFRESH_TOKEN=default
ARG AZURE_AUTH_TOKEN=default
 
RUN apk add --no-cache libc6-compat
WORKDIR /app
 
ENV YOURPROJECT_NPM_REFRESH_TOKEN=$YOURPROJECT_NPM_REFRESH_TOKEN
ENV AZURE_AUTH_TOKEN=$AZURE_AUTH_TOKEN
 
COPY use_app/scripts/preinstall.js ./scripts/preinstall.js
COPY use_app/scripts/cleanauth.js ./scripts/cleanauth.js
COPY use_app/package.json ./
 
RUN node scripts/preinstall.js
RUN npm ci --prefer-offline
 
RUN node scripts/cleanauth.js

Deze Dockerfile kan op twee manieren worden aangeroepen zodat dit een docker container wordt.

 

1. Docker build command


cd root_of_solution
docker build -f use_app/Dockerfile . --no-cache --build-arg DEMO_NPM_REFRESH_TOKEN=${DEMO_NPM_REFRESH_TOKEN}

 

2. Of als Docker compose

docker-compose.yml


version: '3.4'
 
services:
  demo.use.app:
    image: ${DOCKER_REGISTRY-}demouseapp
    build:
      context: .
      dockerfile: use_app/Dockerfile
      args:
        - DEMO_NPM_REFRESH_TOKEN=${DEMO_NPM_REFRESH_TOKEN}
    environment:
      - DEMO=true
    ports:
      - "19443:9443"

 

Docker draait op een lokale omgeving



Een eigen NPM private feed maken met Azure Devops ; Docker ; Azure Devops, npm, node package manager, Docker, container

 

Azure DevOps Pipeline

Om Azure DevOps te configureren gebruik je yaml files, hierin staan alle stappen welke de build pipeline gaat uitvoeren
Zie hiervoor: azure/azure-pipelines.yml en azure/templates/build-docker.yml



Een eigen NPM private feed maken met Azure Devops ; Azure Devops Build pipeline, 403 ; Azure Devops, Docker, container, Azure Devops Build pipeline, 403

Als je een organisation-scoped packages uit de Azure Artifact gebruikt dan krijg je de volgende foutmelding:

“npm ERR! 403 403 Forbidden – GET https://pkgs.dev.azure.com/YOURENV/_packaging/demo/npm/registry/@qdraw-components%2fcomponents – User d9af02c4-b000-4fd4-9754-68706813d7ca”

Om deze te verhelpen voeg je de build user toe aan de private feed



Een eigen NPM private feed maken met Azure Devops ; Volg de pijl ; Azure Devops, private feed

In de onderstaande schermafbeelding zie je hoe ik dat doe.



Een eigen NPM private feed maken met Azure Devops ; Klik Add users/group ; Azure Devops, private feed

Build succesvol



Een eigen NPM private feed maken met Azure Devops ; Azure Devops draait goed ; Azure Devops, Docker, container

Conclusie

Het was een interessante zoektocht naar de mogelijkheden van Azure DevOps en dan met name hoe ze dit onderdeel in elkaar hebben gestoken. Omdat er zo weinig van op internet te vinden was heb ik veel zelf uitgezocht en in deze blog heb ik getracht om mijn kennis zo goed mogelijk uit te schrijven.

En het pakketje is succesvol bezorgd…



Een eigen NPM private feed maken met Azure Devops ; Pakket bezorger ; straatleven, straat, pakketbezorger, post, bezorger

Fout senario: Verbonden met de verkeerde tenant

Als je de volgende melding ziet in de preinstall.js: You are probably connected to the WRONG tenant
Ga dan naar de switcher in Azure Devops https://app.vsaex.visualstudio.com/me en selecteer aan de linkerkant het juiste bedrijf. Ga vervolgens weer terug naar oauth provider, dit is in de meeste gevallen: stateless-vsts-oauth.azurewebsites.net om een nieuw refresh token op te halen. Plaats deze weer als environment variablen en herstart het venster om het daarna opnieuw te proberen

Optionele stap: Zelf hosten van de stateless-vsts-oauth provider

Mocht je de stateless-vsts-oauth zelf willen hosten bijvoorbeeld niet afhankelijk te zijn van: stateless-vsts-oauth.azurewebsites.net dan is de code te vinden op:
github.com/zumwald/stateless-vsts-oauth

Hiervoor dien je een App te registeren op Azure Devops. Ga naar Azure Devops Register oauth app
Vul hier je bedrijfsnaam en contactgegevens in. Bij Application Information vul je bij Application website het adres waar de app gehost wordt. Dit adres moet publiek beschikbaar zijn. Verder de Authorization callback URL is het zelfde adres met als toevoeging /oauth-callback. De Authorized scopes zijn Packaging (read and Write). Verder hoef je niks aan te vinken. Klik op Create Application om verder te gaan. De App ID en de Client Secret ben je nodig in volgende stappen. Om later bij dit overzicht te komen ga je naar https://app.vsaex.visualstudio.com/me

Gebruik het volgende format om de environment variablen te zetten


CLIENT_ID : YOUR_CLIENT_ID_GUID
CLIENT_SECRET : eyJ0eXAi....
PORT : 8080
WEBSITE_HOSTNAME : devopsauth.example.com

De App ID wordt ook gebruikt in de preinstall.js en het adres waar de oauth provider is gehost. Belangrijk is dat je deze hier ook in veranderd

Dit bericht is geschreven door: Dion

Tags: , ,

Gecategoriseerd in:

29 januari 2021 • • 8 min leestijd maart 4, 2022 at 10:20 am

Hoe gebruik je templates met Azure Pipelines

Azure Devops is een Microsoft dienst waar mee code projecten beschikbaar kunnen gesteld worden voor anderen. In deze technische blog geef ik een introductie hoe ik templates hergebruik. De Azure Pipelines werken vrijwel met elke programmeertaal of projecttype. Deze tool combineert Continuous Integration (CI) and Continuous Delivery (CD) om continue het project te compileren en testen. Dit is een methode voor het frequent leveren van applicaties naar (eind)klanten waarbij vrijwel alle stappen geautomatiseerd zijn.

Met Azure Pipelines is het gebruikelijk om de build pipelines in yaml te definieren. Dit zijn tekst bestanden waarin alle stappen worden uitgeschreven. Deze yaml bestanden worden ingecheckt in het versie beheer systeem. Het voordeel van deze werkwijze is dat deze per branch verschillend kunnen zijn, zodat je veranderingen niet op een centraal moment moet doorvoeren.

Vooral bij een complexer project is handig om verschillende onderdelen her te gebruiken voor bijvoorbeeld verschillende onderdelen. In het onderstaande project zijn een aantal onderdelen. Het is namelijk niet altijd nodig dat alles wordt gebouwd, vooral als je er geen veranderingen in heb doorgevoerd.

Een belangrijk voordeel is om het op te splitsen is dat je verschillende templates kunt hergebruiken, dit voorkomt dubbele code en de naam van de bestanden geven al een overzicht van de taken die hierin worden uitgevoerd.

In het project dat ik hier als voorbeeld gebruik hou ik de volgende verdeling aan:

1. Server. De backend van de website, in ons geval is dat is het Sitecore platform
2. Client. Losse front-end frameworks die los staan van de backend.
3. Services. Losse Windows services, die achtergrond taken uitvoeren.
4. Develop CI. Dat is een combinatie van alle bovenstaande pipelines

See English version of this article on Medium

Hieronder een schermafbeelding van hoe de pipeline inrichting eruit ziet.



Een introductie van Azure Pipelines en het hergebruik van templates ; Pipeline screen Azure Devops ; azure, devops, vsts, pipelines, build

Develop CI

Ik begin hier bij de Develop CI omdat deze de verschillende onderdelen allemaal bevat. In Azure Devops klik in het pipeline venster op ‘New pipeline’. Vervolgens selecteer ik de locatie van de repository. Dit kan zowel in Azure Devops als op Github zijn. Dan worden er een aantal opties voorgesteld. Ik begin hier met de starter pipeline en kies een locatie. Voor nu ga ik voor de Develop CI uit startpunt: /azure-pipeline/develop-ci.yml Wanneer je op ‘Save and Run’ klikt dan wordt deze ingecheckt. Vervolgens wordt de pipeline uitgevoerd.



Een introductie van Azure Pipelines en het hergebruik van templates ; Review your pipeline Azure Devops ; azure, devops, vsts, pipelines, build, yaml, starter kit

Parallel lopende machines met Stages

In het (bovenstaande) starter voorbeeld wordt maar 1 machine tegelijk gebruikt. Voor een groter project is het handiger als onderdelen parallel lopen. Hier zijn wel kosten aan verbonden. Als je pipelines afgeschermd zijn en je gebruikt de gratis variant dan heb je 1 machine per organisatie beschikbaar met een maximum van 1800 minuten per maand. Dit kan veranderen maar dit is de huidige situatie van januari 2021.

De stages in de yaml pipeline zijn bijwijze van spreken de verschillende boeken en jobs de hoofdstukken. De jobs worden op verschillende machines uitgevoerd. De laatste stap ‘Merge’ wacht voordat de Client en Server stages zijn afgerond.

Hieronder de yaml pipeline voor /azure-pipeline/develop-ci.yml


trigger: none

pr:
- development

variables:
  buildPlatform: "Any CPU"
  buildConfiguration: "Release"
  webroot: "$(build.artifactstagingdirectory)/wwwroot"
  serverPool: "windows-latest"
  clientPool: "ubuntu-20.04"
  serverSitecoreArtifact: "server_sitecore"
  serverUnicornDataArtifact: "server_unicorn_sitecore_data"
  serverConfigCollectionArtifact: "server_config_collection"
  clientArtifact: "client"
  mergeArtifact: "merge"
  npm_config_cache: $(Pipeline.Workspace)/.npm

stages:
  - stage: Server
    dependsOn: []
    jobs:
      - template: /azure-pipeline/jobs/server_sitecore.yml
        parameters:
          pool: "$(serverPool)"
          artifact: "$(serverSitecoreArtifact)"

      - template: /azure-pipeline/jobs/server_config_collection.yml
        parameters:
          pool: "$(serverPool)"
          artifact: "$(serverConfigCollectionArtifact)"
          serverUnicornDataArtifact: "$(serverUnicornDataArtifact)"

  - stage: Client
    dependsOn: []
    jobs:
      - template: /azure-pipeline/jobs/client.yml
        parameters:
          pool: "$(clientPool)"
          artifact: "$(clientArtifact)"

  - stage: Services
    dependsOn: []
    jobs:
      - template: /azure-pipeline/jobs/services.yml
        parameters:
          pool: "$(serverPool)"
          solutions:
            - key: service1
              value: "/src/service1.csproj" 

  - stage: Merge
    dependsOn:
      - Server
      - Client
    jobs:
      - template: /azure-pipeline/jobs/merge.yml
        parameters:
          pool: "$(serverPool)"
          artifact: "$(collectionArtifact)"
          serverSitecoreArtifact: "$(serverSitecoreArtifact)"
	   clientArtifact: "$(clientArtifact)"
          mergeArtifact: "$(mergeArtifact)"

Loop door een lijst om verschillende services te bouwen

Wanneer je verder inzoomt op de Services taak. Deze staat hieronder uitgeschreven. De services yaml wordt als template aangeroepen in de ‘Develop CI’ pipeline onder het kopje templates. De uitkomst is dat deze job meerdere artifacts maakt. Een artifact is de uitkomst van een build taak. Dit kan een mapje met dll’s zijn of een mapje met css en javascript.

In de ‘Develop CI’ yaml defineer ik de parameters. In de services yaml staan ook parameters, maar deze worden overschreven. Deze staan er alleen maar voor het geval de parameters niet wordt meegegeven.

Hieronder de yaml pipeline voor /azure-pipeline/jobs/services.yml


parameters:
  pool:
    vmImage: windows-2019
  nuGetVersion: "5.2.0"
  solutionDir: '$(Build.SourcesDirectory)/src/'
  slnFile: '$(Build.SourcesDirectory)/src/Project.sln'
  job: ServicesRabbit
  solutions: 
    - key: service1
      value: "/src/service1.csproj" 
  BuildArguments: "/p:DeployOnBuild=True /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem"

jobs:
  - job: ${{ parameters.job }}
    displayName: "Services "
    workspace:
      clean: all
    pool:
      vmImage: ${{ parameters.pool }}
      demands:
        - msbuild
    variables:
      outputPath: '$(build.artifactstagingdirectory)\is_over_written_default'
    steps:
      - checkout: self
        clean: true
        fetchDepth: 1

      - template: /azure-pipeline/steps/server_net_restore.yml
        parameters:
          solution: ${{ parameters.slnFile }}

      - ${{ each item in parameters.solutions }}:

        - task: PowerShell@2
          enabled: true
          displayName: "[net] update output path"
          inputs:
            targetType: 'inline'
            script: |
              $updateOutputPath = "$(build.artifactstagingdirectory)/${{ item.key }}"
              Write-Output "##vso[task.setvariable variable=outputPath]$updateOutputPath"

        - task: VSBuild@1
          displayName: "[net] Build ${{ item.key }}"
          inputs:
            solution: "$(Build.SourcesDirectory)${{ item.value }}"
            vsVersion: "16.0"
            msbuildArgs: '${{ parameters.BuildArguments }} /p:SolutionDir="${{ parameters.solutionDir }}"'
            platform: "$(buildPlatform)"
            configuration: "$(buildConfiguration)"
            maximumCpuCount: true

        - task: PublishPipelineArtifact@1
          enabled: true
          displayName: "[net] Publish Project ${{ item.key }}"
          inputs:
            artifactName: ${{ item.key }}
            targetPath: $(build.artifactstagingdirectory)/${{ item.key }}
            publishLocation: "pipeline"

Losse stappen binnen de jobs

Om onderdelen her te gebruiken binnen de verschillende jobs, splits je de stappen uit. Dit is bijvoorbeeld handig voor het ophalen van de externe software bibliotheken. Deze stap wordt op meerdere plekken gebruikt. Om dubbele code te voorkomen gebruik deze stap als template.

In het onderstaande voorbeeld restore ik de nuget packages van de solution. Voor dit .NET Framework project wordt de packages.config style gebruikt. In de eerste taak wordt gezocht naar alle ‘packages.config’ bestanden in het project. Hier wordt een hash van gemaakt zodat als er iets in één van deze bestanden iets veranderd er geen gebruik wordt gemaakt van de oude cache map. Als de config bestanden niet veranderen dan blijft de key het zelfde. In Azure Devops is de cache key scoped tot de pipeline en als deze niet binnen 7 dagen wordt gebruikt dan wordt de inhoud verwijderd.

De inhoud van: /azure-pipeline/steps/server_net_restore.yml

parameters:
  verbose: false
  nuGetVersion: "5.2.0"
  solution: '$(Build.SourcesDirectory)\src\Project.sln’

steps:

  - task: Cache@2
    displayName: "[net] Cache NuGet packages"
    inputs:
      key: 'nuget | "$(Agent.OS)" | **/packages.config,!**/bin/**'
      restoreKeys: |
        nuget | "$(Agent.OS)"
      path: '$(Build.SourcesDirectory)/src/packages'

  - task: NuGetToolInstaller@1
    displayName: "[net] Use NuGet ${{ parameters.nuGetVersion }}"
    inputs:
      versionSpec: ${{ parameters.nuGetVersion }}
      
  - task: NuGetCommand@2
    displayName: "[net] NuGet Restore"
    inputs:
      restoreSolution: "${{ parameters.solution }}"
      feedsToUse: config

Merge job

Als laatste stap in het build proces is het combineren van de artifacts voordat deze naar een omgeving worden gereleased. Het is aan jou of je dit in de build stap doet of dat je dit tijdens de release doet. In het onderstaande voorbeeld is het niet te zien maar in mijn geval waren verschillende front-end frameworks die op verschillende locaties staan dus leek het mij slim om dit al voordat de release start alles al bij elkaar te brengen.

Dit is : /azure-pipeline/jobs/merge.yml

parameters:
  pool: "ubuntu-latest"
  serverSitecoreArtifact: "server_sitecore"
  clientArtifact: "client"
  mergeArtifact: "merge"

jobs:
  - job: Merge
    displayName: Merge website artifacts
    pool:
      vmImage: ${{ parameters.pool }}
    steps:
      - checkout: none

      - task: DownloadPipelineArtifact@2
        displayName: "[$(serverSitecoreArtifact)] DownloadPipelineArtifact"
        inputs:
          artifact: $(serverSitecoreArtifact)
          downloadPath: '$(pipeline.workspace)/$(serverSitecoreArtifact)'

      - task: DownloadPipelineArtifact@2
        displayName: "[$(clientArtifact)] DownloadPipelineArtifact"
        inputs:
          artifact: $(clientArtifact)
          downloadPath: '$(pipeline.workspace)/$(clientArtifact)'

      - task: PowerShell@2
        enabled: true
        displayName: rename $(serverSitecoreArtifact) to $(mergeArtifact)
        inputs:
          targetType: 'inline'
          script: |
            Move-Item -Path $(pipeline.workspace)/$(serverSitecoreArtifact) -Destination $(pipeline.workspace)/$(mergeArtifact)

      - task: PowerShell@2
        enabled: true
        displayName: Merge $(clientArtifact) in $(mergeArtifact) (When /assets/client already exist)
        inputs:
          targetType: 'inline'
          script: |
            Get-ChildItem -Path $(pipeline.workspace)/$(clientArtifact) | Copy-Item -Destination $(pipeline.workspace)/$(mergeArtifact)/assets/client -Recurse -Force

      - task: PublishPipelineArtifact@1
        enabled: true
        displayName: Publish $(mergeArtifact) Artifact
        inputs:
          targetPath: "$(pipeline.workspace)/$(mergeArtifact)"
          artifact: $(mergeArtifact)
          publishLocation: "pipeline"

De conclusie dat het goed mogelijk is om pipelines op te splitsen in losse bestanden waardoor het makkelijker wordt om het overzicht te houden. Op deze manier voorkom je dubbele code en automatiseer je de release van de applicatie.

Deze blog verscheen voor het eerst op qdraw.nl

Dit bericht is geschreven door: Dion

Tags: , , ,

Gecategoriseerd in: