웹 개발 공부 : Back-end/JPA

[JPA 프로젝트 회고 - 5] JPA에서 DTO를 사용하는 이유 (feat. 코드 보안을 위해 수정)

Developer KTU 2024. 8. 19. 14:21
반응형

JPA 초기 구현 시, API 요청 JSON 데이터를 바로 엔티티에 적용하여 개발했다.
하지만, 이렇게 코드를 구현하면서 문득 든 생각이 있었으니...

 

 

'만약 API 스펙이 변경되면 엔티티와 매핑이 되는건가...?'


나의 의심은 역시나.. 실무에서 엔티티를 외부에 노출시키거나, 파라미터로 받으면 절대 절대!! 안된다!
그 이유를 한번 살펴보겠다.


1. 보안

회사 및 서비스 데이터베이스 정보는 굉장히 민감한 정보이다. 엔티티를 직접 파라미터로 받거나 노출시킨다면, API를 호출하는 과정에서 엔티티에 대한 정보가 모두 노출될 수 있다. 이는 곧 서비스의 민감한 정보가 노출될 수 있음을 의미한다.

 

 

2. 유연성

API 요청 스펙이 변경되면, 엔티티의 구조도 같이 변경해야할 수도 있다. 예를 들어, User라는 엔티티에 uuid라는 컬럼이 있고, API 요청 스펙중 uuid라는 key가 있는 상황에서 uuid -> uuids 로 변경된다면, User 엔티티의 uuid 컬럼도 변경되어야한다. 데이터베이스 구조가 수시로 변경되면... 생각만해도 아찔하다..

 

하지만, DTO를 사용한다면, DTO의 멤버 변수명만 변경해주면 된다. 어처피 getter를 사용하여 엔티티에 값을 넣어주기 때문에 유연성이 보장된다!

 

 

3. 검증 및 가공

API 요청 데이터를 엔티티에 넣어주기 전에 데이터를 검증할 수 있다. 예를들어 @Vaild를 사용하여 DTO에 @NotNull, @Email 등을 사용하여 요청 데이터의 유효성을 검증할 수 있다. 또한 getter로 데이터를 받아오기 때문에 데이터를 필요한 형태로 가공할 수 있다.

 

 

4. 예시코드

4-1. Entity를 직접 사용했을 경우

// Entity
@Entity
public class User{
    @Id
    private String uuid;
    private String username;
    private String email;
}

// Controller
@RestController
@RequestMapping("/user")
public class UserContoller{
	
    private UserSevice userSerivce;
    
    @Autowired
    public UserContoller(UserSevice userSerivce){
    	this.userSerivce = userSerivce;
    }
    
    @PostMapping("/v1/createuser")
    public String userCreate(@RequestBody User user){
    	return userSerivce.createUser(user).getBody();
    }
}

 

4-2. DTO를 사용했을 경우

// DTO
@Data
public class UserDto{
    // 해당 컬럼 데이터에 맞는 유효성 검증 제공
    @NotNull
    private String uuid;
    @NotNull
    private String username;
    @Email
    private String email;
}

// Entity
@Entity
public class User{
    @Id
    private String uuid;
    private String username;
    private String email;
    
    @Builder
    public User(String uuid, String username, String email){
        this.uuid = uuid;
        this.username = username;
        this.email = email;
    }
}

// Controller
@RestController
public class UserContoller{
	
    private UserSevice userSerivce;
    
    @Autowired
    public UserContoller(UserSevice userSerivce){
    	this.userSerivce = userSerivce;
    }
    
    // DTO를 통해 API 요청데이터를 받아옴 -> @Vaild를 통해 데이터 유효성 검증
    @PostMapping("/v1/createuser")
    public String userCreate(@RequestBody @Vaild UserDto userDto){
    	return userSerivce.createUser(userDto).getBody();
    }
}

// ServiceImpl
@Service
@Transactional
public class UserServiceImpl implements UserService{
    private final UserRepository userRepository;

    @Autowired
    public UserServiceImpl(UserRepository userRepository){
        this.userRepository = userRepository;
    }

    public ResponseEntity<String> createUser(UserDto userDto){
        try{
            User user = User.builder()
                            .uuid(userDto.getUuid())
                            .username(userDto.getUsername())
                            .email(userDto.getEmail())
                            .build();
            userRepository.save(user);
            return new ResponseEntity<String>("200", HttpStatus.OK);
        }catch(Exception e){
            e.printStackTrace();
            return new ResponseEntity<String>("400", HttpStatus.BAD_REQUEST);
        }
    }
}

 

5. 마무리

위와 같은 이슈는 실무에서 더욱 중요시되는 부분인 것 같다. 단순 기능 구현에만 치중하여 개발해선 안되며, 유연성, 확장성, 보안성 등 개발하는데에 있어 얼마나 중요한 요소인지 다시 한번 깨닫게 되었다.

반응형