2014년 11월 25일 화요일

클라이언트, 서버 자바 어플리케이션에서 DTO 없이 계층 간 데이터 교환하기

현재 개발 중인 게임 서버에서 사용자의 정보를 담고 있는 Game이라는 이름의 클래스의 내용을 MySQL, Couchbase, Redis에 저장하고 게다가 클라이언트로 전송해야 하는 요구 사항이 있다. 현재는 MySQL은 Hibernate ORM을 이용하여 저장하고 Couchbase, Redis는 Jackson Json Processor를 이용해서 객체를 Json 형태로 변환하여 저장하고 있다. 클라이언트로 전송하는 패킷의 포맷은 이전에는 Json 형태였으나 현재는 MessagePack Serializer을 이용하여 압축된 바이너리 포맷을 사용하여 패킷을 생성할 수 있도록 하였다. 이 각각의 라이브러리들은 공통적으로 Object Mapping을 지원하는데 Java에서는 Reflection과 Annotation의 언어적인 특징을 이용하여 POJO라 불리는 일반적인 클래스의 객체를 필요한 형태로 매핑하여 용도에 따라 처리한다.

@javax.persistence.Entity
@javax.persistence.Table(name = "game")
@org.msgpack.annotation.Message
public class Game implements Serializable {
  private static final long serialVersionUID = -6305522860872314804L;

  // 채널 아이디 (패킷에서 제외)
  @javax.persistence.Id
  @org.msgpack.annotation.Ignore
  public String channelId = "";
 
  // 이름 (패킷에서 제외)
  @org.msgpack.annotation.Ignore
  public String nickName = "";
 
  // 마켓 (패킷에서 제외)
  @org.msgpack.annotation.Ignore
  public int marketId = 0;
 
  // 푸쉬 아이디 (패킷에서 제외)
  @org.msgpack.annotation.Ignore
  public String pushId = "";

  // 로그인 블록 상태
  public int loginBlock = 0;

  // 메시지 블록 상태
  public int mailBlock = 0;

  // 푸쉬 블록 상태
  public int pushBlock = 0;

  // 운영 보상 아이디
  public int lastRewardId = 0;

  (생략)

  // 쪽지 (MySQL, Couchbase, Redis, 패킷에서 제외) 
  @javax.persistence.Transient
  @org.msgpack.annotation.Ignore
  @com.fasterxml.jackson.annotation.JsonIgnore
  public Map mails = new HashMap();
 
  public Game() {
  }
}

위와 같이 게임 서버에서 사용 중인 각각의 라이브러리에서 제공하는 Annotation을 Game 클래스에 적절히 적용하면 Game 클래스 하나를 가지고 다양한 용도에 사용이 가능하다.


위의 그림은 현재 진행 중인 프로젝트의 구성도 중 일부이다. 각각의 블록을 잇는 모든 선은 다양한 형태의 데이터로 변환된 동일한 Game 클래스 객체 데이터의 흐름이다. 이와 같은 설계의 가장 큰 장점은 MySQL, Couchbase, Redis와 같은 Persistence 계층과 Game Server와 같은 Controller 계층, 게임 사용자와 같은 Present 계층이 모두 같은 클래스를 공유할 수 있어서 각 계층 간 데이터 전환을 위한 DTO가 필요하지 않아 간결한 코드를 유지할 수 있었다. 예외적으로 GWT로 만들어진 운영툴의 경우 GWT 서버-클라이언트 코드 간에 MySQL에서 읽어들인 Game 클래스 객체 교환이 불가능하기 때문에 (Hibernate가 Game 클래스 객체를 POJO가 아닌 객체로 동적 변경을 하므로 변경된 부분을 클라이언트 Java Script 코드로 생성이 불가능하기 때문에) 반드시 DTO를 생성해야만 했다.

댓글 없음:

댓글 쓰기