0% found this document useful (0 votes)
2 views15 pages

Spring Boot Microservices

The document outlines the development of a Quantity Measurement Application using Spring Boot and microservices architecture, highlighting key concepts such as service registry with Eureka, API Gateway, and interservice communication with OpenFeign. It contrasts monolithic and microservices approaches, detailing the benefits of microservices like independent deployment and technology freedom. The document also provides practical setup instructions, including project structure, configuration files, and code examples for implementing the application.

Uploaded by

prashar85211
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views15 pages

Spring Boot Microservices

The document outlines the development of a Quantity Measurement Application using Spring Boot and microservices architecture, highlighting key concepts such as service registry with Eureka, API Gateway, and interservice communication with OpenFeign. It contrasts monolithic and microservices approaches, detailing the benefits of microservices like independent deployment and technology freedom. The document also provides practical setup instructions, including project structure, configuration files, and code examples for implementing the application.

Uploaded by

prashar85211
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Spring Boot Microservices

Quantity Measurement Application

What you will learn


​ Architecture Principles — Monolith vs Microservices
​ Spring Boot 3.x & Spring Cloud 2023.x foundations
​ Service Registry using Netflix Eureka
​ API Gateway & Routing with Spring Cloud Gateway
​ Interservice Communication using OpenFeign

Prerequisites: Java 17+, Spring Boot basics, Maven, REST APIs


Tech stack: Java 17 • Spring Boot 3.2 • Spring Cloud 2023.x • Eureka • OpenFeign • H2 • Maven

CHAPTER 01

Architecture Principles
Monolith vs Microservices — when and why to split

What is the Quantity Measurement App?


The Quantity Measurement App converts and computes units of measurement: length (km ↔
miles), weight (kg ↔ lbs), temperature (°C ↔ °F), and volume (L ↔ gallons). We will build this
as a microservices system — where each concern lives in its own independently deployable
service.

The Monolith Approach


A monolith puts everything — controllers, services, repositories, UI — into a single Spring Boot
application packaged as one JAR. This is perfectly valid for small apps, but it creates problems
as the team and feature set grow.
Monolith — Challenges Microservices — Benefits
​ One deploy for every ​ Deploy only what changed.
change. Even fixing a typo in Update measurement-service
length service means without touching user-service.
redeploying the entire app. ​ Scale independently. Run 5
​ Scaling is all-or-nothing. If instances of
conversion requests spike, measurement-service, 1 of
you must scale the whole app, user-service.
not just the busy part. ​ Technology freedom. Each
​ Technology lock-in. The service can use the tech that
whole codebase must use the suits it best.
same Java version, Spring ​ Isolated failures. If
version, and libraries. user-service crashes,
​ Failure cascades. A crash in conversions still work.
one module can bring down ​ Small, focused codebases.
the entire application. Each service is easy to
​ Large, complex codebase. understand and maintain.
Hard to onboard new
developers as the app grows.

Our Microservices Architecture


Here is how the Quantity Measurement App is split into four independent services:
Service Port Responsibility
eureka-serve :8761 Service registry — all services register and discover each other here
r

api-gateway :8080 Single entry point — routes client requests to the correct service
measurement- :8081 Core logic — handles all unit conversions (length, weight, temp,
service volume)
user-service :8082 User profiles — stores user data and conversion history

Design Principles in Play


​ Single Responsibility — Each service has one job. measurement-service only
knows about conversions.
​ Loose Coupling — Services communicate via HTTP/REST. Changing one
service's internals never breaks others.
​ High Cohesion — Everything related to measurement (model, controller,
service, repository) lives in one place.
​ Database per Service — Each service has its own database. They never share
a DB directly — only APIs.
​ Resilience — If user-service is down, measurement-service continues to serve
conversions.
Key insight — Database per Service
Microservices each own their data. measurement-service has its own H2 database; user-service has its
own. They never query each other's database directly. If user-service needs data from
measurement-service, it calls the REST API. This is the most important pattern to internalise.

Startup Order
Always start services in this order to avoid connection errors:
​ eureka-server — must be running first so others can register
​ measurement-service and user-service — register with Eureka on startup
​ api-gateway — registers with Eureka and begins routing traffic

CHAPTER 02

Spring Boot & Spring Cloud


The foundational framework and cloud-native extensions

Spring Boot in a Nutshell


Spring Boot eliminates boilerplate configuration. You get an embedded Tomcat server,
auto-configuration, and production-ready defaults out of the box. Your service is a single
runnable JAR — no separate server installation required.

Spring Cloud — What it Adds


Spring Cloud builds on Spring Boot to solve distributed system problems: service discovery,
load balancing, config management, circuit breaking, and API routing.

Spring Cloud Netflix Spring Cloud Gateway OpenFeign Spring Cloud Config
Eureka (registry), Ribbon Reactive API gateway with Declarative HTTP client — Centralised config from a
(load balancer) routing and filters write interfaces, not code Git-backed server

Maven Setup — measurement-service


[Link] — measurement-service

<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<properties>
<[Link]>2023.0.0</[Link]>
</properties>
<dependencies>
<!-- Core web: REST controllers + embedded Tomcat -->
<dependency>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Register with Eureka service registry -->
<dependency>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Feign: declarative HTTP client for inter-service calls -->
<dependency>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- H2 in-memory database (dev/test) -->
<dependency>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

Project Structure — measurement-service


Folder structure

measurement-service/
├── src/main/java/com/qm/measurement/
│ ├── [Link] ← @SpringBootApplication
│ ├── controller/
│ │ └── [Link] ← REST endpoints
│ ├── service/
│ │ └── [Link] ← Business logic
│ ├── model/
│ │ └── [Link] ← Response model
│ └── client/
│ └── [Link] ← Feign client
└── src/main/resources/
└── [Link] ← Port, Eureka, app name

Main Application Class


[Link]
@SpringBootApplication
@EnableFeignClients // Enables Feign for interservice calls
public class MeasurementServiceApplication {
public static void main(String[] args) {
[Link]([Link], args);
}
}

REST Controller
[Link]

@RestController
@RequestMapping("/api/convert")
public class ConversionController {
@Autowired
private ConversionService conversionService;
// GET /api/convert/length?from=km&to=miles&value=10
@GetMapping("/length")
public ConversionResult convertLength(
@RequestParam String from,
@RequestParam String to,
@RequestParam double value) {
return [Link](from, to, value);
}
// GET /api/convert/temperature?from=C&to=F&value=100
@GetMapping("/temperature")
public ConversionResult convertTemperature(
@RequestParam String from,
@RequestParam String to,
@RequestParam double value) {
return [Link](from, to, value);
}
}

[Link]
measurement-service/src/main/resources/[Link]
server:
port: 8081
spring:
application:
name: measurement-service # Used as service ID in Eureka!
datasource:
url: jdbc:h2:mem:measurementdb
driver-class-name: [Link]
h2:
console:
enabled: true
eureka:
client:
service-url:
defaultZone: [Link]
instance:
prefer-ip-address: true

[Link] is critical
This name becomes the service ID in Eureka. Other services and the API gateway use this exact name to look
up and communicate with your service. Think of it as your service's phone number in the registry.

CHAPTER 03

Service Registry with Eureka


Dynamic service discovery — the phone book of microservices

Why Do We Need a Service Registry?


In a monolith, a method call is just a function invocation. In microservices, services run as
separate OS processes — possibly on different machines — and their IP addresses change as
they are restarted or scaled. We cannot hardcode addresses.
Eureka solves this: every service announces its presence on startup, and any service that
needs to call another service asks Eureka for the current address at runtime.

The Registration Flow

1. Service starts 2. Registers 3. Heartbeat 4. Discovery


measurement-service Sends POST to Eureka with Sends keep-alive every 30 Other services query Eureka
boots up on :8081 name, host, port seconds to get the address

Setting Up the Eureka Server


Create a new Spring Boot project called eureka-server with the following dependency:
[Link] — eureka-server

<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

[Link]

@SpringBootApplication
@EnableEurekaServer // This single annotation activates the registry
public class EurekaServerApplication {
public static void main(String[] args) {
[Link]([Link], args);
}
}

eureka-server/[Link]

server:
port: 8761 # Standard Eureka port (convention)
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false # Server doesn't register with itself
fetch-registry: false # Server doesn't fetch its own registry
server:
wait-time-in-ms-when-sync-empty: 0

Eureka Dashboard
After starting eureka-server, open [Link] in your browser. You will see the Eureka dashboard
showing all registered instances. Bookmark this — you will use it constantly during development to verify that
services have registered correctly.

Client Configuration (measurement-service and user-service)


Adding the eureka-client dependency is sufficient — Spring Boot auto-configuration handles
registration automatically on startup. Add this to [Link]:
Eureka client config in [Link]
eureka:
client:
service-url:
defaultZone: [Link]
fetch-registry: true # Download the registry (to discover others)
register-with-eureka: true # Announce ourselves
instance:
lease-renewal-interval-in-seconds: 10 # Heartbeat every 10s
lease-expiration-duration-in-seconds: 30 # Deregister after 30s of silence

Heartbeat and Expiry


After registration, each service sends a heartbeat to Eureka every 30 seconds. If Eureka does
not receive a heartbeat for 90 seconds, it removes that instance from the registry. This ensures
the registry always reflects which services are actually alive.

CHAPTER 04

API Gateway & Routing


One entry point — intelligent routing, filtering, and load balancing

Why an API Gateway?


Without a gateway, clients must know the address and port of every service (:8081, :8082, ...).
As services are scaled, restarted, or moved, clients would break. The API Gateway provides
one stable address (:8080) for all clients. It then routes requests internally.

Routing Load Balancing Filters Rate Limiting


Maps URL paths to Round-robin across multiple Auth checks, logging, Protect backends from traffic
backend services instances via Eureka headers before/after spikes
requests

Setting Up api-gateway
[Link] — api-gateway

<!-- Spring Cloud Gateway (reactive, WebFlux-based) -->


<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Eureka client so the gateway can discover services -->
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Route Configuration
api-gateway/[Link]

server:
port: 8080
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
# Route 1: /api/convert/** → measurement-service
- id: measurement-route
uri: lb://measurement-service # lb:// = load-balanced via Eureka
predicates:
- Path=/api/convert/**
filters:
- StripPrefix=0
# Route 2: /api/users/** → user-service
- id: user-route
uri: lb://user-service
predicates:
- Path=/api/users/**
eureka:
client:
service-url:
defaultZone: [Link]

lb:// prefix explained


The lb:// prefix tells Spring Cloud Gateway to use load-balanced service discovery. Instead of a hardcoded URL
like [Link] the gateway asks Eureka for all instances running under the name
'measurement-service' and distributes requests across them using round-robin.

How a Request is Routed — Step by Step


​ Client sends: GET
[Link]
​ Gateway receives the request and checks all defined routes in order.
​ Predicate check: Does the path /api/convert/length match the pattern
/api/convert/**? YES.
​ Eureka lookup: lb://measurement-service → query Eureka → returns
[Link]
​ Gateway forwards: GET
[Link]
​ measurement-service processes the request and returns the response to the
client via the gateway.
Adding a Global Logging Filter
Filters intercept every request passing through the gateway. A global filter applies to all routes:
[Link] — inside api-gateway

@Component
public class LoggingFilter implements GlobalFilter {
private static final Logger log =
[Link]([Link]);
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
ServerHttpRequest req = [Link]();
[Link]("Incoming: {} {}",
[Link](), [Link]());
// Record the timestamp before forwarding
long start = [Link]();
return [Link](exchange).then([Link](() -> {
long duration = [Link]() - start;
[Link]("Completed in {}ms | status: {}",
duration,
[Link]().getStatusCode());
}));
}
}

CHAPTER 05

Interservice Communication
Feign clients, resilience patterns, and graceful degradation

The Scenario
When a user requests a unit conversion, measurement-service should also save a record to the
user's conversion history. That history lives in user-service. measurement-service must call
user-service over HTTP.

RestTemplate vs Feign
RestTemplate — verbose OpenFeign — recommended
​ Manual URL construction ​ Just write an interface. Spring
required generates the HTTP client.
​ Explicit response type ​ Uses Eureka service names. No
handling hardcoded URLs.
​ No integration with Eureka ​ Looks like a local method call.
service names Clean, readable code.
​ More code, more error ​ Built-in load balancing.
surface Automatically uses lb:// routing.
​ Still works — used in ​ Easy fallbacks. Integrate with
legacy Spring apps Resilience4j circuit breaker.

Step 1 — Declare the Feign Client


Inside measurement-service, create an interface that mirrors user-service's endpoints:
[Link] — inside measurement-service/client/

// 'name' must match the [Link] of user-service exactly


@FeignClient(name = "user-service")
public interface UserServiceClient {
// Maps to POST /api/users/{userId}/history in user-service
@PostMapping("/api/users/{userId}/history")
ConversionHistoryResponse saveHistory(
@PathVariable("userId") Long userId,
@RequestBody ConversionHistoryRequest request
);
// Maps to GET /api/users/{userId}/history in user-service
@GetMapping("/api/users/{userId}/history")
List<ConversionHistoryResponse> getHistory(
@PathVariable("userId") Long userId
);
}

You never implement this interface


Spring generates a full HTTP client at runtime. Feign looks up user-service in Eureka, constructs the URL,
serialises the request body to JSON, makes the call, and deserialises the response — all from the interface
declaration. You write zero HTTP code.

Step 2 — Use the Feign Client in a Service


[Link] — measurement-service
@Service
public class ConversionService {
@Autowired
private UserServiceClient userServiceClient; // Injected like any Spring bean
public ConversionResult convertLength(
String from, String to, double value, Long userId) {
// Step 1: Perform the actual unit conversion
double result = performConversion(from, to, value);
// Step 2: Save the record to user's history via user-service
ConversionHistoryRequest histReq = new ConversionHistoryRequest(
"LENGTH", from, to, value, result
);
[Link](userId, histReq);
// ^ This looks like a local call, but it's actually an HTTP POST
// to user-service :8082 via Eureka load balancing
return new ConversionResult(from, to, value, result);
}
private double performConversion(String from, String to, double value) {
// km to miles: multiply by 0.621371
if ([Link]("km") && [Link]("miles")) return value * 0.621371;
if ([Link]("miles") && [Link]("km")) return value / 0.621371;
// ... other conversions
throw new IllegalArgumentException("Unsupported conversion: " + from + " -> " + to);
}
}

Step 3 — Endpoint in user-service


[Link] — user-service

@RestController
@RequestMapping("/api/users")
public class HistoryController {
@Autowired
private HistoryRepository historyRepository;
@PostMapping("/{userId}/history")
@ResponseStatus([Link])
public ConversionHistory saveHistory(
@PathVariable Long userId,
@RequestBody ConversionHistoryRequest request) {
ConversionHistory history = new ConversionHistory();
[Link](userId);
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
[Link]([Link]());
return [Link](history);
}
}

Step 4 — Fallback for Resilience


Network calls can fail. If user-service is unavailable, we should still return the conversion result
— we simply skip saving the history. This is called graceful degradation.
[Link] — with fallback

@FeignClient(
name = "user-service",
fallback = [Link] // Used when user-service is unreachable
)
public interface UserServiceClient {
@PostMapping("/api/users/{userId}/history")
ConversionHistoryResponse saveHistory(
@PathVariable Long userId,
@RequestBody ConversionHistoryRequest request
);
}
@Component
class UserServiceClientFallback implements UserServiceClient {
private static final Logger log =
[Link]([Link]);
@Override
public ConversionHistoryResponse saveHistory(Long userId,
ConversionHistoryRequest request) {
[Link]("user-service unavailable — history not saved for user {}", userId);
return new ConversionHistoryResponse(); // Return empty response, not an error
}
}

Resilience principle
The conversion still succeeds even when user-service is down. The user gets their answer; the history is silently
skipped. This is intentional design. Never allow one service's failure to cascade and break unrelated functionality.

Complete End-to-End Request Flow


Client sends: GET
1 [Link]

API Gateway matches predicate Path=/api/convert/** → routes to


2 lb://measurement-service

Eureka lookup: lb://measurement-service → [Link]


3

measurement-service ConversionController → [Link]()


4

ConversionService calculates result: 10 km = 6.21371 miles


5

Feign call: [Link](42, {LENGTH, km, miles, 10, 6.21}) →


6 POST :8082

user-service HistoryController saves record to its H2 database


7
Response flows back: {"from":"km","to":"miles","input":10,"result":6.21}
8

Tutorial Complete
You now have everything needed to build and run the full Quantity Measurement microservices stack.

What You Have Learned


​ Architecture: When microservices make sense, the Database per Service
pattern, and design principles (SRP, loose coupling, cohesion, resilience).
​ Spring Boot + Spring Cloud: Maven setup, @SpringBootApplication, REST
controllers, auto-configuration, and the Spring Cloud ecosystem.
​ Eureka Service Registry: @EnableEurekaServer, client registration, heartbeats,
and service discovery at runtime.
​ API Gateway: Route configuration, lb:// load-balanced URIs, predicates, and
global filters.
​ Interservice Communication: @FeignClient, interface-based HTTP clients,
fallback methods, and graceful degradation.

Suggested Next Steps


​ Run the full stack: Start eureka-server → measurement-service → user-service
→ api-gateway and test with Postman or curl.
​ Add Circuit Breaker: Integrate Resilience4j CircuitBreaker with Feign for
automatic retries and open-circuit protection.
​ Spring Cloud Config Server: Centralise all [Link] files in a Git repo
and serve them from a Config Server.
​ Distributed Tracing: Add Micrometer + Zipkin to trace requests as they flow
across services.
​ Docker Compose: Containerise all four services and define them in a
[Link] for one-command startup.
​ Kubernetes: Deploy to a local k8s cluster (minikube). Kubernetes has its own
service discovery, so Eureka becomes optional.

Quick Reference — Annotations


Annotation Purpose
@SpringBootApplication Marks the main class; enables auto-configuration + component scan
@EnableEurekaServer Turns the app into a Eureka service registry
@EnableFeignClients Scans for @FeignClient interfaces and generates HTTP clients

@FeignClient(name=...) Declares an interface as a Feign HTTP client targeting the named service

@RestController Combines @Controller + @ResponseBody; returns JSON responses

@GetMapping / Maps HTTP GET/POST requests to handler methods


@PostMapping

@RequestParam Binds query string parameters (e.g. ?from=km)


@PathVariable Binds URL path variables (e.g. /{userId})
@RequestBody Deserialises the HTTP request body to a Java object

You might also like