본문 바로가기
Projects/청하-청년을 위한 커뮤니티 서비스

[청하] 31. 찜한 정책 리스트 조회 기능 구현

by Lpromotion 2024. 10. 21.

찜하기 기능에 이어 각 사용자가 자신이 찜한 정책을 조회할 수 있는 기능을 구현했다. 정책 찜하기 기능에서 고려한 사항을 그대로 이어 구현했다.

 

찜하기 기능을 위해 고려해야할 사항

  1. 현재 정책 업데이트 방식
    • 기존 데이터를 모두 삭제하고 외부 API를 호출해 업데이트된 데이터를 새로 저장.
    • 따라서 원본 정책이 사라지면 정책 정보를 불러올 수 없음.
  2. 찜하기 테이블에 정책 정보 저장의 부담
    • 정책 정보를 찜하기 테이블에 저장하기에는 자원이 많이 소모됨.
  3. 외래키 제약 조건 문제
    • 현재 업데이트 방식을 고려하면 찜하기 테이블의 policyId 에 외래키 제약 조건을 설정할 수 없음.

 

해결 방안

  1. 정책 찜하기 테이블
    • 외래키 제약 조건을 설정하기 않고 정책 찜하기 테이블에 정책 id를 저장.
    • isActive 컬럼을 추가. (정책의 활성화 상태를 저장)
    • 비활성화 되는 경우를 대비해 정책 찜하기 호출 시 최소한의 정보(정책 제목 등)를 함께 저장.
  2. 찜한 정책 조회 로직
    • 사용자가 찜한 모든 정책을 조회하고, 각 정책의 policyId를 정책 테이블에서 조회
    • 사용자가 찜한 정책이 정책 테이블에 존재하는 경우
      • isActive가 false이면 true로 변경 (삭제되었다가 다시 활성화 된 경우)
      • 해당하는 정책 정보를 리턴
    • 사용자가 찜한 정책이 정책 테이블에 존재하지 않는 경우
      • isActive가 true이면 false로 변경
      • 최소한의 정보(정책 찜하기 테이블에 저장된) 리턴

위 방법 사용 시

  • 사용자는 비활성화된 정책도 확인할 수 있고, 어떤 정책이 비활성화 되었는지 알 수 있음.
  • isActive가 false인 정책에 대한 추가적인 안내가 필요. (비활성화 되었다는 메시지 등으로 찜하기 해제 유도)

 

1. 프로젝트 구조

src/main/java/com/example/withpeace/
│
├── domain/                   # 도메인 모델 (엔티티)
│   ├── YouthPolicy.java      # 청년 정책 엔티티
│   └── FavoritePolicy.java   # 청년 정책 찜하기 엔티티
│
├── repository/                       # 데이터 접근 계층
│   ├── YouthPolicyRepository.java    # 청년 정책 레포지토리
│   └── FavoritePolicyRepository.java # 정책 찜하기 레포지토리
│
├── dto/                                        # 데이터 전송 객체
│   └── response/
│       └── FavoritePolicyListResponseDto.java  # 찜한 정책 리스트 조회 응답 DTO
│
├── controller/               # 컨트롤러 계층
│   └── PolicyController.java # 정책 관련 API 엔드포인트
│
└── service/                  # 비즈니스 로직 계층
    └── PolicyService.java    # 정책 관련 비즈니스 로직

 

2. 레포지토리 구현

FavoritePolicyRepository

public interface FavoritePolicyRepository extends JpaRepository<FavoritePolicy, Long> {
    // ... (기존 코드 생략)

    List<FavoritePolicy> findByUserOrderByCreateDateDesc(User user);
}

`findByUserOrderByCreateDateDesc`는 사용자가 찜한 정책을 생성일 기준으로 내림차순 조회한다.

 

3. DTO 구현

FavoritePolicyListResponseDto - 찜한 정책 리스트 조회 응답 DTO

@Builder
public record FavoritePolicyListResponseDto(
        String id,
        String title,
        String introduce,
        EPolicyClassification classification,
        EPolicyRegion region,
        String ageInfo,
        boolean isActive) {

    // YouthPolicy 엔티티와 isActive 상태를 받아 DTO를 생성하는 정적 팩토리 메서드
    public static FavoritePolicyListResponseDto from(YouthPolicy policy, boolean isActive) {
        return new FavoritePolicyListResponseDto(
                policy.getId(),
                policy.getTitle(),
                policy.getIntroduce(),
                policy.getClassification(),
                policy.getRegion(),
                policy.getAgeInfo(),
                isActive
        );
    }

    // FavoritePolicy 엔티티로부터 DTO를 생성하는 정적 팩토리 메서드
    // 정책이 삭제된 경우 사용
    public static FavoritePolicyListResponseDto from(FavoritePolicy favoritePolicy) {
        return new FavoritePolicyListResponseDto(
                favoritePolicy.getPolicyId(),
                favoritePolicy.getTitle(),
                null, // introduce
                null, // classification
                null, // region
                null, // ageInfo
                favoritePolicy.isActive()
        );
    }
}

이 DTO는 두 가지 정적 팩토리 메서드를 제공한다.

  1. `from(YouthPolicy policy, boolean isActive)` : 정책이 존재하는 경우 사용된다. YouthPolicy 엔티티의 정보와 isActive 상태를 받아 DTO를 생성한다.
  2. `from(FavoritePolicy favoritePolicy)` : 정책이 삭제되어 YouthPolicy 엔티티가 없는 경우 사용된다. FavoritePolicy 엔티티에 저장된 최소한의 정보만으로 DTO를 생성한다.

 

4. 컨트롤러 구현

YouthPolicyController

@RestController
@RequiredArgsConstructor
@RequestMapping("api/v1/policies")
@Slf4j
public class YouthPolicyController {
    private final YouthPolicyService youthPolicyService;
    // ... (기존 코드 생략)
    
    // 내가 찜한 정책 조회
    @GetMapping("/favorites")
    public ResponseDto<?> getFavoritePolicy(@UserId Long userId) {
        return ResponseDto.ok(youthPolicyService.getFavoritePolicy(userId));
    }

}

`getFavoritePolicy`는 사용자 ID를 입력받아 서비스로 전달한다. 사용자가 자신이 찜한 정책만을 조회할 수 있다.

 

5. 서비스 구현

YouthPolicyService

@Service
@RequiredArgsConstructor
@Slf4j
public class YouthPolicyService {
    // ... (기존 코드 생략)

    @Transactional
    public List<FavoritePolicyListResponseDto> getFavoritePolicy(Long userId) {
        User user = getUserById(userId);

        try{
            // 사용자가 찜한 정책 목록 조회
            List<FavoritePolicy> favoritePolicies = favoritePolicyRepository.findByUserOrderByCreateDateDesc(user);
            List<FavoritePolicyListResponseDto> favoritePolicyListResponseDtos = new ArrayList<>();

            for(FavoritePolicy favoritePolicy : favoritePolicies) {
                // 찜한 정책이 현재 정책 테이블에 존재하는지 확인
                YouthPolicy policy = youthPolicyRepository.findById(favoritePolicy.getPolicyId()).orElse(null);
                if(policy != null) {
                    // 해당 정책이 존재하는 경우
                    if(!favoritePolicy.isActive()) favoritePolicy.setIsActive(true); // 비활성 상태였으면 활성화
                    FavoritePolicyListResponseDto responseDto =
                            FavoritePolicyListResponseDto.from(policy, favoritePolicy.isActive());
                    favoritePolicyListResponseDtos.add(responseDto);
                }
                else { // 해당 정책이 존재하지 않는 경우
                    if(favoritePolicy.isActive()) favoritePolicy.setIsActive(false); // 활성 상태였으면 비활성화
                    FavoritePolicyListResponseDto responseDto =
                            FavoritePolicyListResponseDto.from(favoritePolicy);
                    favoritePolicyListResponseDtos.add(responseDto);
                }
            }

            return favoritePolicyListResponseDtos;

        } catch (Exception e) {
            log.error(e.getMessage());
            throw new CommonException(ErrorCode.YOUTH_POLICY_ERROR);
        }
    }

}
  1. 사용자를 조회한다. (존재 여부 확인)
  2. 해당 사용자가 찜한 모든 정책을 조회한다.
  3. 각 찜한 정책에 대해
    • 정책 테이블에 존재하는 경우
      • isActive가 false이면 true로 변경 (삭제되었다가 다시 활성화 된 경우)
      • 해당하는 정책 정보를 리턴
    • 정책 테이블에 존재하지 않는 경우
      • isActive가 true이면 false로 변경
      • 최소한의 정보(정책 찜하기 테이블에 저장된) 리턴
  4. 생성된 DTO 리스트를 반환한다.

 

6. API 응답 예시

요청 URL

[GET] http://cheongha.site/api/v1/policies/favorites

 

Response Body

{
    "data": [
        { // 삭제된 정책일 경우
            "id": "R2024070424688",
            "title": "청년 (예비)창업가 거주시설 지원",
            "introduce": null,
            "classification": null,
            "region": null,
            "ageInfo": null,
            "isActive": false
        },
        {
            "id": "R2024070424704",
            "title": "청년 로컬디저트 메뉴개발 컨설팅",
            "introduce": "청년창업가의 창업 경쟁력을 높이기 위해 로컬을 활용한 디저트 메뉴개발을 지원",
            "classification": "JOB",
            "region": "광주",
            "ageInfo": "만 19세 ~ 39세",
            "isActive": true
        }
    ],
    "error": null
}

 

7. 결과

  1. 사용자는 자신이 찜한 모든 정책을 조회할 수 있다.
  2. 정책이 삭제되더라도 사용자의 찜 목록에서 완전히 사라지지 않고, 비활성화 상태로 표시된다.
  3. 삭제되었던 정책이 다시 추가될 경우, 자동으로 활성화 상태로 변경된다.
  4. 정책 데이터의 주기적인 갱신에도 사용자의 찜 목록은 유지된다.

삭제된 정책이 늘어날수록 비활성화된 찜한 정책이 많아질 것이므로, 이후 이 데이터들을 주기적으로 삭제하는 것을 고려중이다.

반응형

댓글