티스토리 뷰
728x90
실습 2부 - 의존성 역전하기
- 외부 연동들에 의존성 역전 사용!
- MailSender
- Repository
- Repository 들의 네이밍 JpaRepository로 변경!
의존성 역전을 위해 인터페이스 만들기.
- UserRepository 인터페이스를 생성
- 구현체 UserRepositoryImpl 생성
- 구현체는 UserJpaRepository를 주입받음.
UserRepository는 infrastructure에 있을 경우 상위 모듈인 service에서 하위인 infrastructure를 의존하는 그림이 나오게 됨.
따라서 서비스 하위에 외부 연동을 담당하는 port 패키지 생성 후 service에서 사용하는 인터페이스들은 port에 위치 시킴.
분리된 Repository에 따라 코드 수정
post
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
private final UserService userService;
public PostEntity getById(long id) {
return postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Posts", id));
}
public PostEntity create(PostCreate postCreate) {
UserEntity userEntity = userService.getById(postCreate.getWriterId());
PostEntity postEntity = new PostEntity();
postEntity.setWriter(userEntity);
postEntity.setContent(postCreate.getContent());
postEntity.setCreatedAt(Clock.systemUTC().millis());
return postRepository.save(postEntity);
}
public PostEntity update(long id, PostUpdate postUpdate) {
PostEntity postEntity = getById(id);
postEntity.setContent(postUpdate.getContent());
postEntity.setModifiedAt(Clock.systemUTC().millis());
return postRepository.save(postEntity);
}
}
public interface PostRepository {
Optional<PostEntity> findById(long id);
PostEntity save(PostEntity postEntity);
}
@Repository
@RequiredArgsConstructor
public class PostRepositoryImpl implements PostRepository {
private final PostJpaRepository postJpaRepository;
@Override
public Optional<PostEntity> findById(long id) {
return postJpaRepository.findById(id);
}
@Override
public PostEntity save(PostEntity postEntity) {
return postJpaRepository.save(postEntity);
}
}
user
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final JavaMailSender mailSender;
public UserEntity getByEmail(String email) {
return userRepository.findByEmailAndStatus(email, UserStatus.ACTIVE)
.orElseThrow(() -> new ResourceNotFoundException("Users", email));
}
public UserEntity getById(long id) {
return userRepository.findByIdAndStatus(id, UserStatus.ACTIVE)
.orElseThrow(() -> new ResourceNotFoundException("Users", id));
}
@Transactional
public UserEntity create(UserCreate userCreate) {
UserEntity userEntity = new UserEntity();
userEntity.setEmail(userCreate.getEmail());
userEntity.setNickname(userCreate.getNickname());
userEntity.setAddress(userCreate.getAddress());
userEntity.setStatus(UserStatus.PENDING);
userEntity.setCertificationCode(UUID.randomUUID().toString());
userEntity = userRepository.save(userEntity);
String certificationUrl = generateCertificationUrl(userEntity);
sendCertificationEmail(userCreate.getEmail(), certificationUrl);
return userEntity;
}
@Transactional
public UserEntity update(long id, UserUpdate userUpdate) {
UserEntity userEntity = getById(id);
userEntity.setNickname(userUpdate.getNickname());
userEntity.setAddress(userUpdate.getAddress());
userEntity = userRepository.save(userEntity);
return userEntity;
}
@Transactional
public void login(long id) {
UserEntity userEntity = userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Users", id));
userEntity.setLastLoginAt(Clock.systemUTC().millis());
}
@Transactional
public void verifyEmail(long id, String certificationCode) {
UserEntity userEntity = userRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Users", id));
if (!certificationCode.equals(userEntity.getCertificationCode())) {
throw new CertificationCodeNotMatchedException();
}
userEntity.setStatus(UserStatus.ACTIVE);
}
private void sendCertificationEmail(String email, String certificationUrl) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject("Please certify your email address");
message.setText("Please click the following link to certify your email address: " + certificationUrl);
mailSender.send(message);
}
private String generateCertificationUrl(UserEntity userEntity) {
return "http://localhost:8080/api/users/" + userEntity.getId() + "/verify?certificationCode=" + userEntity.getCertificationCode();
}
}
public interface UserRepository {
Optional<UserEntity> findById(long id);
Optional<UserEntity> findByIdAndStatus(long id, UserStatus userStatus);
Optional<UserEntity> findByEmailAndStatus(String email, UserStatus userStatus);
UserEntity save(UserEntity userEntity);
}
@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository {
private final UserJpaRepository userJpaRepository;
@Override
public Optional<UserEntity> findById(long id) {
return userJpaRepository.findById(id);
}
@Override
public Optional<UserEntity> findByIdAndStatus(long id, UserStatus userStatus) {
return userJpaRepository.findByIdAndStatus(id, userStatus);
}
@Override
public Optional<UserEntity> findByEmailAndStatus(String email, UserStatus userStatus) {
return userJpaRepository.findByEmailAndStatus(email, userStatus);
}
@Override
public UserEntity save(UserEntity userEntity) {
return userJpaRepository.save(userEntity);
}
}
코드 수정 후 테스트를 돌려서 확인

MailSender 별도 서비스로 분리
@Service
@RequiredArgsConstructor
public class CertificationService {
private final JavaMailSender mailSender;
public void send(String email, String certificationUrl) {
String title = "Please certify your email address";
String content = "Please click the following link to certify your email address: " + certificationUrl;
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject(title);
message.setText(content);
mailSender.send(message);
}
public String generateCertificationUrl(UserEntity userEntity) {
return "http://localhost:8080/api/users/" + userEntity.getId() + "/verify?certificationCode=" + userEntity.getCertificationCode();
}
}
send 리팩토링
- 분리 된 MailSender의 send 부분이 외부에서 주입받아 어색함.
- CertificationService에서 직접 생성하도록 변경.
- 호출하는 부분도 이에 맞도록 변경.
public void send(String email, long userId, String certificationsCode) {
String certificationUrl = generateCertificationUrl(userId, certificationsCode);
String title = "Please certify your email address";
String content = "Please click the following link to certify your email address: " + certificationUrl;
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject(title);
message.setText(content);
mailSender.send(message);
}
public String generateCertificationUrl(long userId, String certificationCode) {
return "http://localhost:8080/api/users/" + userId + "/verify?certificationCode=" + certificationCode;
}
MailSender 의존성 역전 적용
public interface MailSender {
void send(String email, String title, String content);
}
@Component
@RequiredArgsConstructor
public class MailSenderImpl implements MailSender {
private final JavaMailSender javaMailSender;
@Override
public void send(String email, String title, String content) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject(title);
message.setText(content);
javaMailSender.send(message);
}
}
@Service
@RequiredArgsConstructor
public class CertificationService {
private final MailSender mailSender;
public void send(String email, long userId, String certificationsCode) {
String certificationUrl = generateCertificationUrl(userId, certificationsCode);
String title = "Please certify your email address";
String content = "Please click the following link to certify your email address: " + certificationUrl;
mailSender.send(email, title, content);
}
public String generateCertificationUrl(long userId, String certificationCode) {
return "http://localhost:8080/api/users/" + userId + "/verify?certificationCode=" + certificationCode;
}
}
의존성 역전 후 테스트

새로 생긴 CertificationService에 대한 테스트 작성
- SpringBoot 어노테이션 없이 테스트 작성!
- 이메일과 컨텐츠가 제대로 생성되어 동작하는지 확인하고자함.
MailSender를 필요로 하기 때문에 Fake를 만들어서 테스트에 사용
public class FakeMailSender implements MailSender {
public String email;
public String title;
public String content;
@Override
public void send(String email, String title, String content) {
this.email = email;
this.title = title;
this.content = content;
}
}
public class FakeMailSender implements MailSender {
public String email;
public String title;
public String content;
@Override
public void send(String email, String title, String content) {
this.email = email;
this.title = title;
this.content = content;
}
}
테스트 결과

728x90
'개발 > Spring' 카테고리의 다른 글
015 도메인에 테스트 추가하기 - Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 (0) | 2024.07.12 |
---|---|
014 도메인과 영속성 객체 구분하기 - Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 (0) | 2024.07.10 |
012 패키지 구조 개선 - Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 (0) | 2024.06.26 |
011 어떻게 변경할 것인가 - Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 (0) | 2024.06.25 |
010 레이어드 아키텍처의 문제점과 해결책 - Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 (0) | 2024.06.24 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 16기
- server
- test
- 프로그래머스
- 인프런
- 1주차
- 글로컬
- 육.지.행
- 스터디
- 중간발표
- 글또
- 15기
- 해커톤
- it 동아리
- 후기
- 리빙랩
- 서버
- 10기
- 파이썬
- python
- spring boot
- AWS
- 회고
- tdd
- 연합 동아리
- 알고리즘
- 6팀
- 디프만
- 육지행
- 백엔드
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
글 보관함