Build a distributed pizza store in .NET with serverless Dapr APIs

Build a distributed pizza store in with serverless Dapr APIs. The distributed system is buit with two Dapr services (.NET), two serverless functions (JavaScript), a front-end (Vue), a key value state store, a pub/sub message broker, and Diagrid Catalyst as the serverless API layer to connect everything.

Build a distributed pizza store in .NET with serverless Dapr APIs

Intro

When developers build distributed systems, they need to think about cross-cutting concerns such as security, observability, and resiliency. Developers also write a lot of glue code to orchestrate services, create long-running and durable workflows, and connect to external resources, such as message brokers and state stores.

Dapr, the Distributed Application Runtime, is ideal for building distributed systems since it has built-in support for cross-cutting concerns, and it offers a suite of APIs for communication, state and workflow, that make developers more efficient.  According to the State of Dapr Report, 95% of Dapr developers claim that Dapr saves time, and 55% of the developers surveyed claimed it saved them more than 30% of development time.

The usage of Dapr is also increasing thanks to a growing number of platforms that support the Dapr APIs. Dapr is no longer limited to running as a sidecar in Kubernetes, as is described in Dapr Deployment Models. In this article, Diagrid Catalyst, a suite of serverless Dapr APIs, is used to orchestrate services with Dapr workflow, and interact with message brokers and key-value stores.

Throughout this article, you’ll be using the following to configure a distributed system:

  • Two Dapr services (.NET)
  • Two serverless functions (JavaScript)
  • A front-end (Vue)
  • A key value state store
  • A pub/sub message broker
  • Diagrid Catalyst as the serverless API layer to connect everything

TLDR: Check the code on GitHub and join the Catalyst early access program.

Diagrid Pizza Store

The demo that is used in this article revolves around an online pizza store, where the user selects pizzas, orders them, and follows the progress of the order in realtime. It’s a simplified example where the durable workflow focuses on the inventory of ingredients, and communicating with the kitchen. The source code is on GitHub.

Architecture

The solution includes:

  • Two Dapr web services written in .NET, PizzaOrderService and KitchenService. In this demo, they’re running locally, but these can be hosted in any cloud that supports ASP.NET services.
  • The website (based on Vue) and two serverless functions written in JavaScript, getAblyToken and placeOrder. These are hosted on Vercel.
  • Ably as the realtime communication component between the PizzaOrderService and the website.
  • A key/value store to manage inventory (managed by Diagrid).
  • A pub/sub message broker to communicate between the PizzaOrderService and the KitchenService (managed by Diagrid).
  • Diagrid Catalyst that offers serverless APIs for communication, state, and workflow powered by Dapr.

Workflow

The PizzaOrderService contains a Dapr workflow that orchestrates the activities for checking the inventory and communicating with the KitchenService.

Running the Pizza Store locally

Prerequisites

The following services, tools, and frameworks are required for this demo:

All the code for the website, serverless functions, and back-end services is available in the catalyst-pizza-demo GitHub repository.  This repository contains a devcontainer configuration that has the following preinstalled: .NET 8, Node LTS, Vercel CLI, Diagrid CLI. You can use this devcontainer locally in VSCode (requires Docker Desktop) or directly in GitHub Codespaces. The npm install and dotnet build commands described in this article can be skipped if the devcontainer is used.

GitHub

  1. Fork the catalyst-pizza-demo and clone it locally, or use GitHub Codespaces (recommended).

Ably

Ably is used as the serverless realtime messaging component. There is a default Ably app when you sign up for an account that can be used for this demo. Alternatively, you can create a new Ably app.

  1. Log into the Ably portal.
  2. Using the Ably portal: copy the Root API key from the Ably app. This will be copied later as an environment variable for both Vercel and Diagrid.

Vercel

The Vue-based front-end and two JavaScript functions are hosted on Vercel. The Vercel CLI is used to configure and run these resources locally.

  1. Open a terminal in the root of the repository and login with the Vercel CLI:
vercel login
  1. Go to the front-end folder and run:
npm install
  1. Go back to the root of the repository and set up the Vercel project by running:
vercel
  1. An environment variable is used in the getAblyToken function to generate a token for the website to communicate with the Ably realtime service. Add the Ably API token variable by running vercel env add:
  • Variable name: ABLY_API_KEY
  • Variable value: Use the Ably API key obtained from the Ably portal earlier
  • Select Development as the environment.
  1. Another environment variable is used in the placeOrder function to send a request to the PizzaOrderService that will start the workflow. Add the WORKFLOW_URL variable by running vercel env add:
  1. Run vercel pull to pull the configuration from Vercel to your local environment.
  2. Run vercel build to build the website and the serverless functions to ensure there are no errors.

Diagrid Catalyst

Diagrid Catalyst provides serverless Dapr APIs that enables developers to quickly build distributed applications with workflow, pub/sub messaging, service invocation, and state management capabilities. Diagrid also provides managed infrastructure for storing data in a key/value store, pub/sub messaging, and workflow, which are all used in this solution.

The Diagrid CLI is used to configure the resources and run the .NET services locally.

  1. Open another terminal in the root of the repository and use the Diagrid CLI to log in to Diagrid:
diagrid login
  1. Create a new Catalyst project named catalyst-pizza-project and use the Diagrid managed pub/sub broker & KV store, and enable the managed workflow API:
diagrid project create catalyst-pizza-project --deploy-managed-pubsub --deploy-managed-kv --enable-managed-workflow --wait
  1. To set this project as the default in the CLI run:
diagrid project use catalyst-pizza-project
  1. Create a new App ID for the PizzaOrderService:
diagrid appid create pizzaorderservice
  1. Create a new App ID for the KitchenService:
diagrid appid create kitchenservice
  1. Before continuing, check the App ID status to make sure they are ready:
diagrid appid list
  1. Create a pub/sub subscription for the KitchenService to receive messages from the PizzaOrderService:
diagrid subscription create pizzaorderssub --connection pubsub --topic pizza-orders --route /prepare --scopes kitchenservice
  1. Create a pub/sub subscription for the PizzaOrderService to receive messages from the KitchenService:
diagrid subscription create preparedorderssub --connection pubsub --topic prepared-orders --route /workflow/orderPrepared --scopes pizzaorderservice
  1. Verify that creation of the subscriptions is completed:
diagrid subscription list
  1. Run diagrid dev scaffold to create a new local dev environment file. This creates a yaml file, named dev-<PROJECT NAME>.yaml with the following content:
project: catalyst-pizza-project
apps:
- appId: kitchenservice
appPort: 0
env:
	DAPR_API_TOKEN: diagrid://
	DAPR_APP_ID: kitchenservice
	DAPR_GRPC_ENDPOINT: https://
	DAPR_HTTP_ENDPOINT: https://
workDir: kitchenservice
command: []
- appId: pizzaorderservice
appPort: 0
env:
	DAPR_API_TOKEN: diagrid://
	DAPR_APP_ID: pizzaorderservice
	DAPR_GRPC_ENDPOINT: https://
	DAPR_HTTP_ENDPOINT: https://
workDir: pizzaorderservice
command: []
appLogDestination: ""

This file is gitignored since it contains secrets from Diagrid Catalyst. The appPort, command, and workDir attributes still need to be updated as follows:

  • Update the appPort for the kitchenservice to 5066
  • Update the appPort for the pizzaorderservice to 5064.
  • Update the command arguments to ["dotnet", "run"] for both apps.
  • Update the workDir argument to point to back-end/KitchenService and back-end/PizzaOrderService respectively.
  • Update the appLogDestination to console.
  • Add an ABLY_API_KEY environment variable for the pizzaorderservice appId and set the value to the Ably API key obtained from the Ably portal.
  • Save the changes to the file.

Update the key/value store to allow shared state

The two .NET services share the same state in the key/value store to manage inventory and orders. Since this is not the default usage of state stores and services when using Dapr, an attribute needs to be set to enable this. The default behavior is that a key is prefixed with the app ID of the service that is using it. In this case, however, the keyPrefix is set to name to make sure both services use the same keys.

  1. Run the following command to update the managed key/value store:
diagrid connection apply -f ./infra/kv.yml

This will upload the kv.yml file to Diagrid and update the configuration of the kvstore connection so keyPrefix is set to name.

Inspect the DaprClient configuration

The two .NET services use the Dapr .NET client SDK for workflow, state management and pub/sub messaging. The DaprClient is configured to use the endpoints provided by Diagrid Catalyst in the Program.cs file:

var apiToken = Environment.GetEnvironmentVariable("DAPR_API_TOKEN");
var grpcEndpoint = Environment.GetEnvironmentVariable("DAPR_GRPC_ENDPOINT");
var httpEndpoint = Environment.GetEnvironmentVariable("DAPR_HTTP_ENDPOINT");

builder.Services.AddDaprClient(options => {
    options.UseDaprApiToken(apiToken);
    options.UseGrpcEndpoint(grpcEndpoint);
    options.UseHttpEndpoint(httpEndpoint);
});

To use service invocation with the HTTP API, the HttpClient is configured with the DAPR_APP_ID and DAPR_API_TOKEN environment variables:

var appId = Environment.GetEnvironmentVariable("DAPR_APP_ID");
var apiToken = Environment.GetEnvironmentVariable("DAPR_API_TOKEN");

builder.Services.AddHttpClient(
    "daprEndpoint",
    client => {
        client.BaseAddress = new Uri(httpEndpoint);
        client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.Add("dapr-app-id", appId);
        client.DefaultRequestHeaders.Add("dapr-api-token", apiToken);
   });

Running & testing the solution

  1. Open a terminal in the root of the repository.
  2. To restore and build the .NET projects run:
dotnet build ./back-end/PizzaOrderService
dotnet build ./back-end/KitchenService
  1. Run diagrid dev start to start the PizzaOrderService and the KitchenService.
  2. Using another terminal in the root of the repository, run vercel dev to start the website and the serverless functions locally.
  3. Navigate to the URL provided by the Vercel CLI to view the website.
  4. Select some pizzas, place an order, and watch the progress of the workflow in realtime.

Using the Catalyst API explorer

You can use the API explorer in the Catalyst web UI to interact with the APIs. If you want to retrieve the order item from the key/value store that has just been processed, follow these steps:

  1. Open the developer tools console of the browser that is running the demo (a pizza order must be started or completed).
  2. The order is logged to the console as a JSON object. Copy the OrderId property value.
  1. Navigate to the Catalyst web UI. Ensure you're in the catalyst-pizza-project project.
  2. Select App IDs in and click on the API explorer tab.
  • Select the State API.
  • Select the pizzaorderservice as the app ID.
  • Select GET as the API operation.
  • Select kvstore as the state connection.
  • Enter Order-{ORDER-ID} as the key, where {ORDER-ID} is substituted with the value copied from the developer tools console.
  1. Click Send. The response should show the state of the order item with "status": "CompletedPreparation".

What’s next?

Congratulations! You’ve now used the Diagrid Catalyst serverless Dapr APIs for workflow, pub/sub messaging, service invocation, and state management. The ability to use these APIs from anywhere, without the overhead of managing Kubernetes clusters, brings great flexibility to developers on any platform to build distributed applications. The Dapr applications used in this demo can be hosted on any cloud. The demo uses the Diagrid managed infrastructure for key/value storage and pub/sub messaging, but these can be swapped out for other cloud-based resources, similarly to switching open-source Dapr components. You can extend this demo with an alternative pub/sub broker or state store by configuring other infrastructure connections.

Any questions or comments about this demo? Join the Diagrid Community on Discourse and post a message in the Catalyst category. Have you made something with Catalyst? Post a message in the Built with Catalyst category, we love to see your creations!

Diagrid Newsletter

Subscribe today for exclusive Dapr insights