찜하기 기능에 이어 각 사용자가 자신이 찜한 정책을 조회할 수 있는 기능을 구현했다. 정책 찜하기 기능에서 고려한 사항을 그대로 이어 구현했다.
찜하기 기능을 위해 고려해야할 사항
- 현재 정책 업데이트 방식
- 기존 데이터를 모두 삭제하고 외부 API를 호출해 업데이트된 데이터를 새로 저장.
- 따라서 원본 정책이 사라지면 정책 정보를 불러올 수 없음.
- 찜하기 테이블에 정책 정보 저장의 부담
- 정책 정보를 찜하기 테이블에 저장하기에는 자원이 많이 소모됨.
- 외래키 제약 조건 문제
- 현재 업데이트 방식을 고려하면 찜하기 테이블의 policyId 에 외래키 제약 조건을 설정할 수 없음.
해결 방안
- 정책 찜하기 테이블
- 외래키 제약 조건을 설정하기 않고 정책 찜하기 테이블에 정책 id를 저장.
- isActive 컬럼을 추가. (정책의 활성화 상태를 저장)
- 비활성화 되는 경우를 대비해 정책 찜하기 호출 시 최소한의 정보(정책 제목 등)를 함께 저장.
- 찜한 정책 조회 로직
- 사용자가 찜한 모든 정책을 조회하고, 각 정책의 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는 두 가지 정적 팩토리 메서드를 제공한다.
- `from(YouthPolicy policy, boolean isActive)` : 정책이 존재하는 경우 사용된다. YouthPolicy 엔티티의 정보와 isActive 상태를 받아 DTO를 생성한다.
- `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);
}
}
}
- 사용자를 조회한다. (존재 여부 확인)
- 해당 사용자가 찜한 모든 정책을 조회한다.
- 각 찜한 정책에 대해
- 정책 테이블에 존재하는 경우
- isActive가 false이면 true로 변경 (삭제되었다가 다시 활성화 된 경우)
- 해당하는 정책 정보를 리턴
- 정책 테이블에 존재하지 않는 경우
- isActive가 true이면 false로 변경
- 최소한의 정보(정책 찜하기 테이블에 저장된) 리턴
- 정책 테이블에 존재하는 경우
- 생성된 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. 결과
- 사용자는 자신이 찜한 모든 정책을 조회할 수 있다.
- 정책이 삭제되더라도 사용자의 찜 목록에서 완전히 사라지지 않고, 비활성화 상태로 표시된다.
- 삭제되었던 정책이 다시 추가될 경우, 자동으로 활성화 상태로 변경된다.
- 정책 데이터의 주기적인 갱신에도 사용자의 찜 목록은 유지된다.
삭제된 정책이 늘어날수록 비활성화된 찜한 정책이 많아질 것이므로, 이후 이 데이터들을 주기적으로 삭제하는 것을 고려중이다.
반응형
'Projects > 청하-청년을 위한 커뮤니티 서비스' 카테고리의 다른 글
[청하] 33. 정책 조회 API - 찜하기 여부 추가 & 인증 설정 변경 (2) | 2024.10.23 |
---|---|
[청하] 32. 정책 찜하기 해제 기능 구현 (0) | 2024.10.22 |
[청하] 30. 정책 찜하기 기능 구현 (0) | 2024.10.21 |
[청하] 29. GCP 환경 구축 - SSH 키 설정 및 CI/CD 파이프라인 구성 (2) | 2024.10.19 |
[청하] 28. GCP 설정 (feat. GCS 설정 및 스프링부트 연동) (0) | 2024.10.19 |
댓글