-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] 작가가 program을 만드는 api 개발 (#281) #282
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
base: main
Are you sure you want to change the base?
Changes from all commits
10aa5f1
a294a91
584fe52
48e77fc
29195f3
e6686a3
1fa0496
171d3cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package net.catsnap.CatsnapReservation.program.application; | ||
|
|
||
| import net.catsnap.CatsnapReservation.program.domain.Program; | ||
| import net.catsnap.CatsnapReservation.program.application.dto.request.ProgramCreateRequest; | ||
| import net.catsnap.CatsnapReservation.program.application.dto.response.ProgramResponse; | ||
| import net.catsnap.CatsnapReservation.program.infrastructure.repository.ProgramRepository; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| /** | ||
| * Program 애그리거트의 Application Service | ||
| * | ||
| * <p>도메인 계층의 객체들을 오케스트레이션하여 비즈니스 유스케이스를 구현합니다. | ||
| */ | ||
| @Service | ||
| public class ProgramService { | ||
|
|
||
| private final ProgramRepository programRepository; | ||
|
|
||
| public ProgramService(ProgramRepository programRepository) { | ||
| this.programRepository = programRepository; | ||
| } | ||
|
|
||
| /** | ||
| * 프로그램 생성 유스케이스 | ||
| * <p> | ||
| * 원시 타입을 도메인 엔티티에 전달하고, 도메인 엔티티가 VO 생성 및 검증을 담당합니다. | ||
| * | ||
| * @param photographerId 작가 ID | ||
| * @param request 프로그램 생성 요청 정보 | ||
| * @return 생성된 프로그램 응답 | ||
| */ | ||
| @Transactional | ||
| public ProgramResponse createProgram(Long photographerId, ProgramCreateRequest request) { | ||
| Program program = Program.create( | ||
| photographerId, | ||
| request.title(), | ||
| request.description(), | ||
| request.price(), | ||
| request.durationMinutes() | ||
| ); | ||
|
|
||
| Program savedProgram = programRepository.save(program); | ||
|
|
||
| return new ProgramResponse(savedProgram.getId()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package net.catsnap.CatsnapReservation.program.application.dto.request; | ||
|
|
||
| import jakarta.validation.constraints.Min; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| /** | ||
| * 프로그램 생성 요청 DTO | ||
| */ | ||
| public record ProgramCreateRequest( | ||
| @NotBlank(message = "프로그램 제목은 필수입니다") | ||
| String title, | ||
|
|
||
| String description, | ||
|
|
||
| @NotNull(message = "가격은 필수입니다") | ||
| @Min(value = 0, message = "가격은 0원 이상이어야 합니다") | ||
| Long price, | ||
|
|
||
| @NotNull(message = "소요 시간은 필수입니다") | ||
| @Min(value = 1, message = "소요 시간은 1분 이상이어야 합니다") | ||
| Integer durationMinutes | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package net.catsnap.CatsnapReservation.program.application.dto.response; | ||
|
|
||
| /** | ||
| * 프로그램 생성 응답 DTO | ||
| */ | ||
| public record ProgramResponse( | ||
| Long id | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,186 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package net.catsnap.CatsnapReservation.program.domain; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.Column; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.Convert; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.Entity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.EntityListeners; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.GeneratedValue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.GenerationType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.persistence.Id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDateTime; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Objects; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.AccessLevel; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.NoArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import net.catsnap.CatsnapReservation.program.domain.vo.Description; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import net.catsnap.CatsnapReservation.program.domain.vo.Duration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import net.catsnap.CatsnapReservation.program.domain.vo.Price; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import net.catsnap.CatsnapReservation.program.domain.vo.Title; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import net.catsnap.CatsnapReservation.program.infrastructure.converter.DescriptionConverter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import net.catsnap.CatsnapReservation.program.infrastructure.converter.DurationConverter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import net.catsnap.CatsnapReservation.program.infrastructure.converter.PriceConverter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import net.catsnap.CatsnapReservation.program.infrastructure.converter.TitleConverter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.data.annotation.CreatedDate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.data.annotation.LastModifiedDate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.data.jpa.domain.support.AuditingEntityListener; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 작가 프로그램 엔티티 (Aggregate Root) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 작가가 제공하는 촬영 프로그램을 표현하는 도메인 엔티티입니다. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 프로그램 제목, 설명, 가격, 소요 시간 정보를 관리합니다. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Entity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @EntityListeners(AuditingEntityListener.class) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class Program { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(nullable = false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long photographerId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(nullable = false, length = 100) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Convert(converter = TitleConverter.class) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Title title; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(length = 500) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Convert(converter = DescriptionConverter.class) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Description description; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(nullable = false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Convert(converter = PriceConverter.class) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Price price; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(nullable = false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Convert(converter = DurationConverter.class) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Duration duration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private LocalDateTime deletedAt; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @CreatedDate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(nullable = false, updatable = false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private LocalDateTime createdAt; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @LastModifiedDate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Column(nullable = false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private LocalDateTime updatedAt; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Program( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Long photographerId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Title title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Description description, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Price price, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Duration duration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validatePhotographerId(photographerId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.photographerId = photographerId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.title = title; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.description = description; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.price = price; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.duration = duration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 새로운 프로그램 생성 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 원시 타입을 받아 내부에서 VO를 생성하고 검증합니다. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 도메인 엔티티가 자신의 불변식(invariant)을 보장합니다. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param photographerId 작가 ID | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param titleValue 프로그램 제목 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param descriptionValue 프로그램 설명 (nullable) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param priceValue 가격 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param durationMinutes 소요 시간 (분) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return 생성된 Program | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static Program create( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Long photographerId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String titleValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String descriptionValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Long priceValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Integer durationMinutes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Title title = new Title(titleValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Description description = new Description(descriptionValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Price price = new Price(priceValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Duration duration = new Duration(durationMinutes); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Program(photographerId, title, description, price, duration); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 프로그램 정보 수정 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 원시 타입을 받아 내부에서 VO를 생성하고 검증합니다. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param titleValue 프로그램 제목 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param descriptionValue 프로그램 설명 (nullable) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param priceValue 가격 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param durationMinutes 소요 시간 (분) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void update( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String titleValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String descriptionValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Long priceValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Integer durationMinutes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.title = new Title(titleValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.description = new Description(descriptionValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.price = new Price(priceValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.duration = new Duration(durationMinutes); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 프로그램 소프트 삭제 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void delete() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.deletedAt = LocalDateTime.now(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+140
to
+142
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
♻️ Clock 사용 예시// 엔티티에 Clock 필드 추가 또는 메서드 파라미터로 전달
public void delete(Clock clock) {
this.deletedAt = LocalDateTime.now(clock);
}
// 기본 메서드 오버로드 (기존 호출 유지)
public void delete() {
delete(Clock.systemDefaultZone());
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 삭제 여부 확인 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public boolean isDeleted() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return deletedAt != null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 작가 소유권 확인 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public boolean isOwnedBy(Long photographerId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this.photographerId.equals(photographerId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void validatePhotographerId(Long photographerId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (photographerId == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new IllegalArgumentException("작가 ID는 필수입니다."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public boolean equals(Object o) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this == o) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (o == null || getClass() != o.getClass()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Program program = (Program) o; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Objects.equals(id, program.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public int hashCode() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Objects.hash(id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+164
to
+179
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial JPA 엔티티의 현재 구현은 현재 사용 패턴에서 이 문제가 발생하지 않는다면 괜찮지만, 향후 영속화 전 엔티티를 컬렉션에 담는 경우가 생긴다면 주의가 필요합니다. ♻️ 대안: id가 null일 때 인스턴스 비교 사용 `@Override`
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Program program = (Program) o;
- return Objects.equals(id, program.id);
+ return id != null && Objects.equals(id, program.id);
}
`@Override`
public int hashCode() {
- return Objects.hash(id);
+ return getClass().hashCode();
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public String toString() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return String.format("Program{id=%d, photographerId=%d, title=%s}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, photographerId, title); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| package net.catsnap.CatsnapReservation.program.domain.vo; | ||
|
|
||
| import java.util.Objects; | ||
| import lombok.Getter; | ||
| import net.catsnap.CatsnapReservation.shared.domain.error.DomainErrorCode; | ||
| import net.catsnap.CatsnapReservation.shared.domain.error.DomainException; | ||
|
|
||
| /** | ||
| * 프로그램 설명 값 객체 (Value Object) | ||
| * | ||
| * <p>프로그램의 간단 설명을 표현하는 불변 객체입니다. | ||
| * null을 허용하며, 값이 있는 경우 최대 500자까지 허용합니다. | ||
| */ | ||
| @Getter | ||
| public class Description { | ||
|
|
||
| private static final int MAX_LENGTH = 500; | ||
|
|
||
| private final String value; | ||
|
|
||
| public Description(String value) { | ||
| validate(value); | ||
| this.value = value; | ||
| } | ||
|
|
||
| private void validate(String value) { | ||
| if (value != null && value.length() > MAX_LENGTH) { | ||
| String message = String.format("프로그램 설명은 %d자 이하여야 합니다. 현재: %d자", | ||
| MAX_LENGTH, value.length()); | ||
| throw new DomainException(DomainErrorCode.DOMAIN_CONSTRAINT_VIOLATION, message); | ||
| } | ||
| } | ||
|
|
||
| public boolean isEmpty() { | ||
| return value == null || value.isBlank(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } | ||
| if (o == null || getClass() != o.getClass()) { | ||
| return false; | ||
| } | ||
| Description that = (Description) o; | ||
| return Objects.equals(value, that.value); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(value); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return value == null ? "" : value; | ||
| } | ||
| } |
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.
createdAt/updatedAt에@CreatedDate/@LastModifiedDate를 사용하면서 컬럼을nullable = false로 지정했는데, reservation 모듈에서는@EnableJpaAuditing설정을 찾을 수 없습니다. 이 상태면 엔티티 저장 시 auditing 값이 채워지지 않아 insert 시점에 NOT NULL 제약 위반으로 실패할 수 있습니다. reservation 애플리케이션에 JPA Auditing 활성화(@EnableJpaAuditing) 설정을 추가하거나, 해당 컬럼을 nullable로 두고 직접 값을 세팅하는 방식으로 수정이 필요합니다.