티스토리 뷰

인프런 이도원님의 'Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)' 듣고 정리한 내용입니다.

 

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

Spring framework의 Spring Cloud 제품군을 이용하여 마이크로서비스 애플리케이션을 개발해 보는 과정입니다. Cloud Native Application으로써의 Spring Cloud를 어떻게 사용하는지, 구성을 어떻게 하는지에 대해

www.inflearn.com

 

1. Users Microservice 기능 추가

UserMicroservice를 API Gateway에 등록하면 변경된 포트번호와 상관없이 Eureka의 naming으로 접근하기 때문에 편하게 작업할 수 있다.

 

API Gateway에 등록해보자.

 

UserController.java

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

포트번호 확인할 수 있게 변경하였다.

 

다음은 Api Gateway Servcie 프로젝트를 열어서 application.yml 파일에 user service를 등록해주었다.

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

 

Api Gateway를 따로 호출하여 IP주소/8000/user-service/health-check로 실행시켰는데 404에러 발생했다.

이유는? user service의 uri와 api gateway의 uri가 다르기 때문이다.

➡️ user-service/health-check 라는 prefix와 함께 전달이 되는데 실제 Controller에서 GetMapping 된 주소는 /health-check이기 때문에 못 찾은 것이다!

 

UserController.java

@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 URI에 user-service 추가하면 포트번호 8000에서도 잘 작동하는 것을 확인할 수 있다.

 

하지만! 모든 URI에 prefix를 다는 것은 번거로우니 Controller의 RequestMapping에 설정해주자.

 

다음은 전체 사용자 목록과 개별 사용자의 상세 보기할 수 있는 기능을 추가해보자.

 

ResponseUser.java - 사용자에게 반환하기 위한 정보

@Data
@JsonInclude(JsonInclude.Include.NON_NULL) // 필드 중에서도 채워지지 않은 것도 있을 수 있는데, 그러면 null값이 들어감 -> 불필요한 데이터를 제어하기 위해 "null값 데이터가 있는 것은 버리고 없는 것만 전달하자!"
public class ResponseUser {

    private String email;
    private String name;
    private String userId;

    // 사용자가 주문했던 전체 기록들 확인하기 위해
    private List<ResponseOrder> orders;
}

 

ResponseOrder.java

@Data
public class ResponseOrder {

    private String productId;
    private Integer gty;
    private Integer unitPrice;
    private Integer totalPrice;
    private Date createdAt;

    private String orederId;
}

회원이 주문한 이력이 있을 경우 조회해서 데이터를 반환한다.

 

UserService.java

public interface UserService {

    UserDto createdUser(UserDto userDto);

    UserDto getUserByUserId(String userId);

    Iterable<UserEntity> getUserByAll();
}

 

사용자의 id에 따라서 검색해줄 수 있는 getUsersByUserId 메서드와 전체 회원을 다 가져올 수 있는 getUserByAll 메서드를 추가하였다.

 

UserServiceImpl.java

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

        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

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

        return null;
    }

    @Override
    public Iterable<UserEntity> getUserByAll() {
        return userRepository.findAll();
        // 가공하지 않고 바로 다 가져오는 걸로 구현했지만(그래서 반환 데이터를 UserEntity로 설정) 필요에 따라 부분만 가져오게 설정할 수 있음
    }

 

UserController.java

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

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

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

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

        ResponseUser returnValue = new ModelMapper().map(userDto, ResponseUser.class);

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

@Pathvariable을 쓰지 않았을 경우 이름이 일치하지 않으면 오류가 발생할 수 있다.

이름이 다르더라도 매개변수로 들어온 userid 값을 원하는 변수명으로 지정 가능

 

UserRepository.java

public interface UserRepository extends CrudRepository<UserEntity, Long> {
    UserEntity findByUserId(String userId);
}

 

 

2. Catalogs Microservice 프로젝트 생성

Catalogs Microservice는 상품 목록을 조회하기 위한 서비스이다.

 

API는 다음 표와 같다.

기능 마이크로서비스 URI (API Gateway 사용 ⭕️) HTTP Method
상품 목록 조회 Catalogs Microservice /catalog-service/catalogs GET

 

application.yml

server:
  port: 0

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

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.catalogservice: DEBUG

application name을 catalog service로 바꿔주고 user service와 다르게 jpa 설정하였다. 또한, logging level 값을 지정하여 콘솔에 출력되는 값을 제어할 수 있다.

 

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 Date createdAt;
}

여기서 데이터의 직렬화를 위해 Serializable를 사용했다고 한다.

 

Serialization 직렬화란?

객체에 저장된 데이터를 스트림에 쓰기 위해 연속적인 데이터로 변환하는 것을 의미한다.

 

CatalogRepository.java

public interface CatalogRepository extends CrudRepository<CatalogEntity, Long> {

    CatalogEntity findByProductId(String productId);
}

마찬가지로 CrudRepository를 상속받고 이 클래스에서는 id로 상품들을 조회할 수 있는 메서드를 선언하였다.

 

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;
}

나중에 확장을 위해 orderId와 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 Date createdAt;
}

 

 

CatalogService.java

public interface CatalogService {

    Iterable<CatalogEntity> getAllCatalog();
}

 

CatalogServiceImpl.java

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

    CatalogRepository catalogRepository;

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

    @Override
    public Iterable<CatalogEntity> getAllCatalog() {
        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.getAllCatalog();

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

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

status 메서드는 상태 체크할 수 있는 메서드로 랜덤 포트번호를 확인할 수 있다. getCatalogs에서는 모든 상품 주문 목록을 볼 수 있다.

 

h2 DB 실행 시

 

포스트맨으로 상품 목록 조회

 

3. Orders Microservice 프로젝트 생성

Orders Microservice는 회원이 상품을 주문하고 주문 내역을 조회하기 위한 서비스이다.

 

API는 다음 표와 같다.

기능 마이크로서비스 URI (API Gateway 사용 ⭕️) HTTP Method
사용자 별 상품 주문 Orders Microservice /order-service/{user_id}orders POST
사용자 별 주문 내역 조회 Orders Microservice /order-service/{user_id}/orders GET

 

application.yml에서 application name만 order service로 바꿔주었다.

 

OrderEntity.java

@Data
@Entity
@Table(name = "orders")
public class OrderEntity {

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

    @Column(nullable = false, length = 120, unique = true)
    private String productId;
    @Column(nullable = false)
    private String 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 Date createdAt;
}

 

OrderRepository.java

public interface OrderRepository extends CrudRepository<OrderEntity, Long> {

    OrderEntity findByOrderId(String orderId);

    Iterable<OrderEntity> findByUserId(String userId);
}

id로 주문 내역을 찾는 메서드와 사용자가 주문한 내역들을 보여주는 메서드를 구현하였다.

 

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 String qty;
    private Integer unitPrice;
    private Integer totalPrice;
    private Date createdAt;

    private String orderId;
}

 

OrderService.java

public interface OrderService {

    OrderDto createOrder(OrderDto orderDetails);
    OrderDto getOrderByOrderId(String orderId);

    Iterable<OrderEntity> getOrdersByUserId(String userId);
}

 

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);

        orderRepository.save(orderEntity);

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

        return returnValue;
    }

    // orderId로 조회하여 주문 내역 찾기
    @Override
    public OrderDto getOrderByOrderId(String orderId) {
        OrderEntity orderEntity = orderRepository.findByOrderId(orderId);
        OrderDto orderDto = new ModelMapper().map(orderEntity, OrderDto.class);

        return orderDto;
    }

    // userId로 조회하여 사용자 주문 내역들 찾기
    @Override
    public Iterable<OrderEntity> getOrdersByUserId(String userId) {
        return orderRepository.findByUserId(userId);
    }
}

 

RequestOrder.java - 입력받는 주문 정보

@Data
public class RequestOrder {
    private String productId;
    private Integer qty;
    private Integer unitPrice;
}

 

OrderController.java

@RestController
@RequestMapping("/order-service")
public class OrderController {

    Environment env;
    OrderService orderService;

    @Autowired
    public OrderController(Environment env, OrderService orderService) {
        this.env = env;
        this.orderService = orderService;
    }

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

    // 사용자 별 주문 내역 조회
    @PostMapping("{userId}/orders")
    public ResponseEntity<ResponseOrder> createOrder(@PathVariable String userId, @RequestBody RequestOrder orderDetails) {
        ModelMapper mapper = new ModelMapper();
        mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

        OrderDto orderDto = mapper.map(orderDetails, OrderDto.class);
        orderDto.setUserId(userId);
        OrderDto createdOrder = orderService.createOrder(orderDto);

        ResponseOrder responseOrder = mapper.map(createdOrder, ResponseOrder.class);

        return ResponseEntity.status(HttpStatus.CREATED).body(responseOrder);
    }

    // 사용자 별 주문 내역 조회
    @GetMapping("{userId}/orders")
    public ResponseEntity<List<ResponseOrder>> getOrder(@PathVariable("userId") String userId) {
        Iterable<OrderEntity> orderList = orderService.getOrdersByUserId(userId);

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

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

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함