SpringBoot와 Spring Cloud Gateway 사용하기
## 개요
- 마이크로서비스 아키텍처는 여러 서비스를 배포 할 수 있도록 하는 기술이다.
- 각각의 서버들에
Request를 보낼 때 인증을 거쳐야 하는데, 마이크로서비스가 늘어날수록 서비스의 수 만큼 인증을 받아야 하는 번거로움이 있다 - 다수의 인증을 줄여주고, 한번의 인증으로 여러서비스를 한번에 사용 할 수 있도록 도와주는 Spring Cloud Gateway를 사용 해 보도록 하자
## Overview
- Microservices architecture is a technology that enables deployment of multiple services.
- When sending
Requests to each server, authentication is required, and as microservices increase, there's the inconvenience of having to authenticate for each service. - Let's use Spring Cloud Gateway, which reduces multiple authentications and helps use multiple services with a single authentication.
## API Gateway
- API Gateway는 다수의 서버를 하나로 묶어, 외부 애플리케이션 혹은 클라이언트에 대한 하나의 진입점이라고 생각하면 된다
- 외부 어플리케이션 및 외부 클라이언트들은 마이크로 서비스에 직접 엑세스 하는 것이 제한되어 있기 때문에 그 사이의 중개자 역할을 한다
API Gateway가 필요한 이유
- 마이크로서비스 기반 애플리케이션에서는 일반적으로 서비스 마다 하나의 서버에 배포된다
- 이 때, 서비스를 요창하는 주체는 각각의 서비스의 호스트 및 포트를 기억해야 하고, 보안 인증을 거쳐야 한다
- 위에서 설명했듯, 이 일련의 과정들을 API Gateway로 해결이 가능하다
API Gateway의 단점
- 모든 요청이 API gateway를 거쳐지나가기 때문에 성능이 저하될 수 있음
- Request를 할 때 API Gateway에서 오류가나면 요청이 더이상 처리되지 않음
## API Gateway
- Think of API Gateway as a single entry point for external applications or clients by bundling multiple servers together
- External applications and clients are restricted from directly accessing microservices, so it acts as an intermediary between them
Why API Gateway is Needed
- In microservices-based applications, each service is typically deployed on its own server
- At this point, the entity requesting the service must remember each service's host and port, and go through security authentication
- As explained above, this series of processes can be solved with an API Gateway
Disadvantages of API Gateway
- Performance may degrade because all requests pass through the API gateway
- If an error occurs in the API Gateway when making a request, the request will no longer be processed
## Spring Cloud Gateway 생성하기
- 배달 서비스를 제공하는 어플리케이션의 일부분을 만들어보려고 한다.
- 자세하게 구현은 되지 않겠지만
endpoint가 나뉘고 따로 관리가 된다는 점을 생각하면서 작업 해 보도록 하려고 한다
데이터베이스 생성
- 마찬가지로
docker-compose를 사용해서 데이터베이스를 구축하려고한다
## Creating Spring Cloud Gateway
- I'm going to create part of an application that provides delivery services.
- Although it won't be implemented in detail, I'll work with the concept that
endpointsare divided and managed separately
Creating Database
- I'll also use
docker-composeto set up the database
version: "3"
services:
mysql-docker:
image: arm64v8/mariadb
ports:
- "3306:3306"
environment:
TZ: Asia/Seoul
MYSQL_ROOT_PASSWORD: qwerqwer123
MYSQL_DATABASE: deliveryapp
MYSQL_USER: paul
MYSQL_PASSWORD: qwerqwer123
container_name: "docker-maria"
volumes:
- ./docker-databasea/maria:/var/lib/mysql
- `docker-compose -f docker-compose-database.yml up -d` 명령어로 데이터베이스를 실행시킨다
- Run the database with the command `docker-compose -f docker-compose-database.yml up -d`
### 마이크로 서비스 생성 1. 유레카 서버 생성
- 마이크로 서비스를 서로 이어주고 통신하기 위해서는
Eureka Server를 생성해야 한다 Eureka Server를 생성하고, 다른 서비스 어플리케이션에서는Eureka Client를 세팅 해야 한다
Dependency
- SpringBoot
- Eureka Server
Eureka Annotation 설정
- Spring Boot starter 클래스에
@EnableEurekaServer를 설정 해 주어서 유레카 서버임을 알려준다
### Creating Microservices 1. Creating Eureka Server
- To connect and communicate between microservices, you need to create an
Eureka Server - Create an
Eureka Server, and set upEureka Clientin other service applications
Dependency
- SpringBoot
- Eureka Server
Eureka Annotation Configuration
- Set
@EnableEurekaServeron the Spring Boot starter class to indicate it's a Eureka server
package com.example.springeurekasimple;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class SpringEurekaSimpleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEurekaSimpleApplication.class, args);
}
}application properties
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
### 마이크로 서비스 생성 2. 주문서비스 서버 생성
- 주문서비스를 제공하는 서버를 만든다
- Order는 데이터베이스 테이블명시를 꼭 해주어야한다 (database 에약어 때문)
order service Dependency
- SpringBoot
- Eureka Client
- mariadb
- jpa
- lombok
order service Eureka Annotation 설정
- Spring Boot starter 클래스에
@EnableEurekaClient를 설정 해 주어서 유레카 서버와 연결된 Client임을 명시 해준다
### Creating Microservices 2. Creating Order Service Server
- Create a server that provides order services
- Order must explicitly specify the database table name (due to database reserved words)
order service Dependency
- SpringBoot
- Eureka Client
- mariadb
- jpa
- lombok
order service Eureka Annotation Configuration
- Set
@EnableEurekaClienton the Spring Boot starter class to indicate it's a Client connected to the Eureka server
package com.example.springbootsimpleorderserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class SpringBootSimpleOrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSimpleOrderServerApplication.class, args);
}
}order service applciation properties
```properties
server.port=9009
#Service Id
spring.application.name=ORDERING-SERVICE
#Publish Application(Eureka 등록)
eureka.client.service-url.default-zone=http://localhost:8761/eureka
#id for eureka server
eureka.instance.instance-id=${spring.application.name}:${random.value}
## Database
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/deliveryapp
spring.datasource.username=paul
spring.datasource.password=qwerqwer123
spring.jpa.database-platform=org.hibernate.dialect.MariaDB103Dialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
```
order service Domain
- domain 패키지 생성
- 테이블 이름 설정
- Create domain package
- Set table name
package com.example.springbootsimpleorderserver.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "ORDERS")
public class OrderDomain {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Integer id;
private String orderCode;
private String orderObject;
private String orderStatus;
private Integer orderPrice;
}order service Repository
- JPARepository를 상속받는 OrderRepository 생성
- repository 패키지 생성 후 하위에 작성
- Create OrderRepository that extends JPARepository
- Create repository package and write below it
package com.example.springbootsimpleorderserver.repository; import com.example.springbootsimpleorderserver.domain.OrderDomain; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface OrderRepository extends JpaRepository<OrderDomain, Integer> { }
order service ServiceImpl, Service Interface
- order의 service레이어 작성
- service 패키지 작성 후,
service인터페이스,serviceimpl클래스 생성
- Write the service layer for order
- After creating the service package, create
serviceinterface andserviceimplclass
// service interface
package com.example.springbootsimpleorderserver.service;
import com.example.springbootsimpleorderserver.domain.OrderDomain;
import java.util.List;
public interface OrderService {
public OrderDomain createOrder(OrderDomain order);
public OrderDomain getOrder(Integer orderId);
public OrderDomain updateOrder(OrderDomain order, Integer orderId) throws Exception;
public void deleteOrder(Integer orderId) throws Exception;
public List<OrderDomain> getAllOrders();
}
// serviceImpl
package com.example.springbootsimpleorderserver.service;
import com.example.springbootsimpleorderserver.domain.OrderDomain;
import com.example.springbootsimpleorderserver.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepository;
@Override
public OrderDomain createOrder(OrderDomain order) {
return orderRepository.save(order);
}
@Override
public OrderDomain getOrder(Integer orderId) {
return orderRepository.findById(orderId).orElse(null);
}
@Override
public OrderDomain updateOrder(OrderDomain order, Integer orderId) throws Exception {
/*
order status 변화주기
status: ready -> processing -> shipped -> delivered
*/
OrderDomain orderObject = orderRepository.findById(orderId).orElse(null);
if (orderObject == null) {
throw new Exception("Order not found");
}
switch (orderObject.getOrderStatus()) {
case "ready":
orderObject.setOrderStatus("processing");
break;
case "processing":
orderObject.setOrderStatus("shipped");
break;
case "shipped":
orderObject.setOrderStatus("delivered");
break;
default:
throw new Exception("order status is not ready");
}
return orderRepository.save(orderObject);
}
@Override
public void deleteOrder(Integer orderId) throws Exception {
OrderDomain orderObject = orderRepository.findById(orderId).orElse(null);
if (orderObject != null) {
orderRepository.delete(orderObject);
} else {
throw new Exception("Order not found");
}
}
@Override
public List<OrderDomain> getAllOrders() {
return orderRepository.findAll();
}
}order service Controller
- 컨트롤러 패키지 생성 후 하위에 API 엔트포인트 작성
- Create controller package and write API endpoints below it
package com.example.springbootsimpleorderserver.controller;
import com.example.springbootsimpleorderserver.domain.OrderDomain;
import com.example.springbootsimpleorderserver.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
OrderService orderService;
@PostMapping()
public OrderDomain createOrder(@RequestBody OrderDomain order) {
return orderService.createOrder(order);
}
@GetMapping()
public ResponseEntity<List<OrderDomain>> getOrder() {
return ResponseEntity.ok(orderService.getAllOrders());
}
@GetMapping("/{id}")
public OrderDomain getOrder(@PathVariable Integer id) {
return orderService.getOrder(id);
}
@PutMapping("/{id}")
public OrderDomain updateOrder(@PathVariable Integer id, @RequestBody OrderDomain order) throws Exception {
return orderService.updateOrder(order, id);
}
@DeleteMapping("/{id}")
public String deleteOrder(@PathVariable Integer id) throws Exception {
orderService.deleteOrder(id);
return "Order with id: " + id + " deleted.";
}
}
### 마이크로 서비스 생성 3. 결제서비스 서버 생성
- 결제서비스를 제공하는 서버를 만든다
- 상세 로직말고 컨트롤러만 작성해서, Endpoint를 확인한다
Payment service Dependency
- SpringBoot web
- Eureka Client
Payment service Eureka Annotation 설정
- Spring Boot starter 클래스에
@EnableEurekaClient를 설정 해 주어서 유레카 서버와 연결된 Client임을 명시 해준다
### Creating Microservices 3. Creating Payment Service Server
- Create a server that provides payment services
- Write only the controller without detailed logic to verify the Endpoint
Payment service Dependency
- SpringBoot web
- Eureka Client
Payment service Eureka Annotation Configuration
- Set
@EnableEurekaClienton the Spring Boot starter class to indicate it's a Client connected to the Eureka server
package com.example.springbootsimplepaymentserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class SpringBootSimplePaymentServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSimplePaymentServerApplication.class, args);
}
}Payment service applciation properties
server.port=9560
#Service Id
spring.application.name=PAYMENT-SERVICE
#Publish Application(Eureka ??)
eureka.client.service-url.default-zone=http://localhost:8761/eureka
#id for eureka server
eureka.instance.instance-id=${spring.application.name}:${random.value}Payment service Controller
- 결제 상태를 확인 하는 간단한 컨트롤러만 작성
- 컨트롤러 패키지 생성 후 하위에 작성
- Write only a simple controller to check payment status
- Create controller package and write below it
package com.example.springbootsimplepaymentserver.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private String port;
@GetMapping("/status")
public ResponseEntity<String> getStatus() {
return ResponseEntity.ok("Payment server is running on port " + port);
}
}
### 마이크로 서비스 생성 4. API Gateway 생성
- API Gateway를 제공하는 서버를 만든다
- OrderService, PaymentService 서버를 각각 연결 해 준다
API Gateway Dependency
- Spring Reactive Web
- Eureka Discovery Client
- Gateway
API Gateway Eureka Annotation 설정
- Spring Boot starter 클래스에
@EnableEurekaClient를 설정 해 주어서 유레카 서버와 연결된 Client임을 명시 해준다
### Creating Microservices 4. Creating API Gateway
- Create a server that provides API Gateway
- Connect OrderService and PaymentService servers respectively
API Gateway Dependency
- Spring Reactive Web
- Eureka Discovery Client
- Gateway
API Gateway Eureka Annotation Configuration
- Set
@EnableEurekaClienton the Spring Boot starter class to indicate it's a Client connected to the Eureka server
package com.example.springbootsimplegateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class SpringBootSimpleGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSimpleGatewayApplication.class, args);
}
}API Gateway application properties
server.port=8080
spring.application.name=GATEWAY-SERVICE
eureka.client.service-url.defaultZone=http://localhost:8761/eurekaAPI Gateway Configuration
- API Gateway에 외부로부터 `Request`가 들어왔을 때 처리 해 줄 라우터 설정을 작성 해 준다
- config 패키지 하위에 작성
- Write router configuration to handle incoming `Request`s to the API Gateway from external sources
- Write under config package
package com.example.springbootsimplegateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringCloudGatewayRouting {
@Bean
public RouteLocator configurationRoute(RouteLocatorBuilder rlb) {
return rlb.routes()
.route("paymentId", r -> r.path("/payment/**").uri("lb://PAYMENT-SERVICE"))
.route("orderId", r -> r.path("/order/**").uri("lb://ORDER-SERVICE"))
.build();
}
}
- 위에서 작성한 PAYMENT-SERVICE, ORDER-SERVICE의 이름을 잘 기억 해주고, uri에 등록 해 주어 자동으로 매핑되고 라우팅 할 수 있도록 해 준다
- Remember the names PAYMENT-SERVICE and ORDER-SERVICE written above, and register them in the uri so they can be automatically mapped and routed
## API Gateway 및 Euerka 서버 테스트
- Euerka Server시작
- OrderService, PaymentService 서버 시작
- (option) 각각 OrderSerivce와 PaymentService를 포트만 달리해서 여러개 띄우기
- API Gateway 서버 시작
아래와 같은 로그를 확인했다면, 유레카 서비스와 각각 MSA어플리케이션이 잘 매핑이 되었고 등록이 서로 잘 된 것이다
## Testing API Gateway and Eureka Server
- Start Eureka Server
- Start OrderService and PaymentService servers
- (optional) Run multiple instances of OrderService and PaymentService with different ports
- Start API Gateway server
If you see logs like the following, the Eureka service and each MSA application have been properly mapped and registered with each other
INFO 71481 --- [nio-8761-exec-2] c.n.e.registry.AbstractInstanceRegistry : Registered instance ORDERING-SERVICE/ORDERING-SERVICE:a94db2fd0ad472b21714a9ebcb717736 with status UP (replication=false)
INFO 71481 --- [nio-8761-exec-3] c.n.e.registry.AbstractInstanceRegistry : Registered instance ORDERING-SERVICE/ORDERING-SERVICE:a94db2fd0ad472b21714a9ebcb717736 with status UP (replication=true)
INFO 71481 --- [nio-8761-exec-4] c.n.e.registry.AbstractInstanceRegistry : Registered instance GATEWAY-SERVICE/192.168.31.235:GATEWAY-SERVICE:8080 with status UP (replication=false)
INFO 71481 --- [nio-8761-exec-6] c.n.e.registry.AbstractInstanceRegistry : Registered instance GATEWAY-SERVICE/192.168.31.235:GATEWAY-SERVICE:8080 with status UP (replication=true)
INFO 71481 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Running the evict task with compensationTime 0ms
INFO 71481 --- [nio-8761-exec-7] c.n.e.registry.AbstractInstanceRegistry : Registered instance PAYMENT-SERVICE/PAYMENT-SERVICE:e94906e028963a992b89eea164abe00d with status UP (replication=false)
INFO 71481 --- [nio-8761-exec-8] c.n.e.registry.AbstractInstanceRegistry : Registered instance PAYMENT-SERVICE/PAYMENT-SERVICE:e94906e028963a992b89eea164abe00d with status UP (replication=true)
INFO 71481 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Running the evict task with compensationTime 4ms
## 정리
- Euerka Server는 처음 Spring Euerka에서 설정한 8761 포트로 접속하면 확인 할 수 있다
- 포트를 각각 달리 했음에도 불구하고, API Gateway에서 설정한 port
8080으로 접속이 가능하다 - order서비스, payment서비스는 각각
localhost:8080/order와localhost:8080/payment으로 접속이 가능하다. order서비스,payment서비스를 각각 여러대의 서버를 띄워 확인하면 API Gateway가 자동적으로 포트를 바꿔 연결 해 주는 것을 확인할 수 있다
## Summary
- Eureka Server can be accessed through port 8761, which was initially set in Spring Eureka
- Despite using different ports, access is possible through port
8080set in the API Gateway - Order service and payment service can be accessed at
localhost:8080/orderandlocalhost:8080/paymentrespectively - When running multiple servers for
order serviceandpayment service, you can see that the API Gateway automatically switches ports for connections