---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: Rularea unui server Go în mod scalabil în .NET Aspire
description: 'Cum să rulezi scalabil un server Go cu .NET Aspire: exemplu de configurare pentru scalare orizontală și automatizare folosind reverse proxy-ul YARP.'
language: ro
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 este un instrument creat pentru a ajuta dezvoltatorii în dezvoltarea și configurarea aplicațiilor cloud native, pe măsură ce mediile cloud native se extind. Acest instrument permite dezvoltatorilor .NET să implementeze cu ușurință proiecte .NET și diverse infrastructuri cloud native, precum și servicii sau containere scrise în alte limbaje.

Desigur, de la Docker până la K8s, tehnologiile sunt lansate și operate, iar în multe domenii, industrii și rândul multor dezvoltatori, trecerea de la mediile on-premises tradiționale la mediile cloud native este în curs sau deja finalizată. Acum este un domeniu matur. Prin urmare, cred că nu este necesar să explic inconvenientele existente legate de numele gazdelor, configurarea porturilor, firewall-ul, gestionarea metricilor etc.

Așadar, chiar și din explicațiile de mai sus, este probabil că nu aveți încă o idee clară despre ce este dotnet aspire. Acest lucru se datorează faptului că nici Microsoft nu oferă o definiție precisă. Din acest motiv, nici eu nu voi oferi o definiție proprie. Totuși, în acest articol voi utiliza funcționalitățile de bază ale dotnet aspire pe care le-am înțeles, astfel încât puteți consulta acestea și vă puteți stabili propria poziție.

## Configurarea proiectului

### Crearea unui proiect dotnet aspire

Dacă șablonul dotnet aspire nu este disponibil, trebuie mai întâi să îl instalați. Instalați șablonul cu următoarea comandă. Dacă nu aveți .NET instalat, instalați-l pe cont propriu.

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

Apoi creați o soluție nouă într-un folder adecvat.

```bash
dotnet new sln
```

După aceea, în folderul soluției, executați următoarea comandă pentru a crea un proiect din șablonul aspire-apphost.

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

Va fi creat un proiect aspire-apphost care conține doar un cod simplu pentru configurare.

### Adăugarea Valkey

Acum vom adăuga Valkey, pe scurt.

Înainte de a-l adăuga, trebuie menționat că dotnet aspire oferă diverse soluții terțe prin community hosting.
Desigur, Valkey beneficiază și el de acest suport din partea community hosting și poate fi utilizat cu ușurință prin următorul pachet NuGet.

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

Pe lângă acestea, sunt disponibile și alte integrări de hosting, care pot fi consultate [aici](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview).
Revenind la Valkey, deschideți fișierul Program.cs din proiectul AppHost și modificați-l astfel:

```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` este o implementare a interfeței `IResourceBuilder`, care conține informațiile necesare pentru a construi serviciul Valkey.
`WithDataVolume` creează un volum pentru stocarea datelor din cache, iar `WithPersistence` asigură salvarea continuă a datelor din cache.
Până aici, se pare că aceste metode au un rol similar cu `volumes` din `docker-compose`.
Desigur, și dumneavoastră puteți realiza acest lucru fără dificultate.
Totuși, aceasta depășește scopul acestui articol, așa că nu voi intra în detalii acum.

### Crearea unui server Echo în Go

Acum vom adăuga un server simplu în Go.
Mai întâi, în folderul soluției, creați un workspace utilizând `go work init`.
Pentru dezvoltatorii .NET, workspace-ul Go poate fi considerat asemănător unei soluții.

Apoi creați un folder numit EchoServer, accesați-l și executați `go mod init EchoServer`.
Această comandă creează un modul Go. Modulul poate fi considerat, pentru dezvoltatorii .NET, similar unui proiect.
Apoi creați fișierul `main.go` și scrieți următorul cod:

```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)
}
```

Acest server citește portul din variabila de mediu `PORT`, pe care Aspire AppHost o injectează atunci când rulează, și pornește serverul pe acel port.
Este un server simplu care primește parametrul de interogare `name` și returnează `Hello, {name}`.

Acum vom adăuga acest server în dotnet aspire.

### Adăugarea serverului Echo în Aspire

Reveniți la proiectul Aspire AppHost, în care ați adăugat Valkey, și instalați suportul community hosting pentru Go.

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

Apoi deschideți fișierul Program.cs și adăugați următoarea secțiune:

```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();
```

Aici, `echoServer` este o implementare a interfeței `IResourceBuilder`, care conține informațiile necesare pentru a construi serverul Go.
Metoda `AddGolangApp`, pe care tocmai am adăugat-o, este o metodă de extensie a unui host personalizat, destinată adăugării unui server Go.
Serverul utilizează un port fix, 3000, și se poate observa că variabila de mediu `PORT` este injectată.
În final, `WithExternalHttpEndpoints` permite accesul din exterior.

Pentru testare, accesați `http://localhost:3000/?name=world` și veți putea observa că se afișează `Hello, world`.

Totuși, în prezent, dotnet aspire impune o penalizare considerabilă pentru proiectele care nu sunt .NET.
Aceasta este, mai exact...

## Extinderea proiectului

### Cum se realizează scalarea orizontală?

În prezent, dotnet aspire oferă opțiunea `WithReplica` doar pentru builderele proiectelor .NET adăugate prin metoda `AddProject`.
Însă pentru hosturile Go sau pentru proiecte externe precum `AddContainer`, această opțiune nu este disponibilă.

Prin urmare, trebuie implementată manual o soluție, utilizând un load balancer sau un reverse proxy separat.
Totuși, dacă reverse proxy-ul devine un SPOF, acesta ar trebui să beneficieze de opțiunea `WithReplica`.
În acest caz, reverse proxy-ul trebuie, în mod inevitabil, să fie un proiect .NET.

Până acum, am utilizat nginx, Traefik sau implementări proprii pentru a rezolva această problemă, însă odată cu restricția de a folosi un proiect .NET, nu aveam, pe moment, o soluție la îndemână.
Așa că am căutat un reverse proxy implementat în .NET și, din fericire, a existat opțiunea [YARP](https://microsoft.github.io/reverse-proxy/).
YARP este un reverse proxy implementat în .NET, care poate funcționa și ca load balancer și oferă numeroase funcționalități, motiv pentru care l-am considerat o alegere bună.

Acum vom adăuga YARP.

### Configurarea reverse proxy-ului cu YARP

Mai întâi, creați un proiect pentru utilizarea YARP:

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

Apoi accesați proiectul și instalați YARP:

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

După finalizarea instalării, deschideți fișierul Program.cs și scrieți următorul cod:

```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"}");
```

Acest cod reprezintă configurația de bază pentru utilizarea YARP.
`routes` conține informațiile despre rutele pe care le va utiliza reverse proxy-ul, iar `clusters` conține informațiile despre clusterele utilizate de acesta.
Aceste informații sunt încărcate în reverse proxy prin metoda `LoadFromMemory`.
În final, metoda `MapReverseProxy` mapează și pornește reverse proxy-ul.

Pentru utilizarea efectivă, adăugați în proiectul aspire apphost o referință către proiectul reverse proxy și adăugați sau modificați în fișierul Program.cs următoarea secțiune:

```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();
```

Acum reverse proxy-ul poate face referire la echo server.
Cererile externe sunt primite de reverse proxy și transmise către echo server, astfel că structura a fost modificată în acest sens.

### Modificarea reverse proxy-ului

Mai întâi, trebuie modificată adresa de ascultare a proiectului atribuit reverse proxy-ului.
Eliminați `applicationUrl` din fișierul `Properties/launchSettings.json`.
Apoi deschideți fișierul Program.cs și modificați-l substanțial, după cum urmează:

```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"}");
```

Mai întâi modificăm informațiile despre `routes` și `clusters`.
Am adăugat `echo-route` și `echo-cluster`, care configurează trimiterea cererilor către echo server.
Apoi, adresa echo server-ului este citită din variabilele de mediu și utilizată în aplicație.

Regula pentru această adresă este `services__{service-name}__http__{index}`.
În cazul echo server-ului, numele serviciului este `echo-server`, iar deoarece există o singură instanță, se utilizează indexul `0`.
Dacă adăugăm un server ASP.NET Core, pot fi create mai multe instanțe prin `WithReplica`, caz în care este suficient să se incrementeze indexul.
Valoarea implicită de excepție `http://localhost:8080` este o valoare fără niciun sens.

Acum, rulați proiectul și accesați `http://localhost:3000/?name=world`; veți putea observa că se afișează în continuare `Hello, world`.

### Idei de extindere

Până acum, am confirmat că un server Go poate fi adăugat în dotnet aspire și că cererile pot fi transmise prin intermediul reverse proxy-ului.
Pe această bază, acest proces poate fi extins astfel încât să fie implementat programatic.
De exemplu, pentru echo server, se pot adăuga numere de serie după numele serviciului pentru a crea mai multe instanțe, iar configurația reverse proxy-ului poate fi adăugată automat.

Modificați fișierul Program.cs al proiectului aspire apphost, care utilizează reverse proxy și echo server, după cum urmează:

```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);
}
```

Apoi modificați fișierul Program.cs al proiectului reverse proxy astfel:

```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
    }
};
```

Adăugăm configurația de destinații pentru cele 8 instanțe de echo server.
Acum, reverse proxy-ul dispune de informațiile despre destinațiile tuturor instanțelor echo server și poate transmite cererile corespunzător.
Dacă accesați din nou `http://localhost:3000/?name=world`, veți putea observa că se afișează în continuare `Hello, world`.

## Încheiere

În acest articol, am descris procesul de adăugare a unui server Go în dotnet aspire și de transmitere a cererilor prin intermediul unui reverse proxy.
Totuși, extinderea nu a fost încă prezentată integral; un exemplu care implementează mai programatic, utilizând variabile de mediu, a fost publicat într-un repository separat.
Pentru configurația detaliată a proiectului și codul complet, consultați [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack).

Personal, sper că dotnet aspire își va putea îndeplini propriul rol, atât ca alternativă la docker compose, cât și ca instrument de implementare în cloud.
Există deja [generatoare](https://prom3theu5.github.io/aspirational-manifests/generate-command.html) care creează manifeste docker compose sau k8s, iar acest lucru face ca instrumentele de infrastructură să fie mai accesibile pentru dezvoltatorii obișnuiți.
