6 분 소요


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 endpoints are divided and managed separately

Creating Database

  • I’ll also use docker-compose to 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
    
  • 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 up Eureka Client in other service applications

Dependency

  • SpringBoot
  • Eureka Server

Eureka Annotation Configuration

  • Set @EnableEurekaServer on 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

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 @EnableEurekaClient on 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 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 service interface and serviceimpl class

    
    // 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 @EnableEurekaClient on 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 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 @EnableEurekaClient on 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/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

  1. Start Eureka Server
  2. Start OrderService and PaymentService servers
  3. (optional) Run multiple instances of OrderService and PaymentService with different ports
  4. 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 8080 set in the API Gateway
  • Order service and payment service can be accessed at localhost:8080/order and localhost:8080/payment respectively
  • When running multiple servers for order service and payment service, you can see that the API Gateway automatically switches ports for connections

댓글남기기