Split the monolith — register services with Eureka, route through Spring Cloud Gateway, centralize settings with Config Server, call services with OpenFeign, and add a circuit breaker.
Why: in a microservices system, instances come and go, so services should not hard-code each other’s addresses. A Eureka server is a registry where each service registers itself and looks others up by name. Note: the server enables @EnableEurekaServer; every other service is a client.
// the registry service
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication { }# a client service registers itself by name
spring:
application:
name: book-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/Why: a gateway is the single front door — clients hit one address and it routes each path to the right backend service, applying auth, rate limits, and CORS in one place. Note: lb://book-service resolves the target through Eureka, so the gateway never needs a fixed host.
# gateway service — application.yml
spring:
cloud:
gateway:
routes:
- id: books
uri: lb://book-service # load-balanced via Eureka
predicates:
- Path=/api/books/**
- id: orders
uri: lb://order-service
predicates:
- Path=/api/orders/**Why: rather than each service carrying its own config, a Config Server serves all of it from one place (often a Git repo), so you change a setting once. Note: the server enables @EnableConfigServer; clients fetch their config on startup by application name.
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication { }# config server points at a git repo of property files
spring:
cloud:
config:
server:
git:
uri: https://github.com/example/bookstore-configWhy: OpenFeign turns an HTTP call into a Java interface — you declare the method, it makes the request. Note: the name matches the Eureka-registered service, so calls are load-balanced and you never write a URL. Enable it with @EnableFeignClients.
@FeignClient(name = "book-service") // resolved via Eureka
public interface BookClient {
@GetMapping("/api/books/{id}")
Book getBook(@PathVariable Long id); // a remote call, looks local
}
// inject BookClient and call getBook(1L) like any beanWhy: if a downstream service is slow or down, retrying endlessly drags your service down too. A circuit breaker (Resilience4j) "trips" after repeated failures and immediately runs a fallback instead. Note: @CircuitBreaker names the rule and the fallback method to call when it is open.
@Service
public class CatalogService {
@CircuitBreaker(name = "bookService", fallbackMethod = "cachedBook")
public Book getBook(Long id) {
return bookClient.getBook(id); // remote call that might fail
}
// called instead when the circuit is open
public Book cachedBook(Long id, Throwable cause) {
return Book.placeholder(id);
}
}