개발

BaseEntity 구현 전략

ikkison 2026. 2. 13. 11:04

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 에 백업할 필요가 없어지고 데이터 보관 정책이 깔끔해진다.

그리고, 행위 기록을 보면 앞으로 기능 추가/확장의 방향성을 인사이트로 얻을 수 있어 굳이 전부 기록하는 걸 선호한다.