---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: dotnet aspire'da Go sunucusunu ölçeklenebilir şekilde çalıştırmayı denemek
description: 'Go sunucusunu .NET Aspire ile ölçeklenebilir biçimde çalıştırma yöntemi: YARP ters proxy kullanılarak yatay ölçekleme ve otomatik yapılandırma örneği.'
language: tr
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 nedir?

dotnet aspire, bulut yerel (cloud native) ortamların yaygınlaşmasıyla birlikte geliştiricilerin bulut yerel geliştirme ve yapılandırmasına yardımcı olmak amacıyla oluşturulmuş bir araçtır. Bu araç, .NET geliştiricilerinin .NET projelerini, çeşitli bulut yerel altyapıyı ve diğer dillerle yazılmış hizmetleri veya konteynerleri kolayca dağıtabilmesini sağlar.

Doğal olarak docker'dan k8s'e kadar yayına alınıp işletilmekte olup, geleneksel şirket içi (on-premise) ortamdan çok sayıda alan, sektör ve geliştirici bulut yerel ortama geçmekte ve geçmiş durumdadır. Artık olgunlaşmış bir alandır. Bu nedenle ana bilgisayar adları, bağlantı noktası yapılandırması, güvenlik duvarı, metrik yönetimi gibi konulardaki mevcut zorlukların açıklanmasına gerek olmadığını düşünüyorum.

Dolayısıyla yukarıdaki açıklamalardan yola çıkarak bile dotnet aspire'ın ne olduğuna dair hiçbir fikir edinemeyebilirsiniz. Çünkü bu, Microsoft'un bile kesin bir tanım vermediği bir konudur. Bu yüzden ben de özel bir tanım vermeyeceğim. Bununla birlikte, bu yazıda dotnet aspire'ın benim anladığım temel işlevlerini kullanacağım; dolayısıyla bunlara bakarak kendi konumunuzu belirleyebilirsiniz.

## Proje Yapılandırması

### dotnet aspire projesi oluşturma

Eğer dotnet aspire şablonu yoksa, önce şablonu kurmanız gerekir. Şablonu aşağıdaki komutla kurun. .NET yüklü değilse, bunu kendiniz yüklemeniz gerekir.

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

Ardından uygun bir klasörde yeni bir çözüm (solution) oluşturun.

```bash
dotnet new sln
```

Sonra çözüm klasöründe aşağıdaki komutu çalıştırarak aspire-apphost şablonuyla bir proje oluşturun.

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

Böylece kurulum için yalnızca basit kod içeren bir aspire-apphost projesi oluşturulmuş olacaktır.

### Valkey ekleme

Şimdi Valkey'i basitçe ekleyelim.

Doğrudan eklemeden önce, dotnet aspire topluluk barındırması (community hosting) aracılığıyla çeşitli üçüncü taraf çözümler sunduğunu belirtmek gerekir.  
Valkey de doğal olarak bu topluluk barındırmasının desteğinden yararlanabilir ve aşağıdaki NuGet paketi aracılığıyla kolayca kullanılabilir.

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

Bunun dışında çeşitli entegre barındırmalar da sunulmaktadır, bunları [bu sayfada](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview) inceleyebilirsiniz.  
Valkey'e geri dönersek, AppHost projesindeki Program.cs dosyasını açıp aşağıdaki gibi değiştirin.

```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`, Valkey hizmetini oluşturabilecek bilgileri içeren `IResourceBuilder` arayüzünün bir uygulamasıdır.  
`WithDataVolume`, önbellek verilerini depolayacak bir birim (volume) oluşturur; `WithPersistence` ise önbellek verilerinin kalıcı olarak kaydedilmesini sağlar.  
Buraya kadar bakıldığında, `docker-compose` içindeki `volumes` ile benzer bir rol üstlendiği görülebilir.  
Elbette bunu sizler de zorlanmadan oluşturabilirsiniz.  
Ancak bu, yazının kapsamının dışında kaldığı için şimdi ayrıntısına girmeyeceğim.

### Go dilinde yankı (echo) sunucusu oluşturma

Şimdi basit bir Go dili sunucusu ekleyelim.  
Öncelikle çözüm klasöründe `go work init` komutuyla bir çalışma alanı (workspace) oluşturun.  
.NET geliştiricileri için Go çalışma alanı, çözüme (solution) benzer bir yapı olarak düşünülebilir.

Ardından EchoServer adında bir klasör oluşturun, içine girip `go mod init EchoServer` komutunu çalıştırın.  
Bu komutla bir Go modülü oluşturulur. Modül, .NET geliştiricileri için projeye benzer bir yapı olarak kabul edilebilir.  
Ardından `main.go` dosyasını oluşturup aşağıdaki gibi yazın.

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

Bu sunucu, Aspire AppHost çalıştırıldığında dinlemesi gereken `PORT` ortam değişkeni enjekte edildiğinde, bu bağlantı noktasını okuyarak sunucuyu başlatır.  
Basitçe `name` sorgu parametresini alıp `Hello, {name}` değerini döndüren bir sunucudur.

Şimdi bu sunucuyu dotnet aspire'a ekleyelim.

### Yankı sunucusunu aspire'a ekleme

Valkey'i eklediğimiz Aspire AppHost projesine geri dönüp Go dili için topluluk barındırmasını ekleyin.

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

Ardından Program.cs dosyasını açıp aşağıdaki ifadeyi ekleyin.

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

Burada `echoServer`, Go dili sunucusunu oluşturabilecek bilgileri içeren `IResourceBuilder` arayüzünün bir uygulamasıdır.  
Az önce eklediğimiz `AddGolangApp` metodu, Go dili sunucusunu eklemek için özel bir host'un genişletme metodudur.  
Sabit olarak 3000 numaralı bağlantı noktasını kullanır ve `PORT` ortam değişkenini enjekte ettiği görülebilir.  
Son olarak `WithExternalHttpEndpoints`, uygulamaya dışarıdan erişilebilmesini sağlar.

Test için `http://localhost:3000/?name=world` adresine bağlandığınızda `Hello, world` çıktısını göreceksiniz.

Ancak mevcut haliyle dotnet aspire'da, .NET dışı projelere uygulanan ağır bir ceza bulunmaktadır.  
Bu da tam olarak şudur...

## Proje Genişletme

### Yatay ölçeklendirme nasıl yapılır?

Şu anda dotnet aspire, yalnızca `AddProject` metoduyla eklenen .NET projeleri için oluşturulan builder'da `WithReplica` seçeneğini sunmaktadır.  
Ancak Go dili host'u veya `AddContainer` gibi dış projeler için bu seçenek sunulmamaktadır.

Bu nedenle ayrı bir load balancer veya reverse proxy kullanarak bunu kendiniz uygulamanız gerekir.  
Ancak bu durumda ilgili reverse proxy tek hata noktası (SPOF) haline gelebilir; dolayısıyla reverse proxy'nin de `WithReplica` seçeneğini sunması iyi olacaktır.  
O halde reverse proxy'nin zorunlu olarak bir .NET projesi olması gerekir.

Şimdiye kadar bu sorunu nginx, traefik, doğrudan uygulama gibi yöntemlerle çözmeye çalıştım; ancak .NET projesi kısıtı getirildiğinde elimde hemen uygulanabilir bir yöntem kalmadı.  
Bu yüzden .NET ile geliştirilmiş reverse proxy'leri araştırdım ve neyse ki [YARP](https://microsoft.github.io/reverse-proxy/) adlı bir seçenek buldum.  
YARP, .NET ile geliştirilmiş bir reverse proxy'dir; load balancer görevi de görebilmekte ve çeşitli özellikler sunmaktadır, bu nedenle iyi bir seçim olduğunu düşündüm.

Şimdi YARP'ı ekleyelim.

### YARP ile reverse proxy yapılandırma

Öncelikle YARP'ı kullanmak için bir proje oluşturun.

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

Ardından projeye gidip YARP'ı yükleyin.

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

Kurulum tamamlandığında Program.cs dosyasını açıp aşağıdaki gibi yazın.

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

Bu kod, YARP'ı kullanmak için temel bir koddur.  
`routes`, reverse proxy'nin kullanacağı yönlendirme (route) bilgilerini; `clusters` ise reverse proxy'nin kullanacağı küme (cluster) bilgilerini içerir.  
Bu bilgiler `LoadFromMemory` metodu ile reverse proxy'ye yüklenir.  
Son olarak `MapReverseProxy` metodu kullanılarak reverse proxy eşlenir ve çalıştırılır.

Gerçek kullanım için ayrıca aspire apphost projesine reverse proxy projesini referans olarak eklemeniz ve Program.cs dosyasına aşağıdaki ifadeyi eklemeniz/değiştirmeniz gerekir.

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

Artık reverse proxy, echo server'a başvurabilir.  
Dışarıdan gelen istekler reverse proxy tarafından karşılanıp echo server'a iletilecek şekilde yapı değişmektedir.

### Reverse proxy'yi düzenleme

Öncelikle reverse proxy projesine atanan dinleme (listening) adresini değiştirmeniz gerekir.  
`Properties/launchSettings.json` dosyasının içindeki `applicationUrl` öğesini kaldırın.  
Ardından Program.cs dosyasını açıp aşağıdaki gibi kapsamlı bir şekilde düzenleyin.

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

Öncelikle `routes` ve `clusters` bilgilerini düzenliyoruz.  
Sırasıyla `echo-route` ve `echo-cluster` ekleyerek istekleri echo server'a gönderecek şekilde ayarlıyoruz.  
Ardından echo server'ın adresini ortam değişkeninden okuyacak şekilde değiştiriyoruz.

Bu adresin kuralı `services__{service-name}__http__{index}` biçimindedir.  
Echo server için hizmet adı `echo-server` olduğundan ve tek bir örnek bulunduğundan, indeks olarak `0` kullanılır.  
Eğer bir asp .net core sunucusu eklersek, `WithReplica` ile birden fazla örnek oluşturulabileceğinden, indeksi artırarak kullanmak yeterlidir.  
İstisna işleyicisinde yer alan `http://localhost:8080` değeri anlamsız bir yedek değerdir.

Şimdi projeyi çalıştırıp `http://localhost:3000/?name=world` adresine bağlandığınızda, yine `Hello, world` çıktısını göreceksiniz.

### Genişletme fikri

Artık dotnet aspire'a bir Go sunucusu ekleyip, reverse proxy aracılığıyla istekleri ilettiğimizi gördük.  
Bu süreci programatik olarak uygulayacak şekilde genişletmek de mümkün olacaktır.  
Örneğin, echo server için hizmet adının sonuna numara ekleyerek birden fazla örnek oluşturabilir ve reverse proxy yapılandırmasını otomatik olarak ekleyebilirsiniz.

Reverse proxy ve echo server'ı kullanan kodu aspire apphost projesinin Program.cs dosyasında aşağıdaki gibi düzenleyin.

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

Ardından reverse proxy projesinin Program.cs dosyasını aşağıdaki gibi düzenleyin.

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

8 adede çıkarılan echo server örneklerine yönelik hedef yapılandırmasını ekliyoruz.  
Artık reverse proxy, çoğalan echo server'lara ait hedef bilgilerine sahip olup istekleri iletebilmektedir.  
Mevcut `http://localhost:3000/?name=world` adresine bağlandığınızda yine `Hello, world` çıktısını göreceksiniz.

## Sonuç

Bu yazıda dotnet aspire'a bir Go sunucusu eklemenin ve reverse proxy aracılığıyla istekleri iletmenin sürecini anlattım.  
Ancak genişletme konusunun tamamını henüz yazmadım; ortam değişkenleri aracılığıyla daha programatik bir uygulama yapılabilecek bir örneği ise ayrı bir depoda hazırladım.  
Ayrıntılı proje yapısı ve kod için [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack) deposuna göz atabilirsiniz.

Ben kişisel olarak dotnet aspire'ın docker compose'a bir alternatif olarak ve bulut dağıtım aracı olarak kendine özgü bir rol üstlenebileceğini umuyorum.  
Halihazırda docker compose veya k8s manifest dosyaları üreten [jeneratörler](https://prom3theu5.github.io/aspirational-manifests/generate-command.html) bulunduğundan, sıradan geliştiricilerin altyapı araçlarına erişiminin daha da kolaylaştığını düşünüyorum.
