Skip to content
Senior Software Engineer, Kathmanduopen to work

Ejan
Shrestha

I modernise legacy .NET and keep an insurance platform of 50 projects boring, on purpose.

Solution architectureinsurance platform on Kubernetes

kubernetes clusterclientrabbitmq: outbox → exchangedlqcontrol-planekube-apiserveretcdschedulercontroller-managernode-16/6 podspolicyquotes and issuanceaccountingledgers, cash balancenode-26/6 podsclaimsintimation to settlementreportingregulator and MISnode-36/6 podsreinsurancefacultative, treatycommissionagent payoutspostgresprimary, writesreplica-1streaming, readsreplica-2streaming, readsidentityOIDC, replaces IS4mongodbdocuments, fileshangfirescheduled jobsobservabilityseq, loki, prometheusrediscache, idempotencynginx + TLSingressyarp gatewayrouting, auth

Three replicas per service with autoscaling, Postgres read replicas, Redis, and RabbitMQ with an outbox and a saga. Tap a pod to kill it, or replay a transaction.

Solution architectureinsurance platform on Kubernetes

kubernetes clusterclientrabbitmq: outbox → exchangedlqcontrol-planekube-apiserveretcdschedulercontroller-managernode-16/6 podspolicyquotes and issuanceaccountingledgers, cash balancenode-26/6 podsclaimsintimation to settlementreportingregulator and MISnode-36/6 podsreinsurancefacultative, treatycommissionagent payoutspostgresprimary, writesreplica-1streaming, readsreplica-2streaming, readsidentityOIDC, replaces IS4mongodbdocuments, fileshangfirescheduled jobsobservabilityseq, loki, prometheusrediscache, idempotencynginx + TLSingressyarp gatewayrouting, auth

Three replicas per service with autoscaling, Postgres read replicas, Redis, and RabbitMQ with an outbox and a saga. Tap a pod to kill it, or replay a transaction.

Legend

  • pod running
  • pod starting
  • pod killed
  • request in flight
  • reads to a replica
  • WAL streaming
  • RabbitMQ events
  • control plane to kubelet
.NET projects
50
Kubernetes deployments
28
sockets leak fixed
~390 call sites
target runtime
.NET 10
~/work

Problems I was trusted with.

Written the way I'd brief a new engineer: what broke, what I changed, what it bought us.

  1. case 01 / 05

    ~390

    new HttpClient() call sites retired

    • .NET
    • HTTP
    • Reliability

    Ending socket exhaustion across the platform

    Problem
    Under load, outbound calls failed with address-already-in-use errors and timeouts cascaded between services.
    What I did
    Traced it to roughly 390 new HttpClient() call sites stuck in TIME_WAIT, then moved them to IHttpClientFactory with named and typed clients and explicit timeouts.
    Result
    The error class disappeared from production logs, and outbound HTTP now has one place for retries and tracing.
  2. case 02 / 05

    1 ID

    follows a request across every service

    • Observability
    • Serilog
    • Loki

    One trace from gateway to database

    Problem
    Every service logged differently, and services on .NET Core 2.2 logged almost nothing useful.
    What I did
    Built a shared logging library (Serilog, Seq, Grafana Loki, global exception middleware) plus a mirror for legacy services so both generations emit the same shape.
    Result
    One correlation ID now follows a request end to end.
  3. case 03 / 05

    61%

    of projects were on unsupported .NET

    • Architecture
    • Migration

    From end-of-life .NET to .NET 10

    Problem
    Most projects targeted .NET Core 2.2, 3.1 or 5, and nobody knew which ones blocked the rest.
    What I did
    Audited every project, identified IdentityServer4 as the critical blocker, and planned a direct path to .NET 10.
    Result
    A sequenced roadmap with known risks; the first services already run on .NET 10.
  4. case 04 / 05

    v16

    Next.js, with React 19, Strapi 5 and Node 24 alongside

    • Next.js
    • Strapi
    • Release

    A public website on Next.js 16

    Problem
    The site and CMS sat on unsupported versions with known vulnerabilities.
    What I did
    Upgraded the front end, CMS and mail service together, with a rollback kit prepared before going live.
    Result
    Shipped to production with a tested rollback path.
  5. case 05 / 05

    4

    permanent branches, one promotion path

    • Process
    • Git
    • Leadership

    A release standard people follow

    Problem
    Hotfixes reached prod without reaching dev, and UAT drifted from what shipped.
    What I did
    Wrote and rolled out permanent main, dev, uat and prod branches with a clear promotion path.
    Result
    Releases are predictable enough to schedule.
~/anatomy

184 milliseconds, seven layers.

Every request crosses a proxy, a gateway, a service, an HTTP client, a database and a logger. I've broken and fixed each of them, and wrote up what I learned. Pick a span.

POST/api/policies/quote

trace 7f3a…c91e, 200 OK

0ms

  1. nginx tls
  2. └edge rate-limit
  3. └gateway yarp
  4. └policy quote
  5. └http rating
  6. └postgres insert
  7. └serilog to seq

One request through the stack I run. Every span is a layer I've built, broken or fixed. Pick one to read the article about it.

~/journey

From the controller to architecture, and back.

One feature, issuing a policy, written the way engineers write it from their first year to the architect's chair. Scroll through it.

  1. 01/07Junior

    Make it work.

    Everything lives in the controller: SQL, business rules, email, all of it. It works on my machine and nobody knows why.

    Controllers/PolicyController.cs
    [HttpPost("save")]
    public IActionResult Save(PolicyDto dto)
    {
        var conn = new SqlConnection("Server=prod;User=sa;Password=123");
        conn.Open();
        var sql = "INSERT INTO Policy VALUES ('" + dto.Name + "', " + dto.Premium + ")";
        new SqlCommand(sql, conn).ExecuteNonQuery();
    
        if (dto.Premium > 100000)
            new SmtpClient("mail").Send("boss@co", "Big policy!", dto.Name, "");
    
        return Ok("saved"); // works on my machine
    }
    stage 01/07
  2. 02/07Mid-level

    Make it layered.

    Controllers get thin, a service layer appears and the first unit tests show up. Mostly checking that mocks return mocks.

    Services/PolicyService.cs
    public class PolicyService : IPolicyService
    {
        private readonly IPolicyRepository _repo;
        private readonly IEmailService _email;
    
        public PolicyService(IPolicyRepository repo, IEmailService email)
            => (_repo, _email) = (repo, email);
    
        public async Task<int> CreateAsync(PolicyDto dto)
        {
            var id = await _repo.AddAsync(dto.ToEntity());
            if (dto.Premium > 100_000) await _email.NotifyUnderwriterAsync(id);
            return id;
        }
    }
    stage 02/07
  3. 03/07Senior

    Make it right.

    Red, green, refactor. The test comes first, and SOLID stops being an interview answer and starts shaping the code.

    SOLID, with insurance examples

    Single responsibility

    One reason to change. Pricing knows nothing about email.

    class PremiumCalculator  { Money Calculate(Risk risk) { /* … */ } }
    class UnderwriterNotifier { Task Notify(PolicyId id) { /* … */ } }
    Tests/IssuePolicyTests.cs
    // Red: written before Policy.Issue exists
    [Fact]
    public void Issuing_above_the_limit_needs_underwriting()
    {
        var policy = Policy.Quote(Premium.Of(250_000m, "NPR"));
    
        var result = policy.Issue(Limits.Default);
    
        result.Should().BeOfType<NeedsUnderwriting>();
        policy.Status.Should().Be(PolicyStatus.Referred);
    }
    
    // Green: the smallest Issue() that passes.
    // Refactor: now the rest of the design can move safely.
    stage 03/07
  4. 04/07Tech lead

    Make it last.

    Clean architecture inside each module, a modular monolith around them. Boundaries get agreed in ADRs before they get argued in code review.

    Dependencies point inward. The domain knows nothing about EF Core or HTTP. Pick a ring to see its code.

    Domain/Policy.cs

    public sealed class Policy : AggregateRoot
    {
        public PolicyStatus Status { get; private set; } = PolicyStatus.Quoted;
        public Money Premium { get; private set; }
    
        public Result Issue(Limits limits)
        {
            if (Premium > limits.AutoIssue) return Refer("needs underwriting");
    
            Status = PolicyStatus.Issued;
            Raise(new PolicyIssued(Id, Premium));
            return Result.Ok();
        }
    }
    stage 04/07
  5. 05/07Architect

    Make it scale.

    Modules become services when a team or a load profile demands it, not before. Messages over RabbitMQ, an outbox for reliability, a saga instead of a distributed transaction.

    Reinsurance/PolicyIssuedConsumer.cs
    public sealed class PolicyIssuedConsumer(ITreaties treaties, IPublishEndpoint bus)
        : IConsumer<PolicyIssued>
    {
        public async Task Consume(ConsumeContext<PolicyIssued> ctx)
        {
            var msg = ctx.Message;
            var cession = await treaties.Cede(msg.PolicyId, msg.Premium);
    
            // No distributed lock: each service commits locally, the saga reacts
            await (cession.IsRejected
                ? bus.Publish(new CessionRejected(msg.PolicyId))
                : bus.Publish(new PolicyCeded(msg.PolicyId, cession.Share)));
        }
    }
    stage 05/07
  6. 06/07Solution architect

    It depends.

    Every answer starts with a trade-off table. The solution architecture at the top of this page is the job: every box a decision, every arrow a trade-off.

    docs/adr/0007-cross-service-writes.md
    # ADR 0007: Cross-service writes
    
    Status: accepted
    
    | Option               | Consistency | Ops cost | Team fit |
    |----------------------|-------------|----------|----------|
    | Distributed tx (2PC) | strong      | high     | poor     |
    | Outbox + saga        | eventual    | medium   | good     |
    | Shared database      | strong      | low      | coupling |
    
    Decision: outbox + saga over RabbitMQ.
    Consequence: every consumer is idempotent (Redis keys).
    stage 06/07
  7. 07/07Friday, 4:55 pm

    And then the deadline.

    Ten years of TDD, DDD, clean architecture and microservices later, the hotfix ships in the controller. Again.

    Controllers/PolicyController.csworks on prod
    [HttpPost("hotfix")]
    public async Task<IActionResult> Fix()
    {
        // TODO: move this into the domain after the release
        await _db.Database.ExecuteSqlRawAsync(
            "UPDATE Policy SET Status = 'Issued' WHERE Id = 4021");
    
        return Ok("fixed"); // it's Friday
    }
    stage 07/07
~/experience

From data desk to platform owner.

BSc Computer Science and Information Technology
Vedas College, 2017 – 2022

  1. Jan 2025 – presentnow

    Senior Software Engineer

    Himalayan Everest Insurance, Kathmandu, hybrid

    • Own backend reliability and modernisation for an insurance platform of 50 .NET projects, deployed as 28 Kubernetes workloads, across accounting, claims, reinsurance and reporting.
    • Leading the upgrade from .NET Core 2.2, 3.1 and 5 to .NET 10, including the IdentityServer4 replacement.
    • Built structured logging and tracing across every service.
    • Upgraded the public website and CMS to Next.js 16, React 19, Strapi 5 and Node 24.
  2. Jul 2022 – Nov 2024

    .NET Developer

    EKbana Solutions, Lalitpur, on-site

    • Designed backend services for enterprise insurance workflows with ASP.NET Core, PostgreSQL, EF Core and Dapper.
    • Built REST APIs and integrations for customer portals, payments and external partners.
    • Ran background workflows on Hangfire with idempotency, retries and failure recovery.
    • Shipped on Docker, Linux, Nginx/Traefik and Kubernetes; worked on IdentityServer, JWT and RBAC.
  3. Jan 2022 – Jun 2022

    Dotnet Developer

    NBIT, Lalitpur

    • Built and maintained .NET Framework applications for client projects.
  4. Nov 2017 – Jun 2022

    Data Specialist

    CloudFactory, Kathmandu

    • Delivered high-accuracy data work for international clients while completing my degree.
~/writing

Notes from production.

14 articles on .NET, infrastructure and running a team. Most started as an incident.

~/contact

Got a system that needs a steady hand?

Open to senior and lead roles in backend architecture and .NET modernisation, in Kathmandu or remote.

Local time in Kathmandu: KTM, UTC+5:45

ejan@kathmandu: ~

➜ ~ whoami

Ejan Shrestha, Senior Software Engineer, Kathmandu, Nepal

➜ ~ contact --email

➜ ~

Ejan Shresthaइजन श्रेष्ठ

Senior Software Engineer, backend and .NET modernisation

Senior software engineer in Kathmandu with 4+ years shipping .NET for insurance and enterprise. I own reliability and modernisation for an insurance platform of 50 .NET projects on Kubernetes: structured logging and tracing across old and new services, the plan off end-of-life .NET to .NET 10, and the fix for socket exhaustion across ~390 call sites.

Selected impact

  • ~390Ending socket exhaustion across the platform. The error class disappeared from production logs, and outbound HTTP now has one place for retries and tracing.
  • 1 IDOne trace from gateway to database. One correlation ID now follows a request end to end.
  • 61%From end-of-life .NET to .NET 10. A sequenced roadmap with known risks; the first services already run on .NET 10.
  • v16A public website on Next.js 16. Shipped to production with a tested rollback path.
  • 4A release standard people follow. Releases are predictable enough to schedule.

Experience

  1. Senior Software Engineer, Himalayan Everest Insurance

    Jan 2025 – present

    Kathmandu, hybrid

    • Own backend reliability and modernisation for an insurance platform of 50 .NET projects, deployed as 28 Kubernetes workloads, across accounting, claims, reinsurance and reporting.
    • Leading the upgrade from .NET Core 2.2, 3.1 and 5 to .NET 10, including the IdentityServer4 replacement.
    • Built structured logging and tracing across every service.
    • Upgraded the public website and CMS to Next.js 16, React 19, Strapi 5 and Node 24.
  2. .NET Developer, EKbana Solutions

    Jul 2022 – Nov 2024

    Lalitpur, on-site

    • Designed backend services for enterprise insurance workflows with ASP.NET Core, PostgreSQL, EF Core and Dapper.
    • Built REST APIs and integrations for customer portals, payments and external partners.
    • Ran background workflows on Hangfire with idempotency, retries and failure recovery.
    • Shipped on Docker, Linux, Nginx/Traefik and Kubernetes; worked on IdentityServer, JWT and RBAC.
  3. Dotnet Developer, NBIT

    Jan 2022 – Jun 2022

    Lalitpur

    • Built and maintained .NET Framework applications for client projects.
  4. Data Specialist, CloudFactory

    Nov 2017 – Jun 2022

    Kathmandu

    • Delivered high-accuracy data work for international clients while completing my degree.