From 7128644ef78031f99963bbd7ce8cca4a332e0559 Mon Sep 17 00:00:00 2001 From: Chris Ayers Date: Thu, 7 May 2026 11:06:25 -0400 Subject: [PATCH 1/2] slides: split 10 dense slides for breathing room Spread tight slides into focused single-idea slides (37 -> 47): - Polyglot Problem: stack vs. orchestration pain - One Orchestrator: promises vs. AppHost example - Standalone Dashboard: pitch vs. OTEL wiring example - OpenTelemetry: Python and Node.js separated - Connection Strings: pattern vs. cross-language samples - AppHost Languages: C#/TS authoring vs. workload runtimes - Multi-Language Integrations: ATS authoring vs. TS consumer - aspire.config.json: file vs. CLI management - Agent-Ready CLI: MCP overview vs. setup - Dev to Production: commands vs. console output No content changes; speaker notes preserved. --- slides/Slides.md | 354 ++++++++++++++++++++++++++++------------------- 1 file changed, 208 insertions(+), 146 deletions(-) diff --git a/slides/Slides.md b/slides/Slides.md index 9e0d4c4..26daebd 100644 --- a/slides/Slides.md +++ b/slides/Slides.md @@ -37,9 +37,6 @@ footer: '@Chris_L_Ayers - https://chris-ayers.com' Your team doesn't use one language β€” it uses **five**. -
-
- **Your stack today** - Python ML services - Go microservices @@ -47,21 +44,24 @@ Your team doesn't use one language β€” it uses **five**. - TypeScript/React frontends - .NET backend APIs -
-
+**The question:** How do you orchestrate, observe, and wire all of this **from one place**? -**Your orchestration pain** -- Docker Compose: manual port wiring, no built-in telemetry, no health-aware startup ordering -- Each language has its own config format (`.env`, YAML, `application.properties`...) -- No unified observability β€” good luck tracing a request across 4 services -- 15-step README to run locally β€” "just `docker-compose up`" never works first time + -
-
+--- -**The question:** How do you orchestrate, observe, and wire all of this **from one place**? + - +# The Orchestration Pain + +**Five stacks. Five toolchains. Zero shared model.** + +- 🚒 **Docker Compose** β€” manual port wiring, no built-in telemetry, no health-aware startup ordering +- πŸ“„ **Config sprawl** β€” `.env`, YAML, `application.properties`, `appsettings.json` β€” each language has its own format +- πŸ” **No unified observability** β€” good luck tracing a request across four services in three runtimes +- πŸ“œ **15-step READMEs** β€” "just `docker-compose up`" never works first time + + --- @@ -110,26 +110,30 @@ Logs, traces, metrics, and health for every resource β€” powered by OpenTelemetr --- - + # One Orchestrator for Every Language **Aspire gives you three things, regardless of language:** -
-
- 🎯 **Orchestration** -Define your entire stack β€” Python, Go, Java, TypeScript, .NET β€” in one AppHost file +Define your entire stack β€” Python, Go, Java, TypeScript, .NET β€” in one AppHost file. πŸ“‘ **Service Discovery** -Endpoints and connection strings auto-injected as environment variables +Endpoints and connection strings auto-injected as environment variables. πŸ“Š **Observability** -One dashboard for logs, traces, and metrics across ALL services via OpenTelemetry +One dashboard for logs, traces, and metrics across **all** services via OpenTelemetry. -
-
+ + +--- + + + +# Your Stack in One File + +**One C# AppHost wires Python, React, and .NET β€” auto-discovery, observability, lifecycle:** ```csharp var builder = DistributedApplication.CreateBuilder(args); @@ -153,10 +157,7 @@ builder.AddProject("api") builder.Build().Run(); ``` -
-
- - + --- @@ -189,35 +190,37 @@ Aspire sets `OTEL_EXPORTER_OTLP_ENDPOINT` automatically β€” add OpenTelemetry to --- - + # Standalone Dashboard β€” No AppHost Required ### Already on OTEL? Get the dashboard with zero rewrites. -
-
- **The Aspire Dashboard ships as a standalone container.** Point any OTLP-emitting app at it and you get logs, traces, and metrics β€” no AppHost, no .NET, no commitment. -- βœ… **Node.js / Python / Java / Go / Rust** β€” anything with an OTEL SDK works. -- βœ… **Same UI** as the AppHost-managed dashboard. -- βœ… **Local-only by default** β€” OTLP endpoint and dashboard auth keys printed at startup. -- βœ… **Use it in CI**, in a Dockerfile, in `docker-compose.yml`, or attached to a Kubernetes pod. - -**Migration path:** start with the standalone dashboard for observability today; adopt the AppHost later when you want service discovery, integrations, and `aspire deploy`. - -
-
+- βœ… **Node.js / Python / Java / Go / Rust** β€” anything with an OTEL SDK works +- βœ… **Same UI** as the AppHost-managed dashboard +- βœ… **Local-only by default** β€” OTLP endpoint and dashboard auth keys printed at startup +- βœ… **Use it in CI**, in a Dockerfile, in `docker-compose.yml`, or attached to a Kubernetes pod -**Run it standalone** ```bash docker run --rm -it -p 18888:18888 -p 4317:18889 \ -d --name aspire-dashboard \ mcr.microsoft.com/dotnet/aspire-dashboard:latest ``` -**Point your Node.js app at it** +Open `http://localhost:18888` β€” done. + + + +--- + + + +# Wire Any OTEL App in 30 Seconds + +**Point your Node.js app at the standalone dashboard:** + ```javascript // otel.js import { NodeSDK } from "@opentelemetry/sdk-node"; @@ -233,25 +236,18 @@ const sdk = new NodeSDK({ sdk.start(); ``` -Open `http://localhost:18888` β€” your traces show up live. - -
-
+**Migration path:** start with the standalone dashboard for observability today; adopt the AppHost later when you want service discovery, integrations, and `aspire deploy`. - + --- -# OpenTelemetry Setup by Language +# OpenTelemetry β€” Python -**Add OpenTelemetry once per service β€” Aspire handles the rest:** - -
-
+**Wire OTEL once. Aspire injects `OTEL_EXPORTER_OTLP_ENDPOINT` automatically.** -**Python:** ```python from opentelemetry import trace from opentelemetry.exporter.otlp.proto.grpc \ @@ -270,10 +266,18 @@ provider.add_span_processor( trace.set_tracer_provider(provider) ``` -
-
+**One block of boilerplate** β€” and your Python service is in the dashboard. + + + +--- + + + +# OpenTelemetry β€” Node.js + +**Same idea, different runtime β€” and you get auto-instrumentation for free.** -**Node.js:** ```javascript const { NodeSDK } = require('@opentelemetry/sdk-node'); @@ -291,12 +295,9 @@ const sdk = new NodeSDK({ sdk.start(); ``` -
-
- -**Aspire sets `OTEL_EXPORTER_OTLP_ENDPOINT` automatically** β€” add this boilerplate once per service and traces flow to the dashboard. +**Auto-instrumentations** capture Express, Fastify, fetch, pg, redis β€” without code changes. - + --- @@ -373,9 +374,9 @@ String apiUrl = System.getenv("services__api__http__0"); -# Connection Strings +# Connection Strings β€” The Pattern -**Infrastructure resources get connection strings automatically:** +**Infrastructure resources get connection strings as environment variables:** ```bash # Pattern: CONNECTIONSTRINGS__ @@ -384,26 +385,41 @@ CONNECTIONSTRINGS__db=Host=localhost;Port=5432;Username=postgres;Password=... CONNECTIONSTRINGS__messaging=localhost:9092 ``` -**Same pattern in every language:** - +**Python β€” Redis:** ```python -# Python β€” Redis -client = redis.from_url(f"redis://{os.environ['CONNECTIONSTRINGS__cache']}") +client = redis.from_url( + f"redis://{os.environ['CONNECTIONSTRINGS__cache']}" +) ``` +**Aspire injects the env var** β€” your service reads it using its language's standard mechanism. + + + +--- + + + +# Same Pattern, Every Language + +**Node.js β€” Kafka:** ```javascript -// Node.js β€” Kafka -const kafka = new Kafka({ brokers: [process.env.CONNECTIONSTRINGS__messaging] }); +const kafka = new Kafka({ + brokers: [process.env.CONNECTIONSTRINGS__messaging] +}); ``` +**Java β€” PostgreSQL:** ```java -// Java β€” PostgreSQL via env vars -String url = "jdbc:postgresql://" + System.getenv("PG_HOST") + ":" + System.getenv("PG_PORT") + "/" + System.getenv("PG_DB"); +String url = "jdbc:postgresql://" + + System.getenv("PG_HOST") + ":" + + System.getenv("PG_PORT") + "/" + + System.getenv("PG_DB"); ``` -**Aspire injects environment variables** β€” your service reads them using its language's standard env var mechanism. +**Go, Rust, .NET, PowerShell** β€” same pattern. Aspire sets the env var; your code reads it. - + --- @@ -473,14 +489,14 @@ await builder.build().run(); -# AppHost Languages +# Two AppHost Languages -**Author your AppHost in C# or TypeScript today. Orchestrate workloads in any language.** +**Author your AppHost in C# or TypeScript today β€” both officially supported.**
-πŸ’œ **C# (.NET)** β€” `AppHost.cs` Β· **Official** +πŸ’œ **C# (.NET)** β€” `AppHost.cs` ```csharp var builder = DistributedApplication .CreateBuilder(args); @@ -495,7 +511,7 @@ Best fit: teams already on .NET tooling.
-🟦 **TypeScript** β€” `apphost.ts` Β· **Official** +🟦 **TypeScript** β€” `apphost.ts` ```typescript const builder = await createBuilder(); @@ -510,37 +526,42 @@ Best fit: Node.js / TypeScript workspaces.
-**Same model, different syntax.** Both produce the same dashboard, service discovery, health checks, and deployment artifacts. The TypeScript SDK is auto-generated from the same .NET hosting integrations via the **Aspire Type System (ATS)** β€” there's no separate integration surface to maintain. +**Same model, different syntax.** Both produce the same dashboard, service discovery, health checks, and deployment artifacts. -**Workloads inside the AppHost** can be written in C#, JavaScript, Python, Go, Java, Rust, PowerShell, and more β€” via `AddProject`, `AddJavaScriptApp`, `AddPythonApp`, `AddDockerfile`, `AddContainer`, or `AddExecutable`. Pick the AppHost language that fits your repo; your services don't need to match. - - + --- - + -# Multi-Language Integrations +# Workloads Can Be Anything -### Write the integration once. Use it from C# or TypeScript. +**Pick the AppHost language that fits your repo. Your services don't need to match.** -
-
+The TypeScript SDK is auto-generated from the same .NET hosting integrations via the **Aspire Type System (ATS)** β€” there's no separate integration surface to maintain. -**Aspire Type System (ATS)** β€” the contract that bridges .NET and guest languages. +**Workloads inside the AppHost** can be written in: -- Author your hosting integration **in C#** as you always have. -- Annotate exported APIs with ATS attributes β€” `[AspireExport]`, `[AspireExportType]`, `[AspireExportMethod]`. -- Aspire's analyzer validates the export shape at build time. -- The CLI **auto-generates a TypeScript SDK** into `.modules/` when a TS AppHost runs `aspire add `. -- TypeScript callers get fluent, typed methods β€” same model, different syntax. +- C#, JavaScript, TypeScript, Python, Go, Java, Rust, PowerShell, and more -**The trade-off:** the guest process talks to the .NET host over a local JSON-RPC socket (Unix socket / named pipe), authenticated with a per-session token. One IPC hop, no port exposure, no duplicated integration code per language. +via `AddProject`, `AddJavaScriptApp`, `AddPythonApp`, `AddDockerfile`, `AddContainer`, or `AddExecutable`. -
-
+ + +--- + + + +# Write Once with ATS + +### Author the integration in C#. Use it from any AppHost. + +**Aspire Type System (ATS)** β€” the contract that bridges .NET and guest languages. + +- Author your hosting integration **in C#** as you always have +- Annotate exported APIs with ATS attributes β€” `[AspireExport]`, `[AspireExportType]`, `[AspireExportMethod]` +- Aspire's analyzer validates the export shape at build time -**C# author** ```csharp [AspireExport] public static class MyIntegrationExtensions @@ -553,7 +574,16 @@ public static class MyIntegrationExtensions } ``` -**TypeScript caller β€” zero hand-written bindings** + + +--- + + + +# Use From TypeScript β€” Zero Bindings + +**The CLI auto-generates a TypeScript SDK** into `.modules/` when a TS AppHost runs `aspire add `. TypeScript callers get fluent, typed methods. + ```typescript import { createBuilder } from "./.modules/aspire.js"; import { addMyService } from @@ -563,12 +593,11 @@ const builder = await createBuilder(); const svc = await addMyService(builder, "svc"); ``` -*Status: preview feature in 13.x.* +**The trade-off:** the guest process talks to the .NET host over a local JSON-RPC socket (Unix socket / named pipe), authenticated with a per-session token. One IPC hop, no port exposure, no duplicated integration code per language. -
-
+*Status: preview feature in 13.x.* - + --- @@ -622,17 +651,22 @@ const svc = await addMyService(builder, "svc"); # `aspire.config.json` β€” One Config for Every Language +**This file tells the CLI which language your AppHost uses.** +
-**This file tells the CLI which language your AppHost uses:** - ```json { - "appHost": { "path": "apphost.py", "language": "python" }, + "appHost": { + "path": "apphost.py", + "language": "python" + }, "sdk": { "version": "13.2.0" }, "channel": "stable", - "features": { "polyglotSupportEnabled": true }, + "features": { + "polyglotSupportEnabled": true + }, "profiles": { "default": { "applicationUrl": "https://localhost:17000" @@ -652,19 +686,36 @@ const svc = await addMyService(builder, "svc"); - Feature flags use **boolean `true`** (not string `"true"`)
-
+
-**Manage from CLI:** -- `aspire config list` / `get` / `set` -- `aspire secret set/list/get/delete` -- `aspire certs clean/trust` + -**Every sample in this talk** has one at its root. +--- -
- + - +# Manage Config From the CLI + +**No manual JSON editing required:** + +```bash +# Read & write config values +aspire config list +aspire config get +aspire config set + +# Manage secrets (encrypted at rest) +aspire secret set +aspire secret list / get / delete + +# Local dev certs +aspire certs clean +aspire certs trust +``` + +**Every sample in this talk** has an `aspire.config.json` at its root β€” peek inside. + + --- @@ -706,15 +757,12 @@ aspire export # Capture to zip --- - + # Agent-Ready CLI ### Two MCP servers. One model. Any agent. -
-
- **MCP support out of the box β€” no plugins, no glue.** - πŸ›  **CLI MCP** β€” stdio. Agent spawns `aspire agent mcp` as a subprocess. Set up by `aspire agent init`. @@ -724,8 +772,15 @@ aspire export # Capture to zip **Polyglot bonus:** the agent sees Python tracebacks, Go panics, Java stack traces, and Node errors through the same OTEL pipeline. -
-
+ + +--- + + + +# Wire It Up in 30 Seconds + +**One-time setup, then any MCP-aware agent works:** ```bash # 1. One-time setup in your AppHost dir @@ -749,10 +804,7 @@ $ code . # reads .vscode/mcp.json } } } ``` -
-
- - + --- @@ -893,9 +945,33 @@ $ code . # reads .vscode/mcp.json -# From Dev to Production +# Same Model, Two Commands + +**One AppHost. Local, staging, production.** + +```bash +aspire run # Local development +aspire deploy # Deploy to target (Preview) +aspire publish # Generate artifacts (Preview) +aspire do # Pipeline step (Preview) +``` + +**What Aspire generates from your AppHost:** + +- 🐳 Container images for **all languages** +- ☸️ Azure Container Apps / Kubernetes manifests +- πŸ”Œ Infrastructure wiring (Redis, Postgres, Kafka…) +- πŸ”— Service connections + environment variables + +**No separate deploy config.** The AppHost is the contract. + + + +--- + + -**Same AppHost model** for local, staging, and production β€” one arc, two commands. +# What That Looks Like
@@ -911,6 +987,9 @@ $ code . # reads .vscode/mcp.json Dashboard: http://localhost:15888 ``` +
+
+ **`aspire deploy` (target)** ```text β†’ Building images for python, node, .net @@ -920,29 +999,12 @@ Dashboard: http://localhost:15888 βœ… Deployed to dev environment in 4m 12s ``` -
-
- -**Same model, two commands:** -```bash -aspire run # Local development -aspire deploy # Deploy to target (Preview) -aspire publish # Generate artifacts (Preview) -aspire do # Pipeline step (Preview) -``` - -**What Aspire generates from your AppHost:** -- Container images for all languages -- Azure Container Apps / Kubernetes manifests -- Infrastructure wiring (Redis, Postgres, Kafka…) -- Service connections + environment variables - -**No separate deploy config.** The AppHost is the contract. -
- +**Same code-first AppHost drives both.** + + --- From ca6887578da934def8154843106eec392769f9d2 Mon Sep 17 00:00:00 2001 From: Chris Ayers Date: Thu, 7 May 2026 11:08:01 -0400 Subject: [PATCH 2/2] slides: rename Azure CXP -> Azure EngOps on About slide --- slides/Slides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/Slides.md b/slides/Slides.md index 26daebd..5ac543c 100644 --- a/slides/Slides.md +++ b/slides/Slides.md @@ -20,7 +20,7 @@ footer: '@Chris_L_Ayers - https://chris-ayers.com' ## Chris Ayers -### Principal Software Engineer
Azure CXP AzRel
Microsoft +### Principal Software Engineer
Azure EngOps AzRel
Microsoft BlueSky: [@chris-ayers.com](https://bsky.app/profile/chris-ayers.com) LinkedIn: - [chris\-l\-ayers](https://linkedin.com/in/chris-l-ayers/)