Spring Boot Microservicesでアプリを作成してみた【販売管理システム】

## 1. サービスの構築

(1)What are Spring Boot Microservices?

Spring Boot Microservicesとは、Spring Bootフレームワークを利用してマイクロサービスアーキテクチャを実装するためのフレームワークで、マイクロサービスアーキテクチャは、単一のアプリケーションを小さな、独立したサービスに分割するソフトウェア開発手法。これらのサービスは、それぞれ独自の責任を持ち、独自のテクノロジーで構築およびデプロイできる。

(2)Services to build

- Product Service: Create and view Products, acts as Product Catalog
- Order Service: Can Order Product
- Inventory Service: Can check if product is in stock or not
- Notification Service: Can send notifications, after order is placed
- Order Service, Inventory Service and Notification Service are going to 
interact with each other

## 2. Building Services

① プロジェクトを作成する

```
Project: Maven
Language: Java
SpringBoot: 3.2.5

Group: com.techie.microservices
Artifact: product-service
Name : product-service
Description: Product Service
Package name: com.techie.microservices.product-service

Packagint: Jar 
Java: 21

Dependencies

Lombok
Spring Web
Spring Data MongoDB
Testcontainers
```

② mvn clean verify をビルドする

```
mvn clean verify
```

③ docker-compose.ymlを作成する

```docker-compose.yml
version: '4'
services:
  mongodb:
    image: mango:7.0.5
    container_name: mongodb
    ports:
      - '27017:27017'
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
      MONGO_INITDB_DATABASE: product-service
    volumes:
      - ./data:/data/db
```
```
docker compose up -d
```
④resouces/application.propertiesを編集する
```resouces/application.properties
spring.data.mongodb.uri=mongodb://root:password@localhost:27017/product-service?authSource=admin

```
⑤Java/com/teich/microservice/productにmodel/Product.javaを作成する

```model/Product.java
package com.techie.microservices.product.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.math.BigDecimal;

@Document(value = "product")
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class Product {
    @Id
    private String id;
    private String name;
    private String description;
    private BigDecimal price;
}
```

⑥Java/com/teich/microservice/productにRepository/ProductRepository.javaを作成する

```Repository/ProductRepository.java
package com.techie.microservices.product.repository;

import org.springframework.data.mongodb.repository.MongoRepository;

import com.techie.microservices.product.model.Product;

public interface ProductRepository extends MongoRepository<Product, String> {

}
```
⑦Java/com/teich/microservice/productにcontroller/ProductController.javaを作成する

```controller/ProductController.java
package com.techie.microservices.product.controller;

import com.techie.microservices.product.dto.ProductRequest;
import com.techie.microservices.product.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/product")
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ProductResponse createProduct(@RequestBody ProductRequest productRequest) {
        return productService.createProduct(productRequest);
    }

    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public List<ProductResponse> getAllProducts() {
        return productService.getAllProducts();
    }
}
```
⑧Java/com/teich/microservice/productにdto/ProductRequest.javaを作成する

```dto/ProductRequest.java
package com.techie.microservices.product.dto;

import java.math.BigDecimal;

public record ProductRequest(String id, String name, String description, BigDecimal price) {

}
```
⑨Java/com/teich/microservice/productにdto/ProductResponse.javaを作成する

```dto/ProductResponse.java
package com.techie.microservices.product.dto;

import java.math.BigDecimal;

public record ProductResponse(String id, String name, String description, BigDecimal price) {

}
```

⑩Java/com/teich/microservice/productにservice/ProductService.javaを作成する

```service/ProductService.java
package com.techie.microservices.product.dto;

import java.math.BigDecimal;

public record ProductResponse(String id, String name, String description, BigDecimal price) {

}
```
⑪test/java/com/teich/microservice/productservice/ProductServiceApplicationTests.javaを編集する

```ProductServiceApplicationTests.java
package com.techie.microservices.productservice;

import static org.mockito.ArgumentMatchers.*;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.MongoDBContainer;

import io.restassured.RestAssured;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ProductServiceApplicationTests {

	@ServiceConnection
	static MongoDBContainer mongoDBContainer = new MongoDBContainer("mango:7.0.5");
	@LocalServerPort
	private int port;

	@BeforeEach
	void setup() {
		RestAssured.baseURI = "http://localhost";
		RestAssured.port = port;
	}

	static {
		mongoDBContainer.start();
	}

	@Test
	void shouldCreateProduct() {
		String requestBody = """
				{
				    "name": "iPhone 15",
				    "description": "iPhone 15 is a smartphone from Apple",
				    "price": 1000
				}
				""";

		RestAssured.given()
				.contentType("application/json")
				.body(requestBody)
				.when()
				.post("/api/product")
				.then()
				.statusCode(201)
				.body("id", Matchers.notNullValue())
				.body("name", Matchers.equalTo("iPhone 15"))
				.body("description", Matchers.equalTo("iPhone 15 is a smartphone from Apple"))
				.body("price", Matchers.equalTo("1000"));
	}
}
```
⑫test/java/com/teich/microservice/productservice/TestServiceApplicationTests.javaを編集する

```TestServiceApplicationTests.java
package com.techie.microservices.productservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.utility.DockerImageName;

import com.techie.microservices.product.ProductServiceApplication;

@TestConfiguration(proxyBeanMethods = false)
public class TestProductServiceApplication {

	@Bean
	@ServiceConnection
	MongoDBContainer mongoDbContainer() {
		return new MongoDBContainer(DockerImageName.parse("mongo:latest"));
	}

	public static void main(String[] args) {
		SpringApplication.from(ProductServiceApplication::main).with(TestProductServiceApplication.class).run(args);
	}
}
```

## 3. Order Service

①プロジェクトを作成する
```
Project: Maven
Language: Java
SpringBoot: 3.2.5

Group: com.techie.microservices
Artifact: order-service
Name : order-service
Description: Product Service
Package name: com.techie.microservices.order

Packagint: Jar 
Java: 21

Dependencies

Lombok
Spring Web
Spring Data JPA
MySQL Driver
Testcontainers
Flyway Migration
```
② docker-compose.ymlを作成する

```docker-compose.yml
version: '4'
services:
  mongodb:
    image: mango:7.0.5
    container_name: mongodb
    ports:
      - '27017:27017'
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
      MONGO_INITDB_DATABASE: product-service
    volumes:
      - ./data:/data/db
```
```
docker compose up -d
```

③resouces/application.propertiesを編集する

```resouces/application.properties
spring.data.mongodb.uri=mongodb://root:password@localhost:27017/product-service?authSource=admin

```
④docker/mysql/int.sqlを作成する

```docker/mysql/int.sql
CREATE DATABASE IF NOT EXISTS order_service;
```

⑤Java/com/teich/microservice/orderserviceにmodel/Order.javaを作成する

```model/Order.java
package com.techie.microservices.order.model;

import java.math.BigDecimal;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(name = "t_orders")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.INDENTITY)
    private Long id;
    private String orderNumber;
    private String skuCode;
    private BigDecimal price;
    private Integer quantity;
}
```

⑥Java/com/teich/microservice/orderserviceにmodel/OrderRepository.javaを作成する

```model/OrderRepository.java
package com.techie.microservices.order.model;

import com.techie.microservices.order.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {

}
```
⑦Java/com/teich/microservice/orderにservice/OrderService.javaを作成する

```service/OrderService.java
package com.techie.microservices.order.service;

import com.techie.microservices.order.dto.OrderRequest;
import com.techie.microservices.order.model.Order;
import com.techie.microservices.order.model.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;

    public void placeOrder(OrderRequest orderRequest) {
        Order order = new Order();
        order.setOrderNumber(UUID.randomUUID().toString());
        order.setPrice(orderRequest.price());
        order.setSkuCode(orderRequest.skuCode());
        order.setQuantity(orderRequest.quantity());
        orderRepository.save(order);
    }
}
```
⑧Java/com/teich/microservice/productにdto/OrderRequest.javaを作成する

```dto/OrderRequest.java
package com.techie.microservices.order.dto;

import java.math.BigDecimal;

public record OrderRequest(Long id, String orderNumber, String skuCode, BigDecimal price, Integer quantity) {
}
```
⑨Java/com/teich/microservice/productにcontroller/OrderController.javaを作成する

```controller/OrderController.java
package com.techie.microservices.order.controller;

import com.techie.microservices.order.dto.OrderRequest;
import com.techie.microservices.order.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/order")
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @PostMapping
    @ResponseStatus
    public String placeOrder(@RequestBody OrderRequest orderRequest) {
        orderService.placeOrder(orderRequest);
        return "Order Placed Successfully";
    }
}
```
⑩resouces/db.migrationにV1_init.sqlを作成する

```resouces/db.migration/V1_init.sql
CREATE TABLE `t_orders`
(
    `id` bigint(28) NOT NULL AUTO_INCREMENT,
    `order_number` varchar(255) DEFAULT NULL,
    `sku_code` varchar(255),
    `price` decimal(19, 2),
    `quantity` int(11),
    PRIMARY KEY (`id`)
);
```

## 4. Inventory Service

①プロジェクトを作成する
```
Project: Maven
Language: Java
SpringBoot: 3.2.5

Group: com.techie.microservices
Artifact: inventory-service
Name : inventory-service
Description: Inventory Service
Package name: com.techie.microservices.inventory

Packagint: Jar 
Java: 21

Dependencies

Lombok
Spring Web
Spring Data JPA
MySQL Driver
Testcontainers
Flyway Migration
```
② docker-compose.ymlを作成する

```docker-compose.yml
version: '4'
services:
  mysql:
    image: mysql:8.3.0
    container_name: mysql
    ports:
      - '3306:3306'
    environment:
      MYSQL_ROOT_PASSWORD: mysql
    volumes:
      - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
      - ./docker/mysql/data:/var/lib/mysql
```
③ mysqlフォルダとinit.sqlを作成する

```mysql/init.sql
CREATE DATABASE IF NOT EXISTS inventory_service;
```

④resouces/application.propertiesを編集する

```resouces/application.properties
spring.application.name=inventory-service
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/inventory_service
spring.datasource.username=root
spring.datasource.password=${DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=note
server.port=8082
```
⑤resouces/db.migrationフォルダとV1_init.sqlを作成する

```resouces/db.migration/V1_init.sql
CREATE TABLE `t_inventory`
(
    `id` bigint(28) NOT NULL AUTO_INCREMENT,
    `sku_code` varchar(255),
    `quantity` int(11),
    PRIMARY KEY (`id`)
);
```

⑥resouces/db.migrationにV2_add_inventory.sqlを作成する

```resouces/db.migration/V2_add_inventory.sql
insert into t_inventory(quantity,sku_code)
values(100, 'iphone_15'),
      (100, 'pixel_8'),
      (100, 'galaxy_24'),
      (100, 'oneplus_12');

```
⑦java/com/teich/microservice/inventoryにmodelファイルinventory.javaを作成する

```model/inventory.java
package com.techie.microservices.inventory.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(name = "t_inventory")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class inventory {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String skuCode;
    private Integer quantity;
}
```
⑧java/com/teich/microservice/inventoryにrepositoryファイルinventoryRepository.javaを作成する

```repository/inventoryRepository.java
package com.techie.microservices.inventory.repository;

import com.techie.microservices.inventory.model.Inventory;
import org.springframework.data.jpa.repository.JpaRepository;

public interface inventoryRepository extends JpaRepository<Inventory, Long> {
    boolean inventortRepository.existsBySkuCodeAndQuantityIsGreaterThanEqual(String skuCode, Integer quantity);
}
```
⑨Java/com/teich/microservice/inventoryにservice/inventoryService.javaを作成する

```service/inventoryService.java
package com.techie.microservices.inventory.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class inventoryService {

    private final InventortRepository inventortRepository;

    public boolean isInStock(String skuCode, Integer quantity) {
        return inventortRepository.existsBySkuCodeAndQuantityIsGreaterThanEquals(skuCode, quantity);
    }
}
```

⑩Java/com/teich/microservice/inventoryにcontroller/inventoryController.javaを作成する

```controller/inventoryController.java
package com.techie.microservices.inventory.controller;

import com.techie.microservices.inventory.service.InventoryService;
import com.techie.microservices.inventory.service.inventoryService;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import lombok.RequiredArgsConstructor;

@RequestMapping
@RestController("/api/inventory")
@RequiredArgsConstructor
public class inventoryController {
    private final inventoryService inventoryService;

    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public boolean isInStock(@RequestParam String skuCode, @RequestParam Integer quantity) {
        return inventoryService.isInStock(skuCode, quantity);
    }
}
```

## 5. Spring Cloud OpenFeign & Testing with Wiremock

①order-serviceのSpring Cloudオープンファインライブラリ(OpenFeign)をpom.xmlに追加する

```pom.xml

<spring-cloud.version>2023.0.1</spring-cloud.version>
//省略
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
			<scope>test</scope>
		</dependency>
//省略

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
```
②Java/com/teich/microservice/orderにOrderServiceApplication.javaを編集する

```Java/com/teich/microservice/order/OrderServiceApplication.java
package com.techie.microservices.order;

import com.example.library.ServiceConnection;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootTest(webEnvironment = SpringBootTest.webEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
class OrderServiceApplicationTests {

	@ServiceConnection
	static MySQLContainer MySQLContainer = new MySQLContainer("mysql:8.3.0");
	@LovalServerPort
	private Integer port;

	@BeforeEach
	void setup() {
		RestAssured.baseURI = "http://localhost";
		RestAssured.port = port;
	}

	static {
		MySQLContainer.start();
	}
}
```
③Java/com/teich/microservice/orderにclientフォルダとinventoryClient.javaを作成する

```client/inventoryClient.java
package com.techie.microservices.order.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "inventory", url = "${inventory.url}")
public class inventoryClient {

    @RequestMapping(method = RequestMethod.GET, value = "/api/inventory")
    boolean isInStock(@RequestParam String skuCode, @RequestParam Integer quantity);
}
```
④Java/com/teich/microservice/orderにrepositoryフォルダとOrderRepository.javaを作成する
```repository/OrderRepository.java
package com.techie.microservices.order.repository;

import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {

}
```
⑤Java/com/teich/microservice/order/service/OrderService.javaを編集する

```service/OrderService.java
package com.techie.microservices.order;

import com.example.library.ServiceConnection;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootTest(webEnvironment = SpringBootTest.webEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
class OrderServiceApplicationTests {

	@ServiceConnection
	static MySQLContainer MySQLContainer = new MySQLContainer("mysql:8.3.0");
	@LovalServerPort
	private Integer port;

	@BeforeEach
	void setup() {
		RestAssured.baseURI = "http://localhost";
		RestAssured.port = port;
	}

	static {
		MySQLContainer.start();
	}
}
```

⑥docker-compose.ymlを編集する

```docker-compose.yml
version: '4'
services:
  mongodb:
    image: mango:7.0.5
    container_name: mongodb
    ports:
      - '27017:27017'
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
      MONGO_INITDB_DATABASE: product-service
    volumes:
      - ./data:/data/db
```

⑦resouces/application.propertiesを編集する

```resouces/application.properties
spring.application.name=order-service
spring.datasource.url=jdbc:mysql://localhost:3316/inventory-service
spring.datasource.username=root
spring.datasource.password=${DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=none
server.port=8081
inventory.url=http://localhost:8082
```


⑧test/java/com/teich/microservice/order/TestServiceApplication.javaを編集する

```test/java/com/teich/microservice/order/TestServiceApplication.java
package com.techie.microservices.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;

import com.techie.microservices.order.OrderServiceApplicationTests;

@TestConfiguration(proxyBeanMethods = false)
public class TestOrderServiceApplication {

	@Bean
	@ServiceConnection
	MySQLContainer<?> mysqlContainer() {
		return new MySQLContainer<>(DockerImageName.parse("mysql:latest"));
	}

	public static void main(String[] args) {
		SpringApplication.from(OrderServiceApplicationTests::main).with(TestOrderServiceApplication.class).run(args);
	}
}
```
⑨test/java/com/teich/microservice/orderにstubsフォルダにinventoryClientStub.javaを作成する

```stubs/inventoryClientStub.java
package com.techie.microservices.order.stubs;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.client.WireMock.oResponse;

public class inventoryClientStub {

    public void stubInventoryCall(String skuCode, Integer quantity) {
        stubFor(get(urlEqualTo("/api/inventory?skuCode=" + skuCode + "&quantity=" + quantity))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBoby("true")));

    }
}
```


⑩test/java/com/teich/microservice/order/OrderServiceApplicationTests.javaを編集する

```test/java/com/teich/microservice/order/OrderServiceApplicationTests.java
package com.techie.microservices.order;

import org.flywaydb.database.mysql.MySQLConnection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebMock(port = 0)
class OrderServiceApplicationTests {

	@ServiceConnection
	static MySQLConnection mySQLConnection = new MySQLConnection("mysql:8.3.0");
	@LocalServerPort
	private Integer port;

	@BeforeEach
	void setup() {
		RestAssured.baseURI = "http://localhost";
		RestAssured.port = port;
	}

	static {
		mySQLConnection.start();
	}

	@Test
	void shouldSubmitOrder() {
		String submitOrderJson = """

								{
				"skuCode":"iphone_15",
				"price":10000,
				"quantity":1

								}
									""";

		inventoryClientStub.stubInventoryCall("iphone_15", 1);

		var responseBodyString = RestAssured.given();
		.contentType("application/json")
		.body(submitOrderJson)
		.when()
		.post("/api/order")
	}

}
```
⑪test/java/com/teich/microservice/orderにresoucesフォルダにapplication.propertiesを作成する

```resouces/application.properties
inventory.url=http://localhost:${wiremock.server.port}
```

## 6. Spring Cloud Gateway MVC

①プロジェクトを作成する
```
Project: Maven
Language: Java
SpringBoot: 3.2.5

Group: com.programming.techie
Artifact: api-gateway
Name : api-gateway
Description: API Gateway
Package name: com.programming.techie.gateway

Packagint: Jar 
Java: 21

Dependencies

Gateway
```
②main/java/com/programming/techie/gatewayにroutesフォルダにRoutes.javaを作成する

```routes/Routes.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.function.RequestPredicates;
import org.springframework.web.servlet.function.RoterFunction;
import org.springframework.web.servlet.function.ServerResponse;

@Configuration
public class Routes {

    @Bean
    public RouterFunction<ServerResponse> productServiceRoute() {
        return GatewayRouterFunctions.route("product_service")
                .route(RequestPredicates.path("/api/product"), HandlerFunctions.http("http://localhost:8080"))
                .build();
    }

    @Bean
    public RouterFunction<ServerResponse> orderServiceRoute() {
        return GatewayRouterFunctions.route("order_service")
                .route(RequestPredicates.path("/api/order"), HandlerFunctions.http("http://localhost:8081"))
                .build();
    }

    @Bean
    public RouterFunction<ServerResponse> inventoryServiceRoute() {
        return GatewayRouterFunctions.route("inventory_service")
                .route(RequestPredicates.path("/api/inventory"), HandlerFunctions.http("http://localhost:8082"))
                .build();
    }
}
```

③resouces/application.propertiesを編集する

```resouces/application.properties
spring.application.name=api-gateway
server.port=9000
```
## 7. Security using Keyloak

①docker-compose.ymlを作成する

```docker-compose.yml
version: '3.8'
services:
  keycloak-mysql:
    container_name: keycloak-mysql
    image: mysql:8
    volumes:
      - ./volume-data/mysql_keycloak_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: keycloak
      MYSQL_USER: keycloak
      MYSQL_PASSWORD: password
  keycloak:
    container_name: keycloak
    image: quay.io/keycloak/keycloak:24.0.1
    command: [ "start-dev", "--import-realm" ]
    environment:
      DB_VENDOR: MYSQL
      DB_ADDR: mysql
      DB_DATABASE: keycloak
      DB_USER: keycloak
      DB_PASSWORD: password
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    ports:
      - "8181:8080"
    volumes:
      - ./docker/keycloak/realms/:/opt/keycloak/data/import/
    depends_on:
      - keycloak-mysql
```
```
docker compose up -d
```
② pom.xmlを編集する

```pom.xml
//省略
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-security-oauth2-resource-server</artifactId>
	<version>5.8.0</version>
</dependency>
//省略
```
③main/java/com/programming/techie/gatewayにconfigフォルダとSecurityconfig.javaを作成する

```config/Securityconfig.java
package com.programming.techie.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class Securityconfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity.authorizeHttpRequests(authorize -> authorize.anyRequest()
                .authenticated())
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
                .build();
    }
}
```



## 参考サイト
[Spring Boot Microservices Tutorial - Part 1 - Building Services](https://www.youtube.com/watch?v=NpdG3lmKJ5g)
[Spring Boot Microservices Tutorial - Part 2 - Order Service](https://www.youtube.com/watch?v=vdAkGFCq-M4)
[Spring Boot Microservices Tutorial - Part 3 - Inventory Service](https://www.youtube.com/watch?v=sNu_9aqSk5Y)
[Spring Boot Microservices Tutorial - Part 4 - Spring Cloud OpenFeign & Testing with Wiremock](https://www.youtube.com/watch?v=GpqnYd8VR3k&t=919s)
[Spring Boot Microservices Tutorial - Part 5 - Spring Cloud Gateway MVC](https://www.youtube.com/watch?v=2AtL7qvTjOg)
[Spring Boot Microservices Tutorial - Part 6 - Security using Keyloak](https://www.youtube.com/watch?v=Uf2fwy10c00)
[Spring Boot Microservices Tutorial - Part 7 - Document REST API with OpenAPI](https://www.youtube.com/watch?v=lYXZwnRk4Jg&t=598s)