-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 응원 태그 도메인 추가 #157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat] 응원 태그 도메인 추가 #157
Conversation
Walkthrough응원 태그 도메인 추가: CheerTag 엔티티, CheerTagName/Category enum, CheerTagNames 값객체(검증 포함), 관련 예외코드 추가, DB 마이그레이션 및 시드 추가, 단위 테스트 추가. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant CheerTagNames
participant BusinessException
Client->>CheerTagNames: new(List<CheerTagName>)
CheerTagNames->>CheerTagNames: 중복 검사, 카테고리별 개수 검사(최대2)
alt 검증 실패
CheerTagNames-->>BusinessException: throw(code)
else 검증 성공
CheerTagNames-->>Client: 인스턴스 반환
end
Client->>CheerTagNames: toCheerTags(Cheer)
CheerTagNames-->>Client: List<CheerTag>
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Assessment against linked issues
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
📌 최신 ERD가 자동 생성되었습니다. 👉 ERD 보러가기 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (7)
src/main/java/eatda/domain/cheer/CheerTagName.java (2)
23-24: 불필요한 세미콜론(;) 한 줄 제거 권장enum 상수 목록 종료용 세미콜론은 한 번이면 충분합니다. 현재는 연속된 2개의 세미콜론이 있어 불필요합니다(컴파일은 되지만 잡음입니다).
- MANY_NEARBY_ATTRACTIONS(CheerTagCategory.PRACTICAL); - ; + MANY_NEARBY_ATTRACTIONS(CheerTagCategory.PRACTICAL);
26-34: 이름 명확성: type → category 리네이밍 고려필드명이 type이라 다소 추상적입니다. category가 더 도메인 의도를 잘 드러냅니다. 다만, Getter 이름(getType → getCategory)과 참조부(예: CheerTagNames) 전반 수정이 수반되므로 영향 범위를 감안해 선택하세요.
src/test/java/eatda/domain/cheer/CheerTagNamesTest.java (4)
19-26: 테스트 이름 한글 오타(‘테그’ → ‘태그’) 수정 제안가독성과 검색성을 위해 테스트 메서드명을 정정해 주세요.
- void 각_카테고리별_테그는_최대_개수가_정해져있다() { + void 카테고리별_태그는_최대_2개까지_허용된다() {
28-33: 테스트 이름 한글 오타(‘테그’ → ‘태그’) 수정 제안- void 테그_이름은_비어있을_수_있다() { + void 태그_이름은_비어있을_수_있다() {
35-43: 테스트 이름 한글 오타(‘테그’ → ‘태그’) 및 문장 간결화 제안- void 각_카테고리별_테그는_최대_개수를_초과할_수_없다() { + void 카테고리별_태그는_최대_개수를_초과할_수_없다() {
45-52: 테스트 이름 한글 오타(‘테그’ → ‘태그’) 수정 제안 + 추가 테스트 제안 가능중복 검증 테스트명도 동일하게 정정하는 것을 권장합니다. 원하시면 toCheerTags(Cheer) 변환에 대한 단위 테스트 추가도 도와드릴 수 있습니다.
- void 테그_이름은_중복될_수_없다() { + void 태그_이름은_중복될_수_없다() {src/main/java/eatda/domain/cheer/CheerTagNames.java (1)
23-24: 네이밍 개선 제안: countMaxCountByType → maxCountPerType반환값의 의미(타입별 최대 개수)를 더 명확히 드러내면 읽기 쉬워집니다. 동작은 동일합니다.
- if (countMaxCountByType(cheerTagNames) > MAX_CHEER_TAGS_PER_TYPE) { + if (maxCountPerType(cheerTagNames) > MAX_CHEER_TAGS_PER_TYPE) { throw new BusinessException(BusinessErrorCode.EXCEED_CHEER_TAGS_PER_TYPE); } @@ - private long countMaxCountByType(List<CheerTagName> cheerTagNames) { + private long maxCountPerType(List<CheerTagName> cheerTagNames) { return cheerTagNames.stream() .collect(Collectors.groupingBy(CheerTagName::getType, Collectors.counting())) .values() .stream() .max(Long::compareTo) .orElse(0L); }Also applies to: 35-42
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/main/java/eatda/domain/cheer/CheerTag.java(1 hunks)src/main/java/eatda/domain/cheer/CheerTagName.java(1 hunks)src/main/java/eatda/domain/cheer/CheerTagNames.java(1 hunks)src/main/java/eatda/exception/BusinessErrorCode.java(2 hunks)src/main/resources/db/migration/V6__add_cheer_tag_table.sql(1 hunks)src/main/resources/db/seed/dev/V7__dev_add_tag_data.sql(1 hunks)src/main/resources/db/seed/local/V7__local_add_tag_data.sql(1 hunks)src/main/resources/db/seed/prod/V7__prod_add_tag_data.sql(1 hunks)src/test/java/eatda/domain/cheer/CheerTagNamesTest.java(1 hunks)
🔇 Additional comments (6)
src/main/resources/db/seed/prod/V7__prod_add_tag_data.sql (1)
1-1: Prod 시드 생략 의도 명확 — 버전 정합성 유지 차원에서 적절합니다운영 환경에서는 샘플 태그 데이터를 넣지 않고 애플리케이션 플로우로만 적재한다는 정책으로 이해됩니다. 동일 버전(V7) placeholder를 두어 환경 간 이력 정합성도 유지됩니다.
src/main/resources/db/seed/local/V7__local_add_tag_data.sql (1)
1-12: 로컬 시드 데이터 검증 완료 — CheerTagName 매핑 및 FK 무결성 문제 없음
- seed에 사용된 모든 태그명(GOOD_FOR_FAMILY, GOOD_FOR_DATING, INSTAGRAMMABLE, QUIET, NEAR_SUBWAY, CLEAN_RESTROOM, MANY_NEARBY_ATTRACTIONS, LATE_NIGHT)이
CheerTagNameenum에 정의되어 있습니다.- V2__local_init_data.sql에 cheer_id(1, 2, 4, 5, 6, 7)가 모두 포함되어 있어 FK 무결성에도 문제가 없습니다.
해당 변경사항 승인합니다.
src/main/resources/db/seed/dev/V7__dev_add_tag_data.sql (1)
1-12: Dev 시드 데이터 구성 적절 — 로컬 시드와 동형 유지 확인로컬 시드와 동일한 데이터셋으로 일관성이 좋아 보입니다. 위 로컬 시드 코멘트의 FK/매핑 검증 스크립트로 dev 파일도 동일하게 검증 가능합니다.
src/main/java/eatda/exception/BusinessErrorCode.java (1)
32-35: 새 CheerTag 관련 에러 코드 추가 적절
- 중복 태그(CHEER_TAGS_DUPLICATED), 타입별 최대 초과(EXCEED_CHEER_TAGS_PER_TYPE) 모두 도메인 규칙을 명확히 반영하고 있습니다.
- 기본 HttpStatus BAD_REQUEST도 합리적입니다.
src/main/java/eatda/domain/cheer/CheerTagNames.java (2)
28-33: 중복 태그 검증 로직 적절enum 기반이라면
.distinct()로 중복 검출이 간결하고 정확합니다. 이 부분은 그대로 👍
46-47: JDK 호환성 확인 필요: Stream.toList() API 사용현재
build.gradle에서sourceCompatibility나targetCompatibility설정을 찾을 수 없어 프로젝트가 JDK 16 이상을 타겟으로 하고 있는지 불명확합니다.
만약 JDK 16 미만 환경도 지원해야 한다면, 아래처럼.collect(Collectors.toList())로 대체하는 것을 고려해 주세요.
- 대상 파일:
src/main/java/eatda/domain/cheer/CheerTagNames.java- 대상 라인: 46–47
수정 예시:
- .map(name -> new CheerTag(cheer, name)) - .toList(); + .map(name -> new CheerTag(cheer, name)) + .collect(Collectors.toList());프로젝트 설정에서 JDK 호환성을 명시적으로 확인하거나, 위 변경을 적용해 주시기 바랍니다.
| @Table(name = "cheer_tag") | ||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class CheerTag { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "cheer_id", nullable = false) | ||
| private Cheer cheer; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(nullable = false, length = 63) | ||
| private CheerTagName name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
(중요) 동일 응원 내 태그 중복 방지를 위한 유니크 제약 및 인덱스 추가 권장
도메인/예외코드에서 중복을 금지하고 있으므로, 엔티티/DDL 레벨에서도 보장하는 것이 안전합니다. 또한 태그 검색 성능을 위해 name, cheer_id 인덱스를 권장합니다. 아래와 같이 @table에 uniqueConstraints와 indexes를 추가해 주세요(마이그레이션도 함께 반영 필요).
import jakarta.persistence.Table;
+import jakarta.persistence.Index;
+import jakarta.persistence.UniqueConstraint;
@@
-@Table(name = "cheer_tag")
+@Table(
+ name = "cheer_tag",
+ uniqueConstraints = @UniqueConstraint(
+ name = "uk_cheer_tag_cheer_name",
+ columnNames = {"cheer_id", "name"}
+ ),
+ indexes = {
+ @Index(name = "idx_cheer_tag_name", columnList = "name"),
+ @Index(name = "idx_cheer_tag_cheer", columnList = "cheer_id")
+ }
+)
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CheerTag {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Table(name = "cheer_tag") | |
| @Entity | |
| @Getter | |
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | |
| public class CheerTag { | |
| @Id | |
| @GeneratedValue(strategy = GenerationType.IDENTITY) | |
| private Long id; | |
| @ManyToOne(fetch = FetchType.LAZY) | |
| @JoinColumn(name = "cheer_id", nullable = false) | |
| private Cheer cheer; | |
| @Enumerated(EnumType.STRING) | |
| @Column(nullable = false, length = 63) | |
| private CheerTagName name; | |
| import jakarta.persistence.Table; | |
| import jakarta.persistence.Index; | |
| import jakarta.persistence.UniqueConstraint; | |
| @Table( | |
| name = "cheer_tag", | |
| uniqueConstraints = @UniqueConstraint( | |
| name = "uk_cheer_tag_cheer_name", | |
| columnNames = {"cheer_id", "name"} | |
| ), | |
| indexes = { | |
| @Index(name = "idx_cheer_tag_name", columnList = "name"), | |
| @Index(name = "idx_cheer_tag_cheer", columnList = "cheer_id") | |
| } | |
| ) | |
| @Entity | |
| @Getter | |
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | |
| public class CheerTag { | |
| @Id | |
| @GeneratedValue(strategy = GenerationType.IDENTITY) | |
| private Long id; | |
| @ManyToOne(fetch = FetchType.LAZY) | |
| @JoinColumn(name = "cheer_id", nullable = false) | |
| private Cheer cheer; | |
| @Enumerated(EnumType.STRING) | |
| @Column(nullable = false, length = 63) | |
| private CheerTagName name; |
🤖 Prompt for AI Agents
In src/main/java/eatda/domain/cheer/CheerTag.java around lines 18 to 34, the
entity lacks a DB-level uniqueness constraint and indexes to prevent duplicate
tags per Cheer and to improve lookup performance; add a @Table annotation with
uniqueConstraints for (cheer_id, name) and indexes on (name, cheer_id) (or both
columns) to enforce uniqueness and speed queries, update the JPA annotation
accordingly, and create/modify the migration script to add the corresponding
unique constraint and indexes in the DDL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 유니크 제약 조건 추가 완료
- 검색 인덱스는 이미 FK를 걸어둔 것 만으로 충분하다고 생각함 (MySQL에서는 FK에 자동으로 인덱스를 건다고 알고 있음)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| CREATE TABLE `cheer_tag` | ||
| ( | ||
| `id` BIGINT NOT NULL AUTO_INCREMENT, | ||
| `cheer_id` BIGINT NOT NULL, | ||
| `name` VARCHAR(63) NOT NULL, | ||
| PRIMARY KEY (`id`), | ||
| FOREIGN KEY (`cheer_id`) REFERENCES `cheer` (`id`) ON DELETE CASCADE | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
(중요) cheer_id + name 유니크 제약과 인덱스 추가로 중복 방지 및 검색 성능 보완 제안
도메인 규칙상 동일 응원(cheer) 내 태그 중복은 불가합니다. 이를 DB 레벨에서도 보장하기 위해 (cheer_id, name) 유니크 키를 추가하는 것을 권장합니다. 또한 태그 기반 검색(합집합)이 목표라면 name, cheer_id 인덱스가 유의미한 이점을 제공합니다. FK에 명시적 이름을 부여하면 운영/장애 분석 시 가독성이 좋아집니다.
아직 적용 전이라면 본 마이그레이션을 수정하세요. 이미 일부 환경에 적용되었다면 후속 마이그레이션(V7+)로 추가 제약/인덱스를 생성하세요.
적용 예시(diff):
CREATE TABLE `cheer_tag`
(
`id` BIGINT NOT NULL AUTO_INCREMENT,
`cheer_id` BIGINT NOT NULL,
`name` VARCHAR(63) NOT NULL,
PRIMARY KEY (`id`),
- FOREIGN KEY (`cheer_id`) REFERENCES `cheer` (`id`) ON DELETE CASCADE
+ CONSTRAINT `fk_cheer_tag_cheer`
+ FOREIGN KEY (`cheer_id`) REFERENCES `cheer` (`id`) ON DELETE CASCADE,
+ UNIQUE KEY `uk_cheer_tag_cheer_name` (`cheer_id`, `name`),
+ INDEX `idx_cheer_tag_name` (`name`),
+ INDEX `idx_cheer_tag_cheer` (`cheer_id`)
);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 유니크 제약 조건 추가 완료
- 검색 인덱스는 이미 FK를 걸어둔 것 만으로 충분하다고 생각함 (MySQL에서는 FK에 자동으로 인덱스를 건다고 알고 있음)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leegwichan 좋습니다! 유니크 제약 조건 추가해주셔서 감사합니다.
MySQL의 FK 자동 인덱스에 대한 이해가 정확합니다. MySQL은 외래 키에 자동으로 인덱스를 생성하므로 cheer_id로 필터링하는 쿼리는 이미 최적화됩니다.
name 인덱스의 경우, PR 목표에 언급된 "태그 기반 합집합 검색" 시나리오를 고려했습니다:
-- 특정 태그들을 가진 응원들을 검색하는 경우
SELECT DISTINCT cheer_id FROM cheer_tag WHERE name IN ('GOOD_FOR_DATING', 'INSTAGRAMMABLE');하지만 현재 단계에서는 FK 인덱스만으로도 충분할 수 있고, 실제 성능 이슈가 발생할 때 추가하는 것도 합리적인 접근입니다.
핵심인 중복 방지를 위한 유니크 제약은 완료되었으니 현재 구현으로 진행하시면 됩니다! 🎉
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
|
📌 최신 ERD가 자동 생성되었습니다. 👉 ERD 보러가기 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/main/java/eatda/domain/cheer/CheerTagNames.java (2)
19-26: 요구사항 준수: 중복·타입별 상한 검증 OK (전체 4개 제한은 의도적으로 미포함)
- 중복 금지와 타입별 최대 2개 검증이 명확하며, 현재 비즈니스 정책(“각 타입별 2개”)에 부합합니다.
- PR 설명의 “최대 4개”는 현재 타입이 두 가지(MOOD, PRACTICAL)라는 전제에서 자연스럽게 충족됩니다. 카테고리 확장 시에만 전체 상한 논의가 필요합니다(과거 코멘트 및 러닝에 따라 의도적으로 제외된 것으로 이해).
44-48: DTO 레이어에서 null 보장을 전제로 한 구현 OK + JDK 버전 호환성 확인 요청
- toList() 사용은 JDK 16+ 필요합니다. 프로젝트 타겟이 Java 17 이상이면 문제 없습니다.
- 메서드 인자 cheer의 null 방어는 팀 규약상 DTO에서 처리(@NotNull)하기로 한 기존 합의(과거 코멘트/러닝)와 일치합니다.
아래 스크립트로 리포지토리 내 JDK 타겟 설정을 점검해 주세요.
#!/bin/bash set -euo pipefail echo "== 후보 구성 파일 목록 ==" fd -a build.gradle || true fd -a build.gradle.kts || true fd -a pom.xml || true fd -a gradle.properties || true echo echo "== Gradle 설정 (sourceCompatibility/targetCompatibility/toolchain/java.version) ==" files_gradle="$(fd -a build.gradle; fd -a build.gradle.kts; fd -a gradle.properties)" if [ -n "${files_gradle}" ]; then rg -n --no-heading -S 'sourceCompatibility|targetCompatibility|toolchain|languageVersion|java\.version' -A 2 -B 2 $files_gradle || true fi echo echo "== Maven 설정 (maven-compiler-plugin/컴파일러 속성) ==" files_maven="$(fd -a pom.xml)" if [ -n "${files_maven}" ]; then rg -n --no-heading -S 'maven-compiler-plugin|maven\.compiler\.source|maven\.compiler\.target|release|maven-toolchains-plugin' -A 2 -B 2 $files_maven || true fi
🧹 Nitpick comments (3)
src/main/java/eatda/domain/cheer/CheerTagNames.java (3)
14-17: 생성자 입력 리스트 null/원소 null 방어 여부 확인 필요
- 현재 구현은 cheerTagNames가 null이면 validate 내부 스트림 처리에서 NPE가 발생합니다. 팀 규약상 DTO 레벨에서 @NotNull로 보장한다면 유지 가능하나, 모든 진입점에서 일관되게 지켜지는지 확인 부탁드립니다. 리스트 원소에 null이 섞인 경우 maxCountByType에서 getType 호출 시 NPE가 날 수 있으므로 원소 null 방지도 고려 대상입니다.
원하시면 도메인 레벨에서 즉시-실패 방어를 추가할 수 있습니다:
추가 import:
import java.util.Objects;적용 diff:
- public CheerTagNames(List<CheerTagName> cheerTagNames) { - validate(cheerTagNames); - this.cheerTagNames = List.copyOf(cheerTagNames); - } + public CheerTagNames(List<CheerTagName> cheerTagNames) { + List<CheerTagName> safe = Objects.requireNonNull(cheerTagNames, "cheerTagNames must not be null"); + validate(safe); + this.cheerTagNames = List.copyOf(safe); + }선호하시는 아키텍처(입력 검증을 DTO에서 일괄 처리) 유지가 목표라면, 대응하는 DTO에 아래 사항이 있는지 점검해 주세요:
35-42: 네이밍/가독성 소폭 개선 제안
- 메서드명이 maxCountByType보다는 maxCountPerType이 더 자연스러운 표현입니다. 또한 마지막 단계에서 mapToLong(Long::longValue)로 변환하면 박싱을 한 번 줄일 수 있습니다(미세 최적화).
아래와 같이 리네이밍과 미세 수정 제안을 드립니다.
- private long maxCountByType(List<CheerTagName> cheerTagNames) { + private long maxCountPerType(List<CheerTagName> cheerTagNames) { return cheerTagNames.stream() .collect(Collectors.groupingBy(CheerTagName::getType, Collectors.counting())) - .values() - .stream() - .max(Long::compareTo) - .orElse(0L); + .values().stream() + .mapToLong(Long::longValue) + .max().orElse(0L); }호출부도 함께 변경 필요:
- if (maxCountByType(cheerTagNames) > MAX_CHEER_TAGS_PER_TYPE) { + if (maxCountPerType(cheerTagNames) > MAX_CHEER_TAGS_PER_TYPE) { throw new BusinessException(BusinessErrorCode.EXCEED_CHEER_TAGS_PER_TYPE); }
8-12: 클래스 레벨 주석으로 불변조건 명시 제안
- 유지보수자에게 도메인 불변식(중복 금지, 타입별 최대 2개, 전체 상한 미검증)을 빠르게 전달하기 위해 클래스 Javadoc을 권장합니다.
아래와 같이 간단히 추가할 수 있습니다.
package eatda.domain.cheer; import eatda.exception.BusinessErrorCode; import eatda.exception.BusinessException; import java.util.List; import java.util.stream.Collectors; +/** + * CheerTagNames는 불변 값 객체로서 다음을 보장합니다: + * - 중복 태그 금지 + * - 타입별 최대 2개까지 허용 + * + * 참고: 현재 정책상 "전체 태그 개수 상한"은 별도 검증하지 않습니다. + * (카테고리 확장 시 재검토) + */ public class CheerTagNames {
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/main/java/eatda/domain/cheer/CheerTag.java(1 hunks)src/main/java/eatda/domain/cheer/CheerTagName.java(1 hunks)src/main/java/eatda/domain/cheer/CheerTagNames.java(1 hunks)src/main/resources/db/migration/V6__add_cheer_tag_table.sql(1 hunks)src/test/java/eatda/domain/cheer/CheerTagNamesTest.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- src/main/resources/db/migration/V6__add_cheer_tag_table.sql
- src/main/java/eatda/domain/cheer/CheerTagName.java
- src/main/java/eatda/domain/cheer/CheerTag.java
- src/test/java/eatda/domain/cheer/CheerTagNamesTest.java
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#157
File: src/main/java/eatda/domain/cheer/CheerTagNames.java:19-26
Timestamp: 2025-08-12T03:10:30.145Z
Learning: CheerTagNames 클래스에서 현재 비즈니스 정책은 "각 타입별 2개"만 존재하며, 전체 태그 개수 제한은 현재 구현 범위에 포함되지 않음. 카테고리 추가 시 추후 고려 예정.
📚 Learning: 2025-08-12T03:10:30.145Z
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#157
File: src/main/java/eatda/domain/cheer/CheerTagNames.java:19-26
Timestamp: 2025-08-12T03:10:30.145Z
Learning: CheerTagNames 클래스에서 현재 비즈니스 정책은 "각 타입별 2개"만 존재하며, 전체 태그 개수 제한은 현재 구현 범위에 포함되지 않음. 카테고리 추가 시 추후 고려 예정.
Applied to files:
src/main/java/eatda/domain/cheer/CheerTagNames.java
📚 Learning: 2025-08-12T03:14:40.829Z
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#157
File: src/main/java/eatda/domain/cheer/CheerTagNames.java:44-48
Timestamp: 2025-08-12T03:14:40.829Z
Learning: 이 프로젝트에서는 null 검증 책임이 DTO 레이어에 있으며, `NotNull` 어노테이션을 사용하여 입력 검증을 처리함. 도메인 레이어는 유효한 데이터를 받는다고 가정하고 비즈니스 로직에 집중함.
Applied to files:
src/main/java/eatda/domain/cheer/CheerTagNames.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (2)
src/main/java/eatda/domain/cheer/CheerTagNames.java (2)
10-12: 불변성·도메인 규칙 상수 정의 적절
- 타입별 최대 개수 상수화(2)와 리스트 필드 불변(List.copyOf + final)로 불변 객체 성격이 잘 유지됩니다. 도메인 규칙이 코드에 명확히 드러납니다.
28-33: 중복 검증 구현 적절
- distinct 기반 카운트 비교로 O(n) 복잡도에서 간단·명확하게 동작합니다. 빈 리스트와 단일 원소 케이스도 안전합니다.
|
📌 최신 ERD가 자동 생성되었습니다. 👉 ERD 보러가기 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/main/resources/db/seed/dev/V6__dev_add_tag_data.sql (1)
1-12: 시드-도메인 정합성 자동 점검 실행 권장local 시드와 동일한 검증을 dev 시드에도 적용하세요. 아래 스크립트는 두 시드를 동시에 점검합니다(위 코멘트의 스크립트와 동일).
🧹 Nitpick comments (1)
src/main/resources/db/migration/V5__add_cheer_tag_table.sql (1)
1-9: 엔진/문자셋/콜레이션 명시 여부 검토(선택)프로젝트 표준이 있다면 ENGINE, DEFAULT CHARSET, COLLATE를 명시하는 것이 재현성과 이식성에 유리합니다. 예: ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/main/resources/db/migration/V5__add_cheer_tag_table.sql(1 hunks)src/main/resources/db/seed/dev/V6__dev_add_tag_data.sql(1 hunks)src/main/resources/db/seed/local/V6__local_add_tag_data.sql(1 hunks)src/main/resources/db/seed/prod/V6__prod_add_tag_data.sql(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/main/resources/db/seed/prod/V6__prod_add_tag_data.sql
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#157
File: src/main/java/eatda/domain/cheer/CheerTagNames.java:19-26
Timestamp: 2025-08-12T03:10:30.145Z
Learning: CheerTagNames 클래스에서 현재 비즈니스 정책은 "각 타입별 2개"만 존재하며, 전체 태그 개수 제한은 현재 구현 범위에 포함되지 않음. 카테고리 추가 시 추후 고려 예정.
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#159
File: src/main/java/eatda/domain/cheer/CheerTag.java:28-30
Timestamp: 2025-08-12T03:22:33.982Z
Learning: CheerTag와 Cheer 엔티티 간의 연관관계는 의도적으로 단방향 매핑(ManyToOne)으로 설계되었습니다. Cheer 엔티티에서 CheerTag 컬렉션으로의 양방향 매핑(OneToMany)은 불필요합니다.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (4)
src/main/resources/db/seed/local/V6__local_add_tag_data.sql (2)
1-12: 시드 데이터 자체는 합리적이며 제약(UK: cheer_id+name)과 충돌 없음각 cheer_id에 대해 중복 태그가 없고(UK 만족), 예시로 cheer_id=6에 4개 태그(분위기 2, 실용도 2로 추정)로 비즈니스 룰과도 부합해 보입니다.
1-12: 자동 검증 통과: 시드 데이터의 태그가 CheerTagName enum 및 정책(카테고리별 최대 2개, 전체 최대 4개)을 모두 만족합니다.다음 항목만 추가로 직접 확인해 주세요:
- cheer_id(1, 2, 4, 5, 6, 7)가 dev/local 환경의 기존 시드에서 실제로 생성되는지, 참조 무결성이 보장되는지 검증
검증 대상 파일:
- src/main/resources/db/seed/dev/V6__dev_add_tag_data.sql
- src/main/resources/db/seed/local/V6__local_add_tag_data.sql
src/main/resources/db/migration/V5__add_cheer_tag_table.sql (1)
3-5: 컬럼 정의 길이와 JPA 매핑 길이의 일관성 재확인JPA 엔티티에서 Enum을 STRING으로 저장하며 length=63으로 지정되어 있는 것으로 보입니다. 마이그레이션과 엔티티 간 길이 불일치가 없는지 재확인 바랍니다. 불일치 시 스키마 검증 또는 런타임 예외가 발생할 수 있습니다.
src/main/resources/db/seed/dev/V6__dev_add_tag_data.sql (1)
1-12: dev 시드도 로컬과 동일하게 정상적이며 제약 충돌 없음중복 없이 합리적인 분포로 보이며, (cheer_id, name) 유니크 키를 위반하지 않습니다.
lvalentine6
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이번 PR도 고생하셨습니다! 🦖
ENUM 관련해서 코멘트 한개 남겼습니다!
| @Getter | ||
| public enum CheerTagName { | ||
|
|
||
| OLD_STORE_MOOD(CheerTagCategory.MOOD), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엇 여기에 이런식으로 한글 디스플레이 네임을 추가하는건 어떨까요?
OLD_STORE_MOOD(CheerTagCategory.MOOD, "노포 감성"),
ENERGETIC(CheerTagCategory.MOOD, "활기 넘치는"),
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
서버 내에서는 사용되지 않을 값이라 생각해서 추가 안했는데요. 하나의 주석처럼 생각한다면 있어도 좋을 것 같아요!
일단 추가하고 정 필요없다고 생각되면 그 때 제거하도록 하겠습니다!
|
|
||
| public class CheerTagNames { | ||
|
|
||
| private static final int MAX_CHEER_TAGS_PER_TYPE = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상수 처리👍🏻
지금은 아니지만.. 나중에 중요한 비즈니스 정책과 연관있는 값들은 yml로 분리하면
빌드 없이 재기동만 시켜줘도 반영될테니... 좋을것 같아요 💦
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어느 값까지 yml로 분리해야 되는지가 기준을 정하기 어렵네요;;
저는 이 값도 도메인 규칙의 일부분이라 생각해서 코드에 적는 것을 선호합니다. 만약에 해당 값이 배포 환경이나 서비스 규칙과 별개로 자주 변경된다면 yml도 좋은 생각이네요! 👍
|
📌 최신 ERD가 자동 생성되었습니다. 👉 ERD 보러가기 |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (5)
src/main/java/eatda/domain/cheer/CheerTagName.java (5)
8-16: 디스플레이 네이밍 자잘한 문구/일관성 수정 제안
- GOOD_FOR_DATING: 띄어쓰기 교정(“데이트하기 좋은”)
- GOOD_FOR_FAMILY: 상수명 의미(가족 친화)와 한글 레이블(부모님) 간 의미 불일치 → “가족과 가기 좋은”이 더 자연스럽습니다.
아래 diff 적용 제안:
- GOOD_FOR_DATING(CheerTagCategory.MOOD, "데이트하기좋은"), + GOOD_FOR_DATING(CheerTagCategory.MOOD, "데이트하기 좋은"), ... - GOOD_FOR_FAMILY(CheerTagCategory.MOOD, "부모님과 가기 좋은"), + GOOD_FOR_FAMILY(CheerTagCategory.MOOD, "가족과 가기 좋은"),
17-23: 자연스러운 한국어 표현으로 미세 조정
- NEAR_SUBWAY: “지하철과 가까운” → “지하철역과 가까운”이 일반적 표현입니다.
아래 diff 적용 제안:
- NEAR_SUBWAY(CheerTagCategory.PRACTICAL, "지하철과 가까운"), + NEAR_SUBWAY(CheerTagCategory.PRACTICAL, "지하철역과 가까운"),
26-28: displayName 오용 방지 주석 추가 제안팀 대화 맥락상 displayName은 서버 로직/DB에는 사용하지 않는 “표기용”임을 코드에 명시하는 것이 안전합니다. i18n/클라이언트 의존을 방지합니다.
아래 diff 적용 제안:
private final CheerTagCategory type; - private final String displayName; + // NOTE: 서버 로직/DB에는 사용하지 않는 표기용 레이블입니다. i18n 대상 아님. + private final String displayName;
33-33: getType()와 병행해 getCategory() alias 제공 제안필드명이 type이라도 도메인 용어는 “category”가 더 직관적입니다. 호환성 유지하며 가독성 개선을 위해 alias getter를 추가하는 것을 제안합니다.
아래 diff 적용 제안(생성자 아래에 메서드 추가):
CheerTagName(CheerTagCategory type, String displayName) { this.type = type; this.displayName = displayName; } + public CheerTagCategory getCategory() { + return type; + }
34-36: CheerTagCategory의 재사용 가능성 고려(선택)다른 도메인에서 카테고리 재사용 가능성이 있거나, enum 분리로 순환 의존 우려를 줄이고자 한다면 최상위 enum으로 분리하는 것도 고려해볼 만합니다. 지금 단계에서는 중첩 enum도 충분히 합리적이므로 선택 사항입니다.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/eatda/domain/cheer/CheerTagName.java(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#157
File: src/main/java/eatda/domain/cheer/CheerTagNames.java:19-26
Timestamp: 2025-08-12T03:10:30.178Z
Learning: CheerTagNames 클래스에서 현재 비즈니스 정책은 "각 타입별 2개"만 존재하며, 전체 태그 개수 제한은 현재 구현 범위에 포함되지 않음. 카테고리 추가 시 추후 고려 예정.
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#159
File: src/main/java/eatda/domain/cheer/CheerTag.java:28-30
Timestamp: 2025-08-12T03:22:34.003Z
Learning: CheerTag와 Cheer 엔티티 간의 연관관계는 의도적으로 단방향 매핑(ManyToOne)으로 설계되었습니다. Cheer 엔티티에서 CheerTag 컬렉션으로의 양방향 매핑(OneToMany)은 불필요합니다.
📚 Learning: 2025-08-12T03:22:34.003Z
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#159
File: src/main/java/eatda/domain/cheer/CheerTag.java:28-30
Timestamp: 2025-08-12T03:22:34.003Z
Learning: CheerTag와 Cheer 엔티티 간의 연관관계는 의도적으로 단방향 매핑(ManyToOne)으로 설계되었습니다. Cheer 엔티티에서 CheerTag 컬렉션으로의 양방향 매핑(OneToMany)은 불필요합니다.
Applied to files:
src/main/java/eatda/domain/cheer/CheerTagName.java
📚 Learning: 2025-08-12T03:10:30.178Z
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#157
File: src/main/java/eatda/domain/cheer/CheerTagNames.java:19-26
Timestamp: 2025-08-12T03:10:30.178Z
Learning: CheerTagNames 클래스에서 현재 비즈니스 정책은 "각 타입별 2개"만 존재하며, 전체 태그 개수 제한은 현재 구현 범위에 포함되지 않음. 카테고리 추가 시 추후 고려 예정.
Applied to files:
src/main/java/eatda/domain/cheer/CheerTagName.java
📚 Learning: 2025-07-20T03:52:23.831Z
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#90
File: src/main/java/eatda/service/story/StoryService.java:93-93
Timestamp: 2025-07-20T03:52:23.831Z
Learning: StoreCategory enum has Getter annotation with a categoryName field, so getCategoryName() method is automatically generated by Lombok at compile time and won't appear in source code searches.
Applied to files:
src/main/java/eatda/domain/cheer/CheerTagName.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (2)
src/main/java/eatda/domain/cheer/CheerTagName.java (2)
5-7: LGTM — enum 기반 모델링과 Lombok @Getter 적용이 도메인 의도에 부합합니다태그 이름과 카테고리를 한 곳에서 관리하도록 한 설계가 명확하고, 불변 필드 + @Getter 조합도 적절합니다.
1-3: 확인: CheerTag enum 매핑이 DB에 STRING으로 저장됩니다CheerTag 엔티티의 name 필드에 @Enumerated(EnumType.STRING)이 선언되어 있어 Enum 값이 문자열로 저장됩니다. 또한 @table의 uniqueConstraints에 name 컬럼(uk_cheer_tag_cheer_id_name: {cheer_id, name})이 포함되어 있으므로 ORDINAL 저장에 대한 우려는 해소됩니다.
- src/main/java/eatda/domain/cheer/CheerTag.java — @Enumerated(EnumType.STRING) 선언 (라인 ~36–38)
- src/main/java/eatda/domain/cheer/CheerTag.java — @table uniqueConstraints ... columnNames = {"cheer_id", "name"} (라인 ~19–22)
추가 수정 불필요합니다.
| package eatda.domain.cheer; | ||
|
|
||
| import lombok.Getter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
비즈니스 규칙 정합성 확인: “카테고리별 최대 2개 + 전체 최대 4개”
PR 목적에는 “전체 최대 4개”가 포함되어 있으나, 학습 메모에 따르면 현재 구현은 “카테고리별 2개 제한”까지만 반영되었을 수 있습니다. 도메인(예: CheerTagNames) 혹은 애플리케이션 서비스 레벨에서 전체 4개 제한도 검증하는지 확인 부탁드립니다.
다음 스크립트로 관련 검증/에러코드 존재 여부를 탐색해볼 수 있습니다:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== CheerTagNames 관련 검증 로직 검색 =="
fd -a "CheerTagNames.java" || true
rg -n -A 3 -B 3 "CheerTagNames|validate|검증|limit|size|max|최대|4|EXCEED|CHEER_TAGS" || true
echo
echo "== 전역 4개 초과 관련 에러 코드/메시지 존재 여부 =="
rg -n -A 2 -B 2 "EXCEED.*(TOTAL|ALL|OVERALL)|TOTAL.*CHEER_TAG|전체.*4|최대.*4" || trueLength of output: 244614
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== CheerTagNames 파일 내용 =="
sed -n '1,160p' src/main/java/eatda/domain/cheer/CheerTagNames.java || true
echo
echo "== CheerTagNames 참조 검색 =="
rg -n --hidden --no-ignore-vcs "CheerTagNames|new CheerTagNames|CheerTagName" || true
echo
echo "== Cheer 태그 관련 에러코드 검색 =="
sed -n '1,140p' src/main/java/eatda/exception/BusinessErrorCode.java | sed -n '1,120p' || true
rg -n "EXCEED_CHEER_TAGS|CHEER_TAG" src || true
echo
echo "== Cheer 등록/업데이트에서 태그 검증 가능성 있는 위치 검색 (서비스/컨트롤러/도메인) =="
rg -n "tag(s)?|tagName|tagName(s)?|cheer.*tag|tags" -S src || trueLength of output: 13167
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== CheerService.java =="
sed -n '1,240p' src/main/java/eatda/service/cheer/CheerService.java || true
echo
echo "== CheerRegisterRequest 검색/파일 =="
rg -n "CheerRegisterRequest" || true
sed -n '1,240p' src/main/java/eatda/service/cheer/request/CheerRegisterRequest.java || true
echo
echo "== Cheer 생성/태그 관련 코드 검색 (CheerTagNames 사용처) =="
rg -n "new CheerTagNames|toCheerTags|CheerTagNames(" -S src || true
echo
echo "== CheerTag 엔티티 파일 =="
sed -n '1,240p' src/main/java/eatda/domain/cheer/CheerTag.java || trueLength of output: 7915
조치 필요: "전체 최대 4개" 검증이 구현되지 않았습니다 — 전역(전체) 태그 개수 검사 추가 필요
검증 결과: src/main/java/eatda/domain/cheer/CheerTagNames.java는 중복 검사와 타입별 최대(MAX_CHEER_TAGS_PER_TYPE = 2)만 수행하고 있으며, 전체 최대(=4) 관련 검증이나 에러코드는 코드베이스에서 발견되지 않았습니다.
수정/확인 필요 지점:
- src/main/java/eatda/domain/cheer/CheerTagNames.java
- 현재 validate()는 중복 및 타입별 최대(2)만 검사합니다. 전체 최대(예: MAX_CHEER_TAGS_TOTAL = 4)에 대한 검사 추가 필요.
- (권장) validate()에 전체 개수 검사 추가: if (cheerTagNames.size() > MAX_CHEER_TAGS_TOTAL) throw new BusinessException(...);
- src/main/java/eatda/exception/BusinessErrorCode.java
- 전체 초과를 위한 에러 코드 추가 권장 (예: EXCEED_CHEER_TAGS_TOTAL).
- 태그 생성/저장 흐름 검토 — 아래 파일들에서 CheerTagNames 검증이 호출되고 생성된 CheerTag가 저장되는지 확인 필요:
- src/main/java/eatda/controller/cheer/CheerRegisterRequest.java (요청에 태그가 포함되는지)
- src/main/java/eatda/service/cheer/CheerService.java (등록/수정 시 CheerTagNames로 검증 후 toCheerTags() → DB 저장 연결)
현재 registerCheer 흐름에서는 태그 검증/생성 호출이 보이지 않으므로, 태그 입력이 있다면 서비스 레이어에서 도메인 검증과 저장 로직을 연결해야 합니다.
권장 적용 순서 (간단):
- BusinessErrorCode에 전체 초과 코드 추가.
- CheerTagNames에 MAX_CHEER_TAGS_TOTAL 상수 및 전체 개수 검사 추가.
- Controller/Service 쪽에서 태그 입력이 전달될 때 CheerTagNames로 검증하고 toCheerTags()로 CheerTag를 생성해 저장하도록 연결.
- 단위/통합 테스트로 전체 최대(4) 케이스 추가.
🤖 Prompt for AI Agents
In src/main/java/eatda/domain/cheer/CheerTagNames.java around the existing
validate() (file lines 1-??), add a MAX_CHEER_TAGS_TOTAL = 4 constant and a
check that throws a BusinessException when cheerTagNames.size() >
MAX_CHEER_TAGS_TOTAL; update the validate() to keep current duplicate and
per-type checks but also enforce the global total limit. Add a new error code
(e.g., EXCEED_CHEER_TAGS_TOTAL) to
src/main/java/eatda/exception/BusinessErrorCode.java to represent this
condition. Then ensure controller/service flow: when tags are provided in
src/main/java/eatda/controller/cheer/CheerRegisterRequest.java the controller
passes them to the service which uses CheerTagNames.validate() and toCheerTags()
in src/main/java/eatda/service/cheer/CheerService.java before persisting, and
add unit/integration tests covering total > 4.
|
🎉 This PR is included in version 1.4.0-develop.82 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.8.0-develop.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.8.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |



✨ 개요
작업 목표
태그 도메인 규칙
태그 종류
🧾 관련 이슈
closed #155
🔍 참고 사항 (선택)
Summary by CodeRabbit