<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Java on CodeBytes - The Curious Engineer</title><link>https://chris-ayers.com/tags/java/</link><description>Recent content in Java on CodeBytes - The Curious Engineer</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>© 2026 Chris Ayers</copyright><lastBuildDate>Fri, 06 Nov 2026 12:39:26 -0500</lastBuildDate><atom:link href="https://chris-ayers.com/tags/java/index.xml" rel="self" type="application/rss+xml"/><item><title>A Polyglot App Is More Than a Process List</title><link>https://chris-ayers.com/posts/aspire-field-notes-model-the-whole-app/</link><pubDate>Fri, 06 Nov 2026 12:39:26 -0500</pubDate><guid>https://chris-ayers.com/posts/aspire-field-notes-model-the-whole-app/</guid><description>&lt;p&gt;Starting a frontend, an API, and a database is straightforward. Knowing which address each process should use, when its dependencies are ready, and where a failed request went is the harder part.&lt;/p&gt;&#10;&lt;p&gt;In the catalog companion, &lt;strong&gt;Load catalog&lt;/strong&gt; can return HTTP 503 while the API and inventory service both report healthy. Nothing has to crash for the application to fail.&lt;/p&gt;</description><content:encoded>&lt;p&gt;Starting a frontend, an API, and a database is straightforward. Knowing which address each process should use, when its dependencies are ready, and where a failed request went is the harder part.&lt;/p&gt;&#10;&lt;p&gt;In the catalog companion, &lt;strong&gt;Load catalog&lt;/strong&gt; can return HTTP 503 while the API and inventory service both report healthy. Nothing has to crash for the application to fail.&lt;/p&gt;&#10;&lt;p&gt;In &lt;a href="https://chris-ayers.com/posts/aspire-field-notes-keep-the-failing-run/" &gt;part one&lt;/a&gt;, we kept a failed catalog request. This time, let&amp;rsquo;s look at the wiring that made the request possible and the evidence that tells us which part failed.&lt;/p&gt;&#10;&lt;p&gt;Aspire puts the service relationships in the AppHost. The language integrations help, but each service still has to use the configuration it receives and report what happens inside it.&lt;/p&gt;&#10;&lt;p&gt;The runnable path in this article was validated with &lt;strong&gt;Aspire 13.6.4&lt;/strong&gt;: a Vite frontend, a .NET catalog API, PostgreSQL, and a .NET inventory service. Java and Rust appear later as optional preview examples, not extra requirements for running the companion.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Three contracts, not one&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;An AppHost relationship can serve several purposes, but they are not interchangeable:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Contract&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;What it answers&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;What it does not prove&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Configuration&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;How does this consumer find its dependency?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;That the dependency is ready or the caller is authorized&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Readiness&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;When is it reasonable to start the consumer?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;That every later request will succeed&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Telemetry&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Can we follow an operation through the system?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;That launching a process instrumented all its code&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;code&gt;WithReference&lt;/code&gt; supplies configuration, &lt;code&gt;WaitFor&lt;/code&gt; gates startup, and OpenTelemetry lets us follow the request. A reference can be correct while the dependency is still starting; both can work while the trace is missing.&lt;/p&gt;&#10;&lt;p&gt;David Fowler&amp;rsquo;s &lt;a href="https://devblogs.microsoft.com/aspire/dev-loop-tribal-knowledge/" target="_blank" rel="noreferrer"&gt;developer-loop article&lt;/a&gt; makes the broader case: describe the relationships that otherwise live in shell history and a teammate&amp;rsquo;s memory. You can adopt that model incrementally.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Start with the companion&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Use the &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-field-notes" target="_blank" rel="noreferrer"&gt;sample README&lt;/a&gt; for prerequisites and the one-time PostgreSQL secret setup. Run all commands below from &lt;code&gt;aspire-field-notes/&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;To run the companion, select the catalog AppHost explicitly because the repository also contains a separate terminal AppHost:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;apphost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;catalog/Catalog.AppHost/Catalog.AppHost.csproj&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire start --apphost &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$apphost&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --isolated&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;The &lt;code&gt;apphost&lt;/code&gt; variable keeps the path reusable for later commands; only &lt;code&gt;aspire start&lt;/code&gt; launches the application. In the dashboard, wait for &lt;strong&gt;web&lt;/strong&gt; to become &lt;strong&gt;Healthy&lt;/strong&gt;, open its HTTP endpoint, and click &lt;strong&gt;Load catalog&lt;/strong&gt;. The &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-field-notes/walkthroughs/02-model-the-whole-app#optional-cli-verification" target="_blank" rel="noreferrer"&gt;optional CLI checks&lt;/a&gt; automate readiness and the catalog request; they are not extra startup requirements.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Follow the catalog&amp;rsquo;s service graph&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;For one &lt;strong&gt;Load catalog&lt;/strong&gt; request, the shape of the app is:&lt;/p&gt;&#10;&lt;pre class="not-prose mermaid"&gt;&#10;flowchart TB&#10;accTitle: Catalog request flow&#10;accDescr: The browser calls the Vite frontend. Vite proxies to the catalog API, which queries catalogdb in PostgreSQL and then calls inventory.&#10;&#10;browser["Browser"]&#10;web["web: Vite frontend and proxy"]&#10;api["api: .NET catalog API"]&#10;inventory["inventory: .NET service"]&#10;catalogdb[("catalogdb (PostgreSQL)")]&#10;&#10;browser --&gt;|"GET /api/catalog"| web&#10;web --&gt;|"HTTP via API_HTTP"| api&#10;api --&gt;|"SQL query"| catalogdb&#10;api --&gt;|"GET /inventory"| inventory&#10;&lt;/pre&gt;&#10;&#10;&lt;p&gt;The arrows show request traffic, not startup order. The API reads &lt;code&gt;catalogdb&lt;/code&gt;, hosted by the &lt;code&gt;postgres&lt;/code&gt; resource, before calling inventory. The AppHost supplies configuration and readiness relationships; it is not an HTTP hop in this path.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-field-notes/catalog/Catalog.AppHost" target="_blank" rel="noreferrer"&gt;catalog AppHost project&lt;/a&gt; models five application resources, plus the &lt;code&gt;postgres-password&lt;/code&gt; and &lt;code&gt;catalog-region&lt;/code&gt; parameters:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Resource&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Role&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Contract to inspect&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;postgres&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Local PostgreSQL server&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Container readiness and credentials&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;catalogdb&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;The catalog database&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Connection information supplied to the API&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;inventory&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Downstream HTTP service&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;An instrumented business call, separate from its healthy &lt;code&gt;/health&lt;/code&gt; endpoint&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;api&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Catalog API&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Reads PostgreSQL and makes one inventory request for &lt;code&gt;/api/catalog&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;web&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Vite frontend&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Same-origin &lt;code&gt;/api&lt;/code&gt; requests proxied to the API&amp;rsquo;s assigned endpoint&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The resource list also shows &lt;code&gt;web-installer&lt;/code&gt;, a child resource that runs &lt;code&gt;npm ci&lt;/code&gt; for the frontend; &lt;code&gt;web&lt;/code&gt; waits for it to finish. The dashboard&amp;rsquo;s &lt;strong&gt;Graph&lt;/strong&gt; view draws the same model, with relationships as arrows and resource health shown on the nodes:&lt;/p&gt;&#10;&#10;&lt;figure class="full-width"&gt;&#10; &lt;img&#10; class="my-0 rounded-md"&#10; loading="lazy"&#10; decoding="async"&#10; fetchpriority="auto"&#10; alt="Aspire dashboard Graph view: the finished web-installer feeds web, web points to api, api points to inventory and catalogdb, and catalogdb belongs to postgres; the application resources have green health badges"&#10; width="1220"&#10; height="990"&#10; src="https://chris-ayers.com/posts/aspire-field-notes-model-the-whole-app/resource-graph_hu_e72a5f43bda49e76.png"&#10; srcset="https://chris-ayers.com/posts/aspire-field-notes-model-the-whole-app/resource-graph_hu_e72a5f43bda49e76.png 800w,/posts/aspire-field-notes-model-the-whole-app/resource-graph_hu_21434a3980e180ba.png 1280w"&#10; sizes="(min-width: 768px) 50vw, 65vw"&#10; data-zoom-src="https://chris-ayers.com/posts/aspire-field-notes-model-the-whole-app/resource-graph.png"&#10; /&gt;&#10; &#10; &lt;figcaption&gt;Web calls the API; the API depends on inventory and catalogdb. The finished installer is a separate child resource.&lt;/figcaption&gt;&#10; &lt;/figure&gt;&#10;&lt;p&gt;This focused excerpt from the AppHost shows the API&amp;rsquo;s request wiring. &lt;code&gt;catalogdb&lt;/code&gt; and &lt;code&gt;inventory&lt;/code&gt; are defined earlier in that file:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Catalog_Api&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalogdb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHttpHealthCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalogdb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;The full AppHost keeps the region and state-volume settings in a separate block for the &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-field-notes/walkthroughs/05-portable-state-and-config" target="_blank" rel="noreferrer"&gt;configuration/state walkthrough&lt;/a&gt;. The shared API still requires those settings at startup; omitting them from this excerpt is not a request to remove them from the runnable companion.&lt;/p&gt;&#10;&lt;p&gt;Selecting &lt;strong&gt;Load catalog&lt;/strong&gt; in the frontend makes the browser call &lt;code&gt;/api/catalog&lt;/code&gt; on its own origin. Vite forwards the request to the API, which reads PostgreSQL and calls inventory once:&lt;/p&gt;&#10;&#10;&lt;figure class="full-width"&gt;&#10; &lt;img&#10; class="my-0 rounded-md"&#10; loading="lazy"&#10; decoding="async"&#10; fetchpriority="auto"&#10; alt="The companion frontend after Load catalog: 3 products loaded from local, HTTP 200, a trace ID, and a table with the debugging mug, field notebook, and trace sticker"&#10; width="1920"&#10; height="1440"&#10; src="https://chris-ayers.com/posts/aspire-field-notes-model-the-whole-app/catalog-frontend_hu_6ab45fe061595d74.png"&#10; srcset="https://chris-ayers.com/posts/aspire-field-notes-model-the-whole-app/catalog-frontend_hu_6ab45fe061595d74.png 800w,/posts/aspire-field-notes-model-the-whole-app/catalog-frontend_hu_aed3443d15c2e4b.png 1280w"&#10; sizes="(min-width: 768px) 50vw, 65vw"&#10; data-zoom-src="https://chris-ayers.com/posts/aspire-field-notes-model-the-whole-app/catalog-frontend.png"&#10; /&gt;&#10; &#10; &lt;figcaption&gt;Load catalog returns HTTP 200, three products, and a trace ID to follow in the dashboard.&lt;/figcaption&gt;&#10; &lt;/figure&gt;&#10;&lt;p&gt;Open that trace ID in the dashboard to see the database query and the single correlated inventory call. The &lt;strong&gt;Load catalog&lt;/strong&gt; command on &lt;code&gt;web&lt;/code&gt;, whether invoked from the dashboard or the CLI, also goes through the frontend endpoint. It does not bypass Vite to make the API look successful.&lt;/p&gt;&#10;&#10;&lt;h3 class="relative group"&gt;Readiness needs a real check&#10; &#10; &#10; &#10; &#10;&lt;/h3&gt;&#10;&lt;p&gt;The companion implements &lt;code&gt;/health&lt;/code&gt; in the API, inventory service, and Vite server. The API seeds its catalog before accepting traffic, and its database check participates in readiness. Vite uses an actual health middleware, not a catch-all HTML page mistaken for a successful probe.&lt;/p&gt;&#10;&lt;p&gt;When adapting the sample, check that each probe&amp;rsquo;s endpoint exists. Otherwise, the dependency will stay unhealthy. At the other extreme, a process with no health checks can satisfy a readiness wait as soon as it is running.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;AddViteApp&lt;/code&gt; handles Vite&amp;rsquo;s development endpoint and port arguments. For other programs, declaring an endpoint and an environment variable only works when the application actually listens on that value.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Follow configuration into the consumer&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;The AppHost&amp;rsquo;s &lt;code&gt;.WithReference(inventory)&lt;/code&gt; gives the API connection information. It does not create an HTTP client. Likewise, &lt;code&gt;.WithReference(catalogdb)&lt;/code&gt; supplies database configuration but does not register a database client in the API.&lt;/p&gt;&#10;&lt;p&gt;The relevant registrations in &lt;a href="https://github.com/codebytes/blog-samples/blob/main/aspire-field-notes/catalog/Catalog.Api/Program.cs" target="_blank" rel="noreferrer"&gt;&lt;code&gt;Catalog.Api/Program.cs&lt;/code&gt;&lt;/a&gt; are:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddServiceDefaults&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddNpgsqlDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;catalogdb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;inventory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://inventory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;The API later calls &lt;code&gt;GetAsync(&amp;quot;/inventory&amp;quot;)&lt;/code&gt;, so this client needs a base address. &lt;code&gt;http://inventory&lt;/code&gt; identifies a logical service, not a fixed machine or port. The companion&amp;rsquo;s &lt;a href="https://github.com/codebytes/blog-samples/blob/main/aspire-field-notes/catalog/ServiceDefaults/Extensions.cs" target="_blank" rel="noreferrer"&gt;Service Defaults&lt;/a&gt; registers the HTTP service-discovery handler that resolves the actual endpoint from the supplied configuration. &lt;code&gt;WithReference(inventory)&lt;/code&gt; does not set the client&amp;rsquo;s base address for it. &lt;code&gt;AddNpgsqlDataSource(&amp;quot;catalogdb&amp;quot;)&lt;/code&gt; registers the database client using the named connection string.&lt;/p&gt;&#10;&lt;p&gt;The API also rejects missing &lt;code&gt;ConnectionStrings:catalogdb&lt;/code&gt; or &lt;code&gt;services:inventory:http:0&lt;/code&gt; configuration at startup. It does not quietly substitute a localhost service. Its OpenTelemetry setup lives in Service Defaults and the Npgsql integration; the AppHost reference is not the instrumentation.&lt;/p&gt;&#10;&lt;p&gt;For server-side Node, Python, Go, or Rust code, endpoint URL variables are often the simpler contract. A resource named &lt;code&gt;api&lt;/code&gt; with an endpoint named &lt;code&gt;http&lt;/code&gt; supplies &lt;code&gt;API_HTTP&lt;/code&gt;. A named endpoint called &lt;code&gt;admin&lt;/code&gt; supplies &lt;code&gt;API_ADMIN&lt;/code&gt;; the suffix is the endpoint&amp;rsquo;s name, not necessarily its scheme.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;The &lt;code&gt;services__api__http__0&lt;/code&gt; format serves .NET configuration-based discovery. Do not assume a JavaScript HTTP client understands .NET&amp;rsquo;s logical URI syntax.&lt;/p&gt;&#10;&lt;p&gt;Our frontend uses that convention directly. &lt;code&gt;.WithReference(api)&lt;/code&gt; supplies &lt;code&gt;API_HTTP&lt;/code&gt;, and the server-side Vite proxy reads it. There is no separate environment-variable alias or hard-coded localhost URL to maintain.&lt;/p&gt;&#10;&lt;p&gt;Here is the development wiring for &lt;code&gt;web&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddViteApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;web&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;../web&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithNpm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;installCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;ci&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHttpHealthCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithExternalHttpEndpoints&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;&lt;code&gt;WithReference(api)&lt;/code&gt; supplies connection information; &lt;code&gt;WaitFor(api)&lt;/code&gt; controls startup. Removing one is not a substitute for the other.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Keep server configuration out of the browser bundle&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;The checked-in &lt;a href="https://github.com/codebytes/blog-samples/blob/main/aspire-field-notes/catalog/web/vite.config.mjs" target="_blank" rel="noreferrer"&gt;&lt;code&gt;vite.config.mjs&lt;/code&gt;&lt;/a&gt; provides the health endpoint and configures the development proxy:&lt;/p&gt;&#10;&lt;!-- prettier-ignore --&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vite&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;apiTarget&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./proxy-config.mjs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;field-notes-health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;configureServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text/plain&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Healthy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Production uses the generated YARP proxy, not a build-time VITE_* API URL.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;serve&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/api&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;changeOrigin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}));&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/codebytes/blog-samples/blob/main/aspire-field-notes/catalog/web/proxy-config.mjs" target="_blank" rel="noreferrer"&gt;&lt;code&gt;apiTarget&lt;/code&gt;&lt;/a&gt; reads &lt;code&gt;API_HTTP&lt;/code&gt; from the server environment and rejects a missing value or a URL that is not an HTTP(S) origin. It also rejects embedded credentials, a path, query parameters, or a fragment. Browser code calls &lt;code&gt;/api/catalog&lt;/code&gt; without learning the local API port.&lt;/p&gt;&#10;&lt;p&gt;The production build does not need the injected URL. The companion does not use Vite&amp;rsquo;s development or preview server for production; the &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-field-notes/walkthroughs/06-choose-your-deployment" target="_blank" rel="noreferrer"&gt;deployment walkthrough&lt;/a&gt; inspects the generated static-site and YARP proxy configuration instead.&lt;/p&gt;&#10;&lt;p&gt;Settings with Vite&amp;rsquo;s &lt;code&gt;VITE_&lt;/code&gt; prefix can be embedded in browser code; they are not an appropriate place for secrets or private server-only configuration.&lt;/p&gt;&#10;&lt;p&gt;In the companion run, &lt;code&gt;API_HTTP&lt;/code&gt; on &lt;code&gt;web&lt;/code&gt; matched the API&amp;rsquo;s assigned endpoint. The browser&amp;rsquo;s Network panel showed a request to &lt;code&gt;/api/catalog&lt;/code&gt; on the &lt;strong&gt;web origin&lt;/strong&gt;, and no &lt;code&gt;VITE_*&lt;/code&gt; infrastructure variables were injected. Those checks prove different things: the proxy knows where to send the request, while the browser does not need that internal address.&lt;/p&gt;&#10;&lt;p&gt;Inspect just the settings you need in the dashboard. Do not paste the entire resource environment or raw &lt;code&gt;aspire describe --format Json&lt;/code&gt; output into a report; the API&amp;rsquo;s connection-string fields can contain the PostgreSQL password even when the standalone secret parameter is redacted.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;A green graph can still return 503&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-field-notes/walkthroughs/01-keep-the-failing-run" target="_blank" rel="noreferrer"&gt;failure walkthrough&lt;/a&gt; restarts the catalog with &lt;code&gt;Inventory__FaultEnabled=true&lt;/code&gt;. Inventory continues to answer its health probe, but its &lt;code&gt;/inventory&lt;/code&gt; business endpoint returns a real 503. The catalog API reports &lt;strong&gt;Inventory unavailable&lt;/strong&gt; instead of hiding the failure.&lt;/p&gt;&#10;&lt;p&gt;Here is what the catalog run showed:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Scenario&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;API and inventory readiness&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Catalog response&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Inventory HTTP attempts&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Normal request&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Healthy&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;HTTP 200, three products&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Inventory fault enabled&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Healthy&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;HTTP 503&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Restart with fault disabled&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Healthy&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;HTTP 200, three products&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The API queries PostgreSQL before calling inventory, so the database span is present even in the failed request. For each of those three resource-command traces, the evidence was:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;An API server span for &lt;code&gt;GET /api/catalog&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;A PostgreSQL query span.&lt;/li&gt;&#10;&lt;li&gt;Exactly one API HTTP client span for the inventory request.&lt;/li&gt;&#10;&lt;li&gt;An inventory server span correlated as that HTTP client&amp;rsquo;s child.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The dashboard&amp;rsquo;s trace view is enough for this walkthrough. If you prefer the CLI, replace &lt;code&gt;&amp;lt;trace-id&amp;gt;&lt;/code&gt; with the ID returned by &lt;strong&gt;Load catalog&lt;/strong&gt;:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire otel spans --apphost &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$apphost&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --trace-id &lt;span class="s2"&gt;&amp;#34;&amp;lt;trace-id&amp;gt;&amp;#34;&lt;/span&gt; --format Json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;Vite does not emit application spans in this sample. The browser Network panel verifies the frontend hop; the trace follows the instrumented API, database, and inventory work. Hosting all of them in Aspire does not automatically produce a browser-to-database trace.&lt;/p&gt;&#10;&lt;p&gt;The companion deliberately has no HTTP resilience handler, so a catalog request makes one inventory attempt. Disabling the fault and restarting restored HTTP 200 without adding retries. That is a controlled demonstration, not a recommendation to remove retries from every application: choose a retry policy deliberately and check what it actually does.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;What Java and Rust add in 13.6&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Aspire 13.6 moves Java and Rust hosting into first-party &lt;strong&gt;preview packages&lt;/strong&gt;, building on Community Toolkit contributions. That is different from saying every language feature is generally available.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://www.nuget.org/packages/Aspire.Hosting.Java/13.6.4-preview.1.26523.11" target="_blank" rel="noreferrer"&gt;Java&lt;/a&gt; and &lt;a href="https://www.nuget.org/packages/Aspire.Hosting.Rust/13.6.4-preview.1.26523.11" target="_blank" rel="noreferrer"&gt;Rust&lt;/a&gt; hosting packages matching this baseline are &lt;strong&gt;&lt;code&gt;13.6.4-preview.1.26523.11&lt;/code&gt;&lt;/strong&gt;, not stable &lt;code&gt;13.6.4&lt;/code&gt; packages. With those optional packages referenced, an AppHost can declare:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSpringBootApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;catalog&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;../catalog&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddRustApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pricing&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;../pricing&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHttpEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;PORT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;These fragments go before an existing builder&amp;rsquo;s &lt;code&gt;Build().Run()&lt;/code&gt; and require real applications at those paths. Their package versions and APIs were checked against the published references, but these Java and Rust services were not run as part of the companion. You do not need either toolchain to follow the catalog walkthrough.&lt;/p&gt;&#10;&lt;p&gt;Spring Boot uses the application&amp;rsquo;s Maven or Gradle wrapper and receives its port through &lt;code&gt;SERVER_PORT&lt;/code&gt;. Add an &lt;code&gt;/actuator/health&lt;/code&gt; check only if the application includes and exposes the corresponding Actuator support. &lt;code&gt;WithOtelAgent()&lt;/code&gt; is available when you want Java-agent instrumentation; configuring an exporter by itself does not create spans.&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;The Rust resource runs Cargo. The Rust server must read &lt;code&gt;PORT&lt;/code&gt; and expose an appropriate endpoint. The integration supplies OpenTelemetry settings, but the application still needs the Rust SDK and instrumentation. Cargo features, binary selection, and publishing are separate concerns from HTTP endpoint configuration.&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;If you have an existing Java or Rust service, start with that. Verify its endpoint, readiness, and trace before adding more services to the graph.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Do not confuse two language choices&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;There is the language of the &lt;strong&gt;AppHost&lt;/strong&gt;, and there are the languages of the &lt;strong&gt;services it runs&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Choice&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Position in the 13.6 story&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;C# or TypeScript AppHost&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Established authoring options; TypeScript reached GA in 13.4&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;JavaScript, Python, or Go service&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Existing first-party hosting integrations (Go since 13.4); none is new in 13.6&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Java or Rust service&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;New first-party preview hosting packages&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Additional AppHost languages&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Experimental, feature-flagged authoring paths&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Deno&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Deno 2 can run a TypeScript AppHost; the new Deno guest-hosting API is experimental&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;You can write the AppHost in TypeScript and keep the API in C#, or host Rust from a C# AppHost. Pick an authoring language your team can maintain; service-language support is a separate choice.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Another example: the Node.js weather map&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;David Pine&amp;rsquo;s &lt;a href="https://bsky.app/profile/davidpine.dev/post/3mwmut6556k2a" target="_blank" rel="noreferrer"&gt;Bluesky post&lt;/a&gt; and &lt;a href="https://dotnet.social/@davidpine/117352196531501696" target="_blank" rel="noreferrer"&gt;Mastodon post&lt;/a&gt; point to the &lt;a href="https://aspire.dev/reference/samples/aspire-with-node/" target="_blank" rel="noreferrer"&gt;official Node.js weather-map sample&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;It combines Express and OpenTelemetry, React 19, Vite, Leaflet, and a TypeScript AppHost. The external weather API is modeled too. More importantly, it shows different run and publish arrangements: a development proxy locally, and frontend build output served with the API in the published application.&lt;/p&gt;&#10;&lt;p&gt;Follow one weather request and look at how the external API is configured. Before adapting it for production, you&amp;rsquo;ll also need authentication, rate limits, caching, and a plan for the weather provider&amp;rsquo;s quota.&lt;/p&gt;&#10;&#10;&lt;h2 class="relative group"&gt;Prove one path before adding more resources&#10; &#10; &#10; &#10; &#10;&lt;/h2&gt;&#10;&lt;p&gt;Take one operation through your application and check:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The consumer receives an endpoint derived from its resource reference.&lt;/li&gt;&#10;&lt;li&gt;A configured readiness check gates startup where it matters.&lt;/li&gt;&#10;&lt;li&gt;The request reaches the intended dependency.&lt;/li&gt;&#10;&lt;li&gt;Telemetry connects the instrumented hops.&lt;/li&gt;&#10;&lt;li&gt;An intentional dependency failure stays visible, and any retries match the policy you configured.&lt;/li&gt;&#10;&lt;li&gt;A missing setting fails explicitly rather than falling back to an unrelated localhost service.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;A shared dashboard does not apply .NET retry handlers to Go or Node. Browser trace propagation also needs its own setup. Keep those responsibilities visible rather than assuming the graph handles them.&lt;/p&gt;&#10;&lt;p&gt;Use &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-field-notes/walkthroughs/02-model-the-whole-app" target="_blank" rel="noreferrer"&gt;walkthrough 02&lt;/a&gt; for the detailed configuration and trace checks. When you finish, stop only this AppHost:&lt;/p&gt;&#10;&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire stop --apphost &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$apphost&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;p&gt;Before adding another language or another box to the graph, make one path explainable when it succeeds and when it fails. That is more useful than a longer list of running processes.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;&lt;a href="https://aspire.dev/fundamentals/environment-variables/" target="_blank" rel="noreferrer"&gt;Aspire environment-variable naming&lt;/a&gt; and &lt;a href="https://aspire.dev/fundamentals/service-discovery/" target="_blank" rel="noreferrer"&gt;service discovery&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;&lt;a href="https://devblogs.microsoft.com/aspire/whats-new-aspire-13-6/" target="_blank" rel="noreferrer"&gt;Aspire 13.6 announcement and preview boundaries&lt;/a&gt; and &lt;a href="https://devblogs.microsoft.com/aspire/whats-new-aspire-13-4/" target="_blank" rel="noreferrer"&gt;13.4&amp;rsquo;s TypeScript GA and Go hosting milestones&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:3"&gt;&#10;&lt;p&gt;&lt;a href="https://aspire.dev/integrations/frameworks/java/java-host/" target="_blank" rel="noreferrer"&gt;Java hosting, framework defaults, instrumentation, and migration&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:4"&gt;&#10;&lt;p&gt;&lt;a href="https://aspire.dev/integrations/frameworks/rust/rust-host/" target="_blank" rel="noreferrer"&gt;Rust hosting, endpoint configuration, and publishing&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;</content:encoded><media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://chris-ayers.com/posts/aspire-field-notes-model-the-whole-app/featured.png"/></item></channel></rss>