실제 MSA를 위한 실습 MSA의 동작 학습/응용이 목적이기 때문에 상세한 기능 구현, 다자인은 신경쓰지 않고 RestController로 진행, MSA의 동작위주의 실습
https://github.com/jje951122/Spring_Cloud-MSA_web
시스템 구성
회원 서비스(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);
}
}
'Spring Cloud' 카테고리의 다른 글
Spring Cloud(MSA) 실습 - 웹 쇼핑몰 3. 주문 서비스 및 테스트 (0) | 2021.09.16 |
---|---|
Spring Cloud(MSA) 실습 - 웹 쇼핑몰 2. 카달로그 서비스 (0) | 2021.09.16 |
Spring Cloud - 서비스 디스커버리 (0) | 2021.09.14 |
댓글