본문 바로가기

MSA

Spring Cloud로 개발하는 마이크로서비스 애플리케이션_Catalogs and Orders Microservice

728x90

 

Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)

Users Microservice와 Spring Cloud Gateway 연동

Controller에서 port 출력

@GetMapping("/health_check")
public String status() {
    return String.format("It's Working in User Service on PORT %s", 
            env.getProperty("local.server.port"));
}

 

 

apigateway-service 프로젝트 route 정보 입력

application.yml

routes:
  - id: user-service
    uri: lb://USER-SERVICE
    predicates:
      - Path=/user-service/**

 

두 개의 프로젝트를 실행한다.

유레카 대시보드 확인

 

health_check 메서드 동작 확인

게이트웨이를 통과하는 uri 주소 입력 

 

@RequestMapping("/")
public class UserController {
    @GetMapping("/health_check")
    public String status() {
        return String.format("It's Working in User Service on PORT %s",
                env.getProperty("local.server.port"));
    }
}

 

@GetMapping("/user-service/health_check")
public String status() {
    return String.format("It's Working in User Service on PORT %s",
            env.getProperty("local.server.port"));
}

@GetMapping("/user-service/welcome")
public String welcome() {
    //return env.getProperty("greeting.message");
    return greeting.getMessage();
}

 

Users Microservice - 사용자 조회

User의 전체 사용자 목록과 상세 정보 보기 기능을 추가해보자.

UserController의 각 메서드 uri 매핑 주소에 prefix 격으로 '/user-service/'가 들어간다. 그러므로 클래스 블럭에 있는 @RequestMapping() 어노테이션에 해당 uri를 기재하고, 각 메서드 XXXMapping()에 적혀있는 '/user-service/'는 지워주자.

@RequestMapping("/user-service/")
public class UserController { ... }

 

 

  • ResponseUser.java
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseUser {
    private String email;
    private String name;
    private String userId;

    private List<ResponseOrder> orders;
}

 

  • UserDto.java
private List<ResponseOrder> orders;

 

 

@JsonInclude(JsonInclude.Include.NON_NULL) 어노테이션은 객체를 JSON 포맷으로 변경할 때 필드의 값이 'null'일 경우 해당 필드는 JSON 포맷 시 제외된다. 

{
    "name" : "test",
    "age" : 14,
    "phoneNumber" : null
}

// 어노테이션 사용 시
{
    "name" : "test",
    "age" : 14
}

 

 

  • ResponseOrder.java
@Data
public class ResponseOrder {

    private String productId;
    private Integer qty;
    private Integer unitPrice;
    private Integer totalPrice;
    private LocalDateTime createdAt;

    private String orderId;
}

강의에서는 createdAt 필드를 Date 타입으로 생성했다. 

 

 

  • UserService.interface
UserDto getUserById(String userId);
Iterable<UserEntity> getUserByAll();

userId를 이용해 상세 정보를 가져오는 기능을 하는 메서드 getUserById는 UserDto 타입으로 반환해줄 것이다.

그리고 사용자의 전체 목록을 가져오는 메서드 getUserByAll()은 DB에 저장되어있는 데이터 그대로를 반환해줄 것이기 때문에 UserEntity 타입을 사용해 Iterable로 반환한다. 만약 DB에 가져오는 데이터를 가공할 것이라면 UserDto 타입으로 반환해도 좋다.

 

 

  • UserServiceImpl.java

UserService.interface에서 두 개의 메서드를 생성했으니 구현체에도 두 개의 메서드 getUserById와 getUserByAll 메서드를 구현한다.

@Override
public Iterable<UserEntity> getUserByAll() {
    return userRepository.findAll();
}

getUserByAll() 같은 경우는 이미 JPA에서 findAll()이라는 메서드를 제공하기 떄문에 별 다른 로직 없이 해당 메서드의 반환 값을 리턴해주면 끝이 난다.

 

@Override
public UserDto getUserById(String userId) {
    UserEntity userEntity = userRepository.findByUserId(userId);

    if(userEntity == null)
        throw new UsernameNotFoundException("User not found");

    // UserEntity -> UserDto로 변환
    UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

    List<ResponseOrder> orders = new ArrayList<>();
    userDto.setOrders(orders);
    
    return userDto;
}

 

  • UserRepository.interface
UserEntity findByUserId(String userId);

 

 

Controller도 작성해주자.

  • UserController.java
@GetMapping("/users")
public ResponseEntity<List<ResponseUser>> getUsers() {
    Iterable<UserEntity> userList = userService.getUserByAll();

    //userEntity > ResponseEntity로 변경
    List<ResponseUser> result = new ArrayList<>();
    userList.forEach(v -> {
        result.add(new ModelMapper().map(v, ResponseUser.class));
    });
    return ResponseEntity.status(HttpStatus.OK).body(result);
}

 

 

서버 실행 후 포스트맨 확인

POST Method로 사용자를 하나 생성 후

 

전체 사용자를 조회한다.

 

 

  • UserController.java

하나의 사용자에 대한 상세 정보를 가져오는 method를 구현하자.

@GetMapping("/users/{userId}")
public ResponseEntity<ResponseUser> getUser(@PathVariable("userId") String userId) {
    UserDto userDto = userService.getUserById(userId);

    //userDto > ResponseUser로 변경
    ResponseUser result = new ModelMapper().map(userDto, ResponseUser.class);
    return ResponseEntity.status(HttpStatus.OK).body(result);
}

 

서버 재실행 후 확인

POST Method로 사용자 하나 추가 후 아이디를 복사해서 조회한다.

 

강의에서는 스프링부트 버전을 2.4.2로 선택, 나는 3.0.3으로 선택했다.

Catalog Microservice

build.gradle

ModelMapper dependancy 추가

implementation 'org.modelmapper:modelmapper:2.4.2'

 

 

application.yml

server:
  port: 0

spring:
  application:
    name: catalog-service
  h2:
    console:
      enabled: true
      settings:
        web-allow-others: true
      path: /h2-console
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
    generate-ddl: true
    defer-datasource-initialization: true
  datasource:
    driver-class-name: org.h2.Driver
    url : jdbc:h2:mem:testdb

eureka:
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
logging:
  level:
    com.example.catalogservice: DEBUG

 

 

data.sql

insert into catalog(product_id, product_name, stock, unit_price)
    values('CATALOG-001', 'Berlin', 100, 1500);
insert into catalog(product_id, product_name, stock, unit_price)
    values('CATALOG-002', 'Tokyo', 110, 1000);
insert into catalog(product_id, product_name, stock, unit_price)
    values('CATALOG-003', 'Stockholm', 120, 2000);

 

 

CatalogEntity.java

@Data
@Entity
@Table(name = "catalog")
public class CatalogEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 120, unique = true)
    private String productId;

    @Column(nullable = false)
    private String productName;

    @Column(nullable = false)
    private Integer stock;

    @Column(nullable = false)
    private Integer unitPrice;

    @Column(nullable = false, updatable = false, insertable = false)
    @ColumnDefault(value = "CURRENT_TIMESTAMP")
    private LocalDateTime createAt;
}

 

 

CatalogRepository.interface

public interface CatalogRepository extends CrudRepository<CatalogEntity, Long> {
    CatalogEntity findByProductId(String productId);
}

 

 

CatalogDto.java

@Data
public class CatalogDto implements Serializable {

    private String productId;
    private Integer qty;
    private Integer unitPrice;
    private Integer totalPrice;

    private String orderId;
    private String userId;
    
}

 

 

ResponseCatalog.java

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseCatalog {
    private String productId;
    private String productName;
    private Integer unitPrice;
    private Integer stock;
    private LocalDateTime createdAt;
}

CatalogService.interface

public interface CatalogService {

    Iterable<CatalogEntity> getAllCatalogs();
}

 

 

CatalogServiceImpl.java

@Data
@Slf4j
@Service
public class CatalogServiceImpl implements CatalogService{

    CatalogRepository catalogRepository;

    @Autowired
    public CatalogServiceImpl(CatalogRepository catalogRepository) {
        this.catalogRepository = catalogRepository;
    }

    @Override
    public Iterable<CatalogEntity> getAllCatalogs() {
        return catalogRepository.findAll();
    }
}

 

 

CatalogController.java

@RestController
@RequestMapping("/catalog-service/")
public class CatalogController {

    Environment env;
    CatalogService catalogService;

    @Autowired
    public CatalogController(Environment env, CatalogService catalogService) {
        this.env = env;
        this.catalogService = catalogService;
    }

    @GetMapping("/health_check")
    public String status() {
        return String.format("It's Working in Catalog Service on PORT %s",
                env.getProperty("local.server.port"));
    }

    @GetMapping("/catalogs")
    public ResponseEntity<List<ResponseCatalog>> getCatalogs() {
        Iterable<CatalogEntity> catalogList = catalogService.getAllCatalogs();

        List<ResponseCatalog> result = new ArrayList<>();
        catalogList.forEach(v -> {
            result.add(new ModelMapper().map(v, ResponseCatalog.class));
        });

        return ResponseEntity.status(HttpStatus.OK).body(result);
    }
}

서버 재실행 후 확인

h2 DB 확인

apigateway-service에 Catalog-service 등록

  • application.yml
routes:
  - id: user-service
    uri: lb://USER-SERVICE
    predicates:
      - Path=/user-service/**
  - id: catalog-service
    uri: lb://CATALOG-SERVICE
    predicates:
      - Path=/catalog-service/**

Orders Microservice

 

강의에서는 Spring Boot 버전을 2.4.2를 선택했고, 나는 3.0.4 버전을 선택했다.

 

Model Mapper 라이브러리 추가

  • build.gradle

 

implementation 'org.modelmapper:modelmapper:2.4.2'

 

 

application.yml

server:
  port: 0

spring:
  application:
    name: order-service
  h2:
    console:
      enabled: true
      settings:
        web-allow-others: true
      path: /h2-console
  jpa:
    hibernate:
      ddl-auto: update
    defer-datasource-initialization: true
  datasource:
    driver-class-name: org.h2.Driver
    url : jdbc:h2:mem:testdb

eureka:
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

logging:
  level:
    com.example.orderservice: DEBUG

 

 

OrderEntity.java

@Data
@Entity
@Table(name = "orders")
public class OrderEntity implements Serializable {
    // 직렬화를 사용하는 이유 : 가지고 있는 객체를 전송하거나 DB에 보관하기 위해 마샬링 작업을 위해 사용

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 120, unique = true)
    private String productId;

    @Column(nullable = false)
    private Integer qty;

    @Column(nullable = false)
    private Integer unitPrice;

    @Column(nullable = false)
    private Integer totalPrice;
    
    @Column(nullable = false)
    private String userId;
    
    @Column(nullable = false, unique = true)
    private String orderId;

    @Column(nullable = false, updatable = false, insertable = false)
    @ColumnDefault(value = "CURRENT_TIMESTAMP")
    private LocalDateTime createdAt;

}

 

 

OrderRepository.Interface

public interface OrderRepository extends CrudRepository<OrderRepository, Long> {
    OrderEntity findByOrderId(String orderId);
    Iterable<OrderEntity> findByUserId(String userId);
}

 

 

OrderDto.java

@Data
public class OrderDto implements Serializable {
    private String productId;
    private Integer qty;
    private Integer unitPrice;
    private Integer totalPrice;

    private String orderId;
    private String userId;
}

 

 

ResponseOrder.java

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseOrder {

    private String productId;
    private Integer qty;
    private Integer unitPrice;
    private Integer totalPrice;
    private LocalDateTime createdAt;

    private String orderId;
}

 

 

OrderService.interface

public interface OrderService {
    OrderDto createOrder(OrderDto orderDetails);
    OrderDto getOrderByOrderId(String orderId);
    Iterable<OrderEntity> getAllOrders();
}

 

 

OrderServiceImpl.java

@Service
public class OrderServiceImpl implements OrderService{

    OrderRepository orderRepository;

    @Autowired
    public OrderServiceImpl(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Override
    public OrderDto createOrder(OrderDto orderDto) {

        orderDto.setOrderId(UUID.randomUUID().toString());
        orderDto.setTotalPrice(orderDto.getQty() * orderDto.getUnitPrice());

        ModelMapper mapper = new ModelMapper();
        mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        OrderEntity orderEntity = mapper.map(orderDto, OrderEntity.class);

		
        S save = orderRepository.save(orderEntity); //오류나서 임시로 S타입 매개변수 사용

        OrderDto returnValue = mapper.map(orderEntity, OrderDto.class);

        return returnValue;
    }

    @Override
    public OrderDto getOrderByOrderId(String orderId) {
        OrderEntity orderEntity = orderRepository.findByOrderId(orderId);
        OrderDto orderDto = new ModelMapper().map(orderEntity, OrderDto.class);
        return orderDto;
    }
}

- 출처 : 인프런 Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA) 강의

728x90