사용자와 기업 사용자가 다양한 활동(예: 리뷰 작성, 회원가입 등)을 통해 포인트를 적립할 수 있도록 하는 포인트 시스템을 구현했습니다. 기존 시스템에서는 포인트 적립 로직이 여러 서비스 클래스(AuthService
, ReviewService
등)에 분산되어 있어 코드 중복이 발생하고 유지보수성이 떨어졌습니다. 이러한 문제를 해결하기 위해 단일 책임 원칙(Single Responsibility Principle, SRP)과 관심사 분리(Separation of Concerns, SoC)를 적용하여 포인트 시스템을 리팩토링했습니다.
공통 인터페이스 정의: User
와 CompanyUser
가 공통으로 구현할 CommonPoint
인터페이스를 정의했습니다.
public interface CommonPoint {
Point getPoint();
void setPoint(Point point);
}
인터페이스 구현: User
와 CompanyUser
클래스가 CommonPoint
인터페이스를 구현하도록 수정했습니다.
public class User implements CommonPoint {
private Point point;
@Override
public Point getPoint() {
return point;
}
@Override
public void setPoint(Point point) {
this.point = point;
}
}
public class CompanyUser implements CommonPoint {
private Point point;
@Override
public Point getPoint() {
return point;
}
@Override
public void setPoint(Point point) {
this.point = point;
}
}
포인트 서비스 리팩토링: 포인트 관련 로직을 PointService
클래스에 집중시켰습니다.
@Service
@RequiredArgsConstructor
public class PointService {
private final PointRepository pointRepository;
private final SavedPointRepository savedPointRepository;
@Transactional
public void earnPoints(CommonPoint holder, SpType spType) {
int points = getPointsByType(spType);
Point point = holder.getPoint();
if (point == null) {
point = new Point(points);
} else {
point.setPoint(point.getPoint() + points);
}
pointRepository.save(point);
holder.setPoint(point);
SavedPoint savedPoint = SavedPoint.builder()
.point(point)
.spAmount(points)
.spBalance(point.getPoint())
.spType(spType)
.build();
savedPointRepository.save(savedPoint);
}
private int getPointsByType(SpType spType) {
return switch (spType) {
case REVIEW, BOARD -> 100;
case SIGNUP -> 500;
default -> 0;
};
}
}
서비스 클래스 리팩토링: AuthService
와 ReviewService
에서 포인트 관련 로직을 제거하고, 대신 PointService
를 사용하도록 수정했습니다.
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserRepository userRepository;
private final CompanyUserRepository companyUserRepository;
private final PointService pointService;
@Transactional
public void signup(UserSignupRequestDTO signupRequest) {
validateUserSignupRequest(signupRequest.getEmail(), signupRequest.getPassword(),
signupRequest.getConfirmPassword(), signupRequest.isAgreeToTerms());
User user = User.builder()
.username(signupRequest.getUsername())
.nickname(signupRequest.getNickname())
.email(signupRequest.getEmail())
.password(passwordEncoder.encode(signupRequest.getPassword()))
.loginType(signupRequest.getLoginType())
.build();
userRepository.save(user);
pointService.earnPoints(user, SpType.SIGNUP);
}
@Transactional
public void cpSignup(CpUserSignupRequestDTO requestDTO) {
validateCpSignupRequest(requestDTO.getCpEmail(), requestDTO.getCpPassword(),
requestDTO.getCpConfirmPassword(), requestDTO.isAgreeToTerms());
CompanyUser companyUser = CompanyUser.builder()
.hiringStatus(requestDTO.getHiringStatus())
.employeeCount(requestDTO.getEmployeeCount())
.foundationDate(requestDTO.getFoundationDate())
.description(requestDTO.getDescription())
.cpNum(encryptionService.encryptCpNum(requestDTO.getCpNum()))
.cpName(requestDTO.getCpName())
.cpUsername(requestDTO.getCpUsername())
.cpEmail(requestDTO.getCpEmail())
.cpPhoneNumber(requestDTO.getCpPhoneNumber())
.cpPassword(passwordEncoder.encode(requestDTO.getCpPassword()))
.build();
companyUserRepository.save(companyUser);
pointService.earnPoints(companyUser, SpType.SIGNUP);
}
}
@Service
@RequiredArgsConstructor
public class ReviewService {
private final ReviewRepository reviewRepository;
private final CompanyUserRepository companyUserRepository;
private final UserRepository userRepository;
private final PointService pointService;
@Transactional
public ReviewCreationResponseDTO createReview(Long userId, Long cpUserId, ReviewCreationRequestDTO requestDto) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ApiException(ErrorCode.USER_NOT_FOUND));
CompanyUser companyUser = companyUserRepository.findById(cpUserId)
.orElseThrow(() -> new ApiException(ErrorCode.COMPANY_USER_NOT_FOUND));
Review review = Review.builder()
.user(user)
.cpUser(companyUser)
.reviewTitle(requestDto.getReviewTitle())
.reviewContent(requestDto.getReviewContent())
.rating(requestDto.getRating())
.isPrivate(true)
.build();
review = reviewRepository.save(review);
pointService.earnPoints(user, SpType.REVIEW);
return new ReviewCreationResponseDTO(
review.getReviewId(),
requestDto.getCpUserId(),
review.getReviewTitle(),
review.getReviewContent(),
review.getRating(),
review.getIsPrivate()
);
}
}
PointService
에 집중되어 코드 중복이 제거되었습니다.PointService
만 수정하면 되므로 확장성이 향상되었습니다.