# Microservices with Spring Cloud — Monolith vs Microservices, Eureka and API Gateway

Source: https://skills101.in/learn/spring-boot/spring-boot-microservices-eureka-gateway — Skills101 free tutorial (Advanced Java & Spring Boot). Updated 2026-11-09.

Our Course API is a **monolith**: one app does everything. That's the right start. But as teams and traffic grow, companies split systems into **microservices**. Interviews for product companies almost always ask about them.

![A monolith is one deployable with courses, orders, payments and notifications sharing one database; microservices are separate small services that each own their database](https://skills101.in/learn-img/spring-boot/monolith-micro.svg)

## 1. Monolith vs microservices

| | Monolith | Microservices |
|---|---|---|
| Deploy | one unit | each service on its own |
| Scale | everything together | only the busy service |
| Team | one codebase | each team owns a service |
| Data | one shared database | **each service owns its data** |
| Failure | one bug can take down all | failures can be contained (with care) |
| Complexity | simple | network calls, discovery, tracing, eventual consistency |

**Start with a well-structured monolith**; split when a part needs independent scaling, deployment or ownership. Microservices are a solution to an organisational problem, not a goal.

## 2. Our system

![api-gateway on port 8080 routes /api/courses to course-api and /api/orders to order-service; both register with the Eureka discovery-server; order-service calls course-api through Feign with a circuit breaker and publishes OrderPlacedEvent to Kafka](https://skills101.in/learn-img/spring-boot/micro-arch.svg)

Four Spring Boot apps, all built and tested:

| Service | Port | Job |
|---|---|---|
| `discovery-server` | 8761 | Eureka registry: who is running where |
| `api-gateway` | 8080 | single entry point; routes by path |
| `course-api` | 8081 | the API from lessons 4–11 |
| `order-service` | 8082 | places orders (next two lessons) |

Versions: Spring Boot **4.1.2** with Spring Cloud **2025.1.4** (the release train is imported as a BOM in each `pom.xml`).

## 3. Service discovery with Eureka

Services start and stop, scale to many instances and get new IP addresses. Hard-coding `http://10.0.0.7:8081` breaks. Instead every service **registers** its address with a **discovery server**, and callers ask for it **by name**.

```java
// File: discovery-server/src/main/java/com/skills101/discovery/DiscoveryServerApplication.java
package com.skills101.discovery;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer   // services register here and look each other up by name
public class DiscoveryServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(DiscoveryServerApplication.class, args);
    }
}
```

```properties
# File: discovery-server/src/main/resources/application.properties
spring.application.name=discovery-server
server.port=8761

# the registry itself doesn't register with anyone
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
```

Clients just add `spring-cloud-starter-netflix-eureka-client` and say where Eureka is — `course-api` registers itself as **course-api** (its `spring.application.name`):

```properties
# File: course-api/src/main/resources/application.properties (excerpt)
spring.application.name=course-api
server.port=8081
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
```

Our test starts the registry on a random port and checks that `GET /eureka/apps` answers. Open `http://localhost:8761` to see the dashboard of registered instances.

## 4. API Gateway

Clients shouldn't need to know every service. The **gateway** is the one door: it routes requests, and is the natural place for cross-cutting work — authentication, rate limiting, CORS, logging.

```properties
# File: api-gateway/src/main/resources/application.properties
spring.application.name=api-gateway
server.port=8080
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

# Route 1: /api/courses/** → any healthy instance of course-api ("lb://" = load-balanced via Eureka;
#          ${course-api.uri:...} lets a test or another environment point it elsewhere)
spring.cloud.gateway.server.webmvc.routes[0].id=courses
spring.cloud.gateway.server.webmvc.routes[0].uri=${course-api.uri:lb://course-api}
spring.cloud.gateway.server.webmvc.routes[0].predicates[0]=Path=/api/courses/**
spring.cloud.gateway.server.webmvc.routes[0].filters[0]=AddResponseHeader=X-Served-By, api-gateway

# Route 2: /api/orders/** → order-service
spring.cloud.gateway.server.webmvc.routes[1].id=orders
spring.cloud.gateway.server.webmvc.routes[1].uri=lb://order-service
spring.cloud.gateway.server.webmvc.routes[1].predicates[0]=Path=/api/orders/**
```

- **`lb://course-api`** — "load-balanced": ask Eureka for the healthy instances of `course-api` and spread requests across them.
- **Predicates** decide which route matches (`Path=/api/courses/**`); **filters** change requests or responses (`AddResponseHeader`).
- This is Spring Cloud Gateway's **server-webmvc** flavour (servlet-based, like the rest of our apps); there's also a reactive WebFlux version.

Our gateway test starts the gateway plus a fake course-api, then checks that `GET /api/courses/1` is forwarded, the body comes back unchanged, the `X-Served-By: api-gateway` header is added, and unknown paths get 404:

```java
// File: api-gateway/src/test/java/com/skills101/gateway/ApiGatewayApplicationTests.java (excerpt)
void forwardsCoursesAndAddsHeader() throws Exception {
    HttpResponse<String> res = HttpClient.newHttpClient().send(
            HttpRequest.newBuilder(URI.create("http://localhost:" + port + "/api/courses/1")).build(),
            HttpResponse.BodyHandlers.ofString());
    assertThat(res.statusCode()).isEqualTo(200);
    assertThat(res.body()).isEqualTo("{\"id\":1,\"title\":\"Java Full Stack\"}");
    assertThat(res.headers().firstValue("X-Served-By")).contains("api-gateway");
```

## 5. Other building blocks (know the names)

- **Config Server** — central configuration for all services (Spring Cloud Config).
- **Distributed tracing** — follow one request across services (Micrometer Tracing + Zipkin/Jaeger).
- **Database per service** and **Saga** pattern for multi-service transactions (no shared transactions across services).
- **Kubernetes** often replaces Eureka for discovery in production (its Services do the same job).

## Common mistakes

- Splitting too early or too small ("nano-services") — all the cost, none of the benefit.
- Services sharing one database — they're then coupled, just over the network.
- Hard-coded host names instead of discovery or DNS.
- No timeouts or fallbacks on calls between services (next lesson).

## Quick recap

- Monolith first; microservices when parts need independent scaling, deployment or ownership.
- Eureka: services register by name; callers look them up.
- The API gateway is the single entry point: routes (`lb://name`), predicates, filters.
- Each service owns its data.


Practice quiz and free certificate: https://skills101.in/learn/spring-boot/spring-boot-microservices-eureka-gateway