[Feat/#13] Flyway 마이그레이션·JPA 엔티티 26개 추가 - #16
Conversation
40ce499 to
1cb0e53
Compare
There was a problem hiding this comment.
(member_id, term_type)이 유니크여서 약관 버전 변경 후 재동의할 경우, 이전 버전에 대한 동의 이력이 남지 않고 기존 term_version과 agreed_at이 업데이트되는 구조로 이해했습니다. 이전 약관 동의 이력의 경우는 따로 관리가 필요 없을지 궁금합니다.
There was a problem hiding this comment.
사실 이용 약관 동의 여부에 대한 기록을 가지고 있어야 하는 건 법적인 문제와 관련이 있는 거라서 이전 기록까지 가지고 있어야 하는지는 관련 자료를 한 번 찾아봐야할 거 같네요!
리뷰 감사드립니다~
There was a problem hiding this comment.
JpaEntity와 Domain을 분리한 결정은 외부 기술인 JPA를 내부 구조인 Domain과 분리해서 Domain이 외부 기술에 의존하지 않도록 한 것이라고 생각하면 될까요?
There was a problem hiding this comment.
네 맞습니다. core 모듈의 경우에는 최대한 외부 기술에 의존하지 않기 위해 순수 자바 클래스를 지향하도록 설계했습니다.
트랜잭션, DI 등은 개발 편의성과 스프링을 사용하는 이유기도 하기 때문에 두 의존성과 모듈리스 의존성만 core 모듈에 남겨두었습니다.
There was a problem hiding this comment.
저는 이렇게 도메인과 JPA를 똑같은 쌍으로(두배로) 둘만큼 이를 분리한 구체적인 이유가 좀 더 궁금합니다..!
트랜젝션 측면에서는 (트랜잭션을 제대로 다뤄본 경험이 없어 이해를 잘 못하는 것일 수도 있지만..) JPA엔티티를 도메인이 쓰게된다면 트랜젝션 안에서 값만 바뀌어도 db에 저장되어버릴 수 있어서 그런걸까요?
도메인 객체는 순수 자바 객체라 아무리 바꿔도 DB에 영향이 없으니, 저장은 Repository를 거칠 때만 일어나게 하려는 의도로 이해하면 되는건지 궁금합니다
There was a problem hiding this comment.
간단히 얘기하자면 DB는 실제 비즈니스 로직과 직접적인 연관이 있는 게 아닌 데이터를 저장하기 위한 외부 기술에 해당합니다.
인프라와 비즈니스 로직 간의 분리를 했다고 생각하시면 될 거 같아요.
DB같은 외부 기술은 바뀌기 쉬워서 코드 베이스에 많이 침투해있다면 실제로 그 기술을 덜어낼 때 변경해야 하는 범위가 커져서 유지보수가 힘들어집니다.
이 관점에서 한 번 생각해보시면 좋을 거 같아요!
| @Table( | ||
| name = "archive_related_links", | ||
| indexes = { | ||
| @Index(name = "idx_archive_related_links_archive_id", columnList = "archive_id") |
There was a problem hiding this comment.
이 부분이 이해가 잘 안되는데 archive_id 컬럼을 이용해서 하나의 테이블이나 자료구조를 새롭게 생성하는 게 맞을까요? 그렇다면 수정/삭제 등의 작업에서는 이 구조가 어떻게 동작하게 되는지도 궁금합니다.
There was a problem hiding this comment.
archive_related_links 테이블을 이야기하시는 거라면 아카이빙에서 연관된 링크 모음에 대응하는 테이블입니다. 링크를 추가하거나 삭제하는 경우에는 이 테이블에 내용을 넣거나, 삭제하면 됩니다.
정확히 어떤 부분이 궁금하신 지 코멘트 남겨주시면 추가로 설명드릴게요!
There was a problem hiding this comment.
정확히는 @Index 어노테이션의 동작에 대한 부분에 대한 질문이었습니다. 혼자 알아봤을때는 특정 컬럼에 인덱스를 부여해 값을 빠르게 찾아낼 수 있다 정도로 이해했는데, 이 인덱스라는 것이 테이블에 생성되는 것인지 별도로 관리되는 구조인 것인지가 궁금했습니다!
There was a problem hiding this comment.
Index는 데이터베이스에서 자료를 더 빨리 찾기 위한 보조 자료구조 개념이라고 이해하시면 될 거 같아요!
별도 자료구조라고 생각하시면 될 거 같은데 아래 아티클 읽어보시면 도움이 될 거 같습니다~
https://velog.io/@bagt/DB-Index%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC
|
|
||
| @Entity | ||
| @Table(name = "archives") | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) |
There was a problem hiding this comment.
JPAEntity의 AccessLevel이 protected인 이유가 궁금합니다. 생성자를 막는다는 관점으로 생각하면 Domain처럼 private으로 해야할 것 같은데, 여기에서는 JPA가 객체를 생성할 수 있도록 열어둔 게 맞을까요?
There was a problem hiding this comment.
Jpa는 DB에서 데이터를 가져와 내부적으로 엔티티 인스턴스를 새로 생성할 때 빈 생성자로 인스턴스를 생성한 후에 데이터를 집어넣습니다. 따라서 무조건 접근할 수 있는 빈 생성자가 있어야 하기에 JpaEntity는 protected로 빈 생성자를 생성하도록 구현했습니다.
| @Column(name = "answer_text", columnDefinition = "TEXT") | ||
| private String answerText; | ||
|
|
||
| @JdbcTypeCode(SqlTypes.JSON) |
There was a problem hiding this comment.
바로 아래줄 컬럼에서 json으로 타입을 define해준 것으로 보이는데 JdbcTypeCode가 추가로 필요한 이유가 궁금합니다.
There was a problem hiding this comment.
둘의 역할이 살짝 다릅니다.
@Column(name = "selected_options", columnDefinition = "json")에서의 json은 JPA가 DDL로 컬럼을 생성할 때 컬럼의 타입이나 상세 정의를 넣을 때 사용합니다.
반면, @JdbcTypeCode는 Hibernate가 자바 클래스로 매핑을 해올 때 원래 데이터베이스의 어떤 데이터 타입이었는지를 확인하기 위한 어노테이션입니다.
|
|
||
| public static MemberJpaEntity from(Member member) { | ||
| return new MemberJpaEntity(member); | ||
| } |
There was a problem hiding this comment.
from()은 Domain 객체를 DB에 저장하기 전에 JPA Entity로 변환하고, toDomain()은 DB에서 조회한 Entity를 비즈니스 코드에서 사용할 Domain 객체로 바꾸는 메서드로 이해했는데 맞을까요?
#️⃣연관된 이슈
main으로 바꾼다)🎯 해결하려는 문제가 무엇인가요?
ERD를 이 프로젝트 컨벤션에 맞는 Flyway 마이그레이션 + JPA Entity + 도메인 클래스로 옮긴다. 회원 약관 동의(
member_term_agreements)·알림 설정(member_notification_settings) 테이블을 새로 추가한다.❓ 왜 해결해야 하나요?
모든 도메인 기능 작업의 출발점이 되는 스키마와 매핑이 아직 없다. 원본 SQL은 MySQL에 없는 배열 타입,
AUTO_INCREMENT누락, 오타(OPEM) 등이 있어 그대로 쓸 수 없다.⭐ 어떻게 해결했나요?
V1~V4, 도메인별)로 26개 테이블. FK 제약은 걸지 않는다.{module}.domain.{도메인}.domain에, Entity 26개를infrastructure:db에 추가. 도메인 범위 — member{member, notification} / event{event, locker, archive} / welfare{rental, fee, notice, feedback, chat} / internal{schedule, config, display, file}.student_id등 컬럼명은 원본 유지. Entity는@Column(name = ...)으로 매핑.locker_applications.applied_at만created_at으로 흡수ENUM→VARCHAR+@Enumerated(STRING), 배열 컬럼(BIGINT []등) →JSON+@JdbcTypeCode(SqlTypes.JSON)is_deleted TINYINT(1) NOT NULL DEFAULT 0, 모든 테이블에created_at/updated_at(+ON UPDATE CURRENT_TIMESTAMP)items.type→ItemType { CONSUMABLE, RENTAL },work_schedules는member_id만(staff_id제거),events.recruit_type의OPEM→OPENimage_url→image_key(items, display_posters)member_term_agreements: 회원 × 약관 종류(PRIVACY_POLICY,TERMS_OF_SERVICE)당 1행,term_version·agreed·agreed_at,(member_id, term_type)유니크member_notification_settings: 회원당 1행,rental/event/locker/notice_enabled4개 boolean,member_id유니크members는 generated columnactive_student_id로 활성 학번만 유니크.🧩 이 PR의 한계 & 트레이드오프
ddl-auto: validate검증을 하지 못했다. 작업 환경에 MySQL·Docker가 없었다. 대신 마이그레이션 SQL과 Entity 컬럼명을 스크립트로 대조해 26개 테이블 모두 일치함을 확인했다(의도한 미매핑은 generated columnactive_student_id뿐). 리뷰어 중 로컬 MySQL이 있는 분이bootRun으로 한 번 확인해 주면 좋겠다.members.is_fee_paid↔student_fees,rental_histories.item_code)는 그대로 옮겼다. 개선은 기능 작업에서.⛓️ 기존 기능에 미치는 영향
Member가 원본 컬럼 전체(department, email, fcmToken, feePaid, role, councilDepartment)로 확장된다.MemberJpaEntity의 PK 컬럼이member_id로 매핑된다.core:domain:{event,welfare,internal}에 첫 소스가 생겨 Modulith가 세 모듈을 새로 인식한다.verify()는 통과한다.🔀 Edge Case & 실패 시나리오
event_applications에(event_id, member_id)유니크를 걸지 않았다. 취소 후 재신청을 새 행으로 남기기 위해서다. 중복 신청 방지는 애플리케이션에서 한다.members.role은 원본대로 단일 컬럼이다. 학생회 임원의STUDENT+ADMIN동시 보유는 로그인 작업에서 재검토한다.📋 검토한 대안과 선택 이유
id로 통일(컨벤션): 레거시 데이터 이관 편의를 위해 원본 이름 유지로 결정.💬 리뷰 포인트
[r]V1~V4마이그레이션이 원본 SQL과 맞는지(특히 컬럼 nullable·기본값)[r]로컬 MySQL이 있다면bootRun으로validate통과 확인[c]신규 테이블 2개의 설계[a]도메인 범위(패키지) 나눔