---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: Een schaalbare Go-server uitvoeren in .NET Aspire
description: 'Hoe u Go-servers schaalbaar kunt uitvoeren met .NET Aspire: een voorbeeldconfiguratie voor horizontale schaling en automatisering met de YARP-reverse proxy.'
language: nl
date: 2025-01-05T05:04:22.744778105Z
path: /blog/posts/exploring-the-scalable-execution-of-go-servers-in-net-aspire-z42f3b6ee
category: network
---

## dotnet aspire?

dotnet aspire is een hulpmiddel dat is ontwikkeld om ontwikkelaars te ondersteunen bij de ontwikkeling en configuratie van cloud-native toepassingen, nu cloud-native omgevingen steeds vaker voorkomen. Met dit hulpmiddel kunnen .NET-ontwikkelaars eenvoudig .NET-projecten, diverse cloud-native infrastructuur, en services of containers in andere talen implementeren.

Uiteraard wordt alles van Docker tot k8s uitgerold en beheerd, en veel sectoren, industrieën en ontwikkelaars hebben hun on-premises omgevingen al overgezet naar cloud-native omgevingen, of zijn hier momenteel mee bezig. Dit is inmiddels een volwassen domein. Daarom denk ik dat het niet nodig is om uit te leggen wat het ongemak is van hostnamen, poortconfiguraties, firewalls, metrics-beheer en dergelijke.

Op basis van de bovenstaande uitleg zult u waarschijnlijk nog steeds niet goed weten wat dotnet aspire precies is. Dat komt doordat Microsoft zelf geen exacte definitie heeft gegeven. Daarom zal ik ook geen eigen definitie geven. In dit artikel gebruik ik echter de basisfuncties van dotnet aspire zoals ik die begrijp, dus u kunt dit als referentie gebruiken om uw eigen standpunt te bepalen.

## Projectstructuur

### Een dotnet aspire-project aanmaken

Als de dotnet aspire-template nog niet beschikbaar is, moet u eerst de template installeren. Installeer de template met het volgende commando. Als .NET nog niet geïnstalleerd is, dient u dit zelf te doen.

```bash
dotnet new install Aspire.ProjectTemplates
```

Maak vervolgens in een geschikte map een nieuwe solution aan.

```bash
dotnet new sln
```

Voer daarna in de solutionmap het volgende commando uit om een project van de aspire-apphost-template aan te maken.

```bash
dotnet new aspire-apphost -o AppHost
```

Hiermee wordt een aspire-apphost-project aangemaakt dat alleen eenvoudige code bevat voor de configuratie.

### Valkey toevoegen

Laten we nu eenvoudig Valkey toevoegen.

Voordat we dat doen: dotnet aspire biedt via community hosting diverse oplossingen van derden. Valkey wordt uiteraard ook door deze community hosting ondersteund en is eenvoudig te gebruiken via het volgende NuGet-pakket.

```bash
dotnet add package Aspire.Hosting.Valkey
```

Daarnaast zijn er diverse andere geïntegreerde hostings beschikbaar, die u [hier](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview) kunt bekijken.
Terug naar Valkey: open het bestand Program.cs in het AppHost-project en pas het als volgt aan.

```csharp
var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddValkey("cache")
    .WithDataVolume(isReadOnly: false)
    .WithPersistence(interval: TimeSpan.FromMinutes(5),
        keysChangedThreshold: 100);

builder.Build().Run();
```

`cache` is een implementatie van de `IResourceBuilder`-interface die de informatie bevat om een Valkey-service te bouwen.
`WithDataVolume` maakt een volume aan om cachegegevens op te slaan, en `WithPersistence` zorgt ervoor dat cachegegevens continu worden opgeslagen.
Op basis hiervan lijkt het een vergelijkbare rol te vervullen als `volumes` in `docker-compose`.
Uiteraard kunt u dit ook zonder veel moeite zelf maken.
Dit valt echter buiten het bestek van dit artikel, dus ik ga er nu niet verder op in.

### Een Go-echoserver maken

Laten we nu een eenvoudige Go-server toevoegen.
Maak eerst in de solutionmap met `go work init` een workspace aan.
Voor .NET-ontwikkelaars kunt u een Go-workspace zien als vergelijkbaar met een solution.

Maak vervolgens een map aan met de naam EchoServer, ga ernaartoe en voer `go mod init EchoServer` uit.
Met dit commando maakt u een Go-module aan. Een module kunt u voor .NET-ontwikkelaars beschouwen als vergelijkbaar met een project.
Maak daarna het bestand `main.go` aan en schrijf het als volgt.

```go
package main

import (
	"log"
	"net/http"
	"os"
)

func main() {
	addr := os.Getenv("PORT")
	log.Printf("Server started on %s", addr)

	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		name := request.URL.Query().Get("name")
		writer.Write([]byte("Hello, " + name))
	})

	http.ListenAndServe(":"+addr, nil)
}
```

Deze server leest, wanneer de Aspire AppHost wordt uitgevoerd, de poort uit de omgevingsvariabele `PORT` die wordt meegegeven, en start de server op die poort.
Het is een eenvoudige server die de query-parameter `name` ontvangt en `Hello, {name}` teruggeeft.

Laten we deze server nu aan dotnet aspire toevoegen.

### De echoserver toevoegen aan Aspire

Ga terug naar het Aspire AppHost-project waarin we Valkey hebben toegevoegd, en voeg de community hosting voor Go toe.

```bash
dotnet add package CommunityToolkit.Aspire.Hosting.Golang
```

Open vervolgens het bestand Program.cs en voeg de volgende regels toe.

```csharp
var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddValkey("cache")
    .WithDataVolume(isReadOnly: false)
    .WithPersistence(interval: TimeSpan.FromMinutes(5),
        keysChangedThreshold: 100);

var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
    .WithHttpEndpoint(port: 3000, env: "PORT")
    .WithExternalHttpEndpoints();

builder.Build().Run();
```

Hier is `echoServer` een implementatie van de `IResourceBuilder`-interface die de informatie bevat om een Go-serverservice te bouwen.
De zojuist toegevoegde methode `AddGolangApp` is een extensiemethode van een aangepaste host om een Go-server toe te voegen.
Er wordt een vaste poort 3000 gebruikt, en u kunt zien dat de omgevingsvariabele `PORT` wordt meegegeven.
Tot slot zorgt `WithExternalHttpEndpoints` ervoor dat de service van buitenaf bereikbaar is.

Om te testen kunt u naar `http://localhost:3000/?name=world` gaan, waarna u zult zien dat `Hello, world` wordt weergegeven.

Het huidige dotnet aspire kent echter een zware straf voor niet-dotnet-projecten.
Die is namelijk...

## Projectuitbreiding

### Hoe zit het dan met horizontale schaling?

Momenteel biedt dotnet aspire de optie `WithReplica` alleen voor builders van .NET-projecten die zijn toegevoegd via de methode `AddProject`.
Voor Go-hosts of externe projecten zoals `AddContainer` wordt deze optie echter niet aangeboden.

Daarom moet u zelf een aparte load balancer of reverse proxy gebruiken om dit te realiseren.
Maar als die reverse proxy een SPOF wordt, is het verstandig om de reverse proxy ook de optie `WithReplica` te laten ondersteunen.
Dan moet de reverse proxy noodzakelijkerwijs een .NET-project zijn.

Tot nu toe heb ik dit probleem aangepakt met methoden zoals nginx, Traefik of een eigen implementatie, maar zodra de beperking tot .NET-projecten gold, had ik op dat moment geen oplossing.
Daarom heb ik gezocht naar een reverse proxy die in .NET is geïmplementeerd, en gelukkig was [YARP](https://microsoft.github.io/reverse-proxy/) een optie.
YARP is een in .NET geïmplementeerde reverse proxy die ook als load balancer kan fungeren en diverse functies biedt, dus ik vond het een goede keuze.

Laten we nu YARP toevoegen.

### Reverse proxy opzetten met YARP

Maak eerst een project aan voor het gebruik van YARP.

```bash
dotnet new web -n ReverseProxy
```

Ga daarna naar het project en installeer YARP.

```bash
dotnet add package Yarp.ReverseProxy --version 2.2.0
```

Zodra de installatie is voltooid, opent u het bestand Program.cs en schrijft u het als volgt.

```csharp
using Yarp.ReverseProxy.Configuration;

var builder = WebApplication.CreateBuilder(args);

var routes = new List<RouteConfig>();
var clusters = new List<ClusterConfig>();

builder.Services.AddReverseProxy()
    .LoadFromMemory(routes, clusters);

var app = builder.Build();

app.MapReverseProxy();
app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");
```

Deze code is de basiscode om YARP te gebruiken.
`routes` bevat de routeinformatie die de reverse proxy gebruikt, en `clusters` bevat de clusterinformatie die de reverse proxy gebruikt.
Deze informatie wordt via de methode `LoadFromMemory` in de reverse proxy geladen.
Ten slotte worden de reverse proxy gekoppeld en uitgevoerd met de methode `MapReverseProxy`.

Voeg vervolgens voor daadwerkelijk gebruik het reverse proxy-project als referentie toe aan het Aspire apphost-project, en voeg in het bestand Program.cs de volgende regels toe en pas ze aan.

```bash
dotnet add reference ../ReverseProxy
```

```csharp
var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
    .WithHttpEndpoint(env: "PORT");

var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
    .WithReference(echoServer)
    .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
    .WithExternalHttpEndpoints();
```

Nu kan de reverse proxy de echoserver als referentie gebruiken.
De structuur is veranderd zodat inkomende externe verzoeken door de reverse proxy worden ontvangen en doorgegeven aan de echoserver.

### De reverse proxy aanpassen

Allereerst moet het listening-adres van het project dat aan de reverse proxy is toegewezen worden gewijzigd.
Verwijder de `applicationUrl` in het bestand `Properties/launchSettings.json`.
Open daarna het bestand Program.cs en pas het ingrijpend aan als volgt.

```csharp
using Yarp.ReverseProxy.Configuration;

var builder = WebApplication.CreateBuilder(args);

var routes = new List<RouteConfig>
{
    new RouteConfig
    {
        ClusterId = "cluster-echo",
        RouteId = "route-echo",
        Match = new RouteMatch
        {
            Path = "/"
        }
    }
};

var echoServerAddr = Environment.GetEnvironmentVariable("services__echo-server__http__0") ?? "http://localhost:8080";

var clusters = new List<ClusterConfig>
{
    new ClusterConfig
    {
        ClusterId = "cluster-echo",
        Destinations = new Dictionary<string, DestinationConfig>
        {
            { "destination-echo", new DestinationConfig { Address = echoServerAddr } }
        }
    }
};

builder.Services.AddReverseProxy()
    .LoadFromMemory(routes, clusters);

var app = builder.Build();

app.MapReverseProxy();
app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");
```

Allereerst wordt de informatie over `routes` en `clusters` aangepast.
Er worden respectievelijk `echo-route` en `echo-cluster` toegevoegd, zodat verzoeken naar de echoserver worden gestuurd.
Vervolgens wordt het adres van de echoserver uitgelezen uit een omgevingsvariabele en gebruikt.

De regel voor dit adres is `services__{service-name}__http__{index}`.
Voor de echoserver is de servicenaam `echo-server` en omdat er slechts één instantie is, gebruiken we index `0`.
Als u een ASP.NET Core-server toevoegt, kunnen via `WithReplica` meerdere instanties worden aangemaakt, en dan verhoogt u de index om die te gebruiken.
De fallback `http://localhost:8080` is een betekenisloze waarde zonder enige functie.

Start nu het project en ga naar `http://localhost:3000/?name=world`, waarna u opnieuw zult zien dat `Hello, world` wordt weergegeven.

### Uitbreidingsideeën

Nu hebben we gezien hoe een Go-server aan dotnet aspire wordt toegevoegd en hoe verzoeken via de reverse proxy worden doorgestuurd.
Vervolgens kan dit proces worden uitgebreid zodat het programmatisch kan worden geïmplementeerd.
Zo kunt u bijvoorbeeld voor de echoserver een volgnummer achter de servicenaam toevoegen om meerdere instanties te maken, en de configuratie van de reverse proxy automatisch laten toevoegen.

Pas in het bestand Program.cs van het aspire apphost-project de code die de reverse proxy en de echoserver gebruikt als volgt aan.

```csharp
var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
    .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
    .WithExternalHttpEndpoints();

for (var i = 0; i < 8; i++)
{
    var echoServer = builder.AddGolangApp($"echo-server-{i}", "../EchoServer")
        .WithHttpEndpoint(env: "PORT");
    reverseProxy.WithReference(echoServer);
}
```

Pas daarna het bestand Program.cs van het reverse proxy-project als volgt aan.

```csharp
var echoServerDestinations = new Dictionary<string, DestinationConfig>();
for (var i = 0; i < 8; i++)
{
    echoServerDestinations[$"destination-{i}"] = new DestinationConfig
    {
        Address = Environment.GetEnvironmentVariable($"services__echo-server-{i}__http__0") ?? "http://localhost:8080"
    };
}

var clusters = new List<ClusterConfig>
{
    new ClusterConfig
    {
        ClusterId = "cluster-echo",
        Destinations = echoServerDestinations
    }
};
```

Hier worden bestemmingsinstellingen toegevoegd voor de acht echoserver-instanties.
De reverse proxy beschikt nu over de bestemmingsinformatie van de uitgebreide echoserver-instanties en kan verzoeken daarnaar doorgeven.
Als u naar het bekende `http://localhost:3000/?name=world` gaat, zult u opnieuw zien dat `Hello, world` wordt weergegeven.

## Afsluiting

In dit artikel heb ik beschreven hoe een Go-server aan dotnet aspire wordt toegevoegd en hoe verzoeken via een reverse proxy worden doorgestuurd.
De uitbreiding heb ik echter nog niet volledig uitgewerkt. Een voorbeeld dat laat zien hoe u dit met behulp van omgevingsvariabelen programmatischer kunt implementeren, heb ik wel in een aparte repository gezet.
Raadpleeg voor de volledige projectstructuur en code [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack).

Persoonlijk verwacht ik dat dotnet aspire een eigen rol kan vervullen als alternatief voor docker compose en als hulpmiddel voor cloud-implementatie.
Er bestaan al generatoren die docker compose- of k8s-manifesten maken, [zoals deze](https://prom3theu5.github.io/aspirational-manifests/generate-command.html), en ik denk dat het voor gewone ontwikkelaars daardoor makkelijker is geworden om toegang te krijgen tot infrastructuurtools.
