상태를 가지는 도메인을 효율적으로 관리하는 방법

Taewoo·2023년 11월 17일
0

NFT 거래 서비스

목록 보기
3/3
post-thumbnail

NFT 도메인의 상태

NFT는 항상 어떠한 상태를 가지고 있다.

사용자가 자신의 NFT 상태를 조회할 때 마다 메인넷에 트랜잭션을 조회하면 되지만 사용자 입장에서 직관적이지 않고 성능 또한 만족스럽지 못하다.

그 이유로 블록체인을 사용하는 요청 중 TRC-721(NFT)의 상태는 데이터베이스나 캐시에 저장하여 조금 더 효율적으로 개선해야됐다.

내가 일으킨 트랜잭션을 조회한다.

도메인에서 상태를 가지게 되는 경우 도메인은 상태의 정합성을 꼭 보장해야한다.

아래 status라는 필드로 상태를 관리하고 있는 샘플 코드로 예시를 들어보자.

/**
 * TRC721 (NFT)의 트랜잭션을 기록한다.
 */
@Getter
@Setter
@Entity
@NoArgsConstructor
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로 잘 못 적어도 컴파일 시점에 알 수 없다.

  • 세터 메서드가 모두 열려 있어서 변경 파악이 힘들다.
  • 기본 생성자가 Public 이기 때문에 생명주기를 파악하기 힘들다.

리팩토링 후

@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()의 변경 만으로 추가적인 요구사항에 대처하기 쉬워진다.

0개의 댓글