A DotNet Raider

My adventures in the .NET world!
posts - 49, comments - 12, trackbacks - 0

My Links

News

Website View Martino Bordin's profile on LinkedIn

Archives

Post Categories

mercoledì 17 aprile 2024

Dapr - The end

Throughout this series of articles, we have explored the various features and abstractions provided by Dapr, such as service invocation, pub/sub, state management, configurations, secrets, resiliency, and observability. However, it's important to note that there are even more capabilities within Dapr that we haven't covered, including actors, workflow management, distributed locks, and hosting.

Given the extensive nature of Dapr and its continuous growth, it's challenging to capture every aspect in a limited series of articles. To gain a deeper understanding, I encourage you to explore the official documentation, read relevant books, and explore additional online resources like GitHub projects and YouTube channels dedicated to Dapr. These resources can provide you with in-depth insights into Dapr's features and practical implementations.

Before diving into Dapr and incorporating it into your projects, it's crucial to have a solid understanding of microservices and distributed architecture in general. Consider the trade-offs involved and evaluate whether this architectural approach aligns with your business needs. Assess your team's technical skills and expertise, as both development and infrastructure aspects can become complex when working with distributed systems.

In terms of implementation, it's often beneficial to adopt a "Monolith First" approach, where you start with a monolithic architecture or, better, with a modular monolith. This approach allows you to experience the benefits of modularization and gain insights into the specific areas that can benefit from microservices. Eventually, you can decompose the monolith into a full-fledged microservices solution, leveraging the power of Dapr to simplify the development and management of your distributed system.

Thank you for following along with this series, and best of luck on your Dapr journey!

The end

posted @ mercoledì 17 aprile 2024 09:37 | Feedback (0) | Filed Under [ Dapr Microservices ]

lunedì 25 marzo 2024

Dapr - Observability

Monitoring a distributed system is important to identify potential failures, security issues, bottlenecks and in general to understand how our application is performing to apply remediations (that could be scaling or redesigning some parts). In a distributed system, with several interconnected components, a dynamic environment, and unreliable networks, this becomes challenging.

Dapr, with his runtime running as a "sidecar", is the central point where the instrumentation of monitoring takes place to have distributed tracing, logging, metrics, and health check; as everything in Dapr, we can just configure these features using a YAML file.

In our sample, we'll use Zipkin to collect tracing information, just by specifying the Zipkin endpoint and the sampling rate (0= disable trace, 1 trace all)

apiVersion: dapr.io/v1alpha
kind: Configuration
metadata:   name: daprConfig
spec:   tracing:     samplingRate: "1"     zipkin:       endpointAddress: "http://zipkin:9411/api/v2/spans"1

In the Zipkin UI, we can see both traces and services dependencies

No alt text provided for this image

No alt text provided for this image

Dapr can produce structured logs in plain text (the default, that also sends it to stdout) or JSON (better for storing them in external services like ELK).

No alt text provided for this image

We can also use Open Telemetry Collector, Jaeger, and other tools like New Relic and App Insights.

See you in the next article for the last one of this series.

posted @ lunedì 25 marzo 2024 10:46 | Feedback (0) | Filed Under [ Dapr Microservices ]

lunedì 5 febbraio 2024

Dapr - Resiliency

As we know, in a distributed system we have to take into account more complexity and deal with its fallacies. Since several moving parts could potentially be broken or be unreachable, we must design upfront our application with resiliency in mind, to minimize the impact of bad events and continue operating.

Dapr gives us out-of-the-box features that allow our applications to handle temporary failures.

All we have to do is to create a YAML file where we define policies and targets.

Policies are how we want to handle errors:

  • Timeout,  to terminate operations after defined intervals
  • Circuit breaking, to 'open' the circuit when there are several errors in order not to flood the system and give it time to restart the failing service
  • Retry, to retry failed operations

Targets are where we want to apply such policies:

  • Apps, our services (defined in docker-compose in our sample)
  • Components, the different building blocks of Dapr
  • Actors, the actor operations (not used in our sample)

I won't post the full YAML specification in this article, but I prepared a full example with comments in the GitHub repository.

Check it out here.

Once done, we configure the Dapr sidecar to load the resiliency configuration using the -resources-path argument.

In the next article, we'll see observability in Dapr

posted @ lunedì 5 febbraio 2024 10:27 | Feedback (0) | Filed Under [ Dapr Microservices ]

venerdì 12 gennaio 2024

Dapr - Secret management

Dapr Secrets management is a feature that provides a secure and scalable way to manage application secrets, such as API keys, passwords, and tokens. You can use different cloud providers and environments such as Azure Key Vault, AWS Secrets Manager, HashiCorp Vault, or Kubernetes Secrets.

In our example, we'll use a JSON file (not recommended in production!) to store the following secrets:

"httpbindingtoken": "my auth token",   "ConnectionStrings": {     "SqlServer": "Server=sqlserver;Database=CustomersDb;User Id=sa;Password=admin12345!;TrustServerCertificate=true",     "PostgreSQL": "Server=postgres;Port=5432;Database=ProductsDb;User Id=admin;Password=admin12345;",     "MongoDb": "mongodb://mongo:27017"   },   "MongoDb": {     "DatabaseName": "OrderDb",     "OrdersCollectionName": "Orders"   }
}

We have two way to retrieve them:

  • declaratively, in the components spec files
  • programmatically, using the Dapr APIs

We had a glimpse of the first approach in the previous article, where we configured the Http Output Binding component.

Quick recall:

apiVersion: dapr.io/v1alph
kind: Component
metadata:
  name: httpbinding
spec:
  type: bindings.http
  version: v1
  metadata:
  - name: url
    value: http://echorestbot.azurewebsites.net/microdelivery
  - name: securityToken
    secretKeyRef:
      name: httpbindingtoken
      key: httpbindingtoken
  - name: securityTokenHeader
    value: "Authorization"
auth:
  secretStore: localsecretstore

Here we are configuring some metadata and you may notice that for the item securityToken instead of directly inserting the value, we are declaring that we want to use the key httpbindingtoken retrieve in localsecretstore.

Localsecretstore is the name of the component for secret management, configured as usual in the YAML file

apiVersion: dapr.io/v1alpha
kind: Component
metadata:   name: localsecretstore
spec:   type: secretstores.local.file   version: v1   metadata:   - name: secretsFile     value: components/secrets.json   - name: nestedSeparator     value: ":"

Here we are basically declaring where we store the secrets (in the file secrets.json) and what is the separator character for nested configuration (colon is the default).

To use it programmatically, we have to add the Nuget package Dapr.Extensions.Configuration in our project and register it with the following lines of code

var builder = WebApplication.CreateBuilder(args)
builder.Configuration.AddDaprSecretStore(
"localsecretstore", 
new DaprClientBuilder().Build(), 
new[] { ":" });

Once done, we can access our secret simply using the standard IConfiguration.

public OrdersRepository(IConfiguration configuration
{
   var clientSettings = MongoClientSettings
                           .FromConnectionString(configuration
                           .GetConnectionString("MongoDb"));    var client = new MongoClient(clientSettings);    var database = client
                       .GetDatabase(configuration
                           .GetValue<string>("MongoDb:DatabaseName"));

   this.orders = database.GetCollection<Order>(configuration
                           .GetValue<string>("MongoDb:OrdersCollectionName"));
})

That's it!

In the next article, we'll see how to leverage the resiliency feature of Dapr.

posted @ venerdì 12 gennaio 2024 15:29 | Feedback (0) | Filed Under [ Dapr Microservices ]

mercoledì 6 dicembre 2023

Dapr - Pub/sub & Output Binding

In the last article, once created an order we published a message, called OrderSubmittedIntegrationEvent with all the relevant information.

How can we subscribe to this event in other services?

In Dapr there are two ways:

  • declaratively, where subscriptions are defined in an external file
  • programmatically, where subscriptions are defined in our code

We're going to use the second method in the Notifications and Shipping microservices along with the Output binding building block

What is an Output binding? It's a way to invoke external resources just by passing a payload and additional metadata.

Let's see them in action!

No alt text provided for this image

Notifications microservice

The notification microservice will have just one method that is going to use the SMTP output binding to send an email to inform the customer that the order has been confirmed and what discount has been applied.

Here is the stripped version of the code, check GitHub for the complete one.

[HttpPost
[Topic("rabbitmqpubsub", "OrderSubmittedEventTopic")]
public async Task<ActionResult> OnOrderSubmittedEventAsync(
    OrderSubmittedIntegrationEvent orderSubmittedIntegrationEvent)
{
	var stringBuilder = new StringBuilder();
	stringBuilder.AppendLine($"Hello {orderSubmittedIntegrationEvent.CustomerFirstName} {orderSubmittedIntegrationEvent.CustomerLastName}<br>");
	stringBuilder.AppendLine($"Your order <strong>#{orderSubmittedIntegrationEvent.OrderId.ToString()[..6]}</strong> has been shipped.<br><br>");
	stringBuilder.AppendLine($"Your CRAZY DISCOUNT is <strong>#{orderSubmittedIntegrationEvent.TotalDiscount}%</strong>!<br><br>");

	var message = stringBuilder.ToString();
	var metadata = new Dictionary<string, string>
	{
		{ "emailTo", orderSubmittedIntegrationEvent.CustomerEmail },
		{ "subject", $"Order Shipped!" },
		{ "priority", "1" }
	};
	await this.daprClient
                        .InvokeBindingAsync(
                           "smtpbinding", 
                           "create", 
                           message, 
                           metadata);

	return Ok();
}]

The first thing you can notice is that we're using the Topic attribute to subscribe to the topic OrderSubmittedEventTopic on the rabbitmqpubsub component.

Dapr is going to invoke this method once a new message is published to that topic, and we receive the payload as a parameter (OrderSubmittedIntegrationEvent). We then use the payload to create an email and send it using the InvokeBindingAsync, passing as a parameter the name of the binding component (smtpbinding), the body of the email, and some metadata (subject, mail address, priority).

Of course, we have to configure the binding in the YAML specification:

apiVersion: dapr.io/v1alpha
kind: Component
metadata:   name: smtpbinding
spec:   type: bindings.smtp   version: v1   metadata:   - name: host     value: "smtp4dev"   - name: port     value: "25"   - name: emailFrom     value: "no-reply@microdelivery.com"

For testing purposes, I'm using an email server called smtp4dev hosted in docker to send and check emails.

No alt text provided for this image

The smtp4dev User Interface

Now let's see the shippings service.

No alt text provided for this image

Shipping microservice

All the logic is in the method OnOrderSubmittedEvent, where we're subscribing to the topic OrderSubmittedEventTopic, and we're performing an HTTP request using the HTTP Output Binding (to simulate a call to an external supplier). Once done, we publish the OrderShippedIntegrationEvent that will be handled by the Orders microservice to mark the order as shipped.

[HttpPost
[Topic("rabbitmqpubsub", "OrderSubmittedEventTopic")]
public async Task<ActionResult> OnOrderSubmittedEvent(
   OrderSubmittedIntegrationEvent orderSubmittedIntegrationEvent)
{
	await this.daprClient.InvokeBindingAsync(
                                    "httpbinding", 
                                    "post", 
                                     orderSubmittedIntegrationEvent);


	var orderShippedIntegrationEvent = new OrderShippedIntegrationEvent() 
                      { 
                        OrderId = orderSubmittedIntegrationEvent.OrderId, 
                        ShippedAtUtc = DateTime.UtcNow 
                      };

	await daprClient.PublishEventAsync(
                                        "rabbitmqpubsub", 
                                        "OrderShippedEventTopic", 
                                        orderShippedIntegrationEvent);

	return Ok();
}]

As in the notifications microservice, we use the InvokeBindingAsync, this time to call the HTTP endpoint, passing the orderSubmittedIntegrationEvent as payload.

The binding is configured with the following YAML specification:

apiVersion: dapr.io/v1alpha
kind: Component
metadata:
  name: httpbinding
spec:
  type: bindings.http
  version: v1
  metadata:
  - name: url
    value: http://echorestbot.azurewebsites.net/microdelivery
  - name: securityToken
    secretKeyRef:
      name: httpbindingtoken
      key: httpbindingtoken
  - name: securityTokenHeader
    value: "Authorization"
auth:
  secretStore: localsecretstore

Here you see that we want to perform a POST request to the URL http://echorestbot.azurewebsites.net/microdelivery (it's a simple API I built that gives you back the request details). We are also passing a token in the "Authorization" header; the value of the token is read by using the secret management build block (more info in the next article).

Here is an example of the request we made to the EchoRest endpoint, where you can see the payload containing the order's detail:

{   "RequestIdentifier": "14085696-218e-42f2-8966-7a0b37b506ea",   "Date": "2023-05-01T20:02:07.2657896Z",   "Url": "http://echorestbot.azurewebsites.net/microdelivery",   "Body": "{\"orderId\":\"307998cc-53c9-46cf-9dca-06df25419496\",\"customerId\":1,\"customerFirstName\":\"Joe\",\"customerLastName\":\"Doe\",\"customerEmail\":\"joe.doe@email.com\",\"orderLineItems\":[{\"productId\":1,\"productName\":\"Tomato salad\",\"quantity\":3,\"price\":8,\"discountedPrice\":8},{\"productId\":2,\"productName\":\"Margherita\",\"quantity\":3,\"price\":6,\"discountedPrice\":6}],\"totalDiscount\":0,\"eventId\":\"823132ef-197a-4fc3-8da4-175966bae8dc\"}",   "Headers": [     {       "Key": "Accept",       "Value": "application/json; charset=utf-8"     },     {       "Key": "Host",       "Value": "echorestbot.azurewebsites.net"     },        {       "Key": "Authorization",       "Value": "my auth token"     },     {       "Key": "Content-Type",       "Value": "application/json; charset=utf-8"     },     {       "Key": "Content-Length",       "Value": "412"     }          ],   "Method": "POST",   "Path": {     "Value": "/microdelivery",     "HasValue": true   }
}

After calling the sample endpoint, we're going to publish an OrderShippedIntegrationEvent that will be handled by the Order's microservice to update the order, just subscribing to that event using the Topic attribute we already saw.

[HttpPost("Ship")
[Topic("rabbitmqpubsub", "OrderShippedEventTopic")]
public async Task<ActionResult> OnOrderShippedEvent(OrderShippedIntegrationEvent orderShippedIntegrationEvent)
{
	var order = await this.orderRepository

                     .GetOrderAsync(orderShippedIntegrationEvent.OrderId);
	if (order is null)
	{
		return NotFound();
	}

	order.ShippedAtUtc = orderShippedIntegrationEvent.ShippedAtUtc;
	await this.orderRepository.UpdateOrderAsync(order);

	return Ok();
}]

In the next article, we'll discuss about secret management with DAPR.

posted @ mercoledì 6 dicembre 2023 16:53 | Feedback (0) | Filed Under [ Dapr Microservices ]

sabato 4 novembre 2023

Dapr - Service invocation & Pub/sub

Dapr service discovery & invocation is a feature that allows services to discover and call other services using HTTP or gRPC protocols, providing a simple and consistent way to invoke them.

Communication is also

  • secure (with mutual mTLS authentication)
  • resilient (with configurable retry\circuit breaker\timeout policies)
  • traced and metered (using common protocols)
  • controlled (we can use ACL to restrict accesses\permissions to some APIs)
  • balanced (using round-robin)

The pub/sub is a messaging pattern where a message is published by a sender to a topic, and all the subscribers who have subscribed to that topic will receive a copy of the message. This allows loosely coupled communication between services, as the sender does not need to know who the receivers are or how many there are, and the subscribers do not need to know who the publisher is.

The pub/sub API in DAPR provides a set of APIs that can be used to implement this pattern and allows developers to use different message brokers like Apache Kafka, RabbitMQ, Azure Service Bus, and many others. The API is platform-agnostic and offers an at least-once message delivery guarantee.

Let's see how we can use both functionalities in the orders microservices.

No alt text provided for this image

As always, we have a standard API controller where we inject the DaprClient.

The relevant code is in the SubmitOrder.

Essentially, what we want to do is:

  • retrieve customer info (calling the customer's microservice)
  • apply the current valid discount (calling the discount microservice)
  • retrieve products info
  • create and save a new order
  • publish a message OrderSubmittedIntegrationEvent

[HttpPost("Submit")
[ProducesResponseType(typeof(Order), (int)HttpStatusCode.Created)]
public async Task<ActionResult> SubmitOrder(SubmitOrderRequest request)
{
	var customerInfo = await daprClient
                               .InvokeMethodAsync<CustomerInfo>(
                                HttpMethod.Get,
                                "microdelivery-customers-api", 
                                $"customers/{request.CustomerId}");

	if (customerInfo is null)
	{
		throw new Exception($"Customer {request.CustomerId} not found");
	}

	var discount = await daprClient
                           .InvokeMethodAsync<int>(
                            HttpMethod.Get, 
                            "microdelivery-discount-api", 
                            "discount");
	var order = new Order
	{
		CustomerId = customerInfo.Id,
		CustomerFirstName = customerInfo.FirstName,
		CustomerLastName = customerInfo.LastName,
		CustomerEmail = customerInfo.Email,
		TotalDiscount = discount
	};
	var orderLineItems = new List<OrderLineItem>();
	foreach (var requestOrderLineItem in request.OrderLineItems)
	{
		var productInfo = await daprClient
                                  .InvokeMethodAsync<ProductInfo>(
                                  HttpMethod.Get, 
                                  "microdelivery-products-api", 
                                  $"products/{requestOrderLineItem.ProductId}");
		if (productInfo is null)
		{
			throw new Exception($"Product {requestOrderLineItem.ProductId} not found");
		}

		var discountedPrice = discount == 0 ? productInfo.Price : productInfo.Price - (productInfo.Price * ((double)discount / 100));
		var orderLineItem = new OrderLineItem
		{
			ProductId = productInfo.Id,
			ProductName = productInfo.Name,
			Price = productInfo.Price,
			DiscountedPrice = discountedPrice,
			Quantity = requestOrderLineItem.Quantity
		};
		orderLineItems.Add(orderLineItem);
	}
	order.OrderLineItems = orderLineItems;
	
    await orderRepository.CreateOrderAsync(order);

	var orderSubmittedIntegrationEvent = new OrderSubmittedIntegrationEvent
	{
		OrderId = order.Id,
		CustomerId = order.CustomerId,
		CustomerFirstName = order.CustomerFirstName,
		CustomerLastName = order.CustomerLastName,
		CustomerEmail = order.CustomerEmail,
		TotalDiscount = order.TotalDiscount,
		OrderLineItems = order.OrderLineItems.Select(oli => 
         new OrderSubmittedIntegrationEventLineItem 
         { 
          ProductId = oli.ProductId, 
          ProductName = oli.ProductName, 
          Quantity = oli.Quantity, 
          Price = oli.Price, 
          DiscountedPrice = oli.DiscountedPrice 
         })
	};

    await daprClient.PublishEventAsync(
       "rabbitmqpubsub", 
       "OrderSubmittedEventTopic", 
       orderSubmittedIntegrationEvent);

	return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}

For the service invocation, we use the method InvokeMethodAsync<T> of the DaprClient.

We have to specify the HTTP Verb to use, the application id of the other service (we set it on the sidecar configuration in the docker-compose file), and the method name. If the call is successful, we obtain the response of type T. That's simple.

To publish a message, we use the method PublishEventAsync<T> of the DaprClient.

We have to specify the name of the pubsub component, the topic where we want to publish the message, and the message payload.

In our example, we're going to use RabbitMq, so here is the component specification:

apiVersion: dapr.io/v1alpha
kind: Component
metadata:   name: rabbitmqpubsub
spec:   type: pubsub.rabbitmq   version: v1   metadata:   - name: connectionString     value: "amqp://rabbit:5672"   - name: hostname     value: rabbit    - name: username     value: guest   - name: password     value: guest 

In the next article, we'll see how other services can subscribe to the published event in order to perform their logic.

posted @ sabato 4 novembre 2023 12:39 | Feedback (0) | Filed Under [ Dapr Microservices ]

martedì 10 ottobre 2023

Dapr - Input Binding and Configuration

Dapr binding is a mechanism that allows connections between components and external services by providing a common interface for communication, without needing to know about each other's implementation details.

It allows components to send and receive messages over a range of transport protocols and message brokers, like RabbitMQ and Kafka, Azure Event Grid, AWS SNS, Azure Service Bus, and Amazon SQS. You can also integrate with Twitter, SMTP, SendGrid, HTTP, or CRON.

Dapr has a building block also to manage and retrieve the application configurations (settings, connection strings, identifiers, etc) just by configuring the related component.

At the moment, the supported configuration stores are Redis, Postgres, and Azure App configuration.

Let's see how we can use both functionalities in the discount microservices.

No alt text provided for this image

First of all, we have to reference an additional NuGet package: Dapr.Extensions.Configuration

Once done, just go to the Program.cs file and add these lines of code, where we declare the config store and the list of keys we want to retrieve.

var builder = WebApplication.CreateBuilder(args

var client = new DaprClientBuilder().Build();
builder.Configuration     .AddDaprConfigurationStore(
       "redisconfigstore", 
       new List<string>() { "CrazyDiscountEnabled" }, 
       client, 
       TimeSpan.FromSeconds(20))     .AddStreamingDaprConfigurationStore(
       "redisconfigstore", 
       new List<string>() { "CrazyDiscountEnabled" }, 
       client, 
       TimeSpan.FromSeconds(20));)

Please note that AddDaprConfigurationStore will make the first call to load the config with the given keys, while AddStreamingDaprConfigurationStore will keep watching for changes and update local configurations.

Here's the main code of the discount controller:

[ApiController
[Route("[controller]")]
public class DiscountController : ControllerBase
{
	private readonly DaprClient daprClient;
	private readonly IConfiguration configuration;

	public DiscountController(
       DaprClient daprClient, 
       IConfiguration configuration)
	{
		this.daprClient = daprClient;
		this.configuration = configuration;
	}


	[HttpPost("/discountcronbinding")]
	public async Task<ActionResult> UpdateDiscount()
	{
		var crazyDiscountEnabled = configuration
                          .GetValue<bool>("CrazyDiscountEnabled");

		if (!crazyDiscountEnabled)
		{
			return Ok();
		}

		var random = new Random(Guid.NewGuid().GetHashCode());
		var discount = random.Next(1, 30);

		await daprClient.SaveStateAsync("redisstore", "CrazyDiscountValue", discount);
		return Ok();
	}


	[HttpGet]
	public async Task<int> GetDiscount()
	{
        var discount = 0;       
        var crazyDiscountEnabled = configuration
                          .GetValue<bool>("CrazyDiscountEnabled");      
        if (crazyDiscountEnabled)         
        {   
           discount = await daprClient
                   .GetStateAsync<int>("redisstore", "CrazyDiscountValue");         
        } 
 
         return discount;
	}
}]

We have a standard API Controller where we inject the DaprClient and the standard IConfiguration object that allows us to retrieve the settings.

The UpdateDiscount method checks if the configuration with the key CrazyDiscountEnabled is enabled. If yes, we're going to generate a random discount and save it using the state management we already know.

How is configured the Config store? Using the component specification, of course!

apiVersion: dapr.io/v1alpha
kind: Component
metadata:   name: redisconfigstore
spec:   type: configuration.redis   metadata:   - name: redisHost     value: redis:63791


As you can see we're connecting to Redis, so we can enable the crazy discount feature by running the following command in the Redis CLI

SET CrazyDiscountEnabled true

Now the random discount calculation is enabled, but when and who is calling this API?

We're going to use the CRON input binding, which will trigger a POST request based on the CRON expression we specify.

Here is our configuration:

apiVersion: dapr.io/v1alpha
kind: Component
metadata:   name: discountcronbinding
spec:   type: bindings.cron   version: v1   metadata:   - name: schedule     value: "* * * * * *"

We set the CRON expression in order to trigger every second. The POST will call a method with the same name as the binding (discountcronbinding in our case). So, basically, we're calculating a new random discount every second (if enabled by config).

We can retrieve the current valid discount just by performing a GET request to the Discount endpoint.

No alt text provided for this image

In the next article, we'll see the Pub\Sub building block.

posted @ martedì 10 ottobre 2023 08:37 | Feedback (0) | Filed Under [ Dapr Microservices ]

mercoledì 6 settembre 2023

Dapr - State management

Dapr allows the storage of durable data (key\value pairs) across multiple sessions and services.

With Dapr State Management, you can:

  • Save and retrieve state using different stores with 2 levels of consistency (strong and eventual)
  • Use a consistent API to perform operations, abstracting away the implementation details.
  • Implement caching (with TTL) to improve performance.
  • Filter\Sort\Page state entries

Let's see how we can use it in the customer's microservices.

No alt text provided for this image

The customers' microservice persists data in MS SQL Server and caches data in MongoDB

Here's the code:

public class CustomersController : ControllerBase
{
	private readonly ILogger<CustomersController> logger;
	private readonly DaprClient daprClient;
	private readonly ICustomersRepository customerRepository;


	public CustomersController(ILogger<CustomersController> logger, DaprClient daprClient, ICustomersRepository customerRepository)
	{
		this.logger = logger;
		this.daprClient = daprClient;
		this.customerRepository = customerRepository;
	}

	[HttpGet]
	[ProducesResponseType(typeof(IEnumerable<Customer>), (int)HttpStatusCode.OK)]
	public async Task<ActionResult<IEnumerable<Customer>>> GetCustomers()
	{
		var customers = await GetCustomersFromCache();
		return Ok(customers);
	}

	private async Task<IEnumerable<Customer>> GetCustomersFromCache()
	{
		var customers = await daprClient
                              .GetStateAsync<IEnumerable<Customer>>(
                               "mongostore", 
                               "GetCustomers");

		if (customers == null)
		{
			customers = await customerRepository
                              .GetCustomersAsync();
			await daprClient
                            .SaveStateAsync("mongostore", 
                                            "GetCustomers", 
                                            customers);
		}

		return customers;
	}
}
  

We have a standard API Controller where we inject the DaprClient (view the previous article to see the NuGet package required and how to register it).

In the GetCustomersFromCache method, we use the GetStateAsync to check if we previously cached the data. If yes, we just return it; if no, we load it (using the customer repository), cache it using the SaveStateAsync and return it.

Please note that we are specifying where we want to save our state by passing the store name (in our case "mongostore"). But where is it defined?

If you remember in the previous article we configured the sidecar with the component path; that's the folder where we store the configurations of our components.

Therefore, we just have to create under that folder a new YAML file with the following configuration

apiVersion: dapr.io/v1alpha
kind: Component
metadata:   name: mongostore
spec:   type: state.mongodb   version: v1   metadata:   - name: host     value: "mongo:27017"

The value "mongostore" in the metadata section is the store name we have to use in our code (I set it as "mongostore", but you can follow your preferred naming convention). We also specify how to connect to the MongoDB store.

Where can you find all these settings? In the official documentation, of course!

Now we have all the information to use another state store (i.e. Redis) as I've done in the products microservice.

The products' microservice persists data in PostgreSQL and cache data in Redis

The relevant piece of code is similar to the previous one:

private async Task<IEnumerable<Product>> GetProductsFromCache(
{
	var products = await daprClient
                         .GetStateAsync<IEnumerable<Product>>(
                                                              "redisstore", 
                                                              "GetProducts");
	if (products == null)
	{
		products = await this.productRepository.GetProductsAsync();
		await daprClient.SaveStateAsync("redisstore", "GetProducts", products);
	}

	return products;
})

To configure the Redis state store, we create the following file in the component's folder

apiVersion: dapr.io/v1alpha
kind: Component
metadata:   name: redisstore
spec:   type: state.redis   version: v1   metadata:   - name: redisHost     value: redis:6379   - name: redisPassword     value: ""

To test both customers' and products' microservices, I prepared 2 .http files with the sample GET requests, so we can use Visual Studio.

Just start the solution (using the docker-compose) and executes both requests.

When executing the GET /customers, the result will be cached in Mongo

In the source code on GitHub, you can see also how to delete the state and how to set a TTL of the cached value (otherwise cache will never expire!).

In the next article, we'll see the Configuration and Binding building blocks

posted @ mercoledì 6 settembre 2023 15:54 | Feedback (0) | Filed Under [ Dapr Microservices ]

lunedì 3 luglio 2023

Dapr - Create a Dapr service

In order to use Dapr with C#, you just have to create a new ASP.NET Core Web API Project and reference the Nuget package Dapr.AspNetCore

Once done, just go in the Program.cs and chain the method AddDapr() after the method AddControllers():

builder.Services.AddControllers().AddDapr();

In addition to that, just call these methods to register the Pub\Sub building block:

app.MapSubscribeHandler();

app.UseCloudEvents();

At this point, you can inject the DaprClient, in your controller or services, to interact with the several APIs of Dapr.

public CustomersController(DaprClient daprClient)
{
  this.daprClient = daprClient;
}


We then can run the application along with Dapr sidecar using the CLI

dapr run --app-id microdelivery.customers.api --app-port 8000 -- dotnet run

But, since we're going to create several microservices, I prefer the docker-compose approach. Here the YAML file for the customer's microservice (microdelivery.customers.api) and its sidercar (microdelivery.customers.api.dapr).


version: '3.4

services:
  # Customers Api & Sidecar
  microdelivery.customers.api:
    container_name: microdelivery.customers.api
    image: ${DOCKER_REGISTRY-}microdeliverycustomersapi
    build:
      context: .
      dockerfile: src/MicroDelivery.Customers.Api/Dockerfile
    depends_on:
      - sqlserver
      - redis
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    ports:
      - "8000:80"

  microdelivery.customers.api.dapr:
    container_name: microdelivery.customers.api.dapr
    image: "daprio/daprd:latest"
    network_mode: "service:microdelivery.customers.api"
    depends_on:
      - microdelivery.customers.api
    command: ["./daprd",
      "-app-id", "microdelivery-customers-api",
      "-app-port", "80",
      "-resources-path", "/components",
      "-config", "/configuration/configuration.yaml",
      "-log-level", "debug"
      ]
    volumes:
      - "./dapr/components/:/components"
      - "./dapr/configuration/:/configuration"'

The sidecar is a docker image (daprio/daprd) attached to the network of the customer's microservices where we execute the daprd command, passing some parameters like the app id that identifies our app, the app port, what is the components\config path to customize our microservices behavior and the log level.

We can then just run the application with Visual Studio that will create the two containers and we're ready to debug it!

In the next article, we'll see the first building block: State management

posted @ lunedì 3 luglio 2023 07:19 | Feedback (0) | Filed Under [ Dapr Microservices ]

lunedì 5 giugno 2023

Dapr - The application scenario

In this series of articles (source code is available on GitHub), we'll develop a microservices application for an imaginary food delivery company called "MicroDelivery".

We'll have several services, each with its own logic, that will interact via RPC call or pub\sub messaging and we'll see how to use Dapr to build it.


⚠️ Disclaimer

My goal here is to give you a birds-eye view of the different features of Dapr, not to guide you step by step on building an application from scratch, nor to explain the single details of Dapr (there's the official website and several books for that!)

I won't explain why and when to adopt a microservices architecture and related pros\cons, nor we'll talk about any design\integrations\architectural patterns.

☁️We're not going to use any Cloud Provider (only on-prem docker services), but of course, the nature of Dapr allows you to easily switch from on-prem to the cloud.

⚒️ The application leverages several technologies(Redis, RabbitMq, SqlServer, PostgreSQL, MongoDB, Zipkin) and shows you how it's simple to use them together with Dapr. Of course, in the real world, you need to analyze your requirements and understand if these tools are useful for your needs along with their strong and weak points.


Customers Microservice

No alt text provided for this image

Customers Microservice

It's a CRUD microservice to manage customers' data.

It persists its data in SQL Server using Entity Framework and caches them in MongoDB, using Dapr State block

Products Microservice

No alt text provided for this image

Products Microservice

It's a CRUD microservice to manage products' data.

It persists its data in PostgreSQL using Entity Framework and caches them in Redis using Dapr State block

Orders Microservice

No alt text provided for this image

Orders Microservice

It's a microservice that receives the order requests, performs some, publishes a message (OrderSubmittedEvent) to notify other services, and receives a message (OrderShipped) to mark an order as shipped.

It persists its data in MongoDB, calls Discount\ Customers\Products microservices using service-to-service DAPR block, and send\received message in RabbitMQ using Dapr Pub\Sub block,

Discount Microservice

No alt text provided for this image

Discount Microservice

It's a microservice that, if enabled by configuration, calculates a random discount (very funny, isn't it?) that remains valid until the next recalculation.

The (re)calculation is triggered by a Dapr CRON Binding, and the configuration is stored on the Redis configuration block. It will be invoked by the Orders microservice using Dapr service-to-service communication,

Notifications Microservice

No alt text provided for this image

Notifications Microservice

It's a microservice that receives the message OrderSubmittedEvent and sends a confirmation email to customers, using Dapr SMTP binding.

Shipping Microservice

No alt text provided for this image

Shipping Microservice

It's a microservice that receives the message OrderSubmittedEvent and performs an HTTP call to an external Webhook, using Dapr HTTP binding and reading the Bearer Token from Dapr Secret store. It will also publish an OrderShipped event.

Here is the full application diagram

123

A full diagram of the sample application

The dotted links are the calls to Dapr Sidecar, performed using the C# SDK.

In the next article, we'll see how to set up Dapr within our project.

posted @ lunedì 5 giugno 2023 16:09 | Feedback (0) | Filed Under [ Dapr Microservices ]

Powered by:
Powered By Subtext Powered By ASP.NET