추후에 데이터베이스에 저장할 현재 날짜를 알려주는 Util입니다. 따로 util이라는 패키지를 만들어 클래스를 작성하겠습니다.
public class DateUtil {
public static String dateNow() {
return new SimpleDateFormat("yyyy년 MM월 dd일").format(new Date());
}
}
vo는 read-only의 특성을 가지는 객체입니다. 이 특성을 토대로 RequestRegister, RequestLogin vo들을 작성해보도록 하겠습니다.
@Getter
public class RequestRegister {
@NotNull(message="Email cannot be null")
@Size(min=2, message="Email not be less than 2 characters")
private String email;
@NotNull(message="Password cannot be null")
@Size(min=6, message="Password must be equal or than 6 characters")
private String password;
@NotNull(message="Nickname cannot be null")
@Size(min=2, message="Nickname not be less than 2 characters")
private String nickname;
@NotNull(message="Phone cannot be null")
private String phoneNumber;
}
@Getter
public class RequestLogin {
@NotNull(message="Email cannot be null")
@Size(min=2, message="Email not be less than 2 characters")
private String email;
@NotNull(message="Password cannot be null")
@Size(min=6, message="Password must be equal or than 6 characters")
private String password;
}
@Data
public class ResponseUser {
private String email;
private String nickname;
private String phoneNumber;
private String userId;
private String encryptedPwd;
@Builder
public ResponseUser(
String email,
String nickname,
String phoneNumber,
String userId,
String encryptedPwd
) {
this.email = email;
this.nickname = nickname;
this.phoneNumber = phoneNumber;
this.userId = userId;
this.encryptedPwd = encryptedPwd;
}
}
ResponseUser객체는 컨트롤러에서 반환값인 body에 넣어줄 객체입니다. 원래는 vo객체는 read-only때문에 @Getter 어노테이션만 사용했지만 ResponseUser는 값을 만들어야 하기 때문에 @Data 어노테이션을 이용하여 Setter도 사용하도록 하겠습니다.
앞으로 사용자 등록, 로그인 요청시 이 vo들에 값을 넣고 요청을 하고 값의 반환을 ResponseUser를 사용하도록 하겠습니다.
dto객체는 Controller -> Service -> Repository로 데이터를 이동시킬 때 사용하는 객체입니다. vo는 read-only라는 특성을 가지기 때문에 데이터의 값을 바꿀 수가 없습니다. 하지만 dto객체를 이용하여 레이어들 사이에서 비즈니스 로직이라던지, 데이터를 변경할 상황이 온다면 이 dto객체의 데이터를 변경하고 최종적으로 dto값을 이용하여 entity에 값을 넣어주어 repository에 데이터를 넣을 수 있게끔 합니다.
@Data
public class UserDto {
private String email;
private String password;
private String nickname;
private String phoneNumber;
private String userId;
private Date createdAt;
private String encryptedPwd;
@Builder
public UserDto(
String email,
String password,
String nickname,
String phoneNumber,
String userId,
Date createdAt,
String encryptedPwd
) {
this.email = email;
this.password = password;
this.nickname = nickname;
this.phoneNumber = phoneNumber;
this.userId = userId;
this.createdAt = createdAt;
this.encryptedPwd = encryptedPwd;
}
}
저는 Builder 어노테이션을 이용하여 UserDto객체가 레이어들을 건너가면서 데이터를 변경할 상황이 온다면 Builder패턴을 이용하여 데이터를 수정하도록 하겠습니다.
service 패키지에 인터페이스인 AuthService를 AuthServiceImpl에서 implements하도록 하겠습니다. 여기서 인터페이스를 만들고 impl클래스를 따로 만드는 이유는 다형성이 가장 크다고 생각합니다. 지금은 소규모의 프로젝트이기 때문에 복잡하거나 다양한 구현이 없어 AuthServiceImpl에서 1:1로 인터페이스내의 메서드를 오버라이딩하는 정도로 그치지만 추후에 대규모의 프로젝트로 넘어갔을 때, 하나의 메서드를 두고 여러 개로 오버라이딩했을 때의 효과가 있기 때문에 인터페이스와 impl클래스를 따로 생성하는 습관으로 구현해보도록 하겠습니다.
public interface AuthService {
UserDto registerUser(UserDto userDto);
}
@Service
public class AuthServiceImpl implements AuthService {
private AuthRepository authRepository;
@Autowired
public AuthServiceImpl(AuthRepository authRepository) {
this.authRepository = authRepository;
}
@Transactional
@Override
public UserDto registerUser(UserDto userDto) {
UserEntity userEntity = UserEntity.builder()
.email(userDto.getEmail())
.nickname(userDto.getNickname())
.phoneNumber(userDto.getPhoneNumber())
.encryptedPwd("encrypted_password")
.userId(UUID.randomUUID().toString())
.createdAt(DateUtil.dateNow())
.build();
authRepository.save(userEntity);
return userDto.builder()
.email(userEntity.getEmail())
.nickname(userEntity.getNickname())
.phoneNumber(userEntity.getPhoneNumber())
.encryptedPwd(userEntity.getEncryptedPwd())
.userId(userEntity.getUserId())
.build();
}
}
서비스 레이어에서는 컨트롤러에서 전달받은 RequestRegister 객체를 전달받고 이에 대한 비즈니스 로직을 처리할 예정입니다.
entity는 jpa를 이용하여 실제 데이터베이스와 매핑이 되도록 하는 클래스입니다.
@Data
@Entity
@Table(name="users")
public class UserEntity {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@Column(nullable = false, length=50)
private String email;
@Column(nullable = false, length = 50)
private String nickname;
@Column(nullable = false)
private String phoneNumber;
@Column(nullable = false, unique = true)
private String userId;
@Column(nullable = false)
private String createdAt;
@Column(nullable = false, unique = true)
private String encryptedPwd;
@Builder
public UserEntity(
String email,
String nickname,
String phoneNumber,
String userId,
String createdAt,
String encryptedPwd
) {
this.email = email;
this.nickname = nickname;
this.phoneNumber = phoneNumber;
this.userId = userId;
this.createdAt = createdAt;
this.encryptedPwd = encryptedPwd;
}
}
password가 빠진 이유는 추후에 구현될 encryptedPwd 관련 BCryptedPasswordEncoder를 이용해 encryptedPwd가 password의 역할을 하기 때문입니다.
repository는 인터페이스로 구현되며 JpaRepostiory를 상속받아 findBy..., get..., save와 같은 기본적인 메서드를 사용할 수 있게 합니다.
@Repository
public interface AuthRepository extends JpaRepository<UserEntity, Long> {
}
다음과 같은 코드로 컨트롤러를 수정하였습니다.
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RequestRegister user) {
UserDto userDto = authService.registerUser(UserDto.builder()
.email(user.getEmail())
.password(user.getPassword())
.nickname(user.getNickname())
.phoneNumber(user.getPhoneNumber())
.build());
return ResponseEntity.status(HttpStatus.CREATED)
.body(ResponseUser.builder()
.email(userDto.getEmail())
.nickname(userDto.getNickname())
.phoneNumber(userDto.getPhoneNumber())
.userId(userDto.getUserId())
.encryptedPwd(userDto.getEncryptedPwd())
.build());
}
화면처럼 결과가 잘 나오는 모습을 볼 수 있습니다. Register에 대한 흐름을 살펴보겠습니다.
1) AuthController에 RequestRegister(email, password, nickname, phoneNumber)라는 vo객체가 요청됩니다.
그리고 Controller에서는 UserDto를 반환하는 authService.registerUser(UserDto) userDto 객체를 생성하고 이 UserDto객체를 이용하여 반환값으로 ResponseUser와 201 Created를 반환합니다.
2) AuthService의 registerUser(UserDto)에서는 전달받은 UserDto를 이용하여 UserEntity객체를 만들고 이 객체는 authRepository(userEntity)로 전달하여 데이터베이스에 저장합니다.
그리고 Controller에서 UserDto값을 반환하는 메서드로 호출이 되므로 UserDto를 빌더패턴을 이용하여 UserEntity의 값을 담아 반환합니다.