7

Resource Bindings

In this chapter, we will focus on resource bindings in Distributed Application Runtime (Dapr), a convenient and pluggable approach to invoking external systems from Dapr microservices and triggering Dapr applications based on external events.

The following are the main topics that we will cover in this chapter:

  • Learning how to use Dapr bindings
  • Using Twilio output bindings in Dapr
  • Ingesting data with the Azure Event Hubs input binding

Learning about Dapr resource bindings is important in the scope of developing new solutions and improving existing ones. While the publish/subscribe (pub/sub) pattern we explored in Chapter 6, Publish and Subscribe, is helpful in orchestrating asynchronous communication between Dapr applications, the knowledge we get from resource bindings will bring interoperability into our solution.

The very first step in this journey is to learn more about Dapr resource bindings.

Technical requirements

The code for this chapter can be found on GitHub at https://github.com/PacktPublishing/Practical-Microservices-with-Dapr-and-.NET-Second-Edition/tree/main/chapter07.

In this chapter, the working area for scripts and code is expected to be <repository path>chapter07. In my local environment, it is C:Repospractical-daprchapter07.

Please refer to the Setting up Dapr section in Chapter 1, Introducing Dapr, for a complete guide to the tools needed to develop with Dapr and work with the samples.

Learning how to use Dapr bindings

In previous chapters, we devoted most of our attention to the Dapr architecture, and how to use its building blocks to facilitate communication between microservices in the context of the Dapr environment.

With Dapr’s service-to-service building blocks, we can directly invoke another microservice as the Dapr runtime takes care of routing requests to their destination and handling retries, among other benefits.

By managing the state, Dapr lifts from our microservice the responsibility of maintaining the plumbing code and the libraries necessary to interact with a persistence layer.

By supporting the pub/sub pattern, Dapr enables microservices to communicate in a loosely coupled fashion and allows our overall architecture to grow in complexity, minimizing the impact on existing portions of code.

All these building blocks focus inward on our microservices, although often, the architecture is not completely isolated as there is a need to reach external systems outside of our microservices, whether it is to receive data, react to external events, or communicate events.

The Dapr bindings building block can be used as an output binding, to invoke an external resource to communicate an event or as an input binding, to trigger our Dapr applications based on an external event.

As an example, these are a few of the currently available output bindings, allowing a Dapr application to execute a specific action on a resource:

  • HyperText Transfer Protocol (HTTP)
  • Kafka
  • Message Queuing Telemetry Transport (MQTT)
  • RabbitMQ
  • Twilio
  • Microsoft Azure: Blob storage, Event Hubs, Cosmos DB, Azure Service Bus (ASB), SignalR, Queue Storage, and Event Grid
  • Amazon Web Services (AWS): Simple Storage Service (S3), Simple Queue Service (SQS), and Simple Notification Service (SNS)
  • Google Cloud Platform (GCP): Cloud Pub/Sub and Storage Buckets

Some of the available input bindings, allowing a Dapr application to be triggered based on an event raised by the resource, are listed here:

  • cron
  • Kafka
  • MQTT
  • RabbitMQ
  • Azure: Event Hubs, ASB, Queue Storage, and Event Grid
  • Amazon: SQ and Kinesis
  • GCP: Cloud Pub/Sub

For a complete list of Dapr input and output (I/O) bindings, please check the Dapr documentation at https://docs.dapr.io/reference/components-reference/supported-bindings/.

To use a binding in Dapr, it must first be configured as a component. Let’s see how to configure one of the simplest blocks: a cron input binding.

Configuring a cron input binding

In a local development environment, the .yaml files must be located in the dapr run --app-id "<application>" --components-path "./components" folder specified in the Dapr command-line interface (CLI). Each Dapr application could have a different path but, as some components are used by several Dapr applications in this book’s samples, I will keep all .yaml file components in a common folder at the solution level, for simplicity.

A cron binding adopts the following configuration:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: cron
  namespace: default
spec:
  type: bindings.cron
  version: v1
  metadata:
  - name: schedule
    value: "@every 10s"

More information can be found in the Dapr documentation for bindings at https://docs.dapr.io/developing-applications/building-blocks/bindings/bindings-overview.

The relevant settings for the configuration of this component of the bindings.cron type are name and schedule. With an input binding, the configured name will be used by the Dapr runtime to invoke, as POST, the corresponding route at the application endpoint, with the frequency defined in the schedule.

In other input bindings, the trigger will correspond to the arrival of a message or event.

Testing the cron binding

From an ASP.NET perspective, we need to implement a method with the cron route in the ASP.NET controller; this is an example of an addition to the shipping-service Dapr project. Here’s the code to do this:

[HttpPost("cron")]
public async Task<IActionResult> Cron()
{
    Console.WriteLine($"Cron @{DateTime.Now.ToString()} ");
    return new OkResult();
}

As shown in the preceding code snippet, I am not considering the request payload: the intent with the cron input binding is just to schedule a recurring request.

By returning a successful result, the ASP.NET controller informs the Dapr runtime that the operation has been completed. It could not be simpler than this.

In the next section, we will learn how to configure and use a more sophisticated output binding.

Using Twilio output bindings in Dapr

An output binding enables our microservice to actively interact with an external system or service without having to deal with software development kits (SDKs), libraries, or application programming interfaces (APIs) other than the Dapr API. In our C# sample, we will use the Dapr .NET SDK to abstract this interaction.

In the previous chapter, Chapter 6, Publish and Subscribe, we introduced the shipping-service project: this Dapr application subscribes to the OnOrder_Prepared topic to be informed once all the steps in the order-preparation saga reach a positive conclusion.

We intend to increase the functionality of this microservice by informing the customer that the order is shipped. To do so, we can leverage a notification service such as Twilio to send the customer a Short Message Service (SMS) message, as follows:

Figure 7.1 – Twilio output binding added to the shipping service

Figure 7.1 – Twilio output binding added to the shipping service

In Figure 7.1, you can see the evolution of the shipping-service Dapr service: an output resource binding of the Twilio type is being adopted.

We will start working on the binding by following the following four simple steps:

  1. Signing up for a Twilio trial
  2. Configuring a Twilio output binding
  3. Signaling via the output binding
  4. Verifying the notification

We will begin with the first step and sign up for a Twilio trial.

Signing up for a Twilio trial

The first step is to sign up for a Twilio trial. You can request a Twilio free trial at https://www.twilio.com/. As we are going to send a text message to the customer in our sample, you will need to register a valid phone number. I registered my own mobile number for this purpose.

Important note

Be aware of the Twilio limitations for a free trial, which you can find here: https://support.twilio.com/hc/en-us/articles/360036052753-Twilio-Free-Trial-Limitations. As an example, you can send text messages only to a validated phone number, and the message will start with some default text.

Once you have an account and an active project, there are two strings you need to collect from the Twilio page, as illustrated in the following screenshot:

Figure 7.2 – Twilio account security identifier (SID) and authentication (auth) token

Figure 7.2 – Twilio account security identifier (SID) and authentication (auth) token

ACCOUNT SID and AUTH TOKEN from Figure 7.2 are the configuration strings that will be used to configure the Twilio binding in Dapr.

Configuring a Twilio output binding

The Twilio binding specification details are available in the Dapr documentation repository at https://docs.dapr.io/operations/components/setup-bindings/supported-bindings/twilio/: we have to create a configuration file to access Twilio with our account and credentials. Here is the content of the components wilio.yaml file:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: twilio
spec:
  type: bindings.twilio.sms
  version: v1
  metadata:
  - name: fromNumber # required.
    value: <omitted>
  - name: accountSid # required.
    value: <omitted>
  - name: authToken # required.
    value: <omitted>

Examining the previous configuration file, in the output binding of type bindings.twilio.sms, I did not specify the toNumber metadata key; this will be influenced by our code.

The accountSID and authToken keys must be set to the values we gathered from the Twilio web portal.

Next, we will let the application know when a text message should be sent.

Signaling via the output binding

We have to launch our Dapr application and trigger the subscription by sending a test message.

We can use the Dapr CLI to launch shipping-service, as follows:

dapr run --app-id "shipping-service" --app-port "5005" --dapr-grpc-port "50050" --dapr-http-port "5050" --components-path "./components" -- dotnet run --project ./sample.microservice.shipping/sample.microservice.shipping.csproj --urls=http://+:5005

In the Dapr output logs, we should see an acknowledgment of the binding sent by the runtime, as follows:

== DAPR == … level=info msg="found component twilio (bindings.twilio.sms)" app_id
=shipping-service instance=DB-XYZ scope=dapr.runtime type=log ver=1.8.0

The previous Dapr log trace should be followed by a similar output, which we can see here:

== DAPR == … level=info msg="successful init for output binding twilio (bindings.twilio.sms)" app_id=shipping-service instance=DB-XYZ scope=dapr.runtime type=log ver=1.8.0

Let’s explore how we can use the output binding in the C# code of our ASP.NET ShippingController.cs controller file, as follows:

[Topic(PubSub, Topics.OrderPreparedTopicName)]
[HttpPost(Topics.OrderPreparedTopicName)]
public async Task<ActionResult<Guid>> ship(Shipment
orderShipment, [FromServices] DaprClient daprClient)
{
     var state = await daprClient.GetStateEntryAsync
        <ShippingState>(StoreName, orderShipment.OrderId.ToString());
     state.Value ??= new ShippingState() {OrderId =
      orderShipment.OrderId, ShipmentId = Guid.NewGuid() };
     await state.SaveAsync();
      // return shipment Id
      var result = state.Value.ShipmentId;
      Console.WriteLine($"Shipment of orderId
      {orderShipment.OrderId} completed with id {result}");
      var metadata = new Dictionary<string,string>();
      metadata.Add("toNumber","<omitted>");
      await daprClient.InvokeBindingAsync<string>
      ("twilio","create", $"Dear customer, your order with
      {orderShipment.OrderId} completed and shipped",
        metadata);
      Console.WriteLine($"Shipment of orderId
      {orderShipment.OrderId} notified to customer");
       return result;
}

The instance of DaprClient we run in the ASP.NET controller gives us access to the daprClient.InvokeBindingAsync method. The metadata parameter is a key-value dictionary that can be used to influence the metadata configured in component.yaml: if you remember, we did not specify the toNumber key as it is the microservice’s responsibility to gather it from the order (or from another microservice managing the customer data).

The first and second parameters specify the twilio name of the configured binding and the intended create operation, among those supported by the binding.

We can simulate a message via the Dapr CLI with the dapr publish command, as follows:

dapr publish --pubsub commonpubsub -t OnOrder_Prepared -d
'"{"OrderId": "08ec11cc-7591-4702-bb4d-7e86787b64fe"}"'

From the shipping-service output, we see the message has been received and the shipping has been completed.

All went fine with our code, as the Dapr runtime responded positively to our request. We just need to notify the customer of this, as the last step.

Verifying the notification

The Dapr output binding allows us to interact with an external system. Aside from positive feedback (no exceptions) from the Dapr API, there is only one other thing we can do to verify the process completion: check our phone for text messages! The following screenshot shows this being done:

Figure 7.3 – Twilio SMS received

Figure 7.3 – Twilio SMS received

As you see from Figure 7.3, we received the notification sent from shipping-service via Twilio.

In this sample, we triggered a Dapr application via pub/sub, which in turn signaled an event via the output binding. We could also leverage the runtime endpoint to test it directly, as follows:

POST http://localhost:5050/v1.0/bindings/twilio HTTP/1.1
content-type: application/json
{
    "data": "cookies ready from Dapr",
    "metadata": {
        "toNumber": "<omitted>"
    },
    "operation": "create"
}

Consistent with the previous use of the .NET SDK, toNumber is set as a value in metadata, and operation is set to create.

We have completed our first sample with the Dapr output binding. In the next section, we will understand how to use the Dapr binding to trigger our microservices.

Ingesting data with the Azure Event Hubs input binding

In a previous section of this chapter, we learned how to implement a simple input binding thanks to the cron sample. In this section, we will explore another input binding, leveraging the Azure Event Hubs cloud messaging service, by implementing it in the context of reservation-service.

The responsibility of reservation-service is to allocate quantities of a certain product (cookies) as a new order comes in. In this context, we never considered that if there is a process to reserve (and therefore subtract) quantities, then there should be an equivalent—but opposite—process to increment the available quantity. This is our chance to fix the business logic of our sample.

In the context of our sample’s cookie-selling e-commerce site, let’s suppose there is an external service overseeing the manufacturing process, which produces cookies to be sold and/or customized according to customers’ requests, depending on forecasts and short-term incoming orders. This manufacturing service is not going to participate with other microservices via Dapr: the only link between the two subsystems is via a stream of events through an Azure Event Hubs channel, as illustrated in the following diagram:

Figure 7.4 – Manufacturing service interaction with reservation

Figure 7.4 – Manufacturing service interaction with reservation

As seen in Figure 7.4, the overall context of the saga, influencing the communication pattern for the requests and compensating the transactions, will be affected by additional data coming from an external subsystem, via a Dapr input binding, making it all the more important to orchestrate messaging.

This is what we will implement in the following sections.

Creating an Azure Event Hubs binding

In order to configure an Azure Event Hubs input binding, we first have to provision it in Azure.

First, we create an Azure Event Hubs namespace and an event hub. Detailed step-by-step instructions on how to provision these resources on Azure can be found at https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-create.

The following screenshot from the Azure portal shows the result:

Figure 7.5 – Azure Event Hubs policy

Figure 7.5 – Azure Event Hubs policy

As you can see in Figure 7.5, I created an Azure Event Hubs daprbindingehdb namespace and a reservationrefill event hub, and I configured an access policy with Listen claims.

The access policy with Listen rights should be enough for an input binding; it would have needed Send rights to be used with an output binding instead.

Finally, we need to create an Azure storage account; this resource will be used by the Dapr input binding implementation for Event Hubs to keep track of the offset, the point reached in reading events. Please refer to the step-by-step instructions at https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal.

In the following screenshot, you can see that a storage account has been created from the portal:

Figure 7.6 – Storage account for the Event Hubs processor host

Figure 7.6 – Storage account for the Event Hubs processor host

In Figure 7.6, you can see that I created an Azure storage account and obtained the connection string.

This information will be used in the next steps to configure the Dapr component.

Configuring the input binding

Considering the resources that we previously created, the following file in componentsinding-eh.yaml is needed to instruct Dapr to activate an input binding:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: reservationinput
  namespace: default
spec:
  type: bindings.azure.eventhubs
  version: v1
  metadata:
  - name: connectionString      # Azure EventHubs connection
    string
    value: "<omitted>"
  - name: consumerGroup         # EventHubs consumer group
    value: "group1"
  - name: storageAccountName    # Azure Storage Account Name
    value: "<omitted>"
  - name: storageAccountKey     # Azure Storage Account Key
    value: "<omitted>"
  - name: storageContainerName  # Azure Storage Container Name
    value: "inputbinding"

The Azure Event Hubs specification as a binding is available at https://docs.dapr.io/operations/components/setup-bindings/supported-bindings/eventhubs/.

As you can note from the preceding configuration, an Azure Storage account is also required to persist Event Hubs checkpoint data.

Dapr is now ready to receive messages (events, in this case). Let’s focus on the input binding.

Implementing an Azure Event Hubs input binding

In reservation-service, we implement a new method to receive events via the input binding.

As we learned in previous sections, the name of the ASP.NET route must match the name configured in the Dapr component. In the following code snippet from the sample.microservice.reservationControllersReservationController.cs file, you can see that the attribute reflects the same reservationinput component name:

[HttpPost("reservationinput")]
public async Task<IActionResult> Refill([FromServices]
  DaprClient daprClient)
{
     using (var reader = new
      System.IO.StreamReader(Request.Body))
     {
          … omitted …
          var stateItem = await daprClient.
            GetStateEntryAsync<ItemState>(StoreName_item,
            SKU);
          stateItem.Value ??= new ItemState() { SKU =
            SKU, Changes = new List<ItemReservation>() };
          stateItem.Value.BalanceQuantity += Quantity;
          await stateItem.SaveAsync();
          … omitted …
      }
      return new OkResult();
}

The method signature we use in this case is slightly different from that in the previous sample. We need to interact with the Dapr infrastructure to gather our microservice state; therefore, daprClient is now a parameter.

In the preceding code, we are making many assumptions, such as the payload of the messages being JavaScript Object Notation (JSON) and with a specific schema, to keep this exercise simple.

Specific to Dapr, as the event we receive is intended to contain information for a single item, we retrieve the state via the .NET SDK, update the balance quantity, and subsequently save it back to the state store.

Our Dapr reservation-service application is ready to receive and process events.

Producing events

Using the Azure Event Hubs documentation, available at https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/eventhub/Azure.Messaging.EventHubs/README.md#publish-events-to-an-event-hub, as a starting point, we can create a C# console project to simulate the output of the manufacturing service: a continuous stream of new cookies coming out of the ovens is signaled via Azure Event Hubs to the Dapr input binding.

You can see the overall code in Program.cs of the Console project folder. It continuously sends a refill of a random selection of a cookie’s stock-keeping unit (SKU) to Event Hubs, which in turn is used as an input binding to reservation-service.

We can launch the console via the command line, passing connectionString as a parameter, as shown in the following snippet:

PS C:Repospractical-daprchapter07> dotnet run --project .
Consolechapter07.csproj "… omitted …"
Started sender
Sent batch @ 22:58:51
Sent batch @ 22:58:53
…

The event producer starts to send batches of events to the Azure Event Hubs resource.

Important note

While working with Azure messaging services such as ASB and Azure Event Hubs, it is highly recommended to install the Azure Service Bus Explorer suite by Paolo Salvatori; you can find out more at https://github.com/paolosalvatori/ServiceBusExplorer. Although some of the features offered by this powerful tool have been included in the Azure portal, Service Bus Explorer continues to be the best tool for anyone developing with the Azure messaging stack.

Let’s relaunch the reservation-service Dapr application so that our newest input binding can be invoked as simply as an ASP.NET call by the Dapr sidecar:

PS C:Repospractical-daprchapter07> dapr run --app-
id "reservation-service" --app-port "5002" --dapr-grpc-
port "50020" --dapr-http-port "5020" --components-path "./
components" -- dotnet run --project ./sample.microservice.
reservation/sample.microservice.reservation.csproj
--urls="http://+:5002"

From the reservation-service output, we can verify that the input binding is receiving events via Dapr from an external subsystem, as follows:

== APP == Refill of crazycookie for quantity 1, new balance 44
== APP == Refill of bussola1 for quantity 1, new balance 160
… omitted …

This step concludes the implementation of the input binding in our microservice.

Summary

In this chapter, we explored the I/O binding building blocks of Dapr, and we learned how, with Twilio output binding, we can notify customers via text message without having to deal with libraries, SDKs, and the plumbing code, as it all boils down to a simple call to the Dapr runtime.

We then established a communication channel between a microservice of our sample e-commerce architecture with an external subsystem; both are unaware of each other’s implementation details, and our microservice is unaware of how to interact with Azure Event Hubs as the messaging bus.

The reservation-service application is placed at the center of our sample architecture, unlike the other microservices.

As a point of attention, the sample code doesn’t deal with application-level retries, which could be relevant if the strong consistency of state management and an elevated request rate prevent a reservation from always completing nicely. While this condition should be addressed with more solid code, it does help to expose a case of potential stress in the application, which you might want to tackle as a side exercise.

In the next chapter, we will discover how we can tackle this scenario of high-frequency access to several small, independent state and logic units by introducing Dapr actors.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.142.230