엔티티를 설계할때
- 기본적으로 엔티티는 서비스의 데이터에 가장 직접적인 영향을 끼치는 객체라고 할 수 있지 않나 싶습니다.
- 사용자의 데이터는 아무렇지 않게 만들어지고 바뀌어서는 안되며, 이를 허용케 할 시 서비스의 여러 부분에서 예상치 못한 치명적인 일들이 발생할 수도 있습니다.
- 때문에 엔티티를 설계할때 가장 기본적인 사항이라 한다면, 특정한 데이터의 생성/수정/삭제가 진행되어야 하는 기능에 대해서만 특정 데이터의 생성/수정/삭제를 진행하게 해주고 그 외에 대해서는 데이터의 핸들링을 최대한 억제하는것을 고려하는것이 중요하다고 생각됩니다.
캡슐화
- 기본적으로 엔티티의 인스턴스 필드로의 접근에 대해서 첫번째는 데이터를 변경하는것과 두번째로는 데이터를 조회하는것으로 구분할 수 있습니다.
- 접근 제어자가 허용되어 직접적으로 필드에 접근하는것은 이 두가지 내용을 모두 허용하기 때문에, 보통 캡슐화하여 필드를 숨긴 후, 필요 여하에 따라 데이터의 값을 변경하는 개방된 메소드를 제공하거나(e.g. setter), 데이터의 값을 획득하는 개방된 메소드를 제공합니다(e.g. getter)
setter
- 하지만 모든 필드에 대해 setter를 제공한다는것은 모든 필드의 데이터를 언제든지 바꿀수 있다는 것을 의미합니다.
- 회원정보를 변경해야 할때, 변경할 특정 필드의 setter를 일일히 호출하여 값을 변경하는것보단 회원정보 변경용 함수를 선언하여, 회원정보 변경에 대해서만 해당 함수를 호출하여 원하는 필드의 값을 바꾸어 주는것이 좀 더 명시적이고, 불규칙하고 무분별한 데이터 핸들링을 막을 수 있습니다
- 아래는 Java로 구현할때의 예시입니다
@Entity
@NoArgsConstructor
@Setter
@Getter
public class Member{
private id Long;
private name String;
private age Integer;
@Builder
public Member(Long id, String name, Integer age){
//...
}
}
public Response changeMemberInfo(Long id, Request param){
//조회
Member member = memberRepository.findById(id)
.orElseThrow(()-> new NoSuchElementException());
//회원정보 변경
member.setName(param.getName());
member.setAge(param.getAge());
//...
}
@Entity
@NoArgsConstructor
@Getter
public class Member{
private id Long;
private name String;
private age Integer;
@Builder
public Member(Long id, String name, Integer age){
//...
}
public void changeMemberInfo(String name, Integer age){
this.name = name;
this.age = age;
}
}
public Response changeMemberInfo(Long id, Request param){
//조회
Member member = memberRepository.findById(id)
.orElseThrow(()-> new NoSuchElementException());
//회원정보 변경
member.changeMemberInfo(param.getName(), param.getAge());
//...
}
Kotlin
- 결국 Java에서는 접근과 수정을 모두 허용하는 접근제어자는 private 등으로 제한하여 캡슐화한 뒤, 데이터의 조회는 getter로 전체 제공하며, 데이터의 수정은 특별한 이유가 있을시 그에 맞는 함수를 선언하여 해당 이유에만 수정 할 수 있도록 특정한 경우에만 허용한다고 볼 수 있습니다.
- 그러면 Kotlin에서는 어떨까요? Kotlin은 기본적으로 Kotlin 컴파일러가 getter와 setter를 생성하며, 프로퍼티에 접근할때 컴파일러에 의해 getter와 setter를 하용하는 코드로 변경하여 컴파일 합니다.
- 하지만 저희는 setter의 존재를 없애고 getter만 남겨 조회하고 싶게만 만들고 싶은 상황입니다.
@Entity
class Member (
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id:Long? = null,
var email:String? = null,
var password:String? = null,
var name:String? = null
) : DatetimeAt() {
}
- 변경 전
- 모든 프로퍼티가 public하므로 접근이 가능합니다
- 또한 모든 프로퍼티가 var로 지정되어, 모든곳에서 자유롭게 프로퍼티의 값을 변경할 수 있습니다.
- 이는 매우 맘에 들지 않습니다. 저는 특정 사항에만 특정 메소드를 호출하여 특정 값만 변경했으면 좋겠고, 그 외에는 기본적으로 프로퍼티의 값을 직접 변경하지는 못하되, 조회만 자유로웠으면 좋겠습니다.
@Entity
class Member (
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
email:String? = null,
password:String? = null,
name:String? = null
) : DatetimeAt() {
var email:String? = email
protected set
var password:String? = password
protected set
var name:String? = name
protected set
}
- 변경 후
- ID 값은 어떠한 사항에서도 변경되어선 안되므로 기본적으로 불변으로 선언합니다
- 그 외의 프로퍼티들은, 기본적으로 public하게 값을 조회하는것은 가능하되, setter에 한에서만 protected로 선언하여(
protected set
) public한 값의 변경을 막습니다.
- setter를 private이 아닌 protected로 선언한 가장 큰 이유는 Kotlin의 All-open을 적용하기 위해선 private을 지정 할 수 없기 때문에 All-open이 적용되는 최소한의 접근제어자인 protected로 선언하였습니다
참고
- https://spoqa.github.io/2022/08/16/kotlin-jpa-entity.html