Dapr ve .NET Kullanarak Minimum Efor ile Microservice’ler Geliştirmek – 01

Bildiğimiz gibi her geçen gün teknoloji ve alışkanlıklarımız sürekli değişmekte. Özellikle pandemi sürecinden sonra dijitalleşmeye ve teknolojiye olan eğilim oldukça artmış durumda. Durum böyleyken bizlerde geliştiriciler olarak bu değişimlere ve isteklere adapte olabilmek adına, geliştirdiğimiz uygulamaların mümkün olabildiğince portable ve scalable yapıda olmalarına dikkat etmekteyiz.

Maalesef uygulamalarımızı geliştirirken sadece business fonksiyonalitelerine odaklanabilmemizin yanı sıra, uygulamalarımızın portable, scalable ve resilient bir yapıda olabilmeleri adına düşünmemiz ve odaklanmamız gereken bir çok farklı endişe ve problemleri de bulunmakta.

Örneğin pub/sub kullanabilmem için hangi service bus library’sini kullanacağım, state store edebilmek için ne gerekli, uygulamalar arası iletişimimi nasıl resilient ve reliable bir hale getirebilirim veya observability sağlayabilmek için uygulamalarımdan telemetry verilerini nasıl toplayabilirim gibi bir çok düşünmemiz gereken farklı soru ve incelememiz/denememiz ve kurmamız gereken farklı çözümler ve library’ler bulunmakta. Bunların yanı sıra kullanmaya karar verdiğimiz library’leri ve yaklaşımları da standardize edebilmek için kendi shared library’lerimizi geliştirme gibi ihtiyaçlarımız da olabiliyor.

İki seri olarak ele alacağım bu konunun ilk bölümünde ise distributed ortamlarda karşımıza çıkabilecek ihtiyaçları kolayca ele alıp abstract bir hale getirebilmemizi ve hızlı bir şekilde ilgili business fonksiyonalite’lerine odaklanabilmemizi sağlayan Dapr projesinden bahsedip, ardından örnek bir uygulama geliştireceğiz. Bir sonraki makalede ise farklı Azure servislerini kullanarak geliştirmiş olacağımız uygulamayı Azure Container Apps üzerine nasıl deploy edebileceğimize bir bakacağız.

Distributed Application Runtime (Dapr)

Dapr kısaca kolay ve hızlı bir şekilde portable, reliable ve resilient microservice’ler geliştirebilmemize olanak tanıyan Microsoft tarafından 2019 yılında duyurulmuş bir event-driven runtime’dır. Platform ve yazılım dili agnostic bir şekilde cloud-native uygulama geliştirme süreçlerini oldukça kolaylaştırmakla beraber, bizlerin ilgili core business fonksiyonalite’lerine odaklanabilmesine olanak tanımaktadır. Ayrıca bir CNCF projesidir.

Bildiğimiz gibi container’lar dünyasında sidecar model’i oldukça önemli bir yere sahiptir. Bizlere mevcut container’larımıza dokanmadan onların fonksiyonalitelerini genişletebilmemize olanak tanımaktadır. Dapr‘da uygulamalarımızın bir parçası olarak sidecar model’i ile çalışmaktadır.

Dapr ile microservice’ler geliştirebilmek için SDK‘ler olduğu gibi, ilgili uygulamalar HTTP/gRPC çağrıları yapabildiği sürece SDK‘ler olmadan da geliştirme yapılabilmektedir. Ayrıca platform bağımsız olarak çalışabilmektedir.

Şimdi Dapr‘ın building block’larına kısaca bir göz atalım.

Dapr Building Blocks

Dapr birden çok bağımsız ve farklı endişeleri çözebilmemize olanak sağlayan, standardize edilmiş aşağıdaki gibi farklı API‘lardan oluşmaktadır.

  • Service-to-service invocation
  • State management
  • Publish and subscribe
  • Resource bindings and triggers
  • Actors
  • Observability
  • Secrets
  • Configuration
  • Distributed Lock

Gördüğümüz gibi cloud-native bir microservice mimarisi design edebilmemiz için gerekli olan tüm fonksiyonaliteleri farklı building block’lar altında bizlere sağlamaktadır. Güzel tarafı ise uygulamalarımız sadece Dapr building block’larını bilmektedir, herhangi bir external library’lere bağımlılığı bulunmamaktadır. Ayrıca bu building block’lardan ister birini, istersek de hepsini kullanabiliriz. Building block’lar hakkında daha detaylı bilgilere ise, buradan erişebilirsiniz.

Dapr Components

Bir diğer önemli konu ise component’ler. Component’ler için kısaca building block’ların çalışabilmesi için gerekli olan concrete implementation’lar diyebiliriz. Dapr pluggable bir yapıya sahip olduğu için, ayrıca bu component’leri kolay bir şekilde farklı component’ler ile de değiştirebilmekteyiz.

Örneğin default kurulumda pub/sub building block’u için Redis component’i kullanılmaktadır. Bunu istersek RabbitMQ component’i ile uygulama tarafında herhangi bir değişikliğe gerek olmadan değiştirebilmekteyiz. Kulağa hoş geliyor, değil mi?

components % more pubsub.yaml 
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

Ayrıca component’ler gördüğümüz gibi CustomResourceDefinition (CRD) olarak tanımlanmaktadır ve Dapr‘ın çalıştığı herhangi bir ortamda kullanılabilmektedir. Component schema’sı ve format’ları hakkında detaylı bilgiye ise buradan erişebilirsiniz.

Bir Örnek Gerçekleştirelim

Şimdi Dapr ile uygulama geliştirme süreçlerini daha iyi anlayabilmek adına, Dapr‘ın bir kaç building block’unu kullanarak basit bir ShoppingCart ve Recommendation service’lerini geliştirelim.

İlk Olarak Dapr’ı Kuralım

NOT: Kurulum işleminden önce önerilen development ortamı için Docker‘a sahip olmamız gerekmektedir.

Dapr ile ilgili çeşitli işlemleri gerçekleştirebilmek için öncelikle Dapr CLI‘a sahip olmamız gerekmektedir. Bunun için buradaki adımları takip ederek, ilgili environment için Dapr CLI‘ı install edelim. Ardından development işlemlerini gerçekleştirebilmemiz için Dapr‘ı, self-hosted mode’da local olarak initialize edelim. Bunun için “dapr init” komutunu çalıştırmamız yeterli olacaktır.

Ardından “docker ps” komutu ile Dapr‘ın çalışıp çalışmadığını kontrol edebiliriz.

Bu noktada görebileceğimiz gibi “dapr“, “zipkin” ve “redis” container’ları çalışıyor durumda. Dapr default olarak metrikleri toplayabilmek için Zipkin‘i, state-store ve messaging için ise Redis‘i kullanmaktadır. Ayrıca component’ler Windows için “%USERPROFILE%\.dapr” klasörü, Linux için ise “$HOME/.dapr” klasörü altında tanımlanmaktadır.

Bu noktaya kadar local development için Dapr kurulum işlemini tamamladık. Daha sonrasında ise farklı component’lerin kullanımlarına da değiniyor olacağız.

ShoppingCart’ı Geliştirelim

Şimdi hızlı ve basit bir şekilde bir sepet uygulaması geliştirme ihtiyacımız olduğunu varsayalım. Bu uygulamayı geliştirebilmek için bir state-store’a ihtiyacımız olacak. Çünkü sepet durumlarını tutmamız gerekmektedir. Şimdi teknoloji gibi herhangi bir konu hakkında bir şey düşünmeden, Dapr ile nasıl kolay bir şekilde bu uygulamayı geliştirebileceğimize bir bakalım.

İlk olarak DaprShop.ShoppingCart.API adında bir .NET 6 API projesi oluşturalım ve içerisinden “WeatherForecast” ile ilgili olan kod parçalarını silelim. Ardından projeye “Dapr.Client” ve “Dapr.AspNetCore” library’lerini NuGet üzerinden dahil edelim. Böylece Dapr ile geliştirmelerimizi daha efektif bir şekilde gerçekleştirebileceğiz.

Şimdi Domain adında bir klasör oluşturalım ve aşağıdaki gibi sepet için ihtiyaç duyacağımız modelleri tanımlayalım.

using System;
namespace DaprShop.ShoppingCart.API.Domain;

public class ShoppingCartItem
{
 public string ProductId { get; set; } = String.Empty;
 public string ProductName { get; set; } = String.Empty;
 public decimal Price { get; set; }
 public int Quantity { get; set; }
}
using System;
namespace DaprShop.ShoppingCart.API.Domain;

public class ShoppingCart
{
    public ShoppingCart() => Items = new List<ShoppingCartItem>();

    public string UserId { get; set; } = String.Empty;
    public List<ShoppingCartItem> Items { get; set; }
}

Ardından Services adında bir klasör oluşturalım ve aşağıdaki gibi içerisinde sepet implementasyonunu gerçekleştirelim.

using System;
namespace DaprShop.ShoppingCart.API.Services;

public interface IShoppingCartService
{
    Task<Domain.ShoppingCart> GetShoppingCartAsync(string userId);
    Task AddItemToShoppingCartAsync(string userId, Domain.ShoppingCartItem item);
}
using Dapr.Client;
using DaprShop.ShoppingCart.API.Domain;

namespace DaprShop.ShoppingCart.API.Services;

public class ShoppingCartService : IShoppingCartService
{
    private readonly DaprClient;
    private static readonly string storeName = "statestore";

 public ShoppingCartService(DaprClient daprClient)
 {
        _daprClient = daprClient;
 }

    public async Task AddItemToShoppingCartAsync(string userId, ShoppingCartItem item)
    {
        Domain.ShoppingCart shoppingCart = await GetShoppingCartAsync(userId);

        ShoppingCartItem? existingItem = shoppingCart.Items.Where(x => x.ProductId == item.ProductId).FirstOrDefault();
        if(existingItem != null)
        {
            existingItem.Quantity += item.Quantity;
        }
        else
        {
            shoppingCart.Items.Add(item);
        }

        await _daprClient.SaveStateAsync(storeName, userId, shoppingCart);
    }

    public async Task<Domain.ShoppingCart> GetShoppingCartAsync(string userId)
    {
        var shoppingCart = await _daprClient.GetStateAsync<Domain.ShoppingCart>(storeName, userId.ToString());
        if(shoppingCart == null)
        {
            shoppingCart = new Domain.ShoppingCart()
            {
                UserId = userId
            };
        }

        return shoppingCart;
    }
}


Gördüğümüz gibi AddItemToShoppingCartAsync ve GetShoppingCartAsync method’larını implemente ederken, state-store için herhangi bir teknoloji veya bir library düşünmedik. Sadece Dapr‘ın sunmuş olduğu state-store building block‘unu DaprClient aracılığıyla kullanıp, implementasyon işlemlerini gerçekleştirdik.

Kullanmış olduğumuz storeName field’ında ise, Dapr‘ın kullanmak istediğimiz state-store component’inin adını belirttik. Bu noktada default olarak gelen state-store component’inin adını kullandık. Bu default değere ise ilgili “components” klasörü altındaki “statestore.yaml” dosyasının “metadata” bölümü altından ulaşabiliriz.

Bu default değer ile ise default olarak gelen Redis component’ini kullanmış olduk. Dilersek farklı ihtiyaçlar için kullanabilmek üzere Azure CosmosDB‘yi kullanan bir başka state-store component’i de tanımlayabiliriz. Böylece herhangi bir teknolojiyi farklı noktalarda istediğimiz gibi kullanabilir ve değiştirebiliriz.

Eğer Dapr SDK‘ini de kullanmasaydık, bu işlemi yine aşağıdaki gibi basit bir şekilde HTTP üzerinden gerçekleştirebilirdik.

curl -X POST http://localhost:/v1.0/state/statestore/ \
  -H "Content-Type: application/json" \
  -d '[
        {
          "key": "key",
          "value": "value"
        }
      ]'

Gördüğümüz gibi Dapr, uygulamalarımızı hızlı bir şekilde geliştirebilmemiz için bize oldukça esnek bir yapı sunmaktadır.

Şimdi ShoppingCartController adında bir controller ekleyelim ve aşağıdaki gibi ShoppingCartService‘i kullanarak endpoint’leri de implemente edelim.

using Microsoft.AspNetCore.Mvc;
using DaprShop.ShoppingCart.API.Services;
using Domain = DaprShop.ShoppingCart.API.Domain;

namespace DaprShop.Basket.API.Controllers;

[ApiController]
[Route("api/shopping-cart")]
public class ShoppingCartController : ControllerBase
{
    private readonly IShoppingCartService _shoppingCartService;

    public ShoppingCartController(IShoppingCartService shoppingCartService)
    {
        _shoppingCartService = shoppingCartService;
    }

    [HttpGet("{userId}")]
    public async Task<ActionResult<Domain.ShoppingCart>> Get(string userId)
    {
        Domain.ShoppingCart shoppingCart = await _shoppingCartService.GetShoppingCartAsync(userId);

        return Ok(shoppingCart);
    }

    [HttpPost("{userId}/items")]
    public async Task<ActionResult<Domain.ShoppingCart>> Post(string userId, Domain.ShoppingCartItem item)
    {
        try
        {
            await _shoppingCartService.AddItemToShoppingCartAsync(userId, item);
            return Ok();
        }
        catch (Exception ex)
        {
            return BadRequest(ex);
        }
    }
}

Ardından Program.cs dosyasını aşağıdaki gibi düzenleyelim ve gerekli injection ve configuration işlemlerini gerçekleştirelim.

using DaprShop.ShoppingCart.API.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddDaprClient();
builder.Services.AddScoped<IShoppingCartService, ShoppingCartService>();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapControllers();

app.Run("http://localhost:5000");

Gördüğümüz gibi AddDaprClient extension method’unu kullanarak, Dapr‘ı service collection içerisine dahil ettik.

Bu noktaya kadar hızlı bir şekilde basit bir sepet uygulaması tasarladık. Şimdi ise recommendation service takımının kullanıcılara ilgilendikleri ürünler hakkında önerilerde bulunabilmelerini sağlayacak bir özellik geliştirmemizin istendiğini varsayalım. Bu noktada bir kullanıcı sepetine bir ürün eklediğinde, bu ürün bilgilerini içeren bir event publish etmek istiyoruz. Böylece recommendation service takımını ürünler hakkında bilgilendirilebilir.

Şimdi ise bu işlemi Dapr‘ın sunmuş olduğu pub/sub building block‘u ile kolayca nasıl halledebiliriz bir bakalım.

İlk olarak solution’a DaprShop.Contracts adında bir class library ekleyelim ve içerisinde aşağıdaki gibi publish etmek istediğimiz event’i tanımlayalım.

namespace DaprShop.Contracts
{
 public class ProductItemAddedToShoppingCartEvent
 {
  public string UserId { get; set; } = string.Empty;
  public string ProductId { get; set; } = string.Empty;
 }
}

Ardından DaprShop.ShoppingCart.API projesine referans olarak ekleyelim.

Şimdi implemente etmiş olduğumuz AddItemToShoppingCartAsync method’unu event publish edebilmek için aşağıdaki gibi refactor edelim.

public async Task AddItemToShoppingCartAsync(string userId, ShoppingCartItem item)
{
    Domain.ShoppingCart shoppingCart = await GetShoppingCartAsync(userId);

    ShoppingCartItem? existingItem = shoppingCart.Items.Where(x => x.ProductId == item.ProductId).FirstOrDefault();
    if(existingItem != null)
    {
        existingItem.Quantity += item.Quantity;
    }
    else
    {
        shoppingCart.Items.Add(item);
    }

    await _daprClient.SaveStateAsync(storeName, userId, shoppingCart);

    var productItemAddedToShoppingCartEvent = new Contracts.ProductItemAddedToShoppingCartEvent()
    {
        UserId = userId,
        ProductId = item.ProductId
    };

    const string pubsubName = "pubsub";
 const string topicNameOfShoppingCartItems = "daprshop.shoppingcart.items";

 await _daprClient.PublishEventAsync(pubsubName, topicNameOfShoppingCartItems, productItemAddedToShoppingCartEvent);
}

Gördüğümüz gibi yine DaprClient üzerinden PublishEventAsync method’unu kullanarak, ProductItemAddedToShoppingCartEvent ‘ini kolayca publish edebiliyoruz. State-store için yaptığımız gibi pub/sub component’inin adını ve event’i publish etmek istediğimiz topic’i belirtmemiz yeterlidir.

Şimdi ise bu event’i nasıl consume edebiliriz kısmına bir bakalım.

Recommendation API’ı Geliştirelim

Yine ilk olarak DaprShop.Recommendation.API adında bir .NET 6 API projesi oluşturalım ve içerisinden “WeatherForecast” ile ilgili olan kod parçalarını silelim. Ardından “Dapr.AspNetCore” library’sini NuGet üzerinden projeye dahil edelim ve DaprShop.Contracts class library’sini de referans olarak ekleyelim.

Şimdi RecommendationController adında bir controller ekleyelim ve aşağıdaki gibi ProductItemAddedToShoppingCartEvent ‘ini dinleyebileceğimiz method’u implemente edelim. Böylece herhangi bir kullanıcı sepetine bir ürün eklediğinde, bu bilgileri farklı öneriler gerçekleştirebilmek üzere saklayabileceğiz.

using Dapr;
using Microsoft.AspNetCore.Mvc;

namespace DaprShop.Recommendation.API.Controllers
{
 [ApiController]
 [Route("api/recommendations")]
 public class RecommendationController : ControllerBase
 {
  private const string PubsubName = "pubsub";
  private const string TopicNameOfShoppingCartItems = "daprshop.shoppingcart.items";

  [Topic(PubsubName, TopicNameOfShoppingCartItems)]
  [Route("products")]
  [HttpPost]
  public ActionResult AddProduct(Contracts.ProductItemAddedToShoppingCartEvent productItemAddedToShoppingCartEvent)
  {
   Console.WriteLine($"New product has been added into shopping cart. Product Id: {productItemAddedToShoppingCartEvent.ProductId} User Id: {productItemAddedToShoppingCartEvent.UserId}");

   return Ok();
  }
 }
}

Gördüğümüz gibi ProductItemAddedToShoppingCartEvent ‘ine subscribe olabilmek için tek yaptığımız, controller içerisinde bir method tanımlamak ve Topic attribute’ünü kullanarak ilgili Dapr pub/sub component’inin adı ile birlikte ilgili topic’ini belirtmek oldu. Bu noktada da yine herhangi bir service bus library’si, messaging teknolojisi vs düşünmedik.

Arka planda ise Dapr pub/sub building block’u, pub/sub component’i ile iletişime geçmektedir ve uygulamamız adına subscription işlemini ilgili topic için gerçekleştirmektedir. Böylece ilgili topic’e ne zaman yeni bir event gelirse, bu event’i bu noktada consume edebileceğiz.

Genel olarak Dapr‘ın iyi design edilmiş encapsulation‘ları sayesinde, gördüğümüz gibi uygulamalarımız oldukça bağımlılıktan uzaklar ve daha esnek olarak hareket edebilmektedirler. Ayrıca Dapr‘ı yeni geliştirmekte olduğumuz uygulamalar için kullanmanın yanı sıra, herhangi bir bağımlılık getirmediği için mevcut uygulamalarımız içerisinde de kullanabilmekteyiz.

Bunun yanında pub/sub building block’u bizlere at-least-once message delivery garantisi de sunmaktadır ve farklı message broker’lar ile de çalışabilmektedir.

Şimdi Program.cs dosyasını aşağıdaki gibi düzenleyelim ve gerekli configuration’ları gerçekleştirelim.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

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

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
 app.UseSwagger();
 app.UseSwaggerUI();
}

app.UseCloudEvents();
app.MapControllers();
app.MapSubscribeHandler();

app.Run("http://localhost:6000");

Burada ise AddDapr extension method’unu kullanarak, Dapr‘ı MVC pipeline’ına dahil ediyoruz. Ayrıca Dapr event formatı olarak CloudEvents specification’ını kullandığı için, gelen event’lerin otomatik olarak unwrap olabilmesi adına CloudEvents‘i de ASP.NET Core middleware’ine dahil ediyoruz.

Eklemiş olduğumuz MapSubscribeHandler extension method’u ise, Dapr subscribe endpoint’ini API‘a eklemektedir. Böylece bu endpoint call edildiğinde, otomatik olarak API içerisinde Topic attribute’ünü kullanarak tanımlamış olduğumuz action’ları bulmakta ve Dapr‘a ilgili topic’ler için subscription işlemlerini gerçekleştirmesini söylemektedir.

Gördüğümüz gibi hepsi bu kadar. Her iki service’i de çok fazla bir efor harcamadan, herhangi farklı bir library ihtiyaçları olmadan kolay bir şekilde geliştirdik.

Resiliency

Dapr‘ın ayrıca resilient state’e sahip service’ler geliştirebilmemize olanak sağladığından bahsetmiştik. Dapr bizlerin çok fazla düşünmeden ve efor harcamadan cloud-native microservice’ler geliştirebilmemize olanak tanırken, microservice’lerimizin reliable bir state’e ve fault tolerance’a sahip olabilmelerini de sağlamaktadır. Bu konuda Dapr bizlere farklı noktalarda farklı seçenekler sunmaktadır.

Dapr state-store building block’u, “Strong” ve “Eventual” olmak üzere iki farklı consistency modelini desteklemektedir. Ayrıca eventual consistency ise default davranışıdır. State-store özelinde consistency davranışını değiştirebilmek için ise, client üzerinden aşağıdaki gibi StateOptions belirtmemiz yeterli olmaktadır.

new StateOptions() { Consistency = ConsistencyMode.Strong}

Conflict’leri yönetebilmek için ise “Optimistic Concurrency Control” u desteklemektedir. Yaklaşım olarak update ise conflict’lerin çok nadir olduğunu varsaymaktadır. Yani genellikle işlemlerin sorunsuz gerçekleşeceğini varsayar. Aksi durumda ise tahmin edebileceğimiz üzere tekrar edileceğini. Böylece performans etkisi olmadan conflict durumlarını efektif bir şekilde ele alabilmektedir. Bu işlemleri ise ETag‘leri kullanarak gerçekleştirmektedir.

ETag belirtilmediği sürece default olarak “last-write-wins” stratejisini kullanmaktadır. Bu stratejinin uygun olmadığı durumlarda ise ETag belirterek aşağıdaki gibi “first-write-wins” stratejisini de kullanabilmekteyiz.

var (shoppingCart, etag) = await _daprClient.GetStateAndETagAsync(storeName, userId.ToString());

// ...

await _daprClient.TrySaveStateAsync(storeName, userId, shoppingCart, etag);

Ayrıca her state-store için farklı TTL configuration’ları da uygulayabilmekteyiz.

Dapr bunlara ek olarak retries/back-offs, circuit breakers, timeouts gibi özelliklere daha granular bir şekilde sahip olabilmemiz için, fault tolerance resiliency policy’ler tanımlayabilmemize de olanak tanımaktadır.

Policy’ler de component’ler ile aynı klasör altında aşağıdaki gibi tanımlanmaktadır.

NOT: Dapr‘ı self-hosted mode’da kullanırken, policy ismini “resiliency.yaml” olarak tanımlamamız gerekmektedir.

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  # optionally scope the policy to specific apps
spec:
  policies:
    timeouts:
      # timeout policy definitions

    retries:
      # retry policy definitions

    circuitBreakers:
      # circuit breaker policy definitions

  targets:
    apps:
      # apps and their applied policies here

    actors:
      # actor types and their applied policies here

    components:
      # components and their applied policies here

Ayrıca policy’ler tanımlarken bazı default policy’lere de göz atmamız iyi bir fikir olacaktır. Örneğin retry mekanizması bazı building block’lar için default verilerle aktif bir şekilde gelmektedir ve override etmediğimiz sürece yeni tanımlanan policy’ler ekstra olarak handle edilmektedir. Bu nedenle policy’ler ve policy hiyerarşisi hakkında ayrıltılı bilgi edinebilmek için buraya bakmak, faydamıza olacaktır.

Şimdi local ortamda test işlemleri için her iki API‘ı nasıl ayağa kaldırabiliriz bir ona bakalım.

Test Edelim

Şimdi API‘ları çalıştırabilmek için terminal üzerinden ilgili proje dizinlerine gidelim ve aşağıdaki komut satırlarını kullanarak Dapr ile birlikte API’ları çalıştıralım.

dapr run --app-id shoppingcartapi --app-port 5000 -- dotnet run
dapr run --app-id recommendationapi --app-port 6000 -- dotnet run

NOT: “dapr run” komutu ile ilgili daha farklı seçeneklere ise, “dapr run –help” üzerinden erişebilirsiniz. Örneğin default Dapr gRPC ve HTTP port’larını değiştirebilir, veya size uygun max request ve buffer size’ları ile ilgili değişiklikler yapabilirsiniz.

Gördüğümüz gibi Dapr, DaprShop.ShoppingCart.API ‘ını port 5000 ve DaprShop.Recommendation.API ‘ını da port 6000 üzerinden ayağa kaldırdı.

Şimdi DaprShop.ShoppingCart.API ‘ının Swagger UI‘ına erişelim ve sepet’e bir adet ürün ekleyelim veya aşağıdaki cURL komutunu çalıştıralım.

curl -X 'POST' \
  'http://localhost:5000/api/shopping-cart/123456/items' \
  -H 'accept: text/plain' \
  -H 'Content-Type: application/json' \
  -d '{
  "productId": "1",
  "productName": "Samsung Z Fold 4",
  "price": 1500,
  "quantity": 1
}'

Ürünü sepet’e eklemenin ardından aşağıdaki gibi DaprShop.Recommendation.API terminal ekranı üzerinden de görebileceğimiz üzere, ilgili event belirtmiş olduğumuz topic üzerinden consume edilmiştir.

Ayrıca state-store üzerinde oluşturmuş olduğumuz sepet bilgisine de, DaprShop.ShoppingCart.API ‘ı üzerinden aşağıdaki gibi erişim sağlayabiliriz.

Dapr Dashboard

Ayrıca sahip olduğumuz component’ler, deploy etmiş olduğumuz uygulamalar ve onların configuration’ları hakkındaki bilgilere, Dapr Dashboard üzerinden kolayca ulaşabiliriz.

Bunun için tek yapmamız gereken, “dapr dashboard” komutunu çalıştırmak. Ardından port 8080 üzerinden aşağıdaki gibi erişim sağlayabiliriz.

Dapr Observability

Bildiğimiz gibi observability production ortamları için oldukça kritik bir konu. Özellikle microservice’ler ile çalışıyorsak, bazı sorunları kolayca anlayabilmemiz ve tespit edebilmemiz adına development ortamları için de önemli bir konu olduğunu söyleyebilirim. Bu konuda genellikle uygulamalarımızdan farklı metrikleri toplayabilmek adına, uygulama özelinde çeşitli SDK‘ler kullanıp, configuraton’lar gerçekleştiriyoruz.

Neyseki Dapr‘ın observability için de bir building block’a sahip olduğunu söylemiştik. Dapr sidecar’ı default olarak trafiği intercept ederek tracing, logs, health ve metrics gibi çeşitli verileri extract etmektedir. Ayrıca çeşitli observability tool’ları ile de entegre edebilmekteyiz.

Dapr, event’ler için CloudEvents standart formatını desteklediği gibi, telemetry verileri için de OpenTelemetry ve Zipkin‘i desteklemektedir. Zipkin desteği ise default olarak gelmektedir. Ayrıca tracing context oluşturma ve yayma işlemini de otomatik olarak gerçekleştirmektedir.

Kısacası tracing verilerine default olarak aşağıdaki gibi Zipkin UI‘ı üzerinden erişim sağlayabiliriz.

http://localhost:9411

Ayrıca Dapr, Prometheus‘u metrik standartı olarak kullanır ve performans ve kaynak tüketimi gibi metrikleri sağlar.

Toparlayalım

Dapr ilk duyurulduğu günden bu yana hep aklımda ve ilgi alanlarım arasında yer aldı. Bir süredir Dapr kullanarak bir kaç uygulama geliştiriyorum ve sunmuş olduğu farklı building block’ları kullanırken oldukça keyif alıyorum. Bir developer olarak uygulama geliştirme sürecinde, ilk başta herhangi bir teknoloji veya bazı library’ler hakkında düşünmeden çalışabilmek, hızlı ve efektif bir deneyim. Ayrıca resiliency veya observability endişeleri olmadan yalnızca business fonksiyonalitelerine odaklanabilmemiz de artı tarafı.

Ayrıca farklı business use-case’leri için farklı building block’lar sağlamanın yanında, iyi bir abstraction’a da sahip olması nedeniyle microservice’lerimizin loosely coupled olarak geliştirilebilmesine de katkı sağlamaktadır.

Bir sonraki seri de ise default component’ler yerine farklı Azure service’lerini kullanarak, geliştirmiş olduğumuz API‘ları Azure Container Apps‘e nasıl deploy edebileceğiz konusuna bir bakacağız.

Referanslar

https://learn.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/
https://docs.dapr.io/concepts/overview/

Gökhan Gökalp

View Comments

  • Hi gokhan,
    This is Amit. Thanks for a pretty nice article. Its simple and easy . I have an issue where i just mocked up your steps and able to successfully build and executed both the api's. I am using VS2022 Community edition. I ran it using multiple startup projects. I am able to see the consoles running and showing me ports 5000 and 6000 occupied, but if I try to open those url , i am getting HTTP connection refused error. Please help

Recent Posts

Event-Driven Architecture’larda Conditional Claim-Check Pattern’ı ile Event Boyut Sınırlarının Üstesinden Gelmek

{:en}In today’s technological age, we typically build our application solutions on event-driven architecture in order…

2 ay ago

Containerized Uygulamaların Supply Chain’ini Güvence Altına Alarak Güvenlik Risklerini Azaltma (Güvenlik Taraması, SBOM’lar, Artifact’lerin İmzalanması ve Doğrulanması) – Bölüm 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

9 ay ago

Identity & Access Management İşlemlerini Azure AD B2C ile .NET Ortamında Gerçekleştirmek

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 yıl ago

Azure Service Bus Kullanarak Microservice’lerde Event’ler Nasıl Sıralanır (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 yıl ago

.NET Microservice’lerinde Outbox Pattern’ı ile Eventual Consistency için Atomicity Sağlama

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 yıl ago