Suraj’s Substack

Suraj’s Substack

Share this post

Suraj’s Substack
Suraj’s Substack
Microservices: Building a City of Apps
Copy link
Facebook
Email
Notes
More

Microservices: Building a City of Apps

Suraj Raikwar's avatar
Suraj Raikwar
Jun 08, 2025

Share this post

Suraj’s Substack
Suraj’s Substack
Microservices: Building a City of Apps
Copy link
Facebook
Email
Notes
More
Share

“Think of your software as a bustling city — each service is a building, and Kubernetes is the city planner ensuring everything runs smoothly.”

1. Introduction: From Monoliths to Microservices

In the past, apps were like massive skyscrapers: a single structure containing everything. These “monoliths” worked well initially, but as the app grew, adding features or fixing bugs became like trying to renovate a 50-story building while people still lived in it.

Enter microservices — a way to break down that skyscraper into smaller, self-contained buildings, each responsible for one function. Each building (or service) can be updated, scaled, or repaired independently. Kubernetes (K8s) is like the city planner ensuring all buildings stay connected, powered, and operational.

2. How Kubernetes Fits In

Managing dozens (or hundreds) of services manually is impossible. Kubernetes acts as the city planner:

  1. Deployment Management: Ensures every service (building) is deployed in the right place.

  2. Scaling: Adds more replicas of a service during high traffic (like hiring more cashiers during a sale).

  3. Self-Healing: Restarts services (containers) if they crash.

  4. Networking: Connects all the buildings through defined “roads” (APIs).

3. Understanding Microservices with Kubernetes

The Problem with Monolithic Architectures

Imagine an e-commerce platform where authentication, user management, and order processing are part of a single monolithic application. What happens if one part crashes? The whole system goes down. Scaling is also difficult — if the order system is slow, we can’t scale it separately.

Microservices solve this by separating concerns into independent services. Each service:

  • Runs in its own container (Docker).

  • Communicates via REST APIs or message queues.

  • Can be scaled independently.

Kubernetes (K8s) takes this further by:

  • Deploying services automatically.

  • Handling failures by restarting crashed services.

  • Scaling services based on traffic.

4. Microservices: A Real-World Analogy

Imagine an e-commerce platform like Amazon. Instead of bundling every functionality — login, search, cart, payment — into one monolith, microservices break them into smaller, independent services.

For instance:

  • The Authentication Service handles user logins.

  • The Profile Service manages user details.

  • The Payment Service processes transactions.

These services communicate with each other via APIs, making the system flexible and scalable.

5. Designing Microservices: A Simple Example

5.1 Architecture of Our Microservices System

Our system consists of:

  • API Gateway: Central entry point for handling requests.

  • Auth Service: Manages login and JWT authentication.

  • User Service: Handles user data storage.

  • Order Service: Processes and manages orders.

Here’s a high-level architecture diagram:

  1. The API Gateway routes incoming user requests.

  2. The Auth Service issues JWT tokens for authentication.

  3. The User Service manages profiles and retrieves user details.

  4. The Order Service fetches user orders after validating authentication.

5.2 Implementing Microservices with Spring Boot and Kotlin

  • Service 1: Authentication Service (JWT Token Generation)

This service handles user login and generates JWT tokens.

Controller:

@RestController
@RequestMapping("/auth")
class AuthController(private val authService: AuthService) {
  @PostMapping("/login")
    fun login(@RequestBody request: LoginRequest): ResponseEntity<String> {
        val token = authService.authenticate(request.username, request.password)
        return ResponseEntity.ok(token)
    }
}
data class LoginRequest(val username: String, val password: String)

Service:

@Service
class AuthService {
    fun authenticate(username: String, password: String): String {
        if (username == "admin" && password == "password") {
            return "mock-jwt-token"
        }
        throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials")
    }
}

This service returns a JWT token upon successful authentication.

  • Service 2: User Service (Fetching User Profile)

Once authenticated, users can retrieve their profiles.

Controller:

@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {
  @GetMapping("/{id}")
    fun getUserById(@PathVariable id: Long): ResponseEntity<User> {
        val user = userService.getUserById(id)
        return ResponseEntity.ok(user)
    }
}
data class User(val id: Long, val name: String, val email: String)

Service:

@Service
class UserService {
    fun getUserById(id: Long): User {
        return User(id, "John Doe", "john.doe@example.com")
    }
}
  • Service 3: Order Service (Fetching User Orders)

The Order Service retrieves all orders placed by a user.

Controller:

@RestController
@RequestMapping("/orders")
class OrderController(private val orderService: OrderService) {
  @GetMapping("/{userId}")
    fun getUserOrders(@PathVariable userId: Long): ResponseEntity<List<Order>> {
        val orders = orderService.getOrdersByUserId(userId)
        return ResponseEntity.ok(orders)
    }
}

data class Order(val orderId: Long, val userId: Long, val totalAmount: Double)

Service:

@Service
class OrderService {
    fun getOrdersByUserId(userId: Long): List<Order> {
        return listOf(
            Order(1, userId, 49.99),
            Order(2, userId, 29.99)
        )
    }
}

6. Deploying Microservices with Kubernetes

6.1 Kubernetes Deployment for Authentication Service

apiVersion: apps/v1
kind: Deployment
metadata:
  name: auth-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: auth-service
  template:
    metadata:
      labels:
        app: auth-service
    spec:
      containers:
      - name: auth-service
        image: your-auth-service-image:latest
        ports:
        - containerPort: 8080

The User Service and Order Service would have similar configurations.

6.2 Kubernetes Service for Authentication

apiVersion: v1
kind: Service
metadata:
  name: auth-service
spec:
  selector:
    app: auth-service
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

Each microservice is deployed inside Kubernetes as a container, and Kubernetes manages them.

7. Understanding Kubernetes Structure: How Microservices Fit into the Big Picture

Now that we’ve deployed our microservices in Kubernetes, let’s break down the core components of Kubernetes and how they work together to manage our services.

7.1 Key Kubernetes Components Explained

Kubernetes is designed to manage, scale, and maintain containerized applications. Here’s how the key components fit into our system:

1. Pods (The Smallest Unit in Kubernetes)

  • A pod is the smallest deployable unit in Kubernetes.

  • Each pod contains one or more containers.

  • In our example, the Auth Service, User Service, and Order Service each run inside their own pods.

  • If traffic increases, Kubernetes spins up more pods automatically.

Example:

Pod 1 → Auth Service (1 container)
Pod 2 → User Service (1 container)
Pod 3 → Order Service (1 container)

If traffic increases, Kubernetes might create 3 more replicas of each service..

2. Deployments (Managing Scaling and Updates)

  • A Deployment ensures that a specified number of replicas of a pod are running at all times.

  • If a pod crashes, the deployment restarts it automatically.

  • Example: If we set replicas: 3, Kubernetes ensures that 3 instances of each microservice are always running.

Deployment Example (for Auth Service):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: auth-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: auth-service
  template:
    metadata:
      labels:
        app: auth-service
    spec:
      containers:
      - name: auth-service
        image: your-auth-service-image:latest
        ports:
        - containerPort: 8080

Here, Kubernetes ensures three pods are always running for the Auth Service.

3. Services (Networking & Load Balancing)

  • Kubernetes Services expose our pods to the outside world.

  • Since pods are ephemeral (they can die and restart with a new IP), a Service ensures they can always be accessed via a stable endpoint.

  • Load Balancing: If three instances (pods) of our Order Service are running, the service distributes traffic between them.

Service Example (for User Service):

apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8081

This ensures that no matter which pod is running, requests to user-service are correctly routed.

4. API Gateway (Entry Point for Requests)

  • Instead of calling microservices directly, we use an API Gateway as the central entry point.

  • The API Gateway routes requests based on paths.

  • Example:

  • /auth/login → Routes to Auth Service

  • /users/{id} → Routes to User Service

  • /orders/{userId} → Routes to Order Service

7.2 How Do Microservices Run in Multiple Pods?

  1. Deployment starts multiple replicas of each service.

  2. Pods are distributed across different nodes (physical or virtual machines).

  3. Services act as stable entry points, ensuring traffic is routed correctly.

  4. The API Gateway orchestrates communication between services.

Final Flow:

  1. The user sends a request to api.myapp.com/orders/1.

  2. The API Gateway forwards the request to the Order Service.

  3. The Order Service Pod processes the request and fetches user data from the User Service.

  4. Kubernetes ensures that if one pod fails, another takes over.

7.3 Kubernetes in Action: Scaling Under Load

Let’s say the number of users doubles overnight.

  • Kubernetes detects increased load and automatically creates new pods.

  • Instead of 3 instances per service, it scales up to 6.

  • When traffic decreases, it scales down automatically, saving resources.

8. Key Takeaways: Why Use Microservices?

  • Scalability → Scale individual services independently.

  • Fault Tolerance → If one service fails, others remain functional.

  • Technology Freedom → Different services can use different languages or frameworks.

9. Conclusion: The Future of Microservices

Microservices are not just a trend — they’re a necessity in modern applications. By splitting services into independent, scalable components, you create resilient, flexible applications that can evolve with time. Kubernetes further simplifies deployment, making it an essential tool for backend developers.

“The best architectures are those that allow seamless change.”

Are you working with microservices? What challenges have you faced? Share your thoughts in the comments below!

Share this post

Suraj’s Substack
Suraj’s Substack
Microservices: Building a City of Apps
Copy link
Facebook
Email
Notes
More
Share
© 2025 Suraj
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share

Copy link
Facebook
Email
Notes
More