---
id: 7ecf5f1e3335910ea27e6beb68f04ae7
author: snowmerak
title: Mencoba menjalankan server Go secara skalabel dengan dotnet aspire
description: 'Cara Menjalankan Server Go secara Skalabel dengan .NET Aspire: Contoh Konfigurasi Penskalaan Horizontal dan Otomasi menggunakan Reverse Proxy YARP.'
language: id
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 adalah alat yang dibuat untuk membantu pengembang dalam pengembangan dan konfigurasi cloud native, seiring dengan semakin meluasnya lingkungan cloud native. Alat ini memungkinkan pengembang .NET untuk dengan mudah men-deploy proyek .NET, berbagai infrastruktur cloud native, serta layanan atau kontainer yang ditulis dalam bahasa lain.

Tentu saja, mulai dari docker hingga k8s telah dirilis dan dioperasikan, dan cukup banyak bidang, industri, serta pengembang yang sebelumnya berada di lingkungan on-premise kini sedang bermigrasi, bahkan sudah bermigrasi, ke lingkungan cloud native. Ini sudah menjadi bidang yang matang. Oleh karena itu, saya rasa tidak perlu lagi menjelaskan ketidaknyamanan yang ada sebelumnya terkait nama host, konfigurasi port, firewall, pengelolaan metrik, dan sebagainya.

Karena itu, hanya dengan penjelasan di atas, Anda pasti belum bisa membayangkan dengan jelas apa itu dotnet aspire. Alasannya, Microsoft sendiri belum memberikan definisi yang pasti mengenai hal ini. Oleh karena itu, saya pun tidak akan memberikan definisi tersendiri. Namun, dalam tulisan ini saya akan menggunakan fitur dasar dotnet aspire sebagaimana yang saya pahami, sehingga Anda dapat merujuknya dan menentukan posisi Anda sendiri.

## Konfigurasi Proyek

### Membuat proyek dotnet aspire

Jika template dotnet aspire belum tersedia, Anda perlu menginstal template tersebut terlebih dahulu. Instal template dengan perintah berikut. Jika .NET belum terpasang, silakan instal sendiri.

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

Lalu buat solusi baru di folder yang sesuai.

```bash
dotnet new sln
```

Setelah itu, jalankan perintah berikut di dalam folder solusi untuk membuat proyek dengan template aspire-apphost.

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

Maka akan dibuat proyek aspire-apphost yang hanya berisi kode sederhana untuk konfigurasi.

### Menambahkan Valkey

Sekarang kita akan menambahkan Valkey secara sederhana.

Sebelum menambahkannya, dotnet aspire menyediakan berbagai solusi pihak ketiga melalui community hosting.  
Tentu saja Valkey juga mendapat dukungan dari community hosting ini, dan dapat digunakan dengan mudah melalui paket NuGet berikut.

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

Selain itu, tersedia berbagai hosting terintegrasi lainnya, yang dapat Anda lihat [di sini](https://learn.microsoft.com/ko-kr/dotnet/aspire/fundamentals/integrations-overview).  
Kembali ke Valkey, buka file Program.cs pada proyek AppHost dan ubah seperti berikut.

```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` adalah implementasi dari interface `IResourceBuilder` yang memuat informasi untuk membangun layanan Valkey.  
`WithDataVolume` membuat volume untuk menyimpan data cache, sedangkan `WithPersistence` memastikan data cache disimpan secara berkelanjutan.  
Sejauh ini, tampaknya fungsinya mirip dengan `volumes` pada `docker-compose`.  
Tentu saja, Anda juga dapat membuatnya dengan mudah.  
Namun, hal tersebut berada di luar cakupan tulisan ini, sehingga tidak akan dibahas sekarang.

### Membuat server echo dengan bahasa Go

Sekarang kita akan menambahkan server sederhana dengan bahasa Go.  
Pertama, buat workspace dengan menjalankan `go work init` di folder solusi.  
Bagi pengembang .NET, workspace Go dapat dipahami mirip dengan solusi.

Kemudian buat folder bernama EchoServer, masuk ke dalamnya, dan jalankan `go mod init EchoServer`.  
Perintah ini membuat modul Go. Bagi pengembang .NET, modul dapat dipahami mirip dengan proyek.  
Lalu buat file `main.go` dan tulis seperti berikut.

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

Server ini membaca port dari variabel lingkungan `PORT` yang disuntikkan oleh Aspire AppHost saat dijalankan, lalu menjalankan server pada port tersebut.  
Server ini secara sederhana menerima query `name` dan mengembalikan `Hello, {name}`.

Sekarang mari kita tambahkan server ini ke dotnet aspire.

### Menambahkan server echo ke aspire

Kembali ke proyek Aspire AppHost tempat Valkey ditambahkan, lalu tambahkan community hosting untuk Go.

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

Kemudian buka file Program.cs dan tambahkan kode berikut.

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

Di sini, `echoServer` adalah implementasi dari interface `IResourceBuilder` yang memuat informasi untuk membangun server Go.  
Metode `AddGolangApp` yang baru saja ditambahkan merupakan metode ekstensi dari host kustom untuk menambahkan server Go.  
Server ini menggunakan port 3000 secara tetap, dan dapat dilihat bahwa variabel lingkungan `PORT` disuntikkan.  
Terakhir, `WithExternalHttpEndpoints` berfungsi agar server dapat diakses dari luar.

Untuk pengujian, akses `http://localhost:3000/?name=world`, dan Anda akan melihat keluaran `Hello, world`.

Namun, saat ini dotnet aspire memiliki penalti berat yang diberikan kepada proyek non-dotnet.  
Penalti tersebut adalah...

## Memperluas Proyek

### Bagaimana dengan penskalaan horizontal?

Saat ini dotnet aspire hanya menyediakan opsi `WithReplica` untuk builder proyek .NET yang ditambahkan dengan metode `AddProject`.  
Namun, untuk host bahasa Go atau proyek eksternal seperti `AddContainer`, opsi ini tidak tersedia.

Oleh karena itu, perlu diimplementasikan secara langsung dengan menggunakan load balancer atau reverse proxy terpisah.  
Akan tetapi, jika demikian, reverse proxy tersebut dapat menjadi SPOF, sehingga reverse proxy sebaiknya juga menyediakan opsi `WithReplica`.  
Dengan demikian, reverse proxy secara mutlak harus berupa proyek .NET.

Selama ini, saya telah mencoba berbagai pendekatan untuk masalah ini, seperti nginx, traefik, dan implementasi langsung, tetapi ketika terdapat batasan harus berupa proyek .NET, saya tidak memiliki solusi yang dapat segera diterapkan.  
Maka, saya mencari reverse proxy yang diimplementasikan dengan .NET, dan untungnya ada pilihan [YARP](https://microsoft.github.io/reverse-proxy/).  
YARP adalah reverse proxy yang diimplementasikan dengan .NET, dapat berfungsi sebagai load balancer, dan menyediakan berbagai fitur, sehingga saya menilai ini adalah pilihan yang baik.

Sekarang mari kita tambahkan YARP.

### Mengonfigurasi reverse proxy dengan YARP

Pertama, buat proyek untuk menggunakan YARP.

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

Lalu masuk ke proyek tersebut dan instal YARP.

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

Setelah instalasi selesai, buka file Program.cs dan tulis seperti berikut.

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

Kode ini adalah kode dasar untuk menggunakan YARP.  
`routes` berisi informasi rute yang akan digunakan reverse proxy, sedangkan `clusters` berisi informasi cluster yang akan digunakan reverse proxy.  
Informasi tersebut dimuat ke reverse proxy melalui metode `LoadFromMemory`.  
Terakhir, metode `MapReverseProxy` digunakan untuk memetakan dan menjalankan reverse proxy.

Selanjutnya, untuk penggunaan sebenarnya, tambahkan referensi proyek reverse proxy pada proyek aspire apphost, lalu tambahkan dan ubah kode berikut pada file Program.cs.

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

Sekarang reverse proxy dapat mereferensikan echo server.  
Struktur telah berubah sehingga permintaan dari luar diterima oleh reverse proxy dan kemudian diteruskan ke echo server.

### Memodifikasi reverse proxy

Pertama, kita perlu mengubah alamat listening dari proyek yang dijadikan reverse proxy.  
Hapus `applicationUrl` di dalam file `Properties/launchSettings.json`.  
Lalu buka file Program.cs dan ubah secara menyeluruh seperti berikut.

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

Pertama, informasi untuk `routes` dan `clusters` diubah.  
Masing-masing ditambahkan sebagai `echo-route` dan `echo-cluster`, sehingga permintaan dikirim ke echo server.  
Selain itu, alamat echo server dimodifikasi agar dibaca dari variabel lingkungan.

Aturan alamat ini adalah `services__{service-name}__http__{index}`.  
Untuk echo server, nama layanannya adalah `echo-server` dan karena merupakan instans tunggal, indeks yang digunakan adalah `0`.  
Jika server ASP.NET Core ditambahkan, beberapa instans dapat dibuat melalui `WithReplica`, sehingga indeks cukup ditingkatkan untuk digunakan.  
Nilai `http://localhost:8080` yang berada pada penanganan pengecualian adalah nilai tidak bermakna.

Sekarang jalankan proyek dan akses `http://localhost:3000/?name=world`, Anda akan tetap melihat keluaran `Hello, world`.

### Gagasan perluasan

Sejauh ini, kita telah memastikan bahwa server Go ditambahkan ke dotnet aspire dan permintaan diteruskan melalui reverse proxy.  
Selanjutnya, proses ini dapat diperluas agar diimplementasikan secara programmatic.  
Sebagai contoh, dengan menambahkan nomor urut di belakang nama layanan echo server untuk membuat beberapa instans, konfigurasi reverse proxy dapat ditambahkan secara otomatis.

Ubah kode berikut pada file Program.cs proyek aspire apphost untuk menggunakan reverse proxy dan echo server.

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

Lalu ubah file Program.cs pada proyek reverse proxy seperti berikut.

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

Konfigurasi destinasi ditambahkan untuk delapan instans echo server.  
Sekarang reverse proxy memiliki informasi destinasi untuk seluruh echo server yang bertambah, sehingga dapat meneruskan permintaan.  
Jika Anda mengakses `http://localhost:3000/?name=world` seperti sebelumnya, Anda akan tetap melihat keluaran `Hello, world`.

## Penutup

Dalam tulisan ini, saya telah menjelaskan proses menambahkan server Go ke dotnet aspire dan meneruskan permintaan melalui reverse proxy.  
Namun, bagian perluasan belum sepenuhnya saya tuliskan, dan contoh yang dapat diimplementasikan secara lebih programmatic melalui variabel lingkungan telah saya tulis di repositori terpisah.  
Untuk konfigurasi proyek dan kode yang lebih lengkap, silakan lihat [snowmerak/AspireStartPack](https://github.com/snowmerak/AspireStarterPack).

Secara pribadi, saya berharap dotnet aspire dapat memainkan perannya sendiri sebagai alternatif docker compose sekaligus sebagai alat deployment cloud. Saat ini sudah ada [generator](https://prom3theu5.github.io/aspirational-manifests/generate-command.html) yang menghasilkan manifest docker compose atau k8s, sehingga saya rasa aksesibilitas alat infrastruktur bagi pengembang umum telah meningkat.
