로그인 후 → 주문 넣고 → 주문아이디 반환 받기
package com.example.usersmicroservices1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient //유레카 서버 등록
public class UsersMicroservices1Application {
public static void main(String[] args) {
SpringApplication.run(UsersMicroservices1Application.class, args);
}
//초기에 기동되는 클래스가 이곳이다 따라서 이곳에 빈을 등록 시켜놓으면 가장먼저 초기화가 된다!
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//빈으로 resttemplate 등록
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
package com.example.usersmicroservices1.Service;
import com.example.usersmicroservices1.dto.UserDto;
import com.example.usersmicroservices1.jpa.UserEntity;
import com.example.usersmicroservices1.jpa.UserRepository;
import com.example.usersmicroservices1.vo.ResponseOrder;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.modelmapper.spi.MatchingStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService{
UserRepository userRepository;
BCryptPasswordEncoder passwordEncoder;
//추가로 주입받음
Environment env;
RestTemplate restTemplate;
//userservicedetials 구현
//username == email
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByEmail(username);
if (userEntity == null){
throw new UsernameNotFoundException(username);
}
//security user 객체
//모두 검색이 잘 되었다면 해당 유저를 반환하겠다
//마지막은 권한값
return new User(userEntity.getEmail(), userEntity.getEncryptedPwd(),
true, true, true, true, new ArrayList<>());
}
@Autowired //스프링이 기동되면서 등록할 수 있는 빈을 찾아서 메모리에 생성해주는 것이다.
public UserServiceImpl(UserRepository userRepository,
BCryptPasswordEncoder passwordEncoder,
Environment env,
RestTemplate restTemplate){ //이것도 초기화가 되어있어야 주입이 되는데 주입이 안되면 에러남
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.env = env;
this.restTemplate = restTemplate;
}
@Override
public UserDto createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString());
//model mapper를 통해 request요청을 dto로 변환 , entity로 변환할 수 있다.
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); //모델 메퍼가 변환시, 얼마나 정확하게 매칭되게끔 할지 정하는 전략환경설정을 지정
UserEntity userEntity = mapper.map(userDto, UserEntity.class); //mapper의 map메소드를 통해 userdto를 userentity.class로 변환시킬 수 있다.
userEntity.setEncryptedPwd(passwordEncoder.encode(userDto.getPwd()));
userRepository.save(userEntity);
UserDto returnUserDto = mapper.map(userEntity, UserDto.class);
return returnUserDto;
}
@Override
public UserDto getUserById(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if (userEntity == null){
throw new UsernameNotFoundException("User not found");
}
UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
//List<ResponseOrder> orders = new ArrayList<>();
//orderservice의 controller에서 getmapping으로 선언된, 오더 정보를 갖고옴
String orderUrl = "http://127.0.0.1:8000/order-service/%s/orders";
//주소값, 요청방식, 파라미터전달값, 어떤형식으로 전달 받을 것인지 -> 여기에 써있는 값들은
//order service의 controller의 getOrder부분의 값을 그대로 반환한 것이다.
ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET, null,
new ParameterizedTypeReference<List<ResponseOrder>>() {
});
//resposneorder타입으로 바꾸기
List<ResponseOrder> orderList = orderListResponse.getBody();
userDto.setOrders(orderList);
return userDto;
}
@Override
public Iterable<UserEntity> getUserByAll() {
return userRepository.findAll();
}
@Override
public UserDto getUserDetailsByEmail(String email) {
UserEntity userEntity = userRepository.findByEmail(email);
if(userEntity == null){
throw new UsernameNotFoundException(email);
}
UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
return userDto;
}
}
포스트맨으로 가입한후
URL : order-service/user-id(가입후 반환된)
Order-service에 들어가서 보면 DB에 들어가 있는걸 확인할 수 있다.
# spring:
# datasource:
# driver-class-name: org.h2.Driver
# url: jdbc:h2:mem:testdb
# username: sa
# password: '{cipher}AQC+kErwNCz8WFWUa4HRa8A0ARenASPqOcuhaKNJwplhjgUWRQ0+7JD8FGVp1mznYswEN2TW/Z4Zu4ui2G/Zap6rWaUeICw1jiGOiSKWA4I1lO3FD/rLfHGmcLyRbFqAxvNJ0vt/iLNfylxe1l3ajrwsKmGkKvE7NbcVG16bnaMny3HYgOqL3xK45D+Mssjk56Tpl0LDYMTCWHBpwfzLqxY5e8YoUfYmiS+gqYw5DeIyQizEF+R1Ntos4+mlMFL6naspnBPcmcUkDXk0ZPemEvp6/mA9gjhgkkfA2bvaUaIS5WhmsYRPATvLERBjpPkKA8yo/a5mUK980uZ2Lbyz3BxqWC0omTTy9XARcgxHPvfWwUhEkrc3KXR2V+jzfn9fqeI='
token:
expiration_time: 86400000
secret:"123123"
gateway:
ip: 127.0.0.1
order_service:
url: http://127.0.0.1:8000/order-service/%s/orders
//빈으로 resttemplate 등록
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
//ORDER-SERVICE는 Eureka등록된 서비스 이름이다
<!--feignclient -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
package com.example.usersmicroservices1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient //유레카 서버 등록
@EnableFeignClients //feign 클라이언트 사용
public class UsersMicroservices1Application {
public static void main(String[] args) {
SpringApplication.run(UsersMicroservices1Application.class, args);
}
//초기에 기동되는 클래스가 이곳이다 따라서 이곳에 빈을 등록 시켜놓으면 가장먼저 초기화가 된다!
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//Feign client 사용으로 주석 처리
//빈으로 resttemplate 등록
// @Bean
// @LoadBalanced
// public RestTemplate getRestTemplate(){
// return new RestTemplate();
// }
//}
package com.example.usersmicroservices1.client;
import com.example.usersmicroservices1.vo.ResponseOrder;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@FeignClient(name="order-service")
public interface OrderServiceClient {
//order-service controller의 getmapping으로 된 getordASER의 반환값을 그대로 반환
@GetMapping("/order-service/{userId}/orders") //실질적인 endpoint를 지정
List<ResponseOrder> getOrders(@PathVariable String userId);
}
@Override
public UserDto getUserById(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if (userEntity == null){
throw new UsernameNotFoundException("User not found");
}
UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
/*
Rest Template 코드는 더이상 사용되지 않을것 ->@feign client가 대체 주석처리
*/
// //List<ResponseOrder> orders = new ArrayList<>();
// //orderservice의 controller에서 getmapping으로 선언된, 오더 정보를 갖고옴
// //user-service.yml파일의 값을 갖고옴
// String orderUrl = String.format(env.getProperty("order_service.url"),userId);
// //주소값, 요청방식, 파라미터전달값, 어떤형식으로 전달 받을 것인지 -> 여기에 써있는 값들은
// //order service의 controller의 getOrder부분의 값을 그대로 반환한 것이다.
// ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET, null,
// new ParameterizedTypeReference<List<ResponseOrder>>() {
// });
// //resposneorder타입으로 바꾸기
// List<ResponseOrder> orderList = orderListResponse.getBody();
/**
* Fegin client 용 코드
**/
List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
userDto.setOrders(orderList);
return userDto;
}
feign client는 resttemplate과 똑같이 엔드포인트를 기준으로 해당 요청을 갖고온다.
rest template과 비교했을시 코드가 간결하다는 점에서 이점이 있다. 하지만 interface에서 해당 서비스의 엔드포인트를 기준으로 해당 값을 요청해서 갖고 오는 것 이기 때문에 order -service와user-service의 구조를 잘 알고 있는 사람의 경우 보기가 쉽지만, 제 3자가 보기에는 직관성이 떨어진다는 단점이 있다
logging:
level:
com.example.userservice.client: DEBUG
package com.example.usersmicroservices1;
import feign.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient //유레카 서버 등록
@EnableFeignClients //feign 클라이언트 사용
public class UsersMicroservices1Application {
public static void main(String[] args) {
SpringApplication.run(UsersMicroservices1Application.class, args);
}
//초기에 기동되는 클래스가 이곳이다 따라서 이곳에 빈을 등록 시켜놓으면 가장먼저 초기화가 된다!
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//Feign client 사용으로 주석 처리
//빈으로 resttemplate 등록
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
//Fegin client 예외처리 로깅을 위한 빈등록
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
/*Feign Exception Handling*/
List<ResponseOrder> orderList = null;
try {
orderServiceClient.getOrders(userId);
} catch (FeignException ex){
log.error(ex.getMessage());
}
잘못 호출되었지만, 해당 유저의 정보는 불러와지고, order정보는 불러와 지지 않는 것을 볼 수 있다.
try catch문장을 대체함
package com.example.usersmicroservices1.error;
import feign.Response;
import feign.codec.ErrorDecoder;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class FeignErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
switch(response.status()) {
case 400:
break;
case 404:
if (methodKey.contains("getOrders")) { //getorders라는 함수명에서만 이 예외처리가 작동
return new ResponseStatusException(HttpStatus.valueOf(response.status()),
"User's orders is empty");
}
break;
default:
return new Exception(response.reason());
}
return null;
}
}
package com.example.usersmicroservices1;
import com.example.usersmicroservices1.error.FeignErrorDecoder;
import feign.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient //유레카 서버 등록
@EnableFeignClients //feign 클라이언트 사용
public class UsersMicroservices1Application {
public static void main(String[] args) {
SpringApplication.run(UsersMicroservices1Application.class, args);
}
//초기에 기동되는 클래스가 이곳이다 따라서 이곳에 빈을 등록 시켜놓으면 가장먼저 초기화가 된다!
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//Feign client 사용으로 주석 처리
//빈으로 resttemplate 등록
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
//Fegin client 예외처리 로깅을 위한 빈등록
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
//feign 예외 디코더 클래스 빈 주입
@Bean
public FeignErrorDecoder getFeignErrorDecoder() {
return new FeignErrorDecoder();
}
}
@Override
public UserDto getUserById(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if (userEntity == null){
throw new UsernameNotFoundException("User not found");
}
UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
/*
Rest Template 코드는 더이상 사용되지 않을것 ->@feign client가 대체 주석처리
*/
// //List<ResponseOrder> orders = new ArrayList<>();
// //orderservice의 controller에서 getmapping으로 선언된, 오더 정보를 갖고옴
// //user-service.yml파일의 값을 갖고옴
// String orderUrl = String.format(env.getProperty("order_service.url"),userId);
// //주소값, 요청방식, 파라미터전달값, 어떤형식으로 전달 받을 것인지 -> 여기에 써있는 값들은
// //order service의 controller의 getOrder부분의 값을 그대로 반환한 것이다.
// ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET, null,
// new ParameterizedTypeReference<List<ResponseOrder>>() {
// });
// //resposneorder타입으로 바꾸기
// List<ResponseOrder> orderList = orderListResponse.getBody();
/**
* Fegin client 용 코드
**/
/*Feign Exception Handling*/
// List<ResponseOrder> orderList = null;
// try {
// orderServiceClient.getOrders(userId);
// } catch (FeignException ex){
// log.error(ex.getMessage());
// }
/*feign error decoder로 예외처리*/
List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
userDto.setOrders(orderList);
return userDto;
}
이렇게 지정한 메시지가 호출된것을 알 수 있다.
@Component로 해당 클래스 등록
생성자 주입으로 Environment 등록, 해당 서비스 등록
Application부분 빈 등록된거 삭제(왜냐하면 @Component로 등록 시켜놓았기 때문)
문제점
하나의 데이터 요청(주문 요청) 정보에 대해 분산으로 저장이된다. → 즉 데이터 동기화 문제가 발생!!
kafka가가 가운데 있음으로 두 서비스가 요청이 있을시 바로바로 데이터 요청처리 가능하고, 동시성 문제도 해결된다.
order service 2개실행,(mvn spring-boot:run)
라운드 로빈 방식으로 각각 데이터가 저장된다.
이렇게 데이터가 각각 갖고와지는 것을 볼 수 있다.