레이블이 Hibernate인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Hibernate인 게시물을 표시합니다. 모든 게시물 표시

2014년 12월 6일 토요일

자바 서버 어플리케이션 개발 기반 코드(Boilerplate code) 작성하기

게임 서비스를 개발하다보면 비슷한 구조의 서버 어플리케이션 또는 데몬을 여러개 만들어야할 필요가 있다. 서비스 초기에는 몇 개 안되겠지만 시간이 지날수록 관리해야할 수가 기하급수적으로 늘어나 있을 것이다. 어느 회사나 서버 어플리케이션이라면 자주 사용하는 구성요소들이 대게 정해져 있기 때문에 이런 것들의 사용법을 규격화하여 어플리케이션 개발 기반 코드를 작성해 놓으면 일관성 있는 구조의 어플리케이션 개발이 가능하고 유지보수에도 큰 도움이 될 것이다. 우리 회사의 경우 모든 서버 어플리케이션은 환경설정과 로그설정 파일을 기본으로 사용할 수 있어야 하며 Netty(범용 네트워크 라이브러리), Couchbase(NoSQL), Redis(Cache), MySQL(RDBMS), Quartz(Job Scheduler), Sigar(System Infomatioin) 같이 자주 사용되는 것들의 Wrapper를 작성하고 이를 모든 프로젝트에서 공유하여 필요에 따라서 취사선택하여 사용하고 있다. https://github.com/lekdw/book-boilerplate는 간단하게 구현하여 공개한 자바 서버 어플리케이션 개발 기반 코드와 이를 이용한 게임서버 및 테스트 클라이언트 샘플 프로젝트의 소스 저장소이다.


위의 그림은 소스를 얻어와 Eclipse에서 common, gc, gs 프로젝트를 Import하여 Workspace에 추가한 모습이다. common은 어플리케이션 개발 기반 코드와 기타 공유 코드를 포함한다. 이를 공유해서 작성된 어플리케이션이 테스트 게임 클라이언트 gc와 게임서버 gs 프로젝트이다.

common 프로젝트는 다음과 같은 기능을 어플리케이션에 제공한다.
  • 어플리케이션 시작 코드 (AppImpl.java) - 기본사용
  • 환경설정 (AppConfig.java) - 기본사용
  • Netty 범용 네트워크 라이브러리 Wrapper 코드 (AppNettyXXX.java) - 선택사용
  • Couchbase NoSQL 클라이언트 라이브러리 Wrapper 코드 (AppCouchbaseXXX.java) - 선택사용
  • Hibernate ORM Wrapper 코드 (AppMySQLXXX.java) - 선택사용
  • Redis 클라이언트 라이브러리 Wrapper 코드 (AppRedisXXX.java) - 선택사용
  • 공유 객체 (common.model 패키지) - 선택사용

common 프로젝트 내 ant 프로젝트 build.xml을 실행하면 소스를 빌드하여 lib 디렉토리 내의 jar 라이브러리 파일들을 하나의 external.jar로 묶어서 gs, gc 프로젝트내 lib 디렉토리에 복사한다. 이에 대한 자세한 설명은 지난 글 자바 코드와 외부 jar 라이브러리를 하나로 합치기를 참조한다. 어플리케이션이 common 프로젝트를 공유해서 작성되면 어플리케이션 시작 코드와 환경설정은 기본으로 사용되며 나머지는 어플리케이션의 기능에 따라서 취사선택이 가능하다.

common.model 패키지 내의 객체들은 common.storage 내의 DAO 코드를 통해서 다루어지게되는 Mapping된 POJO 객체이다. 이에 대한 자세한 설명은 지난 글 클라이언트, 서버 자바 어플리케이션에서 DTO 없이 계층 간 데이터 교환하기를 참조한다.

이제 common 프로젝트를 공유하여 게임서버 gs 프로젝트를 만들어보자. 게임서버는 다음과 같은 기능이 필요하다고 가정하고 어플리케이션 시작 코드를 계승받아 다음과 같이 시작한다.
  • Http 서버
  • Couchbase 연결
  • MySQL 연결
  • Redis 연결

App.java

App 클래스는 AppImpl 어플리케이션 시작 코드를 계승하고 AppNettyServerHandler, AppCouchbaseHander, AppMySQLHandler, AppRedisHandler 인터페이스를 구현하기만 하면 어플리케이션은 설정파일을 이용할 수 있고 Couchbase, MySQL, Redis를 사용할 수 있다.


gs 실행 시에는 conf 디렉토리를 classpath에 추가해야하며 conf 디렉토리에는 gameserver.json 환경설정 파일, log4j.properties 로그설정 파일, mysql_01.xml Hibernate 설정 파일이 존재한다. lib 디렉토리에는 위의 common 프로젝트에서 묶어서 복사된 external.jar 파일이 위치하게된다.

gameserver.json

gameserver.json에는 App에서 구현한 인터페이스에 대응하는 설정 내용이 기술되며 작성된 어플리케이션은 기술된 설정대로 초기화되어 동작된다.

위와 같이 기반 코드(Boilerplate Code)를 잘 작성해서 사용한다면 짧은 코드로 복잡한 요구를 만족하는 작업이 가능하며 어플리케이션이 표준화된 형상을 유지할 수 있으므로 유지보수성이 뛰어난 서버 어플리케이션 개발이 가능할 것이다.



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를 생성해야만 했다.