20 February, 2025

Circuit Breaker Pattern in .net 8

 

The Circuit Breaker Pattern will typically send an error response when it is in the open state. This means that the circuit breaker has detected too many failures in the underlying service or resource, and instead of trying to call it repeatedly (which could lead to further failures or resource exhaustion), it immediately returns an error. This helps protect the system from cascading failures.

Key Points:

  • Closed State: When all calls are successful, the circuit is closed and calls go through normally.
  • Open State: If the error threshold is exceeded, the circuit opens, and all calls are immediately rejected with an error response without attempting to call the service.
  • Half-Open State: After a cooling period, the circuit breaker allows a limited number of test calls. If these succeed, it closes the circuit; if they fail, it reopens it.

In summary, the circuit breaker sends an error response when it is in the open state because it has determined that the underlying service is likely to fail.

using System;
using System.Threading;

public enum CircuitState
{
    Closed,
    Open,
    HalfOpen
}

public class CircuitBreaker
{
    private readonly int _failureThreshold;
    private readonly TimeSpan _openTimeout;
    private int _failureCount;
    private DateTime _lastFailureTime;
    private CircuitState _state;

    public CircuitBreaker(int failureThreshold, TimeSpan openTimeout)
    {
        _failureThreshold = failureThreshold;
        _openTimeout = openTimeout;
        _state = CircuitState.Closed;
    }

    public T Execute<T>(Func<T> action)
    {
        // Check state and manage open state timeout
        if (_state == CircuitState.Open)
        {
            if (DateTime.UtcNow - _lastFailureTime > _openTimeout)
            {
                // Move to half-open state to test if service has recovered
                _state = CircuitState.HalfOpen;
            }
            else
            {
                throw new Exception("Circuit breaker is open. Request blocked.");
            }
        }

        try
        {
            T result = action();

            // If the call succeeds in half-open, reset the circuit
            if (_state == CircuitState.HalfOpen)
            {
                Reset();
            }

            return result;
        }
        catch (Exception)
        {
            RegisterFailure();
            throw;
        }
    }

    private void RegisterFailure()
    {
        _failureCount++;
        _lastFailureTime = DateTime.UtcNow;

        if (_failureCount >= _failureThreshold)
        {
            _state = CircuitState.Open;
        }
    }

    private void Reset()
    {
        _failureCount = 0;
        _state = CircuitState.Closed;
    }
}

public class Service
{
    private readonly Random _random = new Random();

    public string GetData()
    {
        // Simulate a service call that may fail
        if (_random.NextDouble() < 0.5)
        {
            throw new Exception("Service failure!");
        }
        return "Success!";
    }
}

public class Program
{
    public static void Main()
    {
        var circuitBreaker = new CircuitBreaker(failureThreshold: 3, openTimeout: TimeSpan.FromSeconds(5));
        var service = new Service();

        for (int i = 0; i < 10; i++)
        {
            try
            {
                // Wrap the service call with circuit breaker logic
                string response = circuitBreaker.Execute(() => service.GetData());
                Console.WriteLine($"Call {i + 1}: {response}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Call {i + 1}: {ex.Message}");
            }

            // Wait a bit between calls
            Thread.Sleep(1000);
        }
    }
}

Explanation:

  • CircuitBreaker Class:

    • The breaker starts in a Closed state.
    • On every failed call, RegisterFailure() increments the failure count and, if the threshold is met, sets the state to Open.
    • If in Open state, further calls will immediately throw an exception unless the timeout has expired, in which case the state moves to HalfOpen.
    • In HalfOpen state, if the next call succeeds, the breaker resets (returns to Closed). Otherwise, it transitions back to Open.
  • Service Class:

    • Simulates a service that randomly fails.
  • Program Class (Main Method):

    • Demonstrates making multiple calls via the circuit breaker, handling errors, and showing the state changes.

This example gives a clear overview of how you might implement a basic circuit breaker in C# for managing service calls.

19 February, 2025

Deploying Microservices API using Azure Kubernetes Service (AKS)

 

Deploying Microservices API using Azure Kubernetes Service (AKS)

Azure Kubernetes Service (AKS) is a managed Kubernetes service that simplifies deploying, managing, and scaling microservices.


🚀 Step-by-Step Guide to Deploy Microservices on AKS

We will deploy a .NET 8 microservices-based API on AKS using Azure Container Registry (ACR) and Kubernetes manifests.


1️⃣ Prerequisites

Azure Subscription
Azure CLI installed (az)
Docker installed
kubectl installed (az aks install-cli)
.NET 8 installed


2️⃣ Build and Containerize Your .NET API

Create a Dockerfile for your microservice (e.g., OrderService).

📌 Dockerfile

# Use the official .NET runtime as the base image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80

# Build the application
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["OrderService/OrderService.csproj", "OrderService/"]
RUN dotnet restore "OrderService/OrderService.csproj"
COPY . .
WORKDIR "/src/OrderService"
RUN dotnet publish -c Release -o /app/publish

# Create final runtime image
FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "OrderService.dll"]

📌 Build and Push Docker Image

# Log in to Azure
az login 

# Create a resource group
az group create --name MyResourceGroup --location eastus

# Create Azure Container Registry (ACR)
az acr create --resource-group MyResourceGroup --name MyACR --sku Basic

# Login to ACR
az acr login --name MyACR

# Tag and push the image
docker build -t myacr.azurecr.io/orderservice:v1 .
docker push myacr.azurecr.io/orderservice:v1

3️⃣ Deploy to Azure Kubernetes Service (AKS)

📌 Create an AKS Cluster

# Create an AKS cluster
az aks create --resource-group MyResourceGroup --name MyAKSCluster --node-count 2 --enable-addons monitoring --generate-ssh-keys

# Get AKS credentials
az aks get-credentials --resource-group MyResourceGroup --name MyAKSCluster

📌 Create Kubernetes Deployment & Service

Deployment YAML (orderservice-deployment.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: orderservice
spec:
  replicas: 2
  selector:
    matchLabels:
      app: orderservice
  template:
    metadata:
      labels:
        app: orderservice
    spec:
      containers:
        - name: orderservice
          image: myacr.azurecr.io/orderservice:v1
          ports:
            - containerPort: 80
          env:
            - name: ASPNETCORE_ENVIRONMENT
              value: "Production"
---
apiVersion: v1
kind: Service
metadata:
  name: orderservice-service
spec:
  selector:
    app: orderservice
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer

📌 Apply the Kubernetes Manifest

kubectl apply -f orderservice-deployment.yaml

4️⃣ Verify and Test the Deployment

📌 Check Pod Status

kubectl get pods

📌 Get Service IP

kubectl get service orderservice-service
  • Note the EXTERNAL-IP.
  • Open a browser and visit http://EXTERNAL-IP/api/orders.

5️⃣ Auto-Scaling and Monitoring

📌 Enable Auto-Scaling

kubectl autoscale deployment orderservice --cpu-percent=50 --min=1 --max=5

📌 Enable Monitoring

az aks enable-addons --resource-group MyResourceGroup --name MyAKSCluster --addons monitoring

✅ Summary

1️⃣ Containerized the .NET API
2️⃣ Pushed the image to Azure Container Registry
3️⃣ Created an AKS cluster
4️⃣ Deployed microservices using Kubernetes YAML
5️⃣ Exposed the service using LoadBalancer
6️⃣ Enabled Auto-Scaling & Monitoring

Would you like a Helm-based deployment for better scalability? 🚀


What is FGA (Fine-Grained Authorization)?

Fine-Grained Authorization (FGA) is an access control model that provides highly detailed permission management, allowing specific access rules based on users, roles, resources, and conditions. It is commonly used for multi-tenant applications and zero-trust security models.

How FGA Works with Azure Kubernetes Service (AKS)?

When using AKS, Fine-Grained Authorization ensures that only authorized users, services, and workloads can access Kubernetes resources. This is achieved through RBAC (Role-Based Access Control), OPA (Open Policy Agent), and Azure AD integration.


🚀 Implementing FGA in AKS

1️⃣ Enforce Access Control with Kubernetes RBAC

Kubernetes RBAC (Role-Based Access Control) is the built-in method to restrict access to AKS resources.

📌 Define a Role for a Microservice

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: mynamespace
  name: orderservice-role
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]

📌 Assign Role to a Service Account

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: orderservice-binding
  namespace: mynamespace
subjects:
  - kind: ServiceAccount
    name: orderservice-sa
    namespace: mynamespace
roleRef:
  kind: Role
  name: orderservice-role
  apiGroup: rbac.authorization.k8s.io

✅ This ensures that only the orderservice microservice can access specific pods.


2️⃣ Use Open Policy Agent (OPA) for Advanced FGA

OPA is a policy engine that enforces custom rules for AKS.

📌 Deploy OPA as an Admission Controller

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml

📌 Example Policy: Allow Only Specific Users to Deploy Pods

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedUsers
metadata:
  name: restrict-users
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    allowedUsers:
      - "alice@example.com"
      - "bob@example.com"

✅ Only Alice and Bob can deploy new pods in AKS.


3️⃣ Enforce FGA with Azure AD (AAD) and AKS

🔹 Azure AD RBAC allows users to access AKS resources based on their roles.

📌 Assign Fine-Grained Permissions to Users

az aks update --resource-group MyResourceGroup --name MyAKSCluster --enable-aad
az role assignment create --assignee alice@example.com --role "Azure Kubernetes Service RBAC Reader" --scope /subscriptions/{subscriptionId}/resourceGroups/MyResourceGroup/providers/Microsoft.ContainerService/managedClusters/MyAKSCluster

Alice now has read-only access to AKS.


🔑 Summary

RBAC: Restrict microservice access
OPA: Enforce custom access policies
Azure AD: Role-based user authentication

Would you like a real-world example of integrating OPA with a .NET API on AKS? 🚀

Types of Data Consistency Models

 

Types of Data Consistency Models

In distributed systems and databases, consistency models define how data is read and written across multiple nodes. The key types are:


1. Strong Consistency

🔹 Definition:

  • Every read receives the most recent write.
  • No stale or outdated data is ever read.
  • Achieved using synchronous replication.

🔹 Example:

  • Google Spanner ensures strong consistency across data centers.
  • A banking system that updates an account balance immediately after a transaction.

🔹 Pros & Cons:

✅ No stale reads.
✅ Ensures correctness.
❌ High latency due to synchronization.
❌ Not highly scalable.


2. Eventual Consistency (BASE Model)

🔹 Definition:

  • Data eventually becomes consistent across all nodes.
  • Temporary inconsistencies (stale reads) may occur.
  • Suitable for highly available and scalable systems.

🔹 Example:

  • DNS Systems take time to propagate changes across the internet.
  • Amazon DynamoDB, Apache Cassandra use eventual consistency for performance.

🔹 Pros & Cons:

✅ Highly available & scalable.
✅ Faster reads and writes.
❌ Users may see outdated data.

Variants of Eventual Consistency:

  1. Causal Consistency → Operations that are causally related are seen in order.
  2. Read-Your-Writes Consistency → A user always sees their own updates.
  3. Monotonic Reads Consistency → A user never sees older versions after reading a newer one.

3. Sequential Consistency

🔹 Definition:

  • All operations appear in the same order to all nodes.
  • Different nodes may see delays, but the sequence is always correct.

🔹 Example:

  • Multiplayer games ensure all players see the same events in the same order.

🔹 Pros & Cons:

✅ Easier debugging.
✅ Maintains logical order.
❌ More latency than eventual consistency.


4. Linearizability (Strict Consistency)

🔹 Definition:

  • Strongest form of consistency.
  • Every read returns the most recent write as if all operations occurred instantly.

🔹 Example:

  • Single-leader databases (e.g., Zookeeper, Etcd) use linearizability.
  • Stock trading platforms require linearizability to prevent race conditions.

🔹 Pros & Cons:

✅ Ensures correctness in critical applications.
❌ Poor performance in distributed environments.


5. Quorum Consistency

🔹 Definition:

  • A write is considered committed after N majority replicas acknowledge it.
  • Reads must check at least M nodes to ensure freshness.

🔹 Example:

  • Apache Cassandra and DynamoDB use quorum-based reads/writes.

🔹 Pros & Cons:

✅ Balances consistency and availability.
✅ Customizable (tunable consistency).
❌ Increased read/write latency.


Summary Table

Consistency Type Guarantees Performance Use Cases
Strong Consistency Always latest data Slow Financial transactions
Eventual Consistency Data syncs over time Fast Social media feeds, DNS
Sequential Consistency Operations in order Medium Multiplayer games
Linearizability Latest data, atomicity Very Slow Stock trading, Etcd, Zookeeper
Quorum Consistency Tunable balance Medium DynamoDB, Cassandra

Would you like an example implementation of any of these in .NET? 🚀

What is the SAGA Pattern?

 

What is the SAGA Pattern?

The SAGA pattern is a design pattern used in microservices architecture to handle long-running transactions and ensure data consistency across multiple services. It is commonly used when distributed transactions with two-phase commits (2PC) are not feasible due to their blocking nature.

A SAGA is a sequence of local transactions, where each step updates the database and triggers the next step. If a failure occurs, compensating transactions are executed to undo previous operations.

Types of SAGA Patterns

There are two primary ways to implement a SAGA pattern:

  1. Choreography (Event-driven)

    • Each service listens to events and reacts accordingly.
    • No centralized controller; services coordinate via events.
    • Best for simple workflows with fewer services.
  2. Orchestration (Command-driven)

    • A central orchestrator service manages the transaction flow.
    • The orchestrator calls each service and waits for responses.
    • Suitable for complex workflows with multiple services.

Implementing SAGA in .NET

Below is a step-by-step guide to implementing both Choreography and Orchestration using .NET.

1. Choreography-based SAGA (Event-driven)

In this approach, each service listens for events and reacts accordingly.

Technologies Used
  • ASP.NET Core Web API
  • MassTransit with RabbitMQ (for event-driven communication)
  • Entity Framework Core (for persistence)
Example: Order Processing System
  • Order Service → Places an order and publishes an OrderCreated event.
  • Payment Service → Listens to OrderCreated and processes payment, then publishes PaymentProcessed.
  • Inventory Service → Listens to PaymentProcessed and updates stock.
Step 1: Create a Shared Event Model
public class OrderCreatedEvent
{
    public Guid OrderId { get; set; }
    public decimal Amount { get; set; }
}

public class PaymentProcessedEvent
{
    public Guid OrderId { get; set; }
}
Step 2: Publish Events in Order Service
public class OrderService
{
    private readonly IBus _bus;

    public OrderService(IBus bus)
    {
        _bus = bus;
    }

    public async Task CreateOrder(Guid orderId, decimal amount)
    {
        // Save order to database (skipped for brevity)
        await _bus.Publish(new OrderCreatedEvent { OrderId = orderId, Amount = amount });
    }
}
Step 3: Handle Events in Payment Service
public class OrderCreatedConsumer : IConsumer<OrderCreatedEvent>
{
    private readonly IBus _bus;

    public OrderCreatedConsumer(IBus bus)
    {
        _bus = bus;
    }

    public async Task Consume(ConsumeContext<OrderCreatedEvent> context)
    {
        var orderId = context.Message.OrderId;
        // Process payment logic here (skipped for brevity)

        await _bus.Publish(new PaymentProcessedEvent { OrderId = orderId });
    }
}
Step 4: Handle Events in Inventory Service
public class PaymentProcessedConsumer : IConsumer<PaymentProcessedEvent>
{
    public async Task Consume(ConsumeContext<PaymentProcessedEvent> context)
    {
        var orderId = context.Message.OrderId;
        // Update inventory (skipped for brevity)
    }
}

2. Orchestration-based SAGA (Command-driven)

In this approach, a central orchestrator manages the entire transaction.

Example: Order Processing Orchestrator
  • Order Service → Calls the orchestrator.
  • SAGA Orchestrator → Calls Payment and Inventory services.
  • Compensation logic → If one step fails, previous steps are undone.
Step 1: Define the SAGA State
public class OrderSagaState : SagaStateMachineInstance
{
    public Guid CorrelationId { get; set; }
    public string CurrentState { get; set; }
}
Step 2: Create the SAGA State Machine
public class OrderStateMachine : MassTransitStateMachine<OrderSagaState>
{
    public State AwaitingPayment { get; private set; }
    public Event<OrderCreatedEvent> OrderCreated { get; private set; }
    public Event<PaymentProcessedEvent> PaymentProcessed { get; private set; }

    public OrderStateMachine()
    {
        InstanceState(x => x.CurrentState);

        Event(() => OrderCreated, x => x.CorrelateById(context => context.Message.OrderId));
        Event(() => PaymentProcessed, x => x.CorrelateById(context => context.Message.OrderId));

        Initially(
            When(OrderCreated)
                .Then(context => Console.WriteLine("Processing payment..."))
                .TransitionTo(AwaitingPayment)
                .Publish(context => new PaymentProcessedEvent { OrderId = context.Data.OrderId })
        );

        During(AwaitingPayment,
            When(PaymentProcessed)
                .Then(context => Console.WriteLine("Updating inventory..."))
                .Finalize()
        );
    }
}
Step 3: Register and Configure MassTransit in .NET
services.AddMassTransit(cfg =>
{
    cfg.AddSagaStateMachine<OrderStateMachine, OrderSagaState>()
        .InMemoryRepository();

    cfg.UsingRabbitMq((context, cfg) =>
    {
        cfg.ConfigureEndpoints(context);
    });
});

Compensation (Handling Failures)

If a failure occurs, we need to roll back the previous steps.

Example: Payment Fails, So Order is Cancelled

Modify the OrderStateMachine to handle failures:

public Event<PaymentFailedEvent> PaymentFailed { get; private set; }

During(AwaitingPayment,
    When(PaymentFailed)
        .Then(context => Console.WriteLine("Payment failed. Cancelling order..."))
        .Publish(context => new OrderCancelledEvent { OrderId = context.Data.OrderId })
        .Finalize()
);

When to Use Choreography vs. Orchestration?

Factor Choreography Orchestration
Complexity Low (fewer services) High (many services)
Scalability High (loosely coupled) Moderate
Observability Harder (many events) Easier (central control)
Flexibility High (autonomous services) Moderate

Conclusion

  • Choreography is best when services are independent and event-driven.
  • Orchestration is better for complex workflows requiring centralized control.
  • Use MassTransit with RabbitMQ for implementing event-driven SAGA in .NET.



Use Azure Service Bus instead of RabbitMQ in your SAGA implementation with MassTransit in .NET. 

Azure Service Bus is a fully managed messaging service that integrates well with MassTransit, making it a great choice for cloud-based applications.


How to Use Azure Service Bus with MassTransit in SAGA

We’ll update the previous SAGA implementation by replacing RabbitMQ with Azure Service Bus.

1. Install Dependencies

First, install the required NuGet packages:

dotnet add package MassTransit.AzureServiceBus

2. Configure MassTransit to Use Azure Service Bus

Modify the Program.cs or Startup.cs file in your .NET application.

using MassTransit;
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMassTransit(cfg =>
{
    cfg.AddSagaStateMachine<OrderStateMachine, OrderSagaState>()
        .EntityFrameworkRepository(r =>
        {
            r.ExistingDbContext<OrderDbContext>(); // Use EF Core for saga persistence
        });

    cfg.UsingAzureServiceBus((context, config) =>
    {
        config.Host("your-azure-service-bus-connection-string");

        config.ReceiveEndpoint("order-created-queue", e =>
        {
            e.ConfigureSaga<OrderSagaState>(context);
        });
    });
});

builder.Services.AddMassTransitHostedService();

var app = builder.Build();
app.Run();

🔹 Key Changes:

  • Replaced UsingRabbitMq with UsingAzureServiceBus
  • Set the Service Bus connection string from Azure
  • Configured a queue for the Order SAGA state machine

3. Publish Events to Azure Service Bus

Instead of publishing to RabbitMQ, we now publish to Azure Service Bus.

Publishing an Event

public class OrderService
{
    private readonly IPublishEndpoint _publishEndpoint;

    public OrderService(IPublishEndpoint publishEndpoint)
    {
        _publishEndpoint = publishEndpoint;
    }

    public async Task CreateOrder(Guid orderId, decimal amount)
    {
        await _publishEndpoint.Publish(new OrderCreatedEvent
        {
            OrderId = orderId,
            Amount = amount
        });
    }
}

Consuming an Event

public class OrderCreatedConsumer : IConsumer<OrderCreatedEvent>
{
    public async Task Consume(ConsumeContext<OrderCreatedEvent> context)
    {
        var orderId = context.Message.OrderId;

        // Process payment logic
        await context.Publish(new PaymentProcessedEvent { OrderId = orderId });
    }
}

4. Enable Compensation (Rollback) on Failure

If Payment Service fails, we trigger a compensating transaction.

Define a Compensation Event

public class PaymentFailedEvent
{
    public Guid OrderId { get; set; }
}

Handle Failure in the SAGA Orchestrator

public Event<PaymentFailedEvent> PaymentFailed { get; private set; }

During(AwaitingPayment,
    When(PaymentFailed)
        .Then(context => Console.WriteLine("Payment failed! Cancelling order..."))
        .Publish(context => new OrderCancelledEvent { OrderId = context.Data.OrderId })
        .Finalize()
);

5. Configure Azure Service Bus in Azure Portal

  1. Go to Azure PortalService Bus
  2. Create a Namespace (if not already created)
  3. Create a Queue (e.g., order-created-queue)
  4. Copy Connection String and update the .NET configuration

Summary

Replaced RabbitMQ with Azure Service Bus
Configured MassTransit to use Azure Service Bus
Published & consumed messages from Azure Service Bus
Handled SAGA failures with compensating transactions

Azure Service Bus is a reliable, cloud-native alternative to RabbitMQ, making it ideal for enterprise-grade microservices.

Would you like a GitHub sample project for this? 🚀

Microservices vs Monolithic Architecture

 Microservices vs Monolithic Architecture Here’s a clear side-by-side comparison between Microservices and Monolithic architectures — fro...