SpringBoot와 Spring Cloud Gateway 사용하기 Using SpringBoot with 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
- 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
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 databaseversion: "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 -
Run the database with the command
docker-compose -f docker-compose-database.yml up -d
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 serverpackage 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
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 serverpackage 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 Registration)
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
- 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
- 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
-
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 { /* Change 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
-
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."; } }
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 serverpackage 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 Registration) 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); } }
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 serverpackage 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/eureka
API Gateway Configuration
- Write router configuration to handle incoming
Requests 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(); } } - Remember the names PAYMENT-SERVICE and ORDER-SERVICE written above, and register them in the uri so they can be automatically mapped and routed
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
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
댓글남기기