NFT는 항상 어떠한 상태를 가지고 있다.
사용자가 자신의 NFT 상태를 조회할 때 마다 메인넷에 트랜잭션을 조회하면 되지만 사용자 입장에서 직관적이지 않고 성능 또한 만족스럽지 못하다.
그 이유로 블록체인을 사용하는 요청 중 TRC-721(NFT)의 상태는 데이터베이스나 캐시에 저장하여 조금 더 효율적으로 개선해야됐다.
내가 일으킨 트랜잭션을 조회한다.
도메인에서 상태를 가지게 되는 경우 도메인은 상태의 정합성을 꼭 보장해야한다.
아래 status라는 필드로 상태를 관리하고 있는 샘플 코드로 예시를 들어보자.
/**
* TRC721 (NFT)의 트랜잭션을 기록한다.
*/
@Entity
public class TRC721ProductActivity extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
// ISSUED
// MINTED
// LISTING
// LISTING_CANCEL
// TRANSFER
// SALE
private String status;
// 필드
}
public class Test {
public static void main(String[] args) {
TRC721ProductActivity trc721ProductActivity1 = new TRC721ProductActivity();
TRC721ProductActivity trc721ProductActivity2 = new TRC721ProductActivity();
TRC721ProductActivity trc721ProductActivity3 = new TRC721ProductActivity();
trc721ProductActivity1.setStatus("MINTED");
trc721ProductActivity2.setStatus("LISTING");
trc721ProductActivity3.setStatus("ISSUE");
}
}
이런 식으로 ISSUED를 ISSUE로 잘 못 적어도 컴파일 시점에 알 수 없다.
@Getter
@RequiredArgsConstructor
public enum ActivityType {
ISSUED("요청"),
MINTED("발행"),
LISTING("판매 등록"),
LISTING_CANCEL("판매 취소"),
TRANSFER("전송"),
SALE("판매 완료");
private final String activity;
}
------------------------------------------------------
/**
* TRC721 (NFT)의 트랜잭션을 기록한다.
*/
@Entity
@NoArgsConstructor(access = PROTECTED)
public class TRC721ProductActivity extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = IDENTITY)
@Getter
private Long id;
@Enumerated(STRING)
private ActivityType activityType;
private TRC721ProductActivity(ActivityType activityType) {
this.activityType = activityType;
}
public static TRC721ProductActivity minted() {
return new TRC721ProductActivity(MINTED);
}
}
기본 생성자을 패키지 수준의 접근 제한자로 변경했다.
게터 메서드를 한정적으로 열어 외부에서 이 값에 함부로 접근하면 안된다는 것을 알려준다.
프로젝트를 하면서 느낀 점이 내가 만든 메서드가 꼭 내 의도대로 사용되지 않는다는 점이다.
이 것을 해결하기 위해서는 다른 개발자들이 나의 메서드를 오용할 염려가 없어지게 뭔가 이상함을 느낄 수 있도록 코드를 짜는 것이다.
"MINTED" 라는 상태를 가지는 TRC721은 외부에서 설정하는 것이 아닌 Activity 도메인 내부에서 발급 해주고 외부에서 상태를 변경할 수 없도록 한다면 도메인 내부에서 상태에 대한 무결성을 유지할 수 있게된다.
요구사항이 추가되어 TRANSFER, SALE을 제외한 상태에서 "취소"가 가능해졌다고 가정해보자. (실제로는 절대 안됨..)
상태가 어떤 행위를 할 수 있는지 없는지를 외부에서 판단하게 되면 중복 로직이 많아지고 나중에 다른 요구사항이 추가되었을 경우 대처하기 힘들어진다.
public void cancelXXX(xxxDto dto){
if(dto.getStatus == TRANSFER || dto.getStatus == SALE){
// CANCEL
}
}
@Getter
@RequiredArgsConstructor
public enum ActivityType {
ISSUED("요청"),
MINTED("발행"),
// 다른 상태들
private final String activity;
public boolean isCancelable(final ActivityType activityType) {
return of(ISSUED, MINTED, LISTING).contains(activityType);
}
}
상태 객체 자체에 취소 여부를 판단해주는 메서드를 두어 isCancelable()의 변경 만으로 추가적인 요구사항에 대처하기 쉬워진다.