Mert Özen Her Satırda Daha İleri
Backend Aspire ile Dağıtık Uygulamalar — #01

Aspire Nedir, Ne Değildir? AppHost, App Model ve Docker Compose/Kubernetes ile Farkı

Mert Özen 14 Kas 2026 14 dk 8 görüntülenme
Aspire Nedir, Ne Değildir? AppHost, App Model ve Docker Compose/Kubernetes ile Farkı

Aspire, çok service'li bir uygulamanın parçalarını ve bağlantılarını AppHost'ta kodla tarif edip tek komutla çalıştıran, telemetry'sini dashboard'da toplayan lokal bir orchestration ve observability katmanı; production orchestrator'ı ya da service mesh değil. Yazı Bookstore'a aspire init ile AppHost eklemeyi, DCP proxy'sini, Aspire'ın verdiği environment variable'ları ve ServiceDefaults olmadan neden trace görünmediğini gerçek çıktılarla gösteriyor.

Sürüm notu: Bu yazıdaki kodlar .NET 10, Aspire 13.6 (Aspire CLI 13.6.5) ve Angular 22 ile derlenip denenmiştir.

Önce Tahmin Et

Bookstore.Api'nin launch profile'ında adres http://localhost:5080. Bu yazının sonunda API'yi Aspire'ın AppHost'una ekleyip aspire run ile başlatacağız. O anda API process'i hangi port'u dinler: 5080'i mi, başka bir port'u mu? Angular'ın dev server'ı API'nin adresini nereden öğrenir? Dashboard'a log düşer ama trace düşmezse sebebi ne olabilir? Üç sorunun cevabını da gerçek çalıştırmanın çıktısında göreceğiz.

Bookstore'da Dağınık Olan Ne?

Bu serinin örneği, sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı). Başlangıç hâli küçük: kitapları listeleyen bir ASP.NET Core API'si (Bookstore.Api) ve onu çağıran bir Angular uygulaması (bookstore-web). Aspire'dan önce geliştirme akışı şöyleydi: bir terminalde dotnet run --project src/Bookstore.Api, ikinci terminalde npm start, Angular tarafında da API adresini bilen bir proxy dosyası:

// Angular dev server'ındaki /api isteklerini Bookstore.Api'ye iletir
const target = process.env.BOOKSTORE_API_URL ?? 'http://localhost:5080';

module.exports = {
  '/api': { target, secure: false, changeOrigin: true },
};

İki process'le bu düzen idare ediyor. Sorun, sisteme her yeni parça eklendiğinde aynı bilgilerin farklı yerlere tekrar yazılması. SQL Server eklenince connection string appsettings.Development.json'a girer; Redis gelince bir connection string daha; ikinci bir API gelince birinci API'nin adresi bir config dosyasına daha yazılır. Port'lar ekibin her makinesinde aynı olmak zorunda kalır, biri 5080'i başka bir iş için kullanıyorsa her şey değişir. Başlatma sırası README'de bir madde olarak durur: "önce container'ları aç, migration'ı çalıştır, sonra API'yi başlat". Log'lar dört terminale dağılır; bir isteğin API'den veritabanına ve oradan başka bir service'e nasıl gittiğini görmek için ayrıca bir telemetry altyapısı kurmak gerekir.

Aspire'ın cevabı bu bilgileri tek bir yerde, kodla tarif etmek: hangi parçalar var, hangisi hangisine bağlı, hangisi hangisini bekler. Geri kalanı (port seçmek, adresleri ve connection string'leri environment variable olarak dağıtmak, process ve container'ları başlatmak, telemetry'yi toplamak) Aspire yapıyor.

Aspire'ın Parçaları: AppHost, Integration'lar, Dashboard ve Deploy

Aspire kendini aspire.dev'deki tanıtım sayfasında "Aspire is a code-first orchestration and observability layer for distributed applications" diye tanımlıyor (What is Aspire, aspire.dev, 2026). Kod-önce bir orchestration ve observability katmanı: bu cümledeki iki kelime, ürünün iki ana işini anlatıyor. Parçalar şunlar:

AppHost. Sistemi tarif eden küçük bir program. C# (proje ya da tek dosya) veya TypeScript ile yazılıyor; bu seride C# kullanacağız. AppHost'ta tarif ettiğin şeylerin her birine resource deniyor: bir .NET projesi, bir container, bir Node.js uygulaması, bir veritabanı, bir parameter. Resource'lar arasındaki WithReference ve WaitFor gibi ilişkilerle birlikte ortaya çıkan grafa app model deniyor. AppHost iki modda çalışıyor: run modunda (aspire run) modeli lokal makinede ayağa kaldırıyor, publish/deploy modunda aynı modelden deploy çıktısı üretiyor.

Integration'lar. SQL Server, Redis, RabbitMQ, PostgreSQL, Keycloak gibi bileşenleri modele eklemeyi kolaylaştıran NuGet paketleri. İki tarafı var: AppHost tarafındaki hosting integration (Aspire.Hosting.SqlServer container'ı ve connection string'i tarif eder) ve service tarafındaki client integration (Aspire.Microsoft.EntityFrameworkCore.SqlServer o connection string'i okuyup DbContext'i health check, retry ve telemetry ile kaydeder). Ayrıntısı 05'te.

Dashboard. Çalışan resource'ları, console log'larını, structured log'ları, trace'leri ve metric'leri gösteren web arayüzü. Service'ler telemetry'yi OTLP (OpenTelemetry Protocol; log, metric ve trace verisini taşıyan standart protokol) ile dashboard'a gönderiyor. 04 tamamen dashboard'a ayrıldı.

Deploy. App model yalnız lokal için değil: aspire publish modelden Docker Compose dosyası, Kubernetes için Helm chart'ı ya da Azure kaynakları gibi çıktılar üretiyor, aspire deploy bunları uyguluyor. 07'nin konusu.

Bu modelde senin kodun değişmiyor. Aspire, service'lerine bir library ya da base class dayatmıyor; yalnız onları başlatıyor, ihtiyaç duydukları adresleri environment variable olarak veriyor ve gönderdikleri telemetry'yi topluyor. Service'in Aspire'a bağımlılığı, birazdan ekleyeceğimiz ve tamamen senin projende duran ServiceDefaults'tan ibaret.

Aspire Ne Değildir?

Aspire production orchestrator'ı değil. AppHost'u production'da çalıştırmıyorsun; AppHost'un lokal tarafında process'leri ve container'ları başlatan bileşen DCP (Developer Control Plane; Aspire'ın lokal orchestration motoru), geliştirici makinesi için yazılmış. Production'da container'ları Kubernetes, Azure Container Apps ya da Docker Compose çalıştırıyor; Aspire en fazla onların manifest'lerini üretiyor. Service mesh de değil: service'ler arasındaki trafiğe sidecar koymuyor, mTLS (iki tarafın da sertifikayla kimliğini kanıtladığı şifreli bağlantı) ya da trafik politikası uygulamıyor. Zorunlu bir framework da değil: AppHost'u silsen bile service'lerin normal birer ASP.NET Core ve Angular uygulaması olarak çalışmaya devam ediyor.

En sık karşılaştırıldığı araçlarla farkı şöyle:

AraçNe yaparAspire'dan farkı
Docker ComposeYAML (girintiyle yazılan bir yapılandırma dosyası formatı) ile container'ları ve ağlarını tarif edip çalıştırırYalnız container bilir; .NET projesini ve npm script'ini doğrudan çalıştırmaz, telemetry toplamaz. Aspire bir Compose dosyası üretebilir (07).
KubernetesProduction'da container'ları planlar, ölçekler, iyileştirirHedef platform; Aspire onun yerine geçmez, ona manifest üretir.
Project TyeMicrosoft'un deneysel orchestration aracıAspire'ın öncülü sayılıyor; GitHub deposu 2023'ten beri arşivde.
DaprSidecar ile state, pub/sub, service invocation gibi API'ler sağlayan runtimeProduction'da da çalışan bir runtime. Aspire, Dapr sidecar'larını Community Toolkit integration'ıyla modele ekleyebilir; birbirinin alternatifi değiller.

Kısacası Compose'un yerini lokal geliştirmede alabiliyor, Kubernetes'in yerini hiçbir yerde almıyor. Docker ve Compose'un temelleri Docker ve Docker Compose serilerinde, Kubernetes kaynakları Kubernetes serisinde; burada onları tekrar anlatmıyoruz.

".NET Aspire"dan "Aspire"a: 13.x Sürümleri

Ürünün adı 11 Kasım 2025'te çıkan 13.0 ile ".NET Aspire"dan "Aspire"a döndü, dokümantasyon da learn.microsoft.com'dan aspire.dev'e taşındı. Ad değişikliğinin sebebi kapsamın genişlemesi: 13.0'dan beri Python ve JavaScript uygulamaları (AddPythonApp, AddJavaScriptApp, AddViteApp) .NET projeleriyle aynı düzeyde destekleniyor, TypeScript ile AppHost yazmak da mümkün. 13.0'ın bir başka şartı .NET 10 SDK'sı: AppHost'u derlemek için .NET 10 gerekiyor, service'lerin hedef framework'ü ise senin kararın. Serinin adını da bu yüzden "Aspire ile Dağıtık Uygulamalar" koyduk.

Aspire o günden beri yaklaşık iki ayda bir minor sürüm çıkarıyor; yazım günü son sürüm 29 Eylül 2026'da çıkan 13.6'nın 13.6.5 yaması. Seri boyunca bu sürümle çalışıyoruz. Arada gelen yenilikleri (TypeScript AppHost, dashboard'da telemetry'nin kalıcı saklanması, Kubernetes deploy'u) ilgili yazılarda, sürüm geçmişinin tamamını ve eski projeyi taşımayı da 08'de anlatıyoruz. Arama motorunda karşına çıkan bir örnek AddNpmApp ya da Aspire.Hosting.NodeJs kullanıyorsa 13 öncesine ait demektir: 13.0'da AddNpmApp obsolete oldu ve paket Aspire.Hosting.JavaScript adını aldı.

Aspire, .NET ile Mikroservis Uygulaması serisinde de lokal orchestrator olarak kullanılıyor. O seri Aspire'ı yalnız kullanıyor ve Bookstore'un service'lere nasıl bölündüğünü anlatıyor; bu seri ise Aspire'ın kendisini ayrıntılı ele alıyor. İki seri aynı Aspire sürümünü (13.6.5) kullanıyor.

Kurulum: Aspire CLI, .NET 10 SDK ve Container Runtime

Üç şey gerekiyor. .NET 10 SDK'sı, bir container runtime (Docker Desktop ya da Podman; Aspire varsayılan olarak Docker'ı kullanıyor, ASPIRE_CONTAINER_RUNTIME=podman ile Podman'a geçiliyor) ve Aspire CLI. CLI'ı Windows'ta PowerShell'den resmi script ile kuruyorsun; .NET global tool olarak kurmak da bir seçenek:

irm https://aspire.dev/install.ps1 | iex

# alternatif: .NET global tool
dotnet tool install -g Aspire.Cli

aspire --version
13.6.5+b33d222049f1ecc3bc7257ec07b622fe550b2580

CLI'ı güncellemek için aspire update --self, projedeki Aspire paketlerini güncellemek için aspire update var. Ortamı kontrol etmek için aspire doctor komutu container runtime'ı, SDK'yı ve HTTPS geliştirme sertifikasını tek tek deniyor. Bizim makinede çıktının ilgili kısmı:

aspire doctor

Container Runtime
  ✅ Docker v29.2.1: running (auto-detected (default)) ← active

Environment
  ⚠️ No HTTPS development certificate found
       Run 'aspire certs trust' to trust the HTTPS development certificate.

Summary: 5 passed, 2 warnings, 0 failed

Sertifika uyarısı önemli: AppHost'un varsayılan https launch profile'ı dashboard'u HTTPS ile açıyor ve güvenilir bir geliştirme sertifikası istiyor. Windows'ta aspire certs trust bir kez çalıştırılıyor ve sorun kalmıyor. Bu yazıdaki çıktıları http profiliyle aldık; o profilde AppHost'un HTTP'ye izin vermesi için ASPIRE_ALLOW_UNSECURED_TRANSPORT=true gerekiyor, birazdan launch settings'e ekleyeceğiz.

Sıfırdan başlıyorsan aspire new hazır template'lerden birini (aspire-starter: ASP.NET Core API + Blazor, aspire-ts-cs-starter: C# AppHost + React, aspire-py-starter: FastAPI + React gibi) seçtiriyor. Bookstore'un ise zaten bir API'si ve bir Angular uygulaması var; var olan bir repo için komut aspire init.

Var Olan Bookstore'a aspire init

Repo kökünde aspire init --language csharp çalıştırdık. İlk denemede komut bir seçim sordu: "hangi solution dosyası?" Repo'da tek bir Bookstore.slnx var, ama init solution ararken web/bookstore-web/node_modules altındaki bir npm paketinin içinde duran .sln dosyasını da buldu. İnteraktif terminalde doğru dosyayı seçmek yetiyor; --non-interactive ile CI'da çalıştırırsan komut "This prompt requires interactive input" hatasıyla duruyor. İkinci denemede init şunları üretti:

Bookstore.AppHost/
    AppHost.cs
    Bookstore.AppHost.csproj
    aspire.config.json
    appsettings.json
    appsettings.Development.json
    Properties/launchSettings.json

Dokümantasyon, solution bulunan bir C# reposunda init'in AppHost'u solution'a ekleyip var olan projeler için AddProject satırları yazdığını söylüyor. 13.6.5 ile bizim denememizde böyle olmadı: AppHost repo kökünde bir klasöre kondu, Bookstore.slnx'e eklenmedi, AppHost.cs boş geldi (yalnız CreateBuilder ve Build().Run()) ve ServiceDefaults projesi oluşmadı. Eksikleri elle tamamladık:

mv Bookstore.AppHost src/
dotnet sln add src/Bookstore.AppHost
dotnet add src/Bookstore.AppHost reference src/Bookstore.Api
dotnet add src/Bookstore.AppHost package Aspire.Hosting.JavaScript

AppHost'un proje dosyası sıradan bir console uygulamasına benziyor; farkı SDK satırında:

<Project Sdk="Aspire.AppHost.Sdk/13.6.5">

  <ItemGroup>
    <ProjectReference Include="..\Bookstore.Api\Bookstore.Api.csproj" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Aspire.Hosting.JavaScript" />
  </ItemGroup>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AspireUseCliBundle>true</AspireUseCliBundle>
    <UserSecretsId>6f54b2e6-158b-4fd3-8de9-6373025aee8d</UserSecretsId>
  </PropertyGroup>

</Project>

Aspire.AppHost.Sdk, Aspire.Hosting.AppHost paketini kendiliğinden ekliyor ve AppHost'un referans verdiği her proje için bir tip üretiyor: Bookstore.Api için Projects.Bookstore_Api. Bu referans AppHost'un API kodunu çağırdığı anlamına gelmiyor; AppHost yalnız projenin yolunu ve launch profile'ını öğreniyor. AspireUseCliBundle, dashboard ve DCP'nin NuGet paketinden değil Aspire CLI'ın kendi dosyalarından alınmasını sağlıyor. Repo'da Central Package Management açık olduğu için paket sürümü Directory.Packages.props'ta duruyor. aspire.config.json ise CLI'a AppHost'un nerede olduğunu söylüyor:

{
  "appHost": {
    "path": "Bookstore.AppHost.csproj"
  }
}

Şimdi modeli yazalım. İki resource ve aralarındaki bir ilişki:

IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(args);

IResourceBuilder<ProjectResource> catalog = builder.AddProject<Projects.Bookstore_Api>("catalog");

builder.AddJavaScriptApp("web", "../../web/bookstore-web", "start")
    .WithHttpEndpoint(env: "PORT")
    .WithReference(catalog)
    .WaitFor(catalog);

builder.Build().Run();

Resource adına dikkat: Bookstore.Api'yi "catalog" adıyla ekledik, çünkü bugün yaptığı iş kitap katalogunu sunmak. Bu ad yalnız dashboard'da görünen bir etiket değil; birazdan göreceğimiz environment variable'ların ve 03'te service discovery'nin kullandığı ad. AddJavaScriptApp Angular uygulamasını npm run start ile başlatıyor, WithHttpEndpoint(env: "PORT") Aspire'ın seçtiği port'u PORT değişkeniyle veriyor. WithReference(catalog) web'e catalog'un adresini veriyor, WaitFor(catalog) da catalog ayağa kalkmadan web'i başlatmıyor. Bu satırların ayrıntısı 02'de.

Angular tarafında proxy dosyası artık sabit port yerine Aspire'ın verdiği değişkeni okuyor:

// Angular dev server'ındaki /api isteklerini catalog API'sine iletir.
// Aspire altında WithReference(catalog) CATALOG_HTTPS / CATALOG_HTTP değişkenlerini verir.
const target = process.env.CATALOG_HTTPS ?? process.env.CATALOG_HTTP ?? 'http://localhost:5080';

module.exports = {
  '/api': { target, secure: false, changeOrigin: true },
};

Son olarak AppHost'un Properties/launchSettings.json'ındaki http profiline "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true" ekledik ve launchBrowser'ı kapattık. Init'in ürettiği port'lar (dashboard için 15278, OTLP için 19010) olduğu gibi kaldı.

İlk aspire run: Process'ler, Port'lar ve Environment Variable'lar

aspire run -lp http

Finding AppHosts...
src/Bookstore.AppHost/Bookstore.AppHost.csproj
🛠️ Building AppHost... src/Bookstore.AppHost/Bookstore.AppHost.csproj
Connecting to AppHost...
Starting dashboard...
     AppHost:  src/Bookstore.AppHost/Bookstore.AppHost.csproj
   Dashboard:  http://localhost:15278/login?t=<token>
               Press CTRL+C to stop the AppHost and exit.

Dashboard adresindeki t parametresi tek seferlik bir browser token'ı; dashboard'u açan kişinin terminali gören kişi olduğunu doğruluyor. Dashboard'a girmeden CLI'dan da bakabilirsin. aspire describe çalışan AppHost'u buluyor ve resource'ları listeliyor:

aspire describe

┌───────────────┬────────────┬──────────┬─────────┬────────────────────────┐
│ Name          │ Type       │ State    │ Health  │ URLs                   │
├───────────────┼────────────┼──────────┼─────────┼────────────────────────┤
│ catalog       │ Project    │ Running  │ Healthy │ http://localhost:5080  │
│ web           │ Executable │ Running  │ Healthy │ http://localhost:58321 │
│ web-installer │ Executable │ Finished │ -       │ -                      │
└───────────────┴────────────┴──────────┴─────────┴────────────────────────┘

İki resource tarif ettik ama üç satır var. web-installer'ı Aspire ekledi: Angular'ı başlatmadan önce npm install çalıştıran kısa ömürlü bir resource. "Finished" durumu işini bitirip 0 koduyla çıktığını gösteriyor; web onu bekliyor. Bu sayede repo'yu yeni klonlayan biri node_modules'ü kendisi kurmadan aspire run diyebiliyor.

01-run-model-tr.png

Önce Tahmin Et'teki ilk sorunun cevabı tablonun URLs sütununda ve process'in environment variable'larında. aspire describe catalog --format Json ile catalog'a verilen değişkenlere baktık (OTLP API key'ini gizledik):

ASPNETCORE_URLS = http://localhost:20270
DOTNET_LAUNCH_PROFILE = http
OTEL_EXPORTER_OTLP_ENDPOINT = http://localhost:19010
OTEL_EXPORTER_OTLP_HEADERS = x-otlp-api-key=<key>
OTEL_EXPORTER_OTLP_PROTOCOL = grpc
OTEL_SERVICE_NAME = catalog
OTEL_TRACES_SAMPLER = always_on

API process'i 5080'i değil, Aspire'ın seçtiği 20270'i dinliyor; bir sonraki çalıştırmada aynı process'in log'unda Now listening on: http://localhost:10077 yazıyordu. 5080'i ise DCP'nin proxy'si dinliyor ve gelen bağlantıyı o anki gerçek port'a iletiyor. Launch profile'daki port'u bu yüzden kaybetmiyorsun: tarayıcıda, Postman'de ve başka resource'ların config'inde adres hep 5080, arkadaki port her çalıştırmada değişse de. Proxy'nin bir yan etkisi de var: process durdurulsa bile 5080 dinlenmeye devam ediyor, bu ayrıntı 03'te karşımıza çıkacak.

İkinci sorunun cevabı web'in değişkenlerinde:

CATALOG_HTTP = http://localhost:5080
services__catalog__http__0 = http://localhost:5080
PORT = 20267
NODE_ENV = development
OTEL_SERVICE_NAME = web

WithReference(catalog) catalog'un adresini iki biçimde veriyor. services__catalog__http__0, .NET'in service discovery library'sinin okuduğu biçim (çift alt çizgi, .NET configuration'da : yerine geçiyor; 03'te ayrıntısı var). CATALOG_HTTP ise .NET olmayan uygulamaların kolayca okuyabildiği, resource adı ve endpoint adından oluşan kısa biçim; proxy.conf.js bunu okuyor. Angular CLI 22 de PORT değişkenini kendisi tanıyor; web'in log'unda Environment variable "PORT" detected. Using port 20267. satırı var. Yani package.json'daki start script'ine dokunmamız gerekmedi. Proxy'nin çalıştığını web'in adresinden API'ye giderek denedik:

curl -i http://localhost:58321/api/books/9780547928227

HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
server: Kestrel

{"isbn":"9780547928227","title":"The Hobbit","author":"J. R. R. Tolkien","price":340.00,"currency":"TRY"}

ServiceDefaults Olmadan Trace Yok

Üçüncü soru. Bu hâliyle dashboard'un Console sekmesinde catalog'un çıktısı görünüyor, ama Traces ve Structured Logs sekmeleri boş. CLI da aynı şeyi söylüyor:

aspire otel traces catalog

❌ Resource 'catalog' not found.

Sebep basit: Aspire catalog'a OTEL_EXPORTER_OTLP_ENDPOINT'i verdi, ama API'de bu değişkeni okuyup telemetry gönderecek OpenTelemetry kodu yok. Console log'u Aspire process'in standart çıktısından okuyor; trace ve structured log ise uygulamanın kendisinin göndermesi gereken veri. Aspire bunun için ServiceDefaults adında, senin repo'nda duran küçük bir library öneriyor. Template'ten ekledik:

dotnet new aspire-servicedefaults -o src/Bookstore.ServiceDefaults -n Bookstore.ServiceDefaults
dotnet sln add src/Bookstore.ServiceDefaults
dotnet add src/Bookstore.Api reference src/Bookstore.ServiceDefaults

Template paket sürümlerini .csproj'a yazıyor (OpenTelemetry 1.17, Microsoft.Extensions.Http.Resilience ve ServiceDiscovery 10.10). Bunları Directory.Packages.props'a taşıyıp NuGet'teki güncel sürümlere (OpenTelemetry 1.21.0, Microsoft.Extensions.* 10.11.0) yükselttik. API tarafında değişiklik iki satır:

using Bookstore.Api.Books;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.Services.AddSingleton<BookCatalog>();

WebApplication app = builder.Build();

app.MapDefaultEndpoints();
app.MapBookEndpoints();

app.Run();

AppHost'u yeniden başlatıp iki istek attıktan sonra:

aspire otel traces catalog

┌──────────────┬──────────────────────────────────────────┬───────┬──────────┬────────┐
│ Timestamp    │ Name                                     │ Spans │ Duration │ Status │
├──────────────┼──────────────────────────────────────────┼───────┼──────────┼────────┤
│ 18:03:34.252 │ catalog: GET /api/books/ 9909a2c         │ 1     │ 34.82ms  │ OK     │
│ 18:03:34.293 │ catalog: GET /api/books/{isbn} e00a1d4   │ 1     │ 1.35ms   │ OK     │
└──────────────┴──────────────────────────────────────────┴───────┴──────────┴────────┘
Showing 2 of 2 traces

Trace adlarında URL değil route template'i (/api/books/{isbn}) var; bu ASP.NET Core instrumentation'ının davranışı ve trace'leri gruplamayı kolaylaştırıyor. AddServiceDefaults() OpenTelemetry'nin yanında health check'leri, service discovery'yi ve her HttpClient'a standard resilience handler'ı da ekliyor; MapDefaultEndpoints() de /health ve /alive endpoint'lerini map'liyor. İçinde ne olduğunu ve neden /health'in yalnız Development'ta açıldığını 03'te satır satır okuyacağız.

Üretimde Dikkat

AppHost bir geliştirme aracı ve production'a deploy edilmiyor. Production'da Aspire'dan kalan iki şey var: service'lerin içindeki ServiceDefaults kodu ve (kullanırsan) aspire publish'in ürettiği deploy çıktısı. ServiceDefaults'taki OpenTelemetry kodu production'da da çalışıyor; OTEL_EXPORTER_OTLP_ENDPOINT tanımlı değilse exporter hiç eklenmiyor, tanımlıysa telemetry o adrese, örneğin bir OpenTelemetry Collector'a gidiyor. Dashboard'u production izleme aracı olarak kullanmak doğru değil; 04'te nedenini anlatıyoruz, production telemetry backend'leri Observability serisinin konusu.

Lokalde gördüğün port'lara güvenme. AppHost'un dışında hiçbir yerde 20270 gibi gerçek bir port yazmamalısın; bir sonraki çalıştırmada değişiyor. Sabit kalması gereken adres launch profile'daki port (5080) ve resource adı (catalog).

Aspire CLI ve paketler aynı sürümde kalsın. CLI 13.6.5, Aspire.AppHost.Sdk/13.6.5 ve Aspire.Hosting.* paketleri aynı sürüm numarasını taşıyor ve birlikte güncellenmeli; ekipte herkesin aynı CLI sürümünü kullanması sürprizleri azaltıyor. aspire update projedeki Aspire paketlerini tek komutla güncelliyor, değişikliği bir pull request'te toplu gözden geçirmek yeterli. Bir de güvenlik notu: dashboard'un URL'indeki browser token'ı ve service'lere verilen OTLP API key'i lokal ortamın anahtarları; ekran paylaşırken ya da log yapıştırırken bunları gizle.

Sık Düşülen Tuzaklar

Init'in her şeyi yaptığını varsaymak. Bizim denememizde init yalnız boş bir AppHost üretti. Solution'a eklenip eklenmediğini, referansları ve ServiceDefaults'u kontrol et; dotnet build AppHost'u derlemiyorsa solution'da değildir.

Angular'ın proxy'sine port yazmak. proxy.conf.js'e http://localhost:20270 yazarsan bir sonraki çalıştırmada bozulur. Aspire'ın verdiği CATALOG_HTTP/CATALOG_HTTPS değişkenini oku, Aspire olmadan çalıştırma için de bir varsayılan bırak.

Dashboard'da trace yoksa Aspire'ı suçlamak. Aspire telemetry'yi toplamıyor, yalnız alıyor. Service'te OpenTelemetry yoksa (ServiceDefaults eklenmemiş ya da AddServiceDefaults() çağrılmamışsa) Console sekmesi dolu, Traces sekmesi boş kalır.

Resource adını gelişigüzel seçmek. Ad yalnız dashboard'da görünmüyor; CATALOG_HTTP gibi environment variable'lara ve service discovery adreslerine de yayılıyor, sonradan değiştirmek bu yerlerin hepsine dokunmak demek. Kısa, service'in işini anlatan ve değişmeyecek bir ad seç.

Aspire'ı Docker Compose'un yerine production'da kullanmayı planlamak. AppHost'u bir sunucuda aspire run ile çalıştırmak ne destekleniyor ne de mantıklı; production'a giden yol aspire publish/deploy.

Küçük Bir Deneme

★☆☆ AppHost çalışırken aspire describe catalog --format Json ile catalog'un ASPNETCORE_URLS değerini not al, AppHost'u durdurup yeniden başlat ve tekrar bak. Değişti mi? curl http://localhost:5080/api/books iki çalıştırmada da cevap veriyor mu?

★★☆ AppHost.cs'ten .WithReference(catalog) satırını sil ve web'i yeniden başlat. CATALOG_HTTP değişkeni kayboluyor mu? Angular sayfası hâlâ kitapları gösteriyor mu, gösteriyorsa neden (proxy dosyasındaki varsayılanı düşün)?

★★★ aspire init --file-based --language csharp ile boş bir klasörde tek dosyalık bir AppHost (apphost.cs) oluştur. #:sdk ve #:package satırlarını incele; Bookstore'un iki resource'unu bu dosyaya taşıyıp proje tabanlı AppHost'la aynı sonucu alabiliyor musun?

Bookstore şimdi tek komutla açılıyor, ama içinde henüz veritabanı yok ve kitaplar bellekte duruyor. Bir sonraki yazıda AppHost'a SQL Server, Redis ve bir migration worker'ı ekliyoruz: resource türleri, WithReference'ın ürettiği connection string'ler, WaitFor ile WaitForCompletion farkı, secret parameter'lar ve AppHost kapandığında silinmeyen kalıcı container'lar.