Xbox services powers many of the core experiences in Xbox Gaming. Our services are primarily HTTP based microservices and enable experiences that range from telling our users who are online and what games are being played, to the ability to log in, to chat services. These services run on Azure compute and are primarily .NET based. Given that our service history spans all the way from Xbox 360 to the current generation of consoles, and that we must maintain compatibility across the multiple device types as well as individual games we support, it is key that any migrations or updates are performed carefully.
Streamlining Innovation with .NET Aspire
For the past couple of years, we have been modernizing our codebase to adopt the latest patterns and versions of .NET as well as a focus on the latest security best practices. This includes upgrading from .NET framework to the latest versions of .NET, and moving to modern orchestration platforms like Azure Kubernetes Service (AKS). As we pushed more on our move to AKS and started to iterate, we realized that doing a full validation in a ‘true’ environment is quite slow! After making our changes, and deploying the code, we’d often find out we missed something subtle in our telemetry with how we send them or a naming issue that required yet another iteration. As we heard more about .NET Aspire, we investigated and found that .NET Aspire was a perfect fit! It lets us find all of those minor issues locally, and removes much of the need for full deployment to do our basic hookup validation. With the ability to do this development locally, hit breakpoints, and make quick changes, .NET Aspire quickly became a key tool for Xbox service.
Now, the team leverages .NET Aspire’s capabilities to streamline the transition. .NET Aspire lets us flight locally the newly transitioned service, including seeing the critical metrics and logs so that we may debug locally prior to deployment. This reduces the need of deploying to a ‘real’ environment to detect many issues.
.NET Aspire also automates emulator usage for Azure dependencies out of the box, saving developers time to focus on writing their own code. No more keeping emulators up to date and writing scripts to wire it all together. .NET Aspire enables our goal of ‘clone + F5’, which will ultimately ease developer onboarding and debugging.
Setting up .NET Aspire
.NET Aspire app host is a new type of project in Visual Studio — I think of the .NET Aspire app host project as a superpowered startup script, and it comes with deep integration with Azure and .NET out of the box. And you get to use C# to wire things up! Using .NET Aspire’s app host, you can fire up Azure emulators, connect them to your service, providing local environmental overrides as well as generate test data to inject at startup directly into the emulated resource. It’s a familiar programming model and formalizes the fact that we are all cloud devs first, and that it’s a fact of life that we would need to integrate many processes + Azure on our own anyway. Having any reliance on real Azure resources during local development comes with a ton of risk, not much repeatability, and a challenge for developers to share those types of resources — so having an easily hooked up local Azure emulator is great.
If you are on the latest .NET, using ASP.NET and integrating with Azure, it’s a no-brainer to kick off your emulators. For our example, we have a worker role consume from an Azure Event Hub and publish to a Cosmos DB, while a microservice frontdoor reads the processed event from the same Cosmos DB. So, we call AddAzureCosmosDB and AddAzureEventHubs and run them as emulators.
IResourceBuilder<AzureCosmosDBResource> cosmosdb = builder.AddAzureCosmosDB(cosmosDbResourceName)
.RunAsEmulator(c => c.WithLifetime(containerLifetime));
var eventHub = builder.AddAzureEventHubs(eventHubsConnectionName)
.RunAsEmulator()
.AddEventHub(eventHubResourceName)
Next, we can set up our front door microservice, making sure our frontdoor will wait for the emulators to finish initializing and then go about injecting test data and off we go. Be sure to also set up your OTEL exporters to get deep telemetry.
var fd = builder.AddProject<FrontDoor>("frontdoor")
.WithEnvironment("ASPNETCORE_ENVIRONMENT", "Development")
.WithReference(cosmosdb)
.WithOtlpExporter()
.WaitFor(cosmosdb)
Below, we are outputting the ‘browser Cosmos DB’ explorer endpoint so we can manually browse the emulator, and then we create relevant databases and inject our test data.
private static async Task<CosmosClient> CreateCosmosCollections(IResourceBuilder<AzureCosmosDBResource> cosmosDbResource)
{
CosmosClient client = await TestDataGenerator.GetCosmosClient(cosmosDbResource.Resource);
Console.WriteLine($"https://localhost:{client.Endpoint.Port}/_explorer/index.html");
using CancellationTokenSource cts = new CancellationTokenSource(CosmosDbInitLoopTimeout);
// Set up the database/containers
DatabaseResponse dbRef = await client.CreateDatabaseIfNotExistsAsync(DatabaseId, cancellationToken: cts.Token);
// Set up container for documents.
_ = await dbRef.Database.CreateContainerIfNotExistsAsync(
new ContainerProperties
{
// Exercise for the reader
},
ThroughputProperties.CreateAutoscaleThroughput(10000),
cancellationToken: cts.Token);
return client;
}
private static async Task LoadCosmosCollections(CosmosClient client)
{
var documents = TestDataGenerator.GenerateDocs([ ]);
foreach (var d in documents)
{
await client.GetContainer(DatabaseId, ContainerLocator.Get<Document>())
.CreateItemAsync<Document>(d, new PartitionKey(d.PartitionKey));
}
}
From there, we have a full end-to-end for anyone who clones and runs the repo! Once you kick off your solution, you get a nice view of all the emulators and services running. Below, shows the Cosmos DB emulator, Event Hub emulator, and then our processor/frontdoor roles.
This drastically speeds up the onboarding process for developers and eases our local debugging experience. Want to test a specific type of test data? You can manually explore to CosmosDB to inject or change the raw data, then perform your experiment. Later, you could formalize your data and turn that into a test.
Aspire Dashboard and Observability
Key to Xbox service’s high availability is our logs, metrics, and traces. Since we are aligning on OTEL, which .NET Aspire supports out of the box, we can see all our data right away, and in real-time. While we are migrating our services to adopt new frameworks, we use .NET Aspire to ensure that we are emitting the right metrics locally (And named properly! This is key!!!) and fully exercise all our call paths to see the output data.
From the initial .NET Aspire dashboard, we can then view all the relevant console output, structured logs, tracing, and metrics that our app produces. We get to see our custom XAC (Xbox Availability Counter) metrics as well!
By providing a unified view of all telemetry data, .NET Aspire reduces the need for custom local monitoring tools and makes for a more efficient local development environment all up. Ultimately, this means we can fine-tune our service and telemetry locally!
Streamlining Xbox Services: The Role of .NET Aspire in Development and Testing
Once setup, .NET Aspire has helped us with multiple development scenarios — Seemingly simple things like just making sure the service starts properly, not even taking traffic yet. For example, we are moving our legacy services to a Dependency Injection (DI) pattern, and there are a lot of subtle issues that can crop up, especially for devs new to the pattern. Just having .NET Aspire get our project up and running, and connecting to emulators, and having the service respond to a simple health check is enough to force a lot of code to initialize and startup. Thanks to .NET Aspire, we can find and iterate on a lot of issues with this simple type of test!
Metrics, tracing, and logs are so important for running our high scale services. We also have downstream consumers we need to ensure that the names and formats align, so having small errors can trigger alerts to not fire and dashboards to not work. Using .NET Aspire, we get a chance to double check all of this locally, before any costly (in time) deployments to a ‘real’ environment
The ability to spin up our own .NET Aspire hosting integrations has been valuable in sharing some of our core services. This capability allows other services to depend on integrations we are creating for our common services for their local development, providing early integrations and testing. Since the .NET Aspire integrations also output their own telemetry, we can start to see how the services interact and both output metrics/trace/logs. This also gives us a fantastic baseline prior to migration to more modern platforms!
The third-party integration capabilities of .NET Aspire are also impressive, particularly with tools like Wiremock to simulate REST services that we don’t otherwise own. This allows us to test our services in a controlled environment, ensuring that they can handle various scenarios and integrations seamlessly. It has made our testing process more comprehensive and reliable!
Conclusion
By providing a comprehensive and unified view of all telemetry data, and allowing us to easily hook our services all up together, .NET Aspire allows for the seamless integration and observation of logs, metrics, and traces. This has streamlined our local development environment — Clone the repo, hit F5 and call your service!
Another major advantage is the elimination of disparate tools. With .NET Aspire, we can programmatically wire up our services/emulators, and put all relevant data in one place, reducing complexity and saving time. This has significantly improved our local dev loop debugging experience!
Xbox + .NET Aspire: Enhancing Local Development
Xbox services, which power various gaming experiences, rely on .NET and Azure-based microservices. To modernize and improve efficiency, the team has been upgrading from older .NET frameworks to newer versions while migrating to Azure Kubernetes Service (AKS).
How .NET Aspire Helps
.NET Aspire enables Xbox developers to test and debug locally before full deployment, speeding up iteration cycles. Key benefits include:
Local validation: Developers can catch errors early without waiting for full cloud deployment.
Emulator automation: Seamless integration with Azure Cosmos DB and Event Hubs without managing separate tools.
Streamlined debugging: Developers can use breakpoints, test data...
Great job, Are you using Windows Server Nano containers?
We (Xbox Services) are transitioning to using Linux containers! But, .NET Aspire currently runs Windows only so we ensure that our code can target both Linux/Windows