본문 바로가기
Spring Cloud

Spring Cloud(MSA) 실습 - 웹 쇼핑몰 1. 기본 설정과 회원 서비스

by 장중앙 2021. 9. 15.

실제 MSA를 위한 실습 MSA의 동작 학습/응용이 목적이기 때문에 상세한 기능 구현, 다자인은 신경쓰지 않고 RestController로 진행, MSA의 동작위주의 실습

 

https://github.com/jje951122/Spring_Cloud-MSA_web

 

GitHub - jje951122/Spring_Cloud-MSA_web

Contribute to jje951122/Spring_Cloud-MSA_web development by creating an account on GitHub.

github.com

시스템 구성


회원 서비스(User Microservice) 설계

기능 apigateway-service URI Method
사용자 등록 /user-service/users POST
사용자 전체 조회 /user-service/users GET
사용자 정보 조회 /user-service/users/{user_id} GET
작동 상태 확인 /user-service/users/health_check GET
환영 메시지 /user-service/users/welcome GET

 


discovery-service 프로젝트(Eureka Server) 생성

#application.yml
 
server:
  port: 8761
 
spring:
  application:
    name: discoveryservice
    
eureka:
  client:
    register-with-eureka: false   # euraka의 registry에 등록할지 여부를 설정, 서버기 때문에 
    fetch-registry: false         # registy에 있는 정보를 가져올지 여부를 결정
// aplication.java
 
package com.example.deliveryservice;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
 
@SpringBootApplication
@EnableEurekaServer
public class Application {
 
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
 
}
// application.java
package com.example.discoveryservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

}

apigateway-service 프로젝트 생성

apigateway-service 프로젝트 생성

application.yml 파일 작성

# application.yml

server:
  port: 8000
  
spring:
  application:
    name: apigateway-service
  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Hello Spring Cloud Gateway Global Filter
            preLogger: true
            postLogger: true

      
      
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

 


user-service 프로젝트 생성 및 Eureka 연동을 위한 기본 설정

// UserServiceApplication.java
package com.example.MSA_web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(UserServiceApplication.class, args);
	}

}

porm.xml에 편의상을 위한 h2버전 변경, DB와의 데이터 형변환/전달을 위한 datamapper, 비밀번호 암호화를 위한 spring-boot-starter-security 의존성 추가  

# porm.xml
...
		<dependency>
		    <groupId>com.h2database</groupId>
		    <artifactId>h2</artifactId>
		    <version>1.3.176</version>
		    <scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.modelmapper</groupId>
			<artifactId>modelmapper</artifactId>
			<version>2.3.8</version>
		</dependency>
		<!-- Spring-security -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
 ...

 

# application.yml

server:
  port: 0
  
spring:
  application:
    name: user-service
  h2:
    console:
      enabled: true
      settings:
        web-allow-others: true
      path: /h2-console
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb
    username: sa
#   password: 1234



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

# 환영 메시지를 위한 greeting
greeting:
  message: Welcome

 

RestController 클래스 추가

//UserController
package com.example.MSA_web.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/")
public class UserController {
	
	@GetMapping("/health_check")
	public String healthCheck() {
		return "OK";
	}
}

실제 구현 전에 Eureka 등록 및 동작 확인

 

H2 Database 연동확인

H2 Database 

  • 자바로 만들어진 오픈소스 RDBMS
  • 내장 server/client 제공
  • JPA 연동 가능
  • H2 1.4.198이후로는 보안 문제로 자동으로 데이터베이스를 생성하지 못하도록 제한(실습에서는 편의상 1.3.176을 사용)

porm.xml에 H2 Database 의존성 추가

application.yml에 H2관련 설정 추가

user-service에 할당된 포트를 확인 후 h2-console로 접속(localhost:{portnum}/h2-console)

* 할당된 포트는 discovery-service의 Eureka에서 확인가능

 

 

user-service를 apigateway-service에 등록

user-service인스턴스가 많아질 경우 라우팅을 위해 apigateway-service에 등록

어플리케이션 이름으로 접근할 수 있도록 하기 위해

apigateway-service의 application.yml에 user-service의 라우팅정보를 추가

# application.yml
spring:
 ....
  cloud:
    gateway:
      .......
      routes:
        - id: user-service
          predicates:
            - Path=/user-service/**
          uri: lb://USER-SERVICE
          
          filters:
            - CustomFilter
...

동작확인 - apigateway-service로 접근하여 페이지 요청가능

 


user-service 기능 구현

DB를 위한 VO, DTO, JPA 클래스

사용자 등록, 사용자 전체 조회, 사용자 정보 조회 기능을 위한 데이터 전처리 작업

 

VO 패키지

// ~.vo

//Greeting.java
// /welcome페이지의 환영 메시지를 위한 데이터
package com.example.MSA_web.vo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import lombok.Data;

@Component
@Data
public class Greeting {
	@Value("${greeting.message}")
	private String message;
}

------------------------------------------------------------------------------------------
// #RequestUser.java
//회원 가입시에는 회원의 이메일, 이름, 패스워드를 반환
package com.example.MSA_web.vo;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class RequestUser {
	@NotNull(message="이메일 입력은 필수 입니다.")
	@Size(min=5, message="이메일은 최소 5글자 이상입니다.")
	@Email
	private String email;
	
	@Size(min=2, message="이름은 최소 2글자 이상입니다.")
	@NotNull(message="이름 입력은 필수 입니다.")
	private String name;
	
	@NotNull(message="비밀번호 입력은 필수 입니다.")
	@Size(min=2, message="비밀번호는 최소 2글자 이상입니다.")
	private String pwd;
}

----------------------------------------------------------------------------------------
// ResponseUser.java
// 요청에대한 데이터
package com.example.MSA_web.vo;

import lombok.Data;

@Data
public class ResponseUser {
//회원가입 후에는 회원의 이메잃, 이름 아이디를 반환
	private String email;
	private String name;
	private String userId;
}

-----------------------------------------------------------------------------------------
// ResponseOrder.java
// 회원의 주문 데이터
package com.example.MSA_web.vo;

import java.util.Date;

import lombok.Data;

@Data
public class ResponseOrder {
	//주문정보
	private String productId;
	private Integer qty;
	private Integer unitPrice;
	private Integer totalPrice;
	private Date createdAt;
	private String orderId;
}

-----------------------------------------------------------------------------------------
// ResponseUser.java
// 회원에 관한 응답 데이터
package com.example.MSA_web.vo;

import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)//null이 아닌 데이터만 반환하도록
public class ResponseUser {
	//회원가입 후에는 회원의 이메잃, 이름 아이디를 반환
	private String email;
	private String name;
	private String userId;
	
	//주문정보
	private List<ResponseOrder> orders; 
}

DTO 패키지

// ~.dto.UserDto.java

package com.example.MSA_web.dto;

import java.util.Date;
import java.util.List;

import com.example.MSA_web.vo.ResponseOrder;

import lombok.Data;

@Data
public class UserDto {
	//VO에서 받아올 데이터
	private String email;
	private String name;
	private String pwd;
	
	//현재(DTo)에서 생성할 데이터
	private String userId;
	private Date createAt;
	private String encryptedPwd;
	
	//사용자가 주문한 내역을 함께 반환
	private List<ResponseOrder> orders;
}

JPA 패키지

// ~.jpa

// UserEntity.java
// 실제 DB를 생성하고 연계를 위한 데이터

package com.example.MSA_web.jpa;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;

@Data
@Entity
@Table(name="users")
public class UserEntity {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(nullable = false, length = 50, unique = true)
	private String email;
	
	@Column(nullable = false, length = 50)
	private String name;
	
	@Column(nullable = false, unique = true)
	private String userId;
	
	@Column(nullable = false)
	private String encryptedPwd;
}

------------------------------------------------------------------------------------------
// UserRepository.java
// Entity에 의해 생성된 DB에 접근하는 메서드 사용을 위한 인터페이스

package com.example.MSA_web.jpa;

import org.springframework.data.repository.CrudRepository;

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

security 패키지

// WebSecurity.java
package com.example.MSA_web.security;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable();
		http.authorizeRequests().antMatchers("/users/**").permitAll();
		http.headers().frameOptions().disable();
	}
}

모듈 기능 구현

controller

package com.example.MSA_web.controller;

import java.util.ArrayList;
import java.util.List;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.MSA_web.dto.UserDto;
import com.example.MSA_web.jpa.UserEntity;
import com.example.MSA_web.service.UserService;
import com.example.MSA_web.vo.Greeting;
import com.example.MSA_web.vo.RequestUser;
import com.example.MSA_web.vo.ResponseUser;

@RestController
@RequestMapping("/user-service") // apigateway 라우팅을 위한
public class UserController {

	Environment env;
	private Greeting greeting;
	private UserService userService;

	@Autowired
	public UserController(Environment env, UserService userService, Greeting greeting) {// 매개변수의 값을 현재 객체에 저장
		this.env = env;
		this.userService = userService;
		this.greeting = greeting;
	}

	@PostMapping("/users")
	public ResponseEntity<ResponseUser> createUser(@RequestBody RequestUser user) {
		// RequestUser -> UserDto
		ModelMapper mapper = new ModelMapper();
		mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
		UserDto userDto = mapper.map(user, UserDto.class);

		// createUser 서비스를 호출
		userService.createUser(userDto);

		// UserDto -> ResponseUser
		ResponseUser responseUser = mapper.map(userDto, ResponseUser.class);

		// 201 created 상태코드를 반환하도록 수정
		return ResponseEntity.status(HttpStatus.CREATED).body(responseUser);
	}

	@GetMapping("/welcome")
	public String welcome() {
		return greeting.getMessage();// application.yml의 greeting.message의 값을 리턴
	}

	@GetMapping("/health_check")
	public String healthCheck() {
		// 할당받은 포트번호 출력
		return String.format("OK ... Port num = %s", env.getProperty("local.server.port"));
	}

	@GetMapping("/users/{userId}") // 해당 회원 정보 조회
	public ResponseEntity<ResponseUser> getUsers(@PathVariable("userId") String userId) {
		// 회원 정보 조회
		UserDto userDto = userService.getUserByUserId(userId);
		
		// UserDto -> ResponseUser
		ResponseUser responseUser = new ModelMapper().map(userDto, ResponseUser.class);
		return ResponseEntity.status(HttpStatus.OK).body(responseUser);
	}

	@GetMapping("/users") // 전체 회원 정보조회
	public ResponseEntity<List<ResponseUser>> getUsers() {
		// 회원 정보 조회
		Iterable<UserEntity> userList = userService.getUserByAll();
		
		// 조회 결과 리스트의 UserEntity -> ResponseUser
		List<ResponseUser> resultList = new ArrayList();
		// Iterable의 요소 하나씩 ResponseUser로 변환, List에 add
		userList.forEach(v->{
			resultList.add(new ModelMapper().map(v, ResponseUser.class));
		});
		
		return ResponseEntity.status(HttpStatus.OK).body(resultList);
	}

}

 

 

 

댓글