Sathsara K.
  • Home
  • About
  • Projects
  • Case Studies
  • Blog
  • Contact
Hire me
  • Home
  • About
  • Projects
  • Case Studies
  • Blog
  • Contact
Sathsara K.
© 2026 Sathsara. Built with intent.
Back to Projects
Microservices Healthcare Platform on Kubernetes
ArchitectureJuly 1, 202613 min read

Microservices Healthcare Platform on Kubernetes

View GitHub RepositorySee Case Study
#React#Vite#Tailwind CSS#TypeScript#Spring Boot 3#Java 17#MongoDB#NGINX#Docker#Kubernetes#Minikube#GitHub Actions#GitHub Container Registry#Jenkins#JWT`

MediCare is a microservices healthcare platform built with Spring Boot, React, and Kubernetes, handling patient records, appointments, telemedicine, and billing.

Table of Contents

  • • Why Split Into Eight Services
  • • Why Domain Boundaries Instead of Technical Layers
  • • The Appointment Service and the State Synchronization Problem
  • • Telemedicine as Its Own Domain
  • • AI Symptom Checking as an Isolated, Replaceable Dependency
  • • The Role of the API Gateway
  • • Authentication Across Eight Independent Services
  • • Deployment: Kubernetes First, Docker Compose as a Fallback
  • • Frontend: A Single Page Application Talking to One Gateway
  • • Secrets Management Across Services
  • • Continuous Integration
  • • The Cost Side of This Architecture
  • • What This Architecture Actually Buys
  • • Getting Started

Building MediCare: A Microservices Healthcare Platform on Kubernetes

Healthcare software has a specific kind of pressure that most side projects do not. Patient records, appointment bookings, billing, and telemedicine sessions cannot fail quietly, and they cannot be bolted together as one large application without the whole system becoming fragile the moment any single part changes. MediCare was built around that constraint from the start, as a polyglot monorepo made up of eight independent backend services sitting behind a single API gateway, deployed through Kubernetes rather than a single server.

This post walks through the reasoning behind the architecture, not just what the stack is, but why it is shaped this way, where the harder engineering problems actually showed up, and what tradeoffs came with each decision.

Why Split Into Eight Services

The backend is horizontally sliced by domain, not by technical layer. Each service owns one piece of the healthcare workflow and nothing else:

ServicePortResponsibility
Auth3001Authentication and JWT issuing across the system
Patient3002Patient demographic data and records
Doctor3003Doctor credentials, specialties, availability
Appointment3004Booking, cancellation, state synchronization
Telemedicine3005Remote video consultation management
Payment3006Invoicing, transaction state, billing history
Notification3007Webhooks, email, and SMS alerts
AI Symptom3008Symptom diagnostics through third party AI integration

The reasoning behind this split is mostly about risk containment. A bug in the notification service should never be able to take down appointment booking, and a spike in telemedicine traffic should not slow down billing. In a monolith, all of that logic shares memory, shares deployment cycles, and shares failure modes. Splitting by domain means each service can be deployed, scaled, and debugged independently, which matters more in healthcare than in most other domains, since an outage in payment processing is a very different severity of problem than an outage in symptom checking.

Every service is built on Spring Boot 3 and Java 17, which keeps the runtime environment consistent across the backend even though each service is functionally isolated. Consistency at the runtime layer while keeping isolation at the domain layer is a deliberate middle ground. It avoids the operational cost of running eight completely different tech stacks while still getting the isolation benefits of separate services.

Why Domain Boundaries Instead of Technical Layers

A common mistake when splitting a system into services is slicing by technical concern instead of by business domain, for example a separate service for database access and another for business logic. That approach tends to reintroduce tight coupling immediately, since almost every request still has to cross both services to do anything useful.

MediCare avoids this by drawing boundaries around real world entities in a healthcare workflow. The Patient service owns everything about patient records. The Doctor service owns everything about doctor availability and credentials. Appointment sits between the two, since booking a slot is inherently a coordination problem between a patient and a doctor's schedule, not something that naturally belongs entirely inside either service. That decision, to give appointment booking its own service rather than folding it into either patient or doctor, was one of the more deliberate calls in the design, because appointment state has to stay consistent even as patient and doctor data changes independently underneath it.

The Appointment Service and the State Synchronization Problem

Of all eight services, appointment carries the most coordination complexity. A booking touches at least three other domains: it needs a valid patient, a doctor whose availability actually allows the requested slot, and eventually a payment record once the appointment is confirmed. None of those three things live inside the appointment service itself.

This is the same problem every microservice system eventually runs into, which is that business transactions rarely respect service boundaries cleanly. The appointment service handles this by treating booking as a sequence of state transitions rather than a single atomic operation. A booking moves from requested, to confirmed, to either completed or cancelled, and each transition is what gets synchronized rather than the entire record at once. This keeps the blast radius of any single failure smaller. If the payment step fails after a slot is tentatively held, the appointment can fall back to a cancelled state without leaving the doctor's calendar incorrectly locked.

Telemedicine as Its Own Domain

Splitting telemedicine out from appointment management, rather than treating a video consultation as just another property of an appointment record, was a deliberate separation. Video session management has an entirely different operational profile than booking logic. It deals with connection state, session duration, and third party video infrastructure, none of which has anything to do with how a slot gets reserved on a calendar.

Keeping telemedicine as an independent service means that if video infrastructure has a bad day, whether that is a provider outage or a spike in concurrent sessions, appointment booking for in person visits keeps working without any dependency on the health of the video layer. That kind of isolation is exactly the payoff that justifies the added operational complexity of running eight services instead of one.

AI Symptom Checking as an Isolated, Replaceable Dependency

The AI Symptom service wraps a third party AI integration for symptom diagnostics behind its own internal API. This is worth calling out specifically because it is the one service in the system most likely to change its underlying provider over time, whether due to cost, accuracy, or availability of a better model.

By keeping it isolated behind its own service boundary, swapping the underlying AI provider becomes a change contained entirely to one service, with no ripple effect into patient records, appointments, or any other part of the system. This is the same reasoning that shows up anywhere an external, evolving dependency is kept behind a stable internal boundary, so the rest of the system does not need to change when the dependency does.

The Role of the API Gateway

Every one of those eight services sits behind a single NGINX gateway on port 8080. This is not just a routing convenience. The gateway is doing real work at the edge of the system:

  • Resolving routes at layer 7, so the frontend only ever needs to know about one entry point
  • Compressing responses with GZIP to cut down payload size
  • Enforcing rate limiting to blunt denial of service attempts before they reach any backend service
  • Adding strict browser security headers, covering XSS protection, HSTS, and frame options
  • Centralizing these concerns at the gateway instead of repeating them inside every microservice matters for a simple reason. Security headers and rate limiting are the kind of thing that get forgotten in at least one service if every team implements them independently. Putting that responsibility in one place means the entire system inherits the same baseline protection, regardless of which service handles a given request.

    Authentication Across Eight Independent Services

    JWT issuance lives entirely in the Auth service, but every other service needs to validate that token on incoming requests without calling back to Auth on every single request, since that would turn one central service into a bottleneck and a single point of failure for the entire platform.

    The practical answer is shared JWT filter logic, actively maintained in the auth service and reused by the other services, rather than reimplemented independently in each one. This is one of the sharper tensions in a microservices system, since keeping services independent is the whole point, but authentication is one of the few concerns that genuinely benefits from being consistent everywhere. The tradeoff accepted here is a small, deliberate coupling on shared filter logic in exchange for avoiding either a synchronous dependency on the Auth service for every request, or worse, eight slightly different implementations of JWT validation that drift out of sync over time.

    Deployment: Kubernetes First, Docker Compose as a Fallback

    MediCare ships with a one click PowerShell setup script that provisions Minikube, downloads missing tooling like kubectl, and deploys the full platform automatically. The project also supports a plain Docker Compose path for anyone who wants to run the system without a Kubernetes cluster.

    Having both options is intentional rather than redundant. Kubernetes is the actual target environment, since it is what gives the system rolling deployments, service level scaling, and the kind of orchestration a multi service healthcare platform eventually needs in production. But requiring Kubernetes knowledge just to run the project locally raises the barrier to entry significantly. Docker Compose exists specifically to let someone spin up the whole backend with a single command while still working against the real microservice boundaries, rather than a simplified local only version of the system.

    Running on Minikube locally also means the deployment manifests used in development are structurally the same kind of artifact used in a real cluster, rather than a separate local configuration that diverges from production over time. That matters because manifest drift between local and production environments is one of the more common sources of "it worked on my machine" failures in Kubernetes based systems.

    Frontend: A Single Page Application Talking to One Gateway

    The web client is a React single page application built with Vite and styled with Tailwind v4, using a shadcn inspired component approach. Architecturally, the frontend's job is simple by design. It never talks to any of the eight backend services directly. Every request goes through the same NGINX gateway that the rest of the system uses, which means the frontend does not need to track eight different service URLs or handle inconsistent auth logic per service. One gateway, one contract, regardless of how many services sit behind it.

    This also means the frontend layer of work can proceed largely independent of backend service boundaries. As long as the gateway's contract stays stable, individual services can be refactored, rescaled, or even rewritten without the frontend needing to know.

    Secrets Management Across Services

    Healthcare data means credentials matter more than usual. MongoDB connection strings are read from environment variables, primarily SPRING_DATA_MONGODB_URI, with service specific fallback keys like MONGO_URI_PAYMENT supported for cases where Kubernetes secrets need to be scoped per service rather than shared globally.

    For CI/CD, Jenkins pulls credentials from a secret file credential matching a checked in template, so the actual values, MongoDB URIs, Stripe keys, OpenAI keys, and JWT secrets, are never present in the repository itself. This is a standard pattern, but it is worth calling out because the alternative, a single shared .env committed at some point in a project's early history, is a common and serious mistake in projects handling anything resembling patient data. The explicit instruction in the project to rotate any credential that gets accidentally pushed reflects that this is treated as a real operational risk, not a checkbox.

    Scoping secrets per service, rather than injecting one shared credential bundle into every pod, also limits what a compromised service can actually access. If the notification service were ever compromised, it has no legitimate reason to hold a payment database credential, so it should not have one available to it in the first place. That is a direct extension of the same isolation principle that justified splitting the backend into eight services in the first place, applied down at the credential level instead of the code level.

    Continuous Integration

    Every push to main triggers a GitHub Actions pipeline that builds and pushes each microservice image to GitHub Container Registry, then validates the Kubernetes manifests in the k8s/ directory before anything is considered deployable. Validating manifests as part of CI, rather than only at deploy time, catches configuration drift early, which matters more here than in a single service project, since a broken manifest for one service can silently prevent that one piece of a multi service system from rolling out while everything else continues running normally.

    Building and pushing eight separate images per push, rather than one combined image, is a direct consequence of the service isolation decision made earlier in the architecture. It costs more in CI runtime and registry storage, but it means a change to the notification service produces a new notification image only, without forcing a rebuild or a version bump across services that were never touched.

    The Cost Side of This Architecture

    It is worth being direct about what this design gives up, since every architectural decision here trades something for something else.

    Debugging a request that touches Auth, Patient, Doctor, and Appointment in sequence means correlating logs across four separate services instead of reading one stack trace in one process. Running eight services locally also means eight separate MongoDB connections, eight sets of environment variables, and eight processes to keep track of during development, even before Kubernetes enters the picture. None of that is free, and a simpler booking application for a single clinic would not justify paying for it.

    The eight way split only earns its complexity because of what the system actually has to guarantee: that a failure in one domain, whether that is a video provider outage in telemedicine or a payment gateway issue, does not take down unrelated parts of a system that is handling real patient data. That guarantee is the entire justification for the added operational overhead, and it is worth stating plainly rather than assuming the architecture is self evidently correct.

    What This Architecture Actually Buys

    The value of this design is not that microservices are inherently better than a monolith. It is that the specific failure isolation, independent scaling, and centralized security enforcement line up with what a healthcare platform actually needs. Appointment booking staying available during a billing service issue, or symptom checking scaling independently from video consultation load, are the kind of properties this architecture is built to guarantee.

    Getting Started

    powershell
    # Fastest path: one click setup
    .\setup-medicare.ps1
    # With a local gateway tunnel on Windows + Minikube
    .\setup-medicare.ps1 -PortForwardGateway
    bash
    # Frontend
    cd web-client
    npm install
    npm run dev

    The live deployment is running at medicarelk.duckdns.org, and the k8s/ directory is the clearest starting point for understanding how the eight services are wired together in production.

    All ArticlesArchitecture
    Sathsara

    Sadeesha Sathsara

    Backend & DevOps Developer

    Writing on async systems design, automated CI/CD pipelines, containerization, and backend patterns.

    More in Architecture

    Workflow Automation Platform with plugin based architectureJuly 1, 2026 · 7 min read

    Keep Reading

    More Articles

    Rubik's Cube Algorithm Development Framework
    ProjectSystems
    July 1, 2026

    Rubik's Cube Algorithm Development Framework

    Read
    Dynamic island on windows
    ProjectSystems
    July 1, 2026

    Dynamic island on windows

    Read
    Workflow Automation Platform with plugin based architecture
    ProjectArchitecture
    July 1, 2026

    Workflow Automation Platform with plugin based architecture

    Read
    Ecolife
    ProjectWeb Systems
    June 30, 2026

    Ecolife

    Read
    TriSense
    ProjectPython
    June 30, 2026

    TriSense

    Read
    Smart Cycle
    ProjectReact
    June 30, 2026

    Smart Cycle

    Read
    Nexa App
    ProjectReact Native
    June 30, 2026

    Nexa App

    Read
    Nexa Web
    ProjectReact
    June 30, 2026

    Nexa Web

    Read
    Wizard Shopping
    ProjectHTML, CSS, JavaScript, JAVA
    June 30, 2026

    Wizard Shopping

    Read
    User Management System
    ProjectReact
    June 30, 2026

    User Management System

    Read
    Digital Marketplace Havest Lanka
    ProjectReact
    June 30, 2026

    Digital Marketplace Havest Lanka

    Read
    tripma
    Projectfullstack
    June 30, 2026

    tripma

    Read
    theatre booking system
    Projectfullstack
    June 30, 2026

    theatre booking system

    Read
    Product Management
    ProjectReact
    June 30, 2026

    Product Management

    Read
    Fine System
    ProjectWeb Systems
    June 30, 2026

    Fine System

    Read