Saturday, August 30, 2025

How to Implement MicroServices in .Net Core Application- Point-to-point to explanation

 

1) Service boundaries & contracts

  • One business capability per service (e.g., Orders, Payments, Catalog). Each owns its own database.
  • API surface: REST (JSON) or gRPC for synchronous calls; events (Kafka/RabbitMQ/Azure Service Bus) for async integration.
  • Version your APIs (e.g., /v1), keep backward compatible changes, use OpenAPI/Swagger and contract tests (Pact).

2) Resilience patterns (timeouts, retries, circuit breaker, bulkhead)

Use HttpClientFactory + Polly on every outbound call:

builder.Services.AddHttpClient("CatalogClient", c =>

{

    c.BaseAddress = new Uri(config["CatalogUrl"]);

    c.Timeout = TimeSpan.FromSeconds(3);

})

.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(

    retryCount: 3,

    sleepDurationProvider: attempt => TimeSpan.FromMilliseconds(200 * Math.Pow(2, attempt)), // expo backoff

    onRetry: (outcome, ts, attempt, ctx) =>

        logger.LogWarning(outcome.Exception, "Retry {Attempt}", attempt)))

.AddTransientHttpErrorPolicy(p => p.CircuitBreakerAsync(

    handledEventsAllowedBeforeBreaking: 5,

    durationOfBreak: TimeSpan.FromSeconds(30)))

.AddPolicyHandler(Policy.BulkheadAsync<HttpResponseMessage>(maxParallelization: 50, maxQueuingActions: 100));

Rules of thumb

  • Always set timeouts on I/O.
  • Prefer idempotent operations; send Idempotency-Key headers for POSTs where possible.
  • Use cancellation tokens end-to-end.

3) Circuit breaker semantics

  • Trip when a dependency is unhealthy; fail fast and return a cached/partial response or friendly error.
  • Expose breaker state via metrics and health checks (see §5).

4) Data & database handling (consistency without distributed transactions)

  • Database per service (EF Core or Dapper). No cross-service joins.
  • Propagate data between services using domain events—not shared tables.
  • Ensure atomicity with the Outbox pattern:
    1. Write your entity and an OutboxMessage row in the same DB transaction.
    2. A background Outbox Publisher reads unpublished rows and emits them to the broker, then marks them sent.

// Inside your OrderService command handler

await using var tx = db.Database.BeginTransactionAsync();

db.Orders.Add(order);

db.Outbox.Add(new OutboxMessage {

    Type = "OrderPlaced",

    Payload = JsonSerializer.Serialize(new { order.Id, order.Total, order.CustomerId }),

    OccurredOn = DateTimeOffset.UtcNow

});

await db.SaveChangesAsync();

await tx.CommitAsync();

// BackgroundService publisher

protected override async Task ExecuteAsync(CancellationToken ct)

{

    while (!ct.IsCancellationRequested)

    {

        var messages = await db.Outbox

            .Where(m => m.SentOn == null)

            .OrderBy(m => m.OccurredOn)

            .Take(100).ToListAsync(ct);

 

        foreach (var msg in messages)

        {

            await broker.PublishAsync(msg.Type, msg.Payload, ct);

            msg.SentOn = DateTimeOffset.UtcNow;

        }

        await db.SaveChangesAsync(ct);

        await Task.Delay(TimeSpan.FromSeconds(1), ct);

    }

}

  • Updating “relative/related” databases: consume those events in other services to update local read models (CQRS). Use Sagas (orchestration or choreography) for multi-step processes (e.g., Order → Reserve Inventory → Process Payment → Arrange Shipping), with timeouts + compensation.

5) Health checks, readiness, and monitoring

Add liveness (process alive) and readiness (deps OK) endpoints:

builder.Services.AddHealthChecks()

    .AddSqlServer(config.GetConnectionString("OrdersDb"))

    .AddRabbitMQ(config["RabbitMq:Connection"])

    .AddUrlGroup(new Uri(config["CatalogUrl"] + "/health"), name: "catalog");

 

app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = _ => false });

app.MapHealthChecks("/health/ready", new HealthCheckOptions {

    Predicate = _ => true,

    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse

});

  • Kubernetes/Containers probe /health/live and gate traffic on /health/ready.
  • Add self-diagnostics (thread pool starvation, GC info) via EventCounters exported by OpenTelemetry.

6) Observability (logs, traces, metrics)

Unified, structured telemetry makes bugs easy to fix.

OpenTelemetry (logs + traces + metrics) → backends like Grafana Tempo/Prometheus/Loki, Jaeger, Elastic, or Azure Application Insights.

builder.Services.AddOpenTelemetry()

 .WithTracing(t => t

     .AddAspNetCoreInstrumentation()

     .AddHttpClientInstrumentation()

     .AddSqlClientInstrumentation()

     .AddSource("OrderService")

     .AddOtlpExporter()) // or Azure Monitor exporter

 .WithMetrics(m => m

     .AddAspNetCoreInstrumentation()

     .AddRuntimeInstrumentation()

     .AddHttpClientInstrumentation()

     .AddPrometheusExporter());

 

app.MapPrometheusScrapingEndpoint(); // /metrics

Structured logging (e.g., Serilog → console/JSON + sink of choice):

Log.Logger = new LoggerConfiguration()

    .Enrich.FromLogContext()

    .Enrich.WithCorrelationId()

    .WriteTo.Console(new JsonFormatter())

    .WriteTo.Seq(config["SeqUrl"]) // or Elastic/Azure

    .CreateLogger();

 

builder.Host.UseSerilog();

  • Include CorrelationId/TraceId in every log; propagate with middleware:

app.Use(async (ctx, next) =>

{

    var cid = ctx.Request.Headers["X-Correlation-ID"].FirstOrDefault() ?? Guid.NewGuid().ToString();

    ctx.Items["CorrelationId"] = cid;

    ctx.Response.Headers["X-Correlation-ID"] = cid;

    using (LogContext.PushProperty("CorrelationId", cid))

        await next();

});

  • Emit domain events and key business metrics (orders placed, failures, latencies, queue depth).

7) API gateway / proxy servers

  • Use a Gateway for routing, TLS, auth/ratelimiting, request shaping:
    • YARP (in-process .NET reverse proxy) or Ocelot; at the edge you can also use NGINX/Envoy.
  • Centralize AuthN/Z (OAuth2/OIDC via Azure AD, Auth0, or Duende IdentityServer), rate limits, request/response size limits, CORS, and header normalization at the gateway.
  • Prefer service discovery (K8s DNS) and mTLS in cluster; consider a service mesh (Istio/Linkerd) for uniform retries/mtls/traffic shifting and better telemetry without code changes.

8) Security & configuration

  • Never share DBs across services. Grant least privilege per service.
  • Secrets via Azure Key Vault / K8s secrets; bind with IOptions<T> and validate on startup.
  • Enforce TLS everywhere, JWT validation, rotating keys, and input validation (FluentValidation).

9) Deployment & runtime

  • Containerize each service (Docker), build once, promote the same image through Dev → QA → Prod.
  • Orchestrate with Kubernetes (AKS) or Azure App Services for simpler cases.
  • Blue/Green or Canary deployments; mesh/gateway can split traffic gradually.
  • Horizontal scaling via HPA (CPU/RPS/latency). Keep services stateless; externalize state (DB/cache).
  • Caching: local memory for tiny TTLs, Redis for shared cache; always set cache keys + expirations.

10) CI/CD and quality gates

  • GitHub Actions / Azure DevOps Pipelines:
    • Build → unit/integration/contract tests → SCA & SAST → container scan → publish image → deploy via Helm/Bicep/Terraform.
    • Block merges without green tests and security checks.
  • Spin ephemeral test environments per PR when feasible.

11) Diagnostics & “easy bug fixing”

  • Global exception handler → consistent error envelopes with traceId:

app.UseExceptionHandler(builder =>

{

    builder.Run(async ctx =>

    {

        var traceId = Activity.Current?.Id ?? ctx.TraceIdentifier;

        var problem = new { traceId, message = "Unexpected error." };

        ctx.Response.StatusCode = 500;

        await ctx.Response.WriteAsJsonAsync(problem);

    });

});

  • Feature flags (Azure App Configuration) for safe rollouts; dark-launch code paths.
  • Replayable messages (don’t auto-ack until processed). Keep dead-letter queues and dashboards.
  • Synthetic checks (probes that place a tiny test order in lower envs and alert on end-to-end failure).

12) Database migrations & uptime

  • Apply EF Core migrations at startup (carefully) or via migration jobs;
    • Prefer expand-and-contract: add new columns/tables (expand), release code that writes both, backfill, then remove old fields (contract).
  • For read models, permit rebuild from event streams or upstream APIs if corrupted.

13) Monitoring & alerts you’ll actually use

  • Golden signals per service: Latency, Traffic, Errors, Saturation.
  • Alert on SLIs/SLOs (e.g., p95 < 300ms, error rate < 1%), queue lag, breaker open rate, DB CPU, connection pool exhaustion.
  • Route alerts to Teams/Slack with runbooks linked (how to diagnose/rollback).

14) Local/dev ergonomics

  • Docker-compose for local broker + DB + dependencies.
  • Seed data + test fixtures; make services start with sensible defaults.
  • Makefile/PS scripts for common tasks; one-command bootstrap.

Minimal reference architecture (quick checklist)

  • ASP.NET Core services (+ gRPC if needed)
  • HttpClientFactory + Polly (timeouts/retries/circuit breaker/bulkhead)
  • Outbox + BackgroundPublisher; events over Kafka/RabbitMQ/Azure Service Bus
  • DB per service (EF Core/Dapper) + migrations (expand/contract)
  • OpenTelemetry (traces/metrics/logs) → Prometheus/Grafana/Tempo or App Insights
  • Serilog structured logging + correlation IDs
  • Health checks: /health/live, /health/ready
  • API Gateway: YARP/Ocelot; edge NGINX/Envoy; OAuth2/OIDC
  • Containerized; AKS/App Service; blue/green or canary
  • CI/CD with tests, scans, infrastructure as code
  • Feature flags; DLQs; synthetic probes; runbooks


 

No comments:

Amazon.in


Don't Copy

Protected by Copyscape Online Plagiarism Checker

Pages

Offers