이번에는 UserService에서 조회 부분에 대한 API를 만들어보자.
Service
기존에 만들었던 UserService 인터페이스에 다음 메서드들을 추가한다.
UserDto findUserById(Long id);
Iterable<Users> findAll();
UserService
package springmsa.springmsa_user_service.service;
import springmsa.springmsa_user_service.dto.UserDto;
import springmsa.springmsa_user_service.entity.Users;
public interface UserService {
Users createUser(UserDto userDto);
UserDto findUserById(Long id);
Iterable<Users> findAll();
}
메서드를 새롭게 추가 했으니 구현 클래스에서 해당 메서드들을 구현해야한다.
UserServiceImpl
package springmsa.springmsa_user_service.service;
import jakarta.ws.rs.NotFoundException;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import springmsa.springmsa_user_service.dto.ResponseOrderDto;
import springmsa.springmsa_user_service.dto.UserDto;
import springmsa.springmsa_user_service.entity.Users;
import springmsa.springmsa_user_service.repository.UserRepository;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final ModelMapper modelMapper;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public Users createUser(UserDto userDto) {
userDto.setUserId(UUID.randomUUID().toString().substring(0, 8));
Users users = modelMapper.map(userDto, Users.class);
users.setEncryptedPwd(bCryptPasswordEncoder.encode(userDto.getPwd()));
userRepository.save(users);
return users;
}
@Override
public UserDto findUserById(Long id) {
Optional<Users> user = userRepository.findById(id);
if (user.isEmpty()) {
throw new NotFoundException("User not found");
}
UserDto userDto = modelMapper.map(user.get(), UserDto.class);
List<ResponseOrderDto> orders = new ArrayList<>();
userDto.setOrders(orders);
return userDto;
}
@Override
public Iterable<Users> findAll() {
return userRepository.findAll();
}
}
findUserById(Long id)와 findAll() 메서드를 구현하는데 내용은 간단하다.
findAll()은 repository에 위임하는것이 끝이고 findUserById(Long id)는 유저 아이디를 파라미터로 받으면 repository에서 먼저 유저를 찾은 후 있다면 ModelMapper를 이용해서 DTO로 변환한다. 유저는 추후에 만들 Order MicroService에 존재하는 주문 내역을 가지는데 우선은 Order MicroService를 만들지 않았으니 유저가 가지고 있는 주문 내역은 빈 리스트로 넣어 반환한다.
DTO는 다음과 같다.
ResponseOrderDto
package springmsa.springmsa_user_service.dto;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ResponseOrderDto {
private String productId;
private Integer qty;
private Integer unitPrice;
private Integer totalPrice;
private LocalDateTime createdAt;
private String orderId;
}
UserDto
package springmsa.springmsa_user_service.dto;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class UserDto {
private String email;
private String name;
private String pwd;
private String encryptedPwd;
private String userId;
private LocalDateTime createdAt;
private List<ResponseOrderDto> orders;
}
Controller
이제 유저 조회에 대한 컨트롤러 작업을 해보자. URI는 다음과 같다.
- 유저 전체 조회: /users
- 유저 단일 조회: /users/{id}
UserController
package springmsa.springmsa_user_service.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import springmsa.springmsa_user_service.dto.*;
import springmsa.springmsa_user_service.entity.Users;
import springmsa.springmsa_user_service.service.UserService;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {
private final UserService userService;
private final ModelMapper modelMapper;
@PostMapping("")
public ResponseEntity<ApiResponseDto<ResponseUserDto>> createUser(@RequestBody RequestUserDto requestUserDto) {
log.info("createUser payload: {}", requestUserDto);
try {
Users createdUser = userService.createUser(modelMapper.map(requestUserDto, UserDto.class));
return ResponseEntity
.status(HttpStatus.CREATED)
.body(new ApiResponseDto<>(modelMapper.map(createdUser, ResponseUserDto.class), null));
} catch (DataIntegrityViolationException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ApiResponseDto<>(null, e.getMessage()));
}
}
@GetMapping("")
public ResponseEntity<ApiResponseDto<List<ResponseUsersDto>>> getUsers() {
Iterable<Users> users = userService.findAll();
List<ResponseUsersDto> result = new ArrayList<>();
users.forEach(user -> {
ResponseUsersDto userDto = modelMapper.map(user, ResponseUsersDto.class);
result.add(userDto);
});
return ResponseEntity.status(HttpStatus.OK).body(new ApiResponseDto<>(result, null));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponseDto<ResponseUserDto>> getUser(@PathVariable Long id) {
UserDto findUser = userService.findUserById(id);
ResponseUserDto userDto = modelMapper.map(findUser, ResponseUserDto.class);
return ResponseEntity.status(HttpStatus.OK).body(new ApiResponseDto<>(userDto, null));
}
}
컨트롤러를 보면 getUsers()와 getUser(@PathVariable Long id)가 있다.
전체 조회 코드를 먼저 보면, 서비스로부터 전체 유저 데이터를 받아온다. 그 다음 받아온 결과를 DTO로 변환해주는 코드가 필요하다.
항상 컨트롤러에서 데이터를 반환할 땐 엔티티 자체가 아닌 DTO로 변환하여 돌려주어야 한다. 그래야 해당 엔티티의 변화에도 API 스펙에 영향이 가지 않을 뿐더러 (사실 이게 제일 중요) 엔티티를 리턴하는 것 자체가 좋은 방법이 아니다. 불필요한 데이터까지 API에 모두 태울 수 있으니.
단일 조회 코드를 보면, URI로부터 유저 ID를 받아온다. 그 ID로 서비스로부터 유저를 조회하여 받아온다. 받아온 유저를 역시나 DTO로 변환한다. 굳이 ResponseUserDto와 ResponseUsersDto로 구분지은 이유는 전체 유저를 조회할 땐 유저의 주문 내역을 반환하지 않기 위해서다.
ResponseUsersDto
package springmsa.springmsa_user_service.dto;
import lombok.Data;
import java.util.List;
@Data
public class ResponseUsersDto {
private String userId;
private String email;
private String name;
}
ResponseUserDto
package springmsa.springmsa_user_service.dto;
import lombok.Data;
import java.util.List;
@Data
public class ResponseUserDto {
private String userId;
private String email;
private String name;
private List<ResponseOrderDto> orders;
}
테스트
Postman으로 테스트를 해보자. 일단 유레카 서버와 API Gateway 서버가 모두 띄워져 있어야 한다. 나는 API Gateway로 요청할것이기 때문에. 확인을 위해 유레카 서버에 접속해보자.
유레카 서버

인스턴스에 APIGATEWAY-SERVICE, USER-SERVICE가 등록되어 있으면 정상이다.
- http://localhost:8000/users (전체 조회)

전체 조회 API에 대해 테스트한 결과이다.
- http://localhost:8000/users/1 (유저 단일 조회)

유저 단일 조회 API다. 이 데이터는 유저가 가지는 주문 내역에 대한 데이터 'orders'가 있다. 테스트에서 조회한 유저는 주문 내역이 없기 때문에 응답된 데이터가 없다.
'MSA' 카테고리의 다른 글
[MSA] Part. 12 Catalog Service (0) | 2024.01.08 |
---|---|
[MSA] Part 11. User Service (WebSecurity) (2) | 2024.01.08 |
[MSA] Part 9. UserService (Create User) (2) | 2023.10.11 |
[MSA] Part 8. H2 Database 연동 그리고 'ddl-auto' property (0) | 2023.10.11 |
[MSA] Part 7. API Gateway를 Eureka에 등록하기 (0) | 2023.10.10 |