Webinar recording
Effortless Distributed Systems with Aspire
See how Aspire, NServiceBus, and Azure Developer CLI simplify building distributed systems in this interactive session.
🔗Why attend?
Distributed systems can feel overwhelming because everything that used to be simple suddenly gets harder: running services, wiring them together, debugging failures, keeping them talking to each other, and deploying them without breaking something else.
In this webinar, you’ll see how leveraging tools like Aspire, NServiceBus, and the Azure Developer CLI removes friction at every stage, from architecture and orchestration to messaging, observability, and deployment.
This interactive session includes three live polls that let you influence the direction of the multi-service system we build together. You’ll see how reliability, service discovery, diagnostics, and cloud deployment become easier when you choose the right tools and reduce the amount of boilerplate holding you back.
You’ll walk away with practical ideas you can use immediately, along with a clearer understanding of how modern tooling transforms distributed systems from difficult to manage into surprisingly simple.
🔗In this webinar you’ll learn about:
- How Aspire orchestrates multi-service applications with built-in reliability, service discovery, and observability
- How to integrate messaging with NServiceBus to improve resilience and decouple services
- How to streamline the path from local development to cloud deployment with Azure Developer CLI
🔗Transcription
- 00:00:06 Britt King
- Hello, everyone, and thank you for joining us for another Particular live webinar. My name is Britt King. Today, I'm joined by my colleague and solution architect, Jason Taylor, who will be speaking today about effortless distributed systems development. We also have Mike Minutillo on the panel who will be helping coordinate your questions throughout the session. So be sure to use the Q&A feature to submit your questions at any time during the presentation.
- 00:00:33 Britt King
- Today's session will run for approximately 60 minutes, followed by a 15-minute Q&A, during which we'll address as many questions as possible. Any remaining questions will be followed up afterward. This webinar is being recorded and a link to the recording will be shared with all of you after the session. With that, let's get started. Jason, welcome.
- 00:00:59 Jason Taylor
- Thanks, Britt. Hi, everyone. I'm Jason Taylor. I'm a Microsoft MVP in developer technologies and a solution architect for Particular Software, the team behind NServiceBus. Building distributed systems often feels complex, slow, and fragile. Today, I'll show you how using the right tools can remove that friction, so building distributed systems feels effortless from local development all the way to the cloud. Today's session will be interactive, so jump onto slido.com and enter Effortless, or grab the link from chat. We'll use Slido for a few polls, and you'll help shape the path we take through the demo. There are eight different variations of this talk, so be sure to get your votes in.
- 00:01:46 Jason Taylor
- While you're joining, let's start by defining what we mean by a distributed system. A distributed system is a system of multiple services or components working together. And those services are running on different machines, so think servers, containers, or cloud resources. And they communicate over a network so the whole thing behaves like one application. Here's a simple but common distributed system. We have a web front end, a web API backend and a database, and these are all running on different servers. Now, whether your system looks small like this one or grows into something much larger and more complex, the same challenges show up. And these include orchestration, service discovery, reliability, configuration, observability, and deployment. And these challenges are exactly what we aim to simplify with effortless distributed systems. Effortless means shifting the focus back to what matters, delivering features that users care about. We reduce wasted effort by eliminating manual setup, plumbing, and boilerplate, and we simplify the hard parts so that teams can move faster and with greater confidence.
- 00:03:05 Jason Taylor
- Now, of course, effortless doesn't mean no effort. It means reducing friction and spending time in the right places. To do that, we need a solid foundation, and that's exactly where Aspire comes in.
- 00:03:18 Jason Taylor
- Aspire simplifies distributed systems development from the start. So what is Aspire? Well, it's a framework. Simply, it's a framework that gives us tools, templates, and packages for building distributed apps. At the core is a code-first app model, which acts as the single source of truth for our services, resources, and how they connect. It also provides pre-built integrations for common infrastructure like messaging, databases, caching, and hosting. And importantly, Aspire is designed to work end-to-end from running locally during development all the way through to deployment in the cloud.
- 00:04:03 Jason Taylor
- All right, let's see Aspire in action. We'll add Aspire to an existing app so you can see just how quickly it can simplify building distributed systems. Okay. This is my application. I have a simple web UI front end, a web API backend, and a shared class library. The web UI front end is running on ASP.NET Core Blazor Server. The web API backend is an ASP .NET Core minimal API with a SQLite database, and the shared class library simply contains contracts and common types that will be shared between the front end and the backend. I'll go ahead and get this application started so you can see it running.
- 00:04:46 Jason Taylor
- First, I'll start the web UI front end. Then I'll start the web API backend. We've got both of our console windows here, so we can see if anything goes wrong. Now, this is quite a simple application. It has a homepage, a counter, because developers love counting things, and a weather forecast. Now, I don't want you to worry. This is not file new project. It is file new project, but actually I made it a little bit worse.
- 00:05:17 Jason Taylor
- 30% of the time requests to this API will fail. I want to resolve this issue as part of what we do today. So if I press Control F5, you can see that a lot of the time it's working quite well, but every now and then it's going to fail. There we go. We get that dreaded 500 error. But I also added a bug report. So if users run into this issue, then they can submit a bug report, which will be stored in our SQLite database. No one's going to read it, but they can submit them if they like. And of course, this is going to fail 30% of the time as well. So hopefully we can get these issues resolved. Let's start to simplify things by adding Aspire.
- 00:06:06 Jason Taylor
- So if we want to add Aspire, we can do that using the command line or your IDE. I'm using Visual Studio today, so I'll show you how to do it using Visual Studio. So I right click on the Web UI project. I go add and add Aspire Orchestrator support. And what that's going to do is add two new projects, the app host and service defaults. I'll click okay. And there we go. We have our app host and service defaults. We'll dive into those shortly.
- 00:06:34 Jason Taylor
- Next, we'll add Web API. So we'll add Aspire Orchestrator support to that. It says the project already exists, so we just click okay, and we can dive in. So the app host is the orchestrator for the whole system. You can think of it as the place to describe the parts of your system and how they connect together. You can see we have two parts already described, the web UI and the web API. We can also define resources such as databases, message brokers, anything else the system needs. We also have the service defaults project, and service defaults is just a simple class library that holds common configuration, which will be shared across our services. At the moment, we have two services, web UI and web API. So we can share things like logging, health checks, telemetry, and resilient HTTP client defaults. So you can see when we add service defaults, which we're doing in web UI and web API, it adds all of these things. We've got smart defaults.
- 00:07:37 Jason Taylor
- Okay. So let's run this application. Now, instead of starting web API and web UI separately, I can just press F5, app host will start, and it will orchestrate the starting of web API and web UI. And this is something that I really love, the Aspire dashboard. So the Aspire dashboard provides a number of features, including resources. You can see all of the resources in your solution, and you can use this feature to start, stop, inspect resources, load the application. It's quite good. We also have console. So instead of juggling a bunch of console windows, we can come here and see a consolidated view. So we have web API and web UI. We could drill down if we wish.
- 00:08:25 Jason Taylor
- Then we've got structured for our structured log, the semantic, more detailed view of what's happening. We've got traces so that we can see requests moving through the systems, and we've got metrics so we can dive in and have a look at graphs and numbers for things that are occurring within our system. So let's see how our system is functioning now that we're using Aspire and we have some smart defaults. So I'm on the weather forecast and I press control F5 and there was a little bit of a delay there. And again, a longer delay, very long delay this time. I'm going to give it a moment. There we go, and it came through.
- 00:09:06 Jason Taylor
- So what's happening there? All of a sudden our system has become reliable, but why? Well, we can use the Aspire dashboard to figure that out. If we go to traces and we have a look at this trace here, we can see that there was a bunch of failures. There was three 500 errors, and then finally a success, a 200 response. And if we want to understand what happened, we can actually dive in and look at the details of this trace, or we can just say, "Explain trace."
- 00:09:40 Jason Taylor
- So that's right, the Aspire dashboard has GitHub Copilot built in, and we get that nice little button there that will give us trace overview, error details, retry logic, error source, key takeaways, but I don't really want to read all that. I don't have time for that. So let's ask a question.
- 00:10:03 Jason Taylor
- Explain it to me as if I was four years old. What happened? Imagine you asked your toy robot to tell you the weather. The robot tried to ask its friend, another robot for the weather, but your robot, but your friend was feeling sick and said, "I can't do it." And it said it three times. Your robot didn't give up. It kept asking again and again. And on the fourth try, the friend felt better and finally gave the weather answer. So that's great. You can see there was some errors, there was a retry, and finally some success. Couldn't get much easier than that, huh? Someone to explain our traces for us. All right, let's stop that and do a little bit more in terms of orchestration. So we've defined our web API, we've defined our web UI, but now let's define how they connect together.
- 00:10:53 Jason Taylor
- So I'll create a variable for web API, and then web UI, I'll say, "You know what? Don't start until the web API is started." So that means that the web UI will now wait for the web API before kicking it off. And we can make that a little bit smarter. We can say also, you should know that these services have health checks, so let's go ahead and add in the endpoint so that Aspire knows whether the services are alive, but also healthy. So that'll make sure that web UI only starts when web API is healthy, and we'll also be able to see those health checks on the Aspire dashboard. Okay. The next thing that I'd like to do is to improve how these two services connect to each other. So the web UI is able to connect to the web API using a HTTP client, but at the moment we've just hard-coded the URL of web API, which is really not good when we go to deploy to different environments or if it's running on different machines.
- 00:12:00 Jason Taylor
- So let's fix that up. We can use a feature of Aspire called Service Discovery. And so I'll say we're going to use HTTPS or HTTP if HTTPS is not available, and I want you to connect to web API. And so this is a special service discovery string, and that name web API actually references the name that we've defined in the app host. And so what's going to happen here, if we include with reference, is that service discovery information will be injected into web UI, and that will be backed by environment variables. So as we're deploying across different environments, all we need to do is set the environment variables. We don't need to manage configuration in app settings or anything like that. Okay. Let's check this now and make sure that everything's working, and we'll have a look at those environment variables that have been created for us.
- 00:12:59 Jason Taylor
- So we could see there that web UI actually waited a little bit until web API was running, and then it started up. If we have a look at the graph, we can see the dependency between the two. If I open up web UI, we can see that it's running, that it's healthy, we can see the URLs so we can access the service. We can see the references now. We've got a reference to web API, so it's reference for service discovery and wait for. We can see the health checks. It was healthy as of 13 seconds ago and just now. And we can see the environment variables. And if we scroll down, we can come to the service discovery environment variables. So we've got services, web API, HTTP, and HTTPS. So that's what's driving our configuration now. Let's give it a test and make sure that we can still get the weather.
- 00:13:53 Jason Taylor
- That's working. Great. So there we go. One thing that I do want to do, I'll just disable those errors from occurring. We won't need them anymore. Okay. So that's it. Aspire is running alongside our app, orchestrating the web UI and the web API as a single unit. There's no more juggling startup projects or console windows. With service defaults, we instantly got service discovery, health checks, resilience, and telemetry, all without manual setup. The Aspire dashboard provides observability out of the box. So we have logs, traces, metrics, issues are now visible and easy to understand, especially with GitHub Copilot, breaking it down into four-year-old mode for us.
- 00:14:43 Jason Taylor
- All right. Next, let's look at Aspire integrations. So Aspire ships with ready-made integrations so you don't have to wire up everything by hand. And these integrations cover the core services every distributed system needs, including databases and storage, messaging, caching, and observability. Now, an Aspire integration is just a ready-made building block that wires up common services for you, such as health checks, telemetry, and smart defaults. As you can see from these examples, Aspire supports a wide range of integrations.
- 00:15:21 Jason Taylor
- And right now, our app is running on SQLite, but it's time to level that up. Let's swap it out using an integration, and I'll let you choose which database we go with. So which database should we use? Azure Postgres or Azure SQL. Get your votes in now.
- 00:15:39 Jason Taylor
- At this stage, it looks like 100% Azure SQL. Oh no, we're getting some more votes. PostgreSQL. Maybe, maybe. We've got strong lean towards Azure SQL. I'll give it a few more seconds. Okay, here we go. Azure PostgreSQL is picking up. 50/50. Okay. We've got a fight on. Azure SQL. We can go whichever way you like. Get your votes in. All right. It looks like we're going to go with Azure SQL. If you haven't joined at slido.com, feel free to do that now. Just enter the code effortless or use the code in chat. Let's see how easy it is to wire up Azure SQL using an Aspire integration.
- 00:16:53 Jason Taylor
- Okay, Azure SQL. So where do we start? We'll need to install the database server. No, this is the effortless approach. Of course, we're going to use an Aspire integration. So to do that, first we right click on AppHost and go add .NET Aspire package. Now this displays all of the hosting integration packages, and these packages configure applications by provisioning resources or pointing to existing instances, such as a local SQL server instance if you had one running.
- 00:17:27 Jason Taylor
- Now, we don't have one running and we don't really want to set it up ourselves. So we're going to search for Azure SQL. So we can see we have an integration package, Aspire hosting Azure SQL. I'll go ahead and install that.
- 00:17:45 Jason Taylor
- Okay. Now with an app host, we can now define a database server. Here we go. So we have a database server. We're adding an Azure SQL server. The type is SQL. So when it's deployed, it's going to be Azure SQL running in the cloud. When we're running locally, we're going to run a SQL server container, and we've specified that the container lifetime will be persistent. So the first time this runs, it's going to create that container, but the second time it runs, it will just use the existing container. So we'll have a slow initial startup, but subsequent startups will be very fast. Now using that database server resource, we can also create a database. So if I say databaseServer.AddDatabase "EffortlessDB," we'll also have that resource available for us.
- 00:18:43 Jason Taylor
- So now we want web API to be able to access that database, and we also want it to wait for that database to be ready before starting. So we can say, wait for the database. And for service discovery, we can say with reference database. So that's of course going to inject service discovery information into web API, which means we won't need to be messing around with connection strings and configuration.
- 00:19:15 Jason Taylor
- Okay. So now we can update the web API. I think we'll start by removing all of the references to SQLite. So I'll remove the package, EF Core SQLite. I'll remove the connection string, keep it clean. Okay. I'll delete the SQLite database and I'll update program.cs. We won't need to grab the connection string anymore. We'll use service discovery and we'll have a different way to wire up our AppDB context, which you'll see in a moment.
- 00:19:53 Jason Taylor
- Okay. So the first thing we'll need to do is to install another Aspire integration package. So if I right click on web API and go add .NET Aspire package. This time we're looking at client integration packages and client integrations wire up DI and configuration and have all of those smart defaults, including some helpful code built in, depending, of course, on which integration you're getting. So we're looking for the Aspire SQL Server package. Do a quick search. So there we go. Aspire, Microsoft Entity Framework Core SQL Server. Install.
- 00:20:33 Jason Taylor
- And of course I could have just installed Microsoft Entity Framework or SQL Server, but I want the smart defaults that Aspire provides. Okay. So then in program.cs, I can say builder.AddSqlServerDbContext, specify my context, and also specify the connection name. And the connection name is the same name that we've provided in AppHost, so EffortlessDb. And we just need to do one more thing. So I have migrations enabled for this project. I'll just replace the SQLite migrations with some SQL Server migrations that I created earlier.
- 00:21:18 Jason Taylor
- Okay. Let's do a quick build and give it a try. Looks like Docker is not running. I'm glad that we have that nice message there to remind me. So I'll start up Docker now and we'll give it a moment. Okay. The SQL Server is starting and once it starts successfully, the EffortlessDb will be created. Web API will then start and followed by web UI. We'll give it a moment. We can see things nicely connected in the graph. There we go. SQL Server, ready for our database. There we go. Web API. And finally, web UI.
- 00:22:27 Jason Taylor
- So now our bug reports should be stored on SQL Server. Let's give it a try. Excellent. Okay, that's it. With Aspire integrations, adding a database became effortless. You can choose the database we want, SQL Server, PostgreSQL, Cosmos DB or anything else, and Aspire handles the rest. The hosting integration provisions and orchestrates the database alongside our app, making sure that it's ready before the web API starts, and the client integration wires up Entity Framework Core with some smart defaults and useful extensions. And thanks to service discovery, there's no connection strings or configuration headaches.
- 00:23:16 Jason Taylor
- Okay. So now that we've seen Aspire in action, let's move on to messaging. We leverage messaging when services need to communicate reliably at scale and without tight coupling. Here's what makes messaging so powerful in distributed systems. Messaging ensures reliable communication between services. Messages don't get lost. They're delivered with durability and retries. It decouples services so they can evolve and scale independently, and it gives us graceful failure handling. If something goes wrong, messages wait safely and can be retried until the system recovers. In this simple example, we have Service A. It sends a message and moves on. The message is stored in a queue by the message bus. When Service B is ready, it takes the request, does the work, and sends a reply back the same way. So the queues are like inboxes, no waiting on live connections, messages wait safely and the system can retry if something fails.
- 00:24:21 Jason Taylor
- And that combination keeps our services decoupled and resilient. We saw an example of retries before when it was a transient error. In the backend, we have Polly .NET retrying that weather forecast if it gets back a 500 error from the web API. But what if the web API is offline? No amount of retries is going to resolve that issue. That's where messages comes in. Those messages will be waiting for when service B or when the web API comes back online and it can fulfill that request.
- 00:24:53 Jason Taylor
- Okay. So in the next demo, we're going to start small. We'll add support for messaging using NServiceBus and what's known as the Learning Transport. The Learning Transport simply lets us see how messaging fits into our app without worrying about infrastructure just yet, but we will get to that.
- 00:25:11 Jason Taylor
- Alright, let's stop this from running. Now, a quick introduction, if you're not familiar with NServiceBus. You can think of NServiceBus as a heart that keeps a distributed system running. NServiceBus works by routing messages to endpoints, and endpoints can send and receive messages. Messages are stored until they're delivered, so communication keeps working even if a server goes down. Now, messages are just plain C# classes. We have one already. So inside of shared contracts, I have this create bug request. Now, so that NServiceBus can recognize this as a message, we apply the iMessage interface, and this interface simply indicates that it's a message that can be sent and received. So we have a message. Now we need an endpoint, because NServiceBus works by routing messages to endpoints. Let's create an endpoint within web UI. Now, the first thing I'll need to do is to install NServiceBus.
- 00:26:13 Jason Taylor
- So I'll right click on web UI and select Managed NuGet Packages and I'll browse for NServiceBus.Extensions.Hosting. Now this will install NServiceBus 10, which is the latest version of NServiceBus released last week. Now that's installed, I can define an endpoint, and I'll do that within program.cs. Just under add services to container, I will define an endpoint called web UI Endpoint, which is going to use the Learning Transport and serialization will be supported using JSON. Now, of course, I could have used XML or a custom serializer, but I chose JSON because it's named after me. Now, this endpoint will be ready when that web application starts. So now we have a message and we have an endpoint. We need to send a message using that endpoint. We can do that by updating our bug report for\\\m. So at the moment, as you know, this is using the API client to send the request to the web API, which then processes it.
- 00:27:21 Jason Taylor
- Instead of injecting an API client, we'll inject an iMessage session. Now, an iMessage session is just a simple NServiceBus service which provides basic messaging operations for us. Now, the way that it currently works is -when the user hits submit, it uses the API client to post the bug request. If the response from the API is successful, it will show a success error message and then reset the form for the next bug. Otherwise, it will show an error. Now, our life is going to be much simpler. All we're going to do is add the message to a queue, so we won't need to show an error. It will be there in the queue waiting safely. So if I say await MessageSession.Send, show success and reset the request. The only other thing we need to do is specify where the message is going, and we're going to send it to a web API endpoint. That doesn't exist yet, but let's not let that stop us. I'll do a quick build and run this.
- 00:28:36 Jason Taylor
- Now, our database is already running, so we can dive straight in. Go to the bug report. We'll do a couple of tests. Test one. Success. Test two. Success. There we go. Okay. So messages are being sent, but where are the messages going? Well, they're actually sitting on disk at the moment because that's what the Learning Transport does. So if I have a look here at the solution folder, we have a Learning Transport. We have a Web UI endpoint and we have those messages waiting to be picked up, but we need to create the web API endpoint now. So as before, we'll go ahead and add NServiceBus.Extensions.Hosting and we'll add it to web API and we'll update program.cs to define a new endpoint.
- 00:29:38 Jason Taylor
- So this one will be the web API endpoint. It's also using the Learning Transport and JSON serialization so that those two endpoints could communicate together. So we have an endpoint that can now receive the message. We need to do something with it. So we'll go ahead and create a create bug request handler inside of web API. So I'll create the bugs folder. That'll be our feature folder. CreateBugRequestHandler will be the name of the class. And we'll go ahead and add an implementation of that class. So it's called CreateBugRequestHandler. It depends on the AppDbContext. It implements NServiceBus interface IHandleMessages of type create bug request. When the message is received, the handle method will be invoked, passing in the message. It will take that message and map it to a bug, which is an Entity Framework Core entity.
- 00:30:40 Jason Taylor
- It will add that bug to the bugs table and it will save the changes to the database. I'll put a break point on here and we can see it running. Oh, let's stop that. Do a quick build.
- 00:30:58 Jason Taylor
- Okay. So I should mention that this iHandleMessages interface will be picked up automatically. So when NServiceBus starts, it's going to scan all available assemblies for types such as these and wire them up for us so that we don't have to. All right, let's test it out. Okay. Web API is starting. Ah, okay. Our first message has come in. So we've got test two and test one. So both of those messages were delivered successfully, even though web API endpoint wasn't running when they were sent. Okay, let's give it another test. One more message. Bug report, test three. Whoops, got the description. That's super important. Okay. And there's our test three message. Great. I'll go ahead and remove that. Breakpoint now. And let's see what else we can do.
- 00:32:06 Jason Taylor
- So one of the things you probably notice is that we have duplication of endpoint configuration across our two endpoints. Now that might be something that you want, but if you don't, here's a simple way to remove that duplication. We can simply update service defaults to include an extension method that can be used whenever we're defined in an endpoint. So let's start by adding this package to service defaults. So we'll remove it first from Web UI and web API. They won't need that reference any longer. And then in service defaults, I'll paste it in and I'll save. I think I'll do a quick rebuild just to make sure all those packages are removed and restored correctly. And then we'll go into extensions. And right down the bottom, we'll add an extension method called add messaging endpoint. It's essentially the same as before. We're going to pass in the name. We'll define it to use the Learning Transport and JSON serialization. And now all we need to do is replace our existing configuration with the use of the extension method.
- 00:33:26 Jason Taylor
- So in the... Where are we? We're in the web API. So we can use this code, AddMessagingEndpoint, WebApiEndpoint, and we need to update the web UI, AddMessagingEndpoint, WebUiEndpoint. The other thing that I'd like to do is leverage OpenTelemetry. Now, fortunately, Aspire and NServiceBus both support OpenTelemetry and it's enabled out of the box. So all we have to do is add a trace and metrics to the current configuration. Now it's configured in service defaults, so we can just go into the configure Open Telemetry method. And here where it says metrics, we'll go ahead and add NServiceBus. And under tracing, we'll add a new source for NServiceBus.
- 00:34:43 Jason Taylor
- Okay. Let's give that a try. Wait for it to start. Good. Let's send some new requests. Test A, Test B, and Test C. Okay. Over to the dashboard. Let's have a look at our traces. So here's the last one we did. So you can see it's tracing this request all the way from submission, from form submission to the database. It submits the form. The handler actually sends the message. And if I scroll down here, we can see all of the additional information that's been provided by NServiceBus. We've got the process message. This is when the message is received. We've got the create bug request handler being invoked. And finally, that new bug record being saved to the database. So that's an example of an end-to-end trace.
- 00:36:02 Jason Taylor
- If we go to metrics, have a look at web API. We've got some NServiceBus metrics here. So we have, let's see, we've got processing time. So it says the time in seconds between when the message was fetched from the input queue until it was successfully processed by the endpoint. So processing time can be used to find handlers that are running particularly slowly or suspiciously quickly. Another interesting one is we have critical time. So critical time can be used to decide when it's time to scale an endpoint up or down. We have message fetches, handler time, and successes. So there's a lot of information right there available for us in local development. All right, let's do a bit of a cleanup. I'll stop this running. Close those and back to our slides. Okay, so that's it. We've added messaging within NServiceBus, and it's already running locally with no extra infrastructure. Because we're building on top of Aspire and NServiceBus, OpenTelemetry was already enabled. It's included by default. So adding on NServiceBus traces and metrics was seamless.
- 00:37:22 Jason Taylor
- So now we've seen messaging using the Learning Transport just to keep things simple, but it's not something we'd actually deploy to production. So before we move on, let's decide together which messaging technology we'll use next. Would you like to use Azure Service Bus or RabbitMQ?
- 00:37:51 Jason Taylor
- It's always interesting to see which way this one goes. There's usually a bit of a battle, but not today. Azure Service Bus at 83%, 86. Wait a little bit longer. If there's some RabbitMQ fans on the webinar today, you'd better speak up. Otherwise, it'll be Azure Service Bus for the win. Here we go. All right. In the next demo, we'll switch from the Learning Transport to Azure Service Bus.
- 00:38:33 Jason Taylor
- The first thing we'll need to do is to install the hosting package for Azure Service Bus. So I'll right click add .NET Aspire Package, and we'll search for the Azure Service Bus package. There we go. Install. Now that's going to allow me to define the Azure Service Bus resource within AppHost.
- 00:39:06 Jason Taylor
- So there we go. I'm using the distributed application builder to AddAzureServiceBus. I've called it messaging. When I deploy to the cloud, it'll be running against a real Azure Service Bus instance, but today I've chosen to run against the emulator here in local development. There are some limitations with that. For example, you've seen I've had to create these queues manually, and it's not going to auto-subscribe. However, I feel that these limitations will eventually be resolved and using the emulator will be the effortless approach. If these limitations are a problem for you, just remove this and Aspire will go into local provisioning mode, and it will actually provision an instance of Azure Service Bus for you for use during local development.
- 00:39:55 Jason Taylor
- Okay. So the next thing that I want to do is a little bit of orchestration. Don't start web API or web UI until Azure Service Bus is ready and also inject service discovery information. So let's do that. So again, we will wait for messaging and with reference to messaging.
- 00:40:18 Jason Taylor
- Okay. We'll do that for web API and Web UI. Next thing we need to do is install the Azure Service Bus Transport for NServiceBus. And we've defined our messaging in one place in service defaults, so we can just install it here. So I'll go to Manage NuGet Packages, and we will browse for NServiceBus.Transport.Azure Service Bus. I'll install that.
- 00:40:51 Jason Taylor
- Okay. And then in service defaults, back down to our messaging endpoint method, we'll go ahead and remove the Learning Transport and we will add in Azure Service Bus. First, we need to grab the connection string. It's going to be injected for us because we're using ServiceDiscovery. And then we need the endpoint configuration and that's going to use the Azure Service Bus transport, passing in the connection string. And finally, because we're using the emulator, we just need to disable the auto-subscribe feature.
- 00:41:32 Jason Taylor
- Okay. Let's do a build and run. Okay. So you can see here that our messaging resource is being created. Alongside of it, it's going to create the web API endpoint queue and the web UI endpoint queue. And it's also going to provision a new instance of SQL Server because the Azure Service Bus emulator uses SQL Server to store its messages. So we've gone from storing them on disk locally to storing them in SQL Server locally and in the cloud, obviously Azure Service Bus. Looks like everything's running nicely. Wait for web API and then web UI. And we'll send one test message just to make sure everything's working okay. This can be Test D.
- 00:42:36 Jason Taylor
- Okay, good. So that's it. We switched from the Learning Transport to Azure Service Bus with minimal effort. Within NServiceBus, it was just a small code change made in one place thanks to the transport abstraction and the refactoring we did in service defaults. The transport abstraction really makes this effortless. We can use the same messaging code with any queuing technology like Azure Service Bus, RabbitMQ, Amazon SQS, take your pick. Using Aspire hosting integrations, we added the new messaging resource and Aspire handled orchestration and service discovery automatically.
- 00:43:16 Jason Taylor
- And the best part is local development couldn't be simpler. One press of F5 launches the entire system ready to test and observe instantly. Pretty great for a new developer joining our team. All right, we've seen everything running locally. Now let's see what it takes to get it into the cloud. So in this section, I'll show you how Aspire and tools such as the Azure Developer CLI work together to make your code to cloud journey truly effortless. So Aspire apps are flexible. The same app can run on your machine, in containers, or in the cloud. And when you're ready to deploy, Aspire can create a deployment manifest, which is a blueprint which describes all of the services and the resources that your app needs. We can then use tools such as the Azure Developer CLI to read that manifest, to provision those resources and to deploy the app to the cloud, including Azure or AWS or any other provider that has a hosting integration for. Of course, you don't have to use Aspire's capabilities to do this, but you can. It's there and certainly the most effortless path for this demonstration.
- 00:44:32 Jason Taylor
- So finally, you can also configure a CI/CD pipeline using tools such as AZD for automated deployments. So this means the deployments become automated and repeatable. So really, the only question is which deployment pipeline should we use? So today you can choose between Azure Pipelines or GitHub Actions.
- 00:44:56 Jason Taylor
- Give you a moment to decide.
- 00:45:11 Jason Taylor
- All right. Azure Pipelines today, perhaps. Let's see. 62%. Oh, getting close. I'll give it a few more if you want to get your votes in. All right. It looks like today we'll be using Azure Pipelines. Okay, let's see it in action. We'll take our demo app and use Azure Developer CLI to set up Azure DevOps.
- 00:45:51 Jason Taylor
- All right. The first thing that we'll need to do is to install a new integration package, and we're installing the Aspire hosting Azure Container Apps package. Now, this allows us to configure our Azure Container Apps (ACA) environment and customize the provisioned infrastructure should we choose to do so.
- 00:46:21 Jason Taylor
- Okay. So I say builder.AddAzureContainerAppEnvironment. Next thing that I want to do is to ensure that the web UI endpoints are accessible externally. So I just need to say with external HTTP endpoints. Now, we're using Azure Service Bus. Locally, it uses a password for authentication. In the cloud, we're going to use managed identity. So we just need to make a change to service defaults in our ad messaging endpoint so that it can use those different approaches. So inside of service defaults, I need to install a new package, Azure.Identity, install.
- 00:47:19 Jason Taylor
- And then inside of our extension method, we can adjust things. We'll get rid of this part and bring in a new code block. Okay. So you could see if we're running in development, then we'll still use the Azure Service Bus transport or pass in the connection string and we'll disable the auto subscribe feature not supported by the emulator. However, if we're running in any other environment, we'll use Managed Identity. So we're using AzureServiceBusTransport with a connection string, passing in the default Azure credential. And of course we don't need to disable auto subscribe because it's supported.
- 00:48:03 Jason Taylor
- So that's pretty much all we need to do from that side of things. So let's switch over to the command line. So Aspire can generate a deployment manifest for us. And if we would like to see that, we can use the Aspire CLI, which supports a number of commands. We'll use Aspire to publish manifest outputting to the artifacts path, which is where all of the build artifacts are created for the solution.
- 00:48:36 Jason Taylor
- So the deployment manifest is based on the AppHost, our code first app model of our system, and it can be used by external tooling to deploy anywhere. Let's have a quick look at it. We're not going to dive in and pick it apart, but I want to at least browse through it. So it's a JSON document describing all of our resources and services and how they connect together. Now, tools such as the Azure Developer CLI can use this document to figure out how to provision the necessary infrastructure in the cloud and also how to deploy our services to the cloud. So we'll run azd init, which will make our system compatible with the Azure Developer CLI. We have a few options there. I'll choose scan the current directory, and it should detect that this is a .NET Aspire app or just Aspire since they've changed the name to Aspire now, which it has. And I can say confirm and continue initializing my app.
- 00:49:43 Jason Taylor
- It's going to ask me for a unique environment name. This is going to be my first cloud environment. I'm going to call it effortless dev. Later on, I can go ahead and add staging and production or whatever environments I want. That's complete. So our application or our system is now compatible with the Azure Developer CLI. It includes an azure.yaml file, which is AZD configuration. It includes a next-steps markdown document, which will show you exactly what you can do with this system now that it's AZD compatible. And it's also recommended some commands that we could try. So for example, we could run azd up to provision and deploy our app to the cloud, which is ideal for rapid demos or debugging or one-off deployments, but for our purposes, we'll be creating a CI/CD pipeline. You can see we could run azd add to add new Azure components to the project, and we can run azd infragen to generate infrastructure as code for our project to disk, allowing us to manually manage it.
- 00:50:48 Jason Taylor
- So when we run that command, it would generate that infrastructure based on the Aspire deployment manifest. We're not going to do that because there's another feature that we can leverage where it uses the integration with Aspire and the deployment manifest to automatically manage infrastructure for us so that we don't have to generate the infrastructure code.
- 00:51:11 Jason Taylor
- Now, if that works for you, that's great. That's going to be easier than managing IaC yourself. If it doesn't, then of course you use azd infragen and take control of it yourself. Okay. So we want a CI/CD pipeline, right? So let's go azd pipeline config and we're specifying the provider as Azure DevOps. We'll use Azure pipelines, and we're specifying a principal name. Now we're going to call it the principal name sp-EffortlessDev. So this is the service principal that will be created in Azure. It's a good idea to give it a name.
- 00:51:48 Jason Taylor
- If we don't, it will give it a name which is something like azd and the date timestamp, which is not really easy to remember what that service principal is for.
- 00:51:59 Jason Taylor
- Okay. So if I press enter, it's going to run a workflow and help me get the CI/CD pipeline up and running. So the first question it asks me is, which subscription would you like to use? I'll choose Visual Studio Enterprise. Next, it's going to ask me, "Where would you like to host these resources?" I'll host them in Australia East. That's the closest one to me. It says, "You don't actually have a CI/CD pipeline. Would you like us to create one for you to get you started?" I'll say, "Yes, please." And it says, "Okay, we're going to use Azure DevOps." So give us the organization name, which is JasonTaylorDev. And it says, "You know what? You're a bit of a cowboy. You're not even using source control. Do you want us to set up source control for you?" And I'll say, "Yes, please, we can do that." And then it says, "Let's create a new Azure DevOps project or you can select an existing one."
- 00:52:55 Jason Taylor
- I'm going to create a new one. The name will be effortless, so I'll just press enter.
- 00:53:06 Jason Taylor
- Give it a moment. It's thinking. And then it says, select how you want to authenticate the pipeline to Azure, or select client credentials. We won't need to manage those passwords. That'll all be done for us, the secret that is. And now it's underway. So it's creating the service principal in the cloud. It's setting up those credentials. It's probably already set up the Azure DevOps project. It's going to make sure that there's a pipeline in place. It'll make sure that the pipeline can connect securely to Azure so that it can deploy all of those resources and those services.
- 00:53:42 Jason Taylor
- And now that that's done, it says, "Hey, would you like to commit and push your local changes to start the configured CI pipeline?" I'll say, "Yes, I would love to do that." So give it a second. Being patient. It's going to ask me for my password.
- 00:54:05 Jason Taylor
- Okay, give me one second. I'll type that in. Being patient. Yay. Okay. Success. Your Azure DevOps pipeline has been configured. So we can have a look at that. Here is our new Azure DevOps project with all of the code has been committed. Here is our pipeline. It's already running. If I dive in, we have a look at the details. You can see that it's going to install azd. It's going to configure azd. It's going to set up .NET 8, 9, and 10. I guess it's covering the bases. We only need 10. We could fix that if we like. It's going to then provision the infrastructure and deploy the application. Now, I've got one that I've created earlier because it does take about 10 minutes to run, so we can have a look at that one that's completed and see it running in the cloud. Before we do that, I'd actually like to just dive in and see some of the things that were created when we did all of this.
- 00:55:30 Jason Taylor
- So I'll fire up Visual Studio code, pop it over here, and let's make that a bit bigger. Okay. So inside of here, we have some Aspire settings. Inside of here, we have the Azure DevOps pipeline that was created for us. You can see that it's a pipeline as code, which means it's ready for us to modify, to extend, to customize, to add some automated tests, to extend it, to deploy also to staging and production with maybe some checks along the way. So this is our code now. It was easy to get up and running, but we have full control of this pipeline and how we want it to evolve in the future. This is not a right-click published scenario. Inside of Azure, we have the details of our azd environments that we've created. Scrolling down, there's our Learning Transport. What else do we have?
- 00:56:39 Jason Taylor
- Here, we've got our azure.yaml, which is the azd configuration file. And we've got that next steps markdown document that I told you about that you can look at if you'd like to understand what you can do with this system now that azd has been enabled. Definitely a lot to dive into, but let's have a look at the deployed solution. So we saw that this one that I created earlier deployed successfully. It's deployed all of the resources that we need for this solution. Let me clean up a bit here. A lot of tabs over. There we go. If I go into the container apps environment, something that I really love is the Aspire dashboard is available. And you can configure whether you want that available or not. It is sitting behind authentication, so it's using my session with the Azure portal to authenticate here. But you can see I've got everything that I had locally from resources to console to structured to traces and metrics, which is really great for your deployed environments.
- 00:57:49 Jason Taylor
- And I can click on web UI and I can still see the Hello World message. I can still count things. I can grab those weather forecasts. So it's communicating with the backend and I can submit bug reports which will now be using Azure Service Bus. So that's a really nice experience.
- 00:58:10 Jason Taylor
- All right. So with Aspire, our C# app model in our AppHost became the source of truth. We didn't need to write any Bicep, ARM, or Terraform. The deployment manifest describes the entire system and feeds directly into azd, which then handles packaging, provisioning, and deployment. Using azd pipeline config, we created a repo. We generated and configured a CI/CD pipeline. We set up secure credentials to connect Azure DevOps securely to Azure, all automatically, no manual steps required, just a couple of commands. And because the pipeline's defined in code, you could see that extending it for staging and production is straightforward.
- 00:58:55 Jason Taylor
- So that's all from me today. If you'd like to explore these ideas further, scan this QR code for 90 days free access to distributed systems design fundamentals course by Particular. It's packed with 11 hours of practical hands-on content to help you level up your distributed systems journey. I'll leave that up for a moment. I hope you've enjoyed my talk today. The key takeaway is simple. Choose the right tools, remove friction, and focus on what truly matters, delivering features that make an impact. Thank you. I'll just wait for Britt to come online and Mike, and we'll kick off the Q&A.
- 00:59:44 Britt King
- Yeah, we can kick off that now. There are some questions coming in. If any of you have additional questions, please add them to the Q&A and we'll get to as many as we can. Let's start with the first one, from Ashith. He asks, when deployed to QA or production, what changes to hosting service defaults need to be coded?
- 01:00:14 Jason Taylor
- Yeah, so it depends on your services, but essentially the changes that I demonstrated today were just differentiated between local development environment and hosted in a cloud environment. So you saw over here, where is it? In service defaults, I've made a change for Azure Service Bus to use managed identity. So it tends to be things like that. Another example would be RabbitMQ in specifying the management API for hosted environments, whereas just using the defaults for local development. The other settings such as configure open telemetry, health checks, service discovery, the use of resilience handler, which is Polly .NET, are up to you, but I'd encourage you to essentially where you can keep local development aligned with production.
- 01:01:24 Britt King
- Right. He was also asking if you can show and NServiceBus telemetry information on the Aspire dashboard. I know that you showed some traces briefly. I'm wondering if you want to touch on that.
- 01:01:38 Jason Taylor
- Mike, where is that telemetry information? Let's see. Let me run this one or run the local one.
- 01:01:53 Mike Minutillo
- Run the local one?
- 01:01:55 Jason Taylor
- Yeah. Okay. So we've got our traces and our metrics. And if we scroll down, scrolling, scrolling, scrolling. Where is it? Oh, could it? Let's see. I have seen it in here. It should have been wired up. Where is it? Yeah, Mike, so I asked you to answer the question, but then I answered it myself. It's supposed to appear in here in metrics, but for some reason it's not. I wonder, did I get that configuration right? NServiceBus.*. Yes. So it should be appearing, but it seems like something's not quite right today. Let's send a couple of messages and see if that helps. Resources, web UI, web report, test one, test, send. Test two, test, send. Back to here, metrics. Ah, there we go. Okay. I don't know why they weren't appearing. Okay. There we go. Yeah. So you can see them directly inside of metrics in the Aspire dashboard.
- 01:03:36 Jason Taylor
- Mike, was there anything that you wanted to add to that?
- 01:03:39 Mike Minutillo
- Well, I asked for clarification about what the asker wanted to actually see, and they said message count message throughput metrics and end-to-end message tracing. So these are the sort of message count and throughput metrics will be in here along with things like critical time, which kind of tells you when your endpoints need to be scaled out or scaled back down. The traces will appear as part of the overall end-to-end application trace. So you already showed those as part of one of the requests that we had.
- 01:04:15 Jason Taylor
- Yeah, there we go. All right. Thank you. Next question.
- 01:04:26 Britt King
- Great. John asks, "We use service-oriented architecture and have different solutions for each service. Could we use Aspire to start all services for a given system along with all necessary resources, Cosmos, ASB, et cetera?"
- 01:04:47 Jason Taylor
- That's a really good question. I haven't tried running Aspire across multiple solutions. That's something that I can look into and get back to you on. Mike, do you have any experience in that area?
- 01:05:02 Mike Minutillo
- I don't have direct experience, but I'm wondering if where you've created those persistent resources, if you created the same persistent resource in each of your solutions, whether or not you would be able to run multiple solutions that can communicate using that shared infrastructure. I haven't tried that, so I don't know whether or not that works, but that might be one avenue that's worth exploring.
- 01:05:23 Jason Taylor
- Yeah, I think so. And that's the whole idea between those persistent resources. If it's named the same, it's going to try and use it. So I think we could be okay with that approach, but yeah, I'm going to investigate that a little bit and I'll get back to you after the webinar. Thanks, John.
- 01:05:40 Britt King
- Thanks, John. Alexis asks, "How many services could Aspire run at the same time on a laptop? In some scenarios, we need to run several services."
- 01:05:52 Jason Taylor
- Yeah, I think that's a great question. Look, when you're using containerized apps, the best thing you can do is make sure that your laptop is pretty beefy. So I think ideally 64 gig of RAM, a bunch of disk space and a nice CPU is going to go a long way. I think for... I don't know. I'm running on a desktop here, but for my laptop, it's a 32 gigabyte and it's quite good. I can get quite a number of resources up and running, at least a dozen, and I haven't noticed a slowdown. But Aspire doesn't really... Aspire doesn't impact. There's not a big performance overhead for having Aspire manage that for you. It just makes it a little bit easier for Aspire to start up and coordinate those services. So if you're running Docker now and starting up many services at the same time, you'll have the same experience with Aspire. Thanks, Alexis.
- 01:07:05 Britt King
- Great. I think that's all the questions for today, Jason.
- 01:07:11 Jason Taylor
- No worries. I did have a couple of emails come in asking some questions, and I recall they're along the lines of, look, we have CI/CD already. We have infrastructure already. We have our hosting sorted out. Maybe they're using infrastructure as code or not, but the question usually is, how do we get Aspire's benefits without breaking what already works for us? And the answer's simple. You don't have to go all in with Aspire. Obviously today I showed you the end-to-end journey, but it's a common approach to just use Aspire for local development and then deploy with your existing CI/CD pipeline and host on-prem or in the cloud as you do. So you can see that the Aspire experience provides the best local development experience. You press F5 and your whole system comes alive. You don't have to think about, well, how do I install RabbitMQ or what permissions do I need to give my SQL Server so that this application can talk to it?
- 01:08:29 Jason Taylor
- You don't have to install SQL Server, this is managed for you. And whether or not you're using Aspire to deploy your solution, the same runtime benefits are still going to apply to you once you do deploy it to wherever you're deploying it. So you'll have your service defaults, you'll have those smart defaults, you'll have open telemetry built in, you'll have resiliency. So you can take it one step at a time. Aspire is not there to replace your platform. It's there to reduce the friction in the areas where you could use some help.
- 01:09:09 Jason Taylor
- So we had one more question come in, which I think we can cover. So this one's from Jerry. When it's time for an NServiceBus upgrade, how difficult is it to migrate forward?
- 01:09:27 Jason Taylor
- And so for the example that I've shown you today, it's going to be about updating the NServiceBus packages within your solution. Now, depending on which parts of the Particular Platform you're running, for example, if you're running ServiceControl and ServicePulse, that will require an upgrade as well, but that's outside of the solution itself. It'll be a matter of moving from the earlier package versions to the new package versions, making sure, of course, that you look at the breaking changes as you would with any package and adjusting your code as necessary. As far as Aspire goes, the things that I've configured today, they're going to continue to work.
- 01:10:17 Jason Taylor
- It's going to continue to spin up the Azure Service Bus Emulator. It's going to continue to spin up SQL Server in local development. So as long as there's no breaking changes that affect those particular resources that you are using, it'll be fairly straightforward. Anything you'd like to add there, Mike?
- 01:10:41 Mike Minutillo
- Yeah, a couple of things. So NServiceBus itself handles wire compatibility between endpoints, which is important to us to try and make it friction-free. The major upgrade process, as always, please read the upgrade guides to see what breaking changes are there and what changes you need to make to your applications. We would normally recommend to customers that they upgrade an individual endpoint at a time and make sure that it's working in a non-production environment before pushing that through to production. That could be challenging in Aspire where you have the shared service defaults project because in the example that we showed today, we had a reference to the specific version of NServiceBus that the endpoints were going to use. It might be worth looking at duplicating that service defaults project and upgrading it so that you have one for the old endpoints and one for the new endpoints, and then for a period of time, switching them one at a time to that, to the newer version of the service default so that it's using the new version of NServiceBus.
- 01:11:47 Mike Minutillo
- One of the benefits of NServiceBus is that it has some fairly stable abstractions at its core. So all of your message sending and message handling codes shouldn't have to change. It's most likely going to be the endpoint configuration that gets set up. And so having all of that in a centralized location makes it a lot easier for you to switch a bunch of endpoints at once. But as always, do that in a non-production environment, test everything, make sure that everything is working, read the upgrade guides, and then when you're happy, you can push everything to prod.
- 01:12:26 Jason Taylor
- Good. All right. I think that's all the questions we have today, Britt.
- 01:12:30 Britt King
- Yeah, I think so. Well, before we say goodbye, I just want to say Jason and some other colleagues here at Particular are scheduled to speak and run workshops at NDC Sydney in the month of April. For a full list of events near you, please visit us at particular.net/events. That's all we have time for today on behalf of Jason Taylor. And Mike, thanks for your help. This is Britt King saying goodbye for now and see you at the next Particular live webinar.
About Jason Taylor
Jason Taylor is a Microsoft MVP for Developer Technologies and a Solutions Architect at Particular Software.