Skip to content

feat(S15P11A705-41): member 엔티티 + BaseEntity(created_at·soft delete) (도메인 스캐폴딩 3/3) - #25

Merged
minyongP merged 12 commits into
devfrom
feat/S15P11A705-41-member-base-entity
Jul 27, 2026
Merged

feat(S15P11A705-41): member 엔티티 + BaseEntity(created_at·soft delete) (도메인 스캐폴딩 3/3)#25
minyongP merged 12 commits into
devfrom
feat/S15P11A705-41-member-base-entity

Conversation

@minyongP

Copy link
Copy Markdown
Contributor

요약

다른 도메인이 복붙할 기준 구현을 만듭니다. Flyway V2로 core.member를 만들고, created_at(감사)과 deleted_at(soft delete)만 공유하는 BaseEntity, 첫 엔티티 Member, 리포지토리, Testcontainers 검증을 추가합니다. 엔티티·영속 계층까지만이며 service·controller·DTO는 다음 PR입니다.

Jira (필수)

변경 사항

  • V2 마이그레이션 core.member(id, created_at, deleted_at) — 제품 명세 06_데이터모델_및_무결성.md §2.1 그대로. updated_at·status 없음, 개인정보 컬럼 없음(익명 서비스). 부분 인덱스 ix_member_active.
  • global/common/BaseEntity@MappedSuperclass + @EntityListeners(AuditingEntityListener), @CreatedDate created_at, deleted_at, isDeleted(), softDelete(). 시간 타입은 Instant(TIMESTAMPTZ ↔ UTC).
  • global/config/JpaAuditingConfig@EnableJpaAuditing. 없으면 created_at이 null로 INSERT되어 NOT NULL 위반.
  • domain/memberMember(@Table(schema="core"), 엔티티별 @SQLDelete/@SQLRestriction), MemberRepository.
  • 테스트 3종 — 마이그레이션 컬럼 집합 정확히 3개 / 감사필드 자동 기록 / soft delete(물리 행 생존 + deleted_at 기록 + findById·findAll 제외, raw SQL로 영속 상태 검증).
  • application-local.yml 추가, configuration.md 로컬 값 정합(15432/pinlog-local/16379, 없던 dev/ 경로 정정).
  • database-conventions.md — 공통 컬럼 매트릭스 + BaseEntity 사용 규약.
  • 문서 구역 기록(CLAUDE.md 10) — BD-02(결정), BI-02(구현 리포트), BT-01(트러블슈팅), WORKLOG 5줄.

설계 결정: BaseEntityupdated_at을 두지 않음

core 테이블 8개의 공통 컬럼 조합이 다릅니다 — created_at 8/8, deleted_at 7/8(place 제외), updated_at 3/8(place·record·collection).

테이블 created_at updated_at deleted_at
member · social_account · collection_record · follow O X O
place O O X
record · collection O O O
context O X (불변) O

ddl-auto=validate이므로 updated_at을 베이스에 넣으면 member·context 등에서 기동 실패합니다. 따라서 updated_at은 필요한 엔티티가 각자 선언하고, placeBaseEntity를 상속하지 않습니다. restore()도 넣지 않았습니다 — @SQLRestriction 때문에 삭제된 행을 다시 로드할 수 없어 동작 불가입니다. 근거·대안 비교는 BD-02 참고.

테스트 / 검증

  • ./gradlew clean check --no-daemon BUILD SUCCESSFUL (14/14, 연속 2회 확인)
  • DB 변경 → PostgreSQL Testcontainers 통합 테스트 (H2 미사용)
  • migration 변경 → 빈 DB에 V1·V2·V100~V102 전체 적용 검증
  • API 계약 변경 (해당 없음 — 엔드포인트 없음)

리뷰 포인트

  1. @SQLRestriction의 파급 — 엔티티 단위 필터라 향후 record/collection@ManyToOne Member를 가지면, 소프트 삭제된 회원을 참조하는 연관 로딩에서 EntityNotFoundException이나 null이 될 수 있습니다. 다음 도메인 설계 시 확인이 필요합니다.
  2. 벌크 삭제는 soft delete를 우회합니다(deleteAllInBatch() 등). 규약 문서에 금지로 명시했고, 리포지토리에 오버라이드로 막을지는 다음 PR에서 판단.
  3. 테스트 컨테이너 싱글톤 전환(6c61272) — 6개 테스트 클래스가 같은 컨테이너를 @Container로 재선언해 클래스마다 정지·재시작되고, 캐시된 컨텍스트가 죽은 포트를 잡아 게이트가 깨졌습니다. 표준 싱글톤 패턴으로 고쳤고 단정 약화·스킵은 없습니다. 상세는 BT-01.

발견된 문서 충돌 (CLAUDE.md 9 — 기록만)

package-structure.md의 member 행 "공개 프로필·소개를 포함합니다"는 명세 §2.1(익명 서비스, 개인정보 저장 없음)과 실제 Member(필드 없음)에 반합니다. 임의로 고치지 않고 해당 위치에 > 검토 필요로 기록했습니다. 팀 확인 후 정리가 필요합니다.

미결 / 후속

  • FlywayMigrationTests의 "빈 DB" 전제가 컨테이너 공유로 실행 순서에 의존(현재 단정은 스키마 수준이라 영향 없음) — BT-01에 후속 과제로 기록
  • Docker 미가동 시 ExceptionInInitializerError가 5건의 NoClassDefFoundError로 번져 진단성 저하
  • spring-boot-docker-composelocal 프로파일에서도 우선하므로 application-local.yml은 컨테이너를 직접 띄우는 경우에 유효
  • place 추가 시 created_at만 가진 상위 클래스 분리 검토

minyongP and others added 11 commits July 27, 2026 09:57
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…arting per class

Every test class extending PostgresContainerSupport redeclared its shared
static container behind its own @container field, so JUnit5's Testcontainers
extension stopped and restarted it in that class's own afterAll. A later
class with an identical @SpringBootTest config then reused a cached Spring
context whose DataSource still pointed at the now-dead port, failing every
DB call in MemberSoftDeleteTests with a 30s connection timeout (14 tests
completed, 3 failed, reproduced twice under `clean check`).

Switch to the Testcontainers singleton-container pattern: start the
container once in a static initializer in PostgresContainerSupport, with
no @container annotation anywhere, so nothing stops it mid-suite. Remove
the now-redundant @Testcontainers/@container redeclarations from the six
subclasses. `clean check` now passes twice in a row (14/14 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…roubleshooting

Document Tasks 1-4 of this branch per CLAUDE.md rule 10: BD-02 (BaseEntity
common-column decision and rejected alternatives), BI-02 (what was built and
how it was verified), BT-01 (the shared-Testcontainers container lifecycle
bug, its fix, and the follow-up it leaves). Register all three in their zone
README index tables and add one WORKLOG line per task (Tasks 1-5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Task 3 (`d1204bf`, `02fcaa7`) only added and strengthened the soft-delete
tests; it completed cleanly. The Testcontainers singleton-container fix
(`6c61272`, see BT-01) surfaced only when Task 4 first ran the full
`clean check` gate, so move it onto the Task 4 line and drop it from
Task 3. Add commit SHAs to every task line per the authoritative
task-to-commit mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final whole-branch review surfaced four documentation gaps: BaseEntity's
@EntityListeners is not inherited, so place must declare it explicitly or
created_at INSERTs null; @SQLDelete never covers bulk delete methods, so
deleteAllInBatch()/deleteAllByIdInBatch()/bulk JPQL delete stay documented
as physical deletes on soft-delete tables; BT-01's follow-up wrongly
recommended @transactional on the member tests, which would defeat the
soft-delete exclusion assertions via first-level cache; and
package-structure.md's member "공개 프로필·소개" claim contradicts the
product spec and the shipped entity, now recorded as an open conflict per
CLAUDE.md rule 9 instead of silently rewritten. Also documents which
soft-delete write path is canonical (DB clock vs. app clock) and that a
deleted in-memory entity still reports isDeleted() == false until reloaded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MemberSoftDeleteTests.deletedMemberIsExcludedFromQueries discarded the
result of memberRepository.findAll(), so BI-02's claim that findAll
exclusion was verified had no test backing it. Replace the dead call
with a real assertion that the deleted member id is absent from
findAll(), covering collection-query filtering without weakening the
existing findById assertion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@minyongP minyongP self-assigned this Jul 27, 2026
@minyongP
minyongP merged commit abe2a05 into dev Jul 27, 2026
1 check passed
@minyongP
minyongP deleted the feat/S15P11A705-41-member-base-entity branch July 27, 2026 04:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants