사용자와 기업 사용자가 다양한 활동(예: 리뷰 작성, 회원가입 등)을 통해 포인트를 적립할 수 있도록 하는 포인트 시스템을 구현했습니다. 기존 시스템에서는 포인트 적립 로직이 여러 서비스 클래스(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만 수정하면 되므로 확장성이 향상되었습니다.