Domain 상세 설계부터 Class Diagram, ERD, 구현 단계를 거치면서 Entity 들의 공통된 Attribute 들이 발견된다.
나는 의미론적으로 무게가 높은 반복되고 명시적인 코드가 아니면 무조건 abstract 로 추상화하는 것을 선호한다.
누군가는 "굳이 귀찮게 공통된 소스를 분리하지말자, 나중에 해~" 라고도 한다.
나는 이런 상황에 직시하자마자 "굳이 이렇게 단순하게 구현하지말자" 라고 한다.
지각하지 않고 그냥 직관적인 나의 반응이다.
이 글은 Entity의 글자, 단어 하나 하나가 아닌 전체적인 의미와 방향성을 애기한다.
객체를 모델링할 때 스코프 내에서 상당히 자세한 행위와 특성을 추출하는데, 구체화하는 하향식 말고 이를 역행하여 상향식으로 접근해보자.
흔히, 웹서비스에서 다루는 도메인들은 회원, 게시글, 주문, 판매물품, 북마크, 팔로우 등등 이 있다.
이 도메인들을 상향식으로 추상화해보자.

1. 회원, 게시글, 주문, 판매물품
- 행위 : 생성, 조회, 수정, 삭제
- 특성 : 행위의 주체, 행위의 목적/대상, 행위의 시점
- 게시글 작성, 게시글 조회, 게시글 목록 조회, 게시글 수정, 게시글 삭제
- 이러한 도메인들은 다양한 행위를 하며, 모든 행위 자체가 의미있는 데이터이다.

2. 북마크, 팔로우
- 행위 : 생성, 조회, 삭제
- 특성 : 행위의 주체, 행위의 목적/대상, 행위의 시점
- 팔로우하다, 언팔로우하다, 내가 팔로우한 목록 조회, 나를 팔로우한 목록 조회, 니캉내캉 맞팔?
- 이 도메인들은 수정이 없으며 도메인의 존재자체가 행위의 유무를 의미한다.

Base Life Entity
생성, 조회, 수정, 삭제 모든 행위를 기록한다.
행위 주체를 기록하는 이유는 관리자에 의한 행위도 서비스의 특징을 파악할 수 있으며, 보안/정책을 위해서도 사용될 수 있다.
import jakarta.persistence.*;
import lombok.Getter;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
import java.util.UUID;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseLifeEntity {
@Id
@Column(name = "id", updatable = false, unique = true, columnDefinition = "BINARY(16)")
private UUID id;
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;
@CreatedBy
@Column(name = "created_by", updatable = false, nullable = false)
private String createdBy;
@LastModifiedDate
@Column(name = "updated_at", updatable = true, nullable = false)
private LocalDateTime updatedAt;
@LastModifiedBy
@Column(name = "updated_by", updatable = true, nullable = false)
private String updatedBy;
@Column(name = "deleted_at")
private LocalDateTime deletedAt;
@Column(name = "deleted_by", updatable = true, nullable = false)
private String deletedBy;
}
Base Toggle Entity
Follow, Bookmak 등 행위 자체가 Entity 인 경우 수정이란건 당연히 존재하지 않는다.
import jakarta.persistence.*;
import lombok.Getter;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
import java.util.UUID;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseToggleEntity {
@Id
@Column(name = "id", updatable = false, unique = true, columnDefinition = "BINARY(16)")
private UUID id;
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;
@CreatedBy
@Column(name = "created_by", updatable = false, nullable = false)
private String createdBy;
}
나는 그냥 Base Life Entity 를 쓰고 있다.
BaseLifeEntity 를 사용하는 이유가 일관성, 귀찮음 혹은 이유가 없으면 안된다.
내가 BaseLifeEntity 를 전부 사용하는 이유는
Follow, Bookmark 에서 BaseLifeEntity 를 사용하면 회원탈퇴철회, 게시글삭제 취소 등 피곤한 상황에 대처할 수 있고, 삭제/탈퇴 시 별도로 Table 에 백업할 필요가 없어지고 데이터 보관 정책이 깔끔해진다.
그리고, 행위 기록을 보면 앞으로 기능 추가/확장의 방향성을 인사이트로 얻을 수 있어 굳이 전부 기록하는 걸 선호한다.
'개발' 카테고리의 다른 글
| 수도 코드/ 의사코드 / Pseudo Code 작성 프로그램 및 에디터 (0) | 2026.03.25 |
|---|---|
| H2 설정 충돌 AUTO_SERVER vs DB_CLOSE_ON_EXIT (0) | 2026.02.19 |
| Google OAuth2.0 구현 전략 (0) | 2026.02.13 |
| Geo Location In Java 3. MaxMind GeoLite2 + SpringBoot (0) | 2026.02.11 |
| Geo Location In Java 2. MaxMind GeoLite2 (0) | 2026.02.11 |