.NET Core

.NET ile Mikroservis Mimarisi: Tasarım ve Uygulama

16 dk okuma9 Şubat 2026Güncellendi 9 Mar 2026
.NET microservicesMicroservices C#Docker .NETKubernetes .NETgRPC .NETRabbitMQ .NETService communicationDistributed systems .NET

Mikroservisler, büyük sistemleri bağımsız deploy edilebilir parçalara böler — ama bu dönüşüm ancak servis sınırları ve operasyonel olgunluk yerindeyse işe yarar. Dağıtık sistemler üzerinde çalıştığım projelerde, başarılı bir mikroservis geçişi ile sancılı bir sürecin arasındaki fark her zaman hazırlık aşamasına dayanıyordu: neden parçalıyorsunuz sorusunu cevaplayabilmek, nasıl sorusundan çok daha önemli.

Tasarım Temelleri

Servis Sınırları

Mikroservis mimarisinde en kritik karar, sınırları nereye çizdiğinizdir. Yanlış çizilen sınırlar, yerini aldığı monolit'ten daha fazla bağımlılık yaratır.

  • Servisleri teknik katmanlara göre değil, iş yeteneklerine göre modelleyin
  • Servisler arasında ortak veritabanı kullanmaktan kaçının — her servis kendi verisine sahip olmalı
  • Kontratları açık ve versiyonlu tutun
  • Monolit'ten çıkarırken Strangler Fig pattern uygulayın
  • Bir ekip bir veya birden fazla servise sahip olmalı, tersi asla olmamalı

Dağıtık sistemler kurduğum projelerde, ekiplerin en çok pişman olduğu şey çok erken bölmek. İyi yapılandırılmış bir monolit ile başlayın, dikişleri belirleyin, sonra ancak net bir operasyonel veya organizasyonel neden olduğunda ayrıştırın.

Bounded Context ve Domain-Driven Design

Mikroservis sınırları, DDD bounded context'leri ile hizalanmalı. Her servis tutarlı bir domain modelini kapsar:

  • Sipariş Servisi — sipariş yaşam döngüsü, doğrulama kuralları, fiyatlandırma
  • Envanter Servisi — stok seviyeleri, rezervasyonlar, depo eşleştirme
  • Ödeme Servisi — ödeme işleme, iade mantığı, muhasebe kayıtları

İki servis aynı kavrama ihtiyaç duyduğunda (ör. "Ürün"), her biri kendi temsilini tutar. Sipariş Servisi yalnızca ProductId, Name ve Price bilgisine ihtiyaç duyarken, Envanter Servisi ProductId, SKU, WarehouseLocation ve StockCount bilgilerine ihtiyaç duyar.

API Gateway Pattern

API Gateway, istemciler ile servisleriniz arasında oturarak çapraz kesen sorunları ele alır:

csharp
// Program.cs — YARP tabanlı API Gateway
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("default", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
    });
});

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

Servisler Arası İletişim Kalıpları

Doğru iletişim kalıbını seçmek kritik önemdedir. Tek bir en iyi seçenek yoktur — her birinin farklı bağlamlarda önemli olan artı ve eksileri vardır.

HTTP/REST

En basit seçenek. Anlık cevap gerektiren senkron istek-yanıt akışlarında iyi çalışır.

Artıları: Evrensel araç desteği, kolay hata ayıklama, okunabilir payload'lar

Eksileri: Çağıran ve çağrılan arasında sıkı bağımlılık, zincirleme hatalar, daha yüksek gecikme

csharp
// Dayanıklılık politikalarıyla tiplenmiş HTTP istemcisi
builder.Services.AddHttpClient<IOrderServiceClient, OrderServiceClient>(client =>
{
    client.BaseAddress = new Uri("https://order-service:5001");
    client.Timeout = TimeSpan.FromSeconds(10);
})
.AddStandardResilienceHandler();

gRPC

HTTP/2 üzerine inşa edilmiş binary protokol. Performansın önemli olduğu dahili servisler arası çağrılar için ideal.

Artıları: .proto dosyaları ile güçlü kontratlar, streaming desteği, JSON'dan yaklaşık 10 kat daha hızlı serializasyon

Eksileri: gRPC-Web olmadan tarayıcı dostu değil, hata ayıklaması zor, proto yönetimi gerektirir

csharp
// inventory.proto
syntax = "proto3";

option csharp_namespace = "InventoryService.Grpc";

service InventoryGrpc {
  rpc CheckStock (StockRequest) returns (StockResponse);
  rpc ReserveItems (ReserveRequest) returns (ReserveResponse);
  rpc StreamStockUpdates (StockSubscription) returns (stream StockUpdate);
}

message StockRequest {
  string product_id = 1;
  string warehouse_id = 2;
}

message StockResponse {
  string product_id = 1;
  int32 available_quantity = 2;
  bool is_available = 3;
}

message ReserveRequest {
  string order_id = 1;
  repeated ReserveItem items = 2;
}

message ReserveItem {
  string product_id = 1;
  int32 quantity = 2;
}

message ReserveResponse {
  bool success = 1;
  string reservation_id = 2;
  string failure_reason = 3;
}

message StockSubscription {
  repeated string product_ids = 1;
}

message StockUpdate {
  string product_id = 1;
  int32 new_quantity = 2;
  string timestamp = 3;
}
csharp
// gRPC sunucu implementasyonu
public class InventoryGrpcService : InventoryGrpc.InventoryGrpcBase
{
    private readonly IInventoryRepository _repository;
    private readonly ILogger<InventoryGrpcService> _logger;

    public InventoryGrpcService(
        IInventoryRepository repository,
        ILogger<InventoryGrpcService> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public override async Task<StockResponse> CheckStock(
        StockRequest request, ServerCallContext context)
    {
        var stock = await _repository.GetStockAsync(
            request.ProductId, request.WarehouseId);

        return new StockResponse
        {
            ProductId = request.ProductId,
            AvailableQuantity = stock?.Quantity ?? 0,
            IsAvailable = (stock?.Quantity ?? 0) > 0
        };
    }
}

RabbitMQ ile Asenkron Mesajlaşma

Anlık yanıt gerektirmeyen iş akışları için mesaj kuyrukları, servisleri hem zaman hem de erişilebilirlik açısından birbirinden ayırır. Üretim sistemlerinde en çok başvurduğum kalıp budur.

csharp
// MassTransit ile RabbitMQ consumer
public class OrderCreatedConsumer : IConsumer<OrderCreatedEvent>
{
    private readonly IInventoryRepository _inventory;
    private readonly ILogger<OrderCreatedConsumer> _logger;

    public OrderCreatedConsumer(
        IInventoryRepository inventory,
        ILogger<OrderCreatedConsumer> logger)
    {
        _inventory = inventory;
        _logger = logger;
    }

    public async Task Consume(ConsumeContext<OrderCreatedEvent> context)
    {
        var order = context.Message;
        _logger.LogInformation(
            "Sipariş {OrderId} için envanter rezervasyonu işleniyor", order.OrderId);

        try
        {
            foreach (var item in order.Items)
            {
                await _inventory.ReserveStockAsync(
                    item.ProductId, item.Quantity, order.OrderId);
            }

            await context.Publish(new InventoryReservedEvent
            {
                OrderId = order.OrderId,
                ReservedAt = DateTime.UtcNow
            });
        }
        catch (InsufficientStockException ex)
        {
            _logger.LogWarning(ex,
                "Sipariş {OrderId} için yetersiz stok", order.OrderId);

            await context.Publish(new InventoryReservationFailedEvent
            {
                OrderId = order.OrderId,
                Reason = ex.Message
            });
        }
    }
}

// MassTransit kayıt yapılandırması
builder.Services.AddMassTransit(x =>
{
    x.AddConsumer<OrderCreatedConsumer>();

    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host("rabbitmq", "/", h =>
        {
            h.Username("guest");
            h.Password("guest");
        });

        cfg.ReceiveEndpoint("inventory-order-created", e =>
        {
            e.ConfigureConsumer<OrderCreatedConsumer>(ctx);
            e.UseMessageRetry(r => r.Intervals(
                TimeSpan.FromSeconds(1),
                TimeSpan.FromSeconds(5),
                TimeSpan.FromSeconds(15)));
        });
    });
});

Kalıp Karşılaştırma Özeti

| Kriter | HTTP/REST | gRPC | Asenkron Mesajlaşma |

|---|---|---|---|

| Bağımlılık | Yüksek | Yüksek | Düşük |

| Gecikme | Orta | Düşük | Değişken |

| Güvenilirlik | Retry gerektirir | Retry gerektirir | Yerleşik dayanıklılık |

| Hata ayıklama | Kolay | Orta | Zor |

| En uygun olduğu alan | Dış API'lar | Dahili yüksek performans | Olay güdümlü iş akışları |

Monolit'ten Mikroservislere Geçiş

Üzerinde çalıştığım her başarılı mikroservis projesi, sıfırdan değil monolit geçişi olarak başladı. İşte savaş alanında test edilmiş bir strateji.

Aşama 1: Monolit'i Hazırlayın

Bir şey çıkarmadan önce, dahili yapıyı düzenleyin. Monolit içinde gelecekteki servis sınırlarınızı yansıtan modül sınırları oluşturun.

  • Domain analizi ile bounded context'leri belirleyin
  • Paylaşılan veri erişimini modül bazında repository'lere ayırın
  • Modüller arasında dahili arayüzler tanımlayın
  • Modül sınırlarında entegrasyon testleri ekleyin

Aşama 2: Strangler Fig ile Ayrıştırma

Bir seferde bir servis çıkarın, en az bağımlı domain'den başlayarak. Trafiği, monolit mi yoksa yeni servis mi çağırılacağına karar veren bir cephe (facade) üzerinden yönlendirin.

  • Salt okunur bir servis ile başlayın (daha düşük risk)
  • Her iki yolu paralel çalıştırın ve sonuçları karşılaştırın
  • Yazma işlemlerini kademeli olarak taşıyın
  • Taşınan modülün monolit kodunu yedek olarak tutun

Aşama 3: Veri Göçü

Bu en zor kısımdır. Her servis kendi veri deposuna sahip olmalıdır.

  • Geçiş süresince senkronizasyon için Change Data Capture (CDC) kullanın
  • Servis sınırları arasında nihai tutarlılığı (eventual consistency) kabul edin
  • Servisler arası işlemler için telafi edici transaction'lar uygulayın
  • Son durumda iki servis arasında asla veritabanı paylaşmayın

Aşama 4: Geçiş Tamamlama

  • Taşınan modülün monolit kodunu kaldırın
  • Paylaşılan veritabanı tablolarını emekli edin
  • Yeni topoloji için izleme ve uyarıları güncelleyin
  • Yeni servis kontratını ve sahipliğini belgeleyin

Konteynerizasyon ve Orkestrasyon

Docker Yapılandırması

İyi yapılandırılmış bir Dockerfile, üretim imajlarını minimal tutmak için çok aşamalı build kullanır:

dockerfile
# Build aşaması
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 --no-restore

# Çalışma zamanı aşaması
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app

RUN adduser --disabled-password --gecos "" appuser
USER appuser

COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "OrderService.dll"]

Yerel Geliştirme için Docker Compose

yaml
# docker-compose.yml
version: "3.8"

services:
  order-service:
    build:
      context: .
      dockerfile: OrderService/Dockerfile
    ports:
      - "5001:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__OrderDb=Host=order-db;Database=orders;Username=postgres;Password=postgres
      - RabbitMq__Host=rabbitmq
    depends_on:
      order-db:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy

  inventory-service:
    build:
      context: .
      dockerfile: InventoryService/Dockerfile
    ports:
      - "5002:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__InventoryDb=Host=inventory-db;Database=inventory;Username=postgres;Password=postgres
      - RabbitMq__Host=rabbitmq
    depends_on:
      inventory-db:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy

  payment-service:
    build:
      context: .
      dockerfile: PaymentService/Dockerfile
    ports:
      - "5003:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__PaymentDb=Host=payment-db;Database=payments;Username=postgres;Password=postgres
      - RabbitMq__Host=rabbitmq
    depends_on:
      payment-db:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy

  order-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: orders
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - order-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

  inventory-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: inventory
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - inventory-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

  payment-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: payments
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - payment-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

  rabbitmq:
    image: rabbitmq:3-management-alpine
    ports:
      - "5672:5672"
      - "15672:15672"
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  order-data:
  inventory-data:
  payment-data:

Sağlık Kontrolleri ve Dayanıklılık

Her mikroservis sağlık bilgisi sunmalıdır. Bu olmadan, orkestratörler akıllı yönlendirme veya yeniden başlatma kararları veremez.

csharp
// Program.cs — Sağlık kontrolü yapılandırması
builder.Services.AddHealthChecks()
    .AddNpgSql(
        builder.Configuration.GetConnectionString("OrderDb")!,
        name: "postgresql",
        tags: new[] { "db", "ready" })
    .AddRabbitMQ(
        new Uri("amqp://guest:guest@rabbitmq:5672"),
        name: "rabbitmq",
        tags: new[] { "messaging", "ready" })
    .AddCheck<OrderProcessingHealthCheck>(
        "order-processing",
        tags: new[] { "custom", "ready" });

var app = builder.Build();

// Canlılık: Süreç hayatta mı?
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false // Kontrol yok, sadece uygulamanın yanıt verdiğini doğrular
});

// Hazırlık: Bu servis trafik alabilir mi?
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready"),
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
csharp
// Özel sağlık kontrolü implementasyonu
public class OrderProcessingHealthCheck : IHealthCheck
{
    private readonly IOrderRepository _repository;

    public OrderProcessingHealthCheck(IOrderRepository repository)
    {
        _repository = repository;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        var stuckOrders = await _repository.GetStuckOrdersCountAsync(
            TimeSpan.FromMinutes(30), cancellationToken);

        if (stuckOrders > 50)
            return HealthCheckResult.Unhealthy(
                $"{stuckOrders} sipariş 30 dakikadan fazla işleme takılmış");

        if (stuckOrders > 10)
            return HealthCheckResult.Degraded(
                $"{stuckOrders} sipariş işleme takılmış");

        return HealthCheckResult.Healthy();
    }
}

Dağıtık İzleme ve Gözlemlenebilirlik

Bir monolit'te stack trace her şeyi anlatır. Mikroservislerde tek bir kullanıcı isteği beş servisten geçebilir — ve dağıtık izleme olmadan hata ayıklama neredeyse imkansızdır.

OpenTelemetry Entegrasyonu

csharp
// Program.cs — OpenTelemetry kurulumu
builder.Services.AddOpenTelemetry()
    .ConfigureResource(res => res
        .AddService(
            serviceName: "OrderService",
            serviceVersion: "1.0.0"))
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddGrpcClientInstrumentation()
        .AddSource("MassTransit")
        .AddOtlpExporter(opt =>
        {
            opt.Endpoint = new Uri("http://otel-collector:4317");
        }))
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddRuntimeInstrumentation()
        .AddMeter("OrderService.Metrics")
        .AddOtlpExporter(opt =>
        {
            opt.Endpoint = new Uri("http://otel-collector:4317");
        }));

Korelasyonlu Yapılandırılmış Loglama

csharp
// Trace korelasyonlu Serilog yapılandırması
builder.Host.UseSerilog((context, config) => config
    .ReadFrom.Configuration(context.Configuration)
    .Enrich.FromLogContext()
    .Enrich.WithProperty("ServiceName", "OrderService")
    .Enrich.WithSpanId()
    .Enrich.WithTraceId()
    .WriteTo.Console(new JsonFormatter())
    .WriteTo.Seq("http://seq:5341"));

Ne Ölçülmeli

Gözlemlenebilirliğin üç sütunu farklı amaçlara hizmet eder:

  • Loglar — ayrık olaylar. Bir isteği servisler arasında takip edebilmeniz için korelasyon ID'leri ile yapılandırılmış loglama kullanın.
  • Metrikler — toplu ölçümler. İstek oranlarını, hata oranlarını, gecikme yüzdeliklerini (p50, p95, p99), kuyruk derinliklerini ve doygunluğu takip edin.
  • Trace'ler — bir isteğin tam yolculuğu. Her servisler arası çağrı, çağrı zincirini yeniden oluşturabilmeniz için trace bağlamını yaymalıdır.

Dağıtık sistemler kurduğum projelerde, en değerli gösterge paneli her servis endpoint'i için p99 gecikmeyi hata oranı ile yan yana gösterir. Bu iki çizgi birbirinden ayrılmaya başladığında, kesinti olmadan önce oluşan bir sorun var demektir.

Yaygın Mikroservis Hataları

Birden fazla mikroservis geçişinde çalıştıktan sonra, ekiplerin tekrar tekrar düştüğü kalıplar bunlar.

1. Dağıtık Monolit

Kod tabanını servislere böldünüz ama her istek hâlâ sırayla beşinin üzerinden senkron çağrılar gerektiriyor. Mikroservislerin tüm karmaşıklığına sahipsiniz ama bağımsızlığından yoksunuz. Servis A'yı, Servis B'yi de deploy etmeden deploy edemiyorsanız, bunlar bağımsız servisler değildir.

2. Erken Ayrıştırma

Domain sınırlarını anlamadan bölmek yanlış servis kesimlerine yol açar. İki servisi birleştirmek, birini bölmekten çok daha zordur. Modüler bir monolit ile başlayın ve sınır için güçlü kanıtınız olduğunda ayrıştırın.

3. Paylaşılan Veritabanı

İki servisin aynı veritabanı tablosunu okuması tüm amacı yok eder. Şema değişiklikleri koordineli deploy'lara dönüşür. Her servis kendi verisine sahip olmalıdır, bu biraz tekrar anlamına gelse bile.

4. Veri Tutarlılığını Görmezden Gelmek

Servisler arasında dağıtık transaction'lar (iki fazlı commit) ölçekte çalışmaz. Nihai tutarlılığı benimseyin, çok adımlı iş akışları için Saga pattern uygulayın ve hatalar için telafi edici aksiyonlar tasarlayın.

5. Kontrat Testi Yok

Tüm servisleri ayağa kaldıran entegrasyon testleri yavaş, kırılgan ve CI'da güvenilir çalışmaz. Servislerin tüm servislerin çalışmasına ihtiyaç duymadan API şekilleri üzerinde anlaştığını doğrulamak için tüketici güdümlü kontrat testleri (örneğin Pact) kullanın.

6. Yetersiz Operasyonel Yatırım

Mikroservisler ciddi altyapı gerektirir: servis başına CI/CD, merkezi loglama, dağıtık izleme, sağlık kontrolleri, uyarılar, sır yönetimi. Bunlar için platform olgunluğunuz yoksa, mikroservisler sizi yavaşlatır.

7. Çok Hızlı Çok Fazla Servis

İlk çeyrekte 30 servis oluşturan ekipler gördüm. Her birinin kendi CI pipeline'ı, izlemesi, nöbet rotasyonu farkındalığı ve belgelendirmesi gerekiyor. 2-3 ayrıştırma ile başlayın, platform yeteneklerini oluşturun, sonra hızlanın.

Ne Zaman Mikroservis?

Her sistem mikroservislerden fayda görmez. Şu durumlarda haklıdırlar:

  • Birden fazla ekip bağımsız sürüm kadansına ihtiyaç duyuyor
  • Farklı domain'ler farklı ölçekleme profillerine sahip
  • Monolit evrim maliyeti kabul edilemez hale geldi
  • Dağıtık sistemleri çalıştırmak için platform olgunluğunuz var
  • Düzenleyici veya uyumluluk gereksinimleri izolasyon talep ediyor

Şu durumlarda haklı değiller:

  • Küçük bir ekibiniz var (5-8 geliştiricinin altında)
  • Domain henüz iyi anlaşılmamış
  • Modern göründüğü için mikroservis kullanmak istiyorsunuz
  • CI/CD, izleme veya konteyner orkestrasyonu hazır değil

Sonuç

Mikroservisler, varsayılan bir teknik yükseltme değil, organizasyonel ve mimari bir karardır. Teknoloji — .NET, Docker, RabbitMQ, gRPC — kolay kısımdır. Zor kısım sınırları doğru çizmek, gözlemlenebilirliğe yatırım yapmak ve üretimde düzinelerce bağımsız servisi çalıştırmak için operasyonel disiplini oluşturmaktır. İş bağlamı gerçekten gerektirdiğinde mikroservisleri seçin ve ayrıştırmaya yatırım yapmadan önce platforma yatırım yapın.

Sisteminiz için hazırlık değerlendirmesi ve düşük riskli bir geçiş planı oluşturmak için birlikte çalışabiliriz.

İlgili Makaleler

Flutter Projeniz mi Var?

iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.

İletişime Geç