MSA

[MSA] Part 10. User Service (Find User/s)

cwchoiit 2024. 1. 8. 09:49
728x90
반응형
SMALL

이번에는 UserService에서 조회 부분에 대한 API를 만들어보자.

728x90
SMALL

 

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'가 있다. 테스트에서 조회한 유저는 주문 내역이 없기 때문에 응답된 데이터가 없다.

 

728x90
반응형
LIST