Google App Engine

오픈소스 비즈니스 컨설팅
둘러보기로 가기 검색하러 가기

Google App Engine(GAE)을 정리 한다.

  • Java Doc
  • 다운로드 :
  • 라이선스 :
  • 플랫폼 : Google App Engine, Java/Python

개요

LXC (LinuX Container)를 대체하는 Google의 Container에서 동작

AppEngine 아케텍처


  • 2008년 4월 : GAE/P (Google App Engine for Python) 서비스 개시
  • 2009년 4월 : GAE/J (Google App Engine for Java) 서비스 개시
  • 가격 정책
  • 서비스 운영 유형
  • appspot.com 도메인을 사용하여 애플리케이션 서비스
  • Google Apps에서 애플리케이션 서비스
  • 자신의 도메인으로 서비스가 가능함

GAE 제약 사항

  • Google App Engine Quotas : GAE의 Quotas가 모두 정리되어 있다.
  • Time-Out에 빠지지 않도록 GAE 내의 애플리케이션을 가볍게 작성 한다.
  • Blobstore는 GAE/J에 대해서 과금을 할 경우에만 사용할 수 있다.
com.google.apphosting.api.ApiProxy$FeatureNotEnabledException:
The Blobstore API will be enabled for this application once billing has been enabled in the admin console.
  • Socket을 사용할 수 없다.

GAE for Python 개발 환경

2008년 4월 GAE의 개발 환경을 Python 개발자에게도 공개, 현재 Python 2.5 지원

GAE for Java 개발 환경

GAE for Java의 주요 기능

  • appengine-api-1.0-sdk-1.3.6.jar 사용

Google App Engine for Java 개요

  • Java 개발 환경 (JavaScript, Ruby), Python 개발 환경
  • Google App Engine SDK 1.3.1 for Java
  • Google App Engine SDK 1.3.1 for Python
  • Google Plugin for Eclipse : Google App Engine, Google WebToolkit 1.7
  • Database : A set of properties (key-value)
  • JDO (Java Data Object), JPA (Java Persistence API)
  • App Engine Service : URL Fetch(java.net), Mail(JavaMail), Memcache, Image Manipulation
  • Scheduled Tasks and Task Queues
  • GAE 개발 환경
  • GAE (Google App Engine) 기반 개발
  • GAE 1.3.2
  • GAE에서 지원하는 Java Technologies
  • GWT (Google Web Toolkit) 기반 개발
  • GAE에 적용된 버전 : GWT 2.0.3, 2010.4
  • 주의 : GWT를 사용하여 Eclipse에서 테스트하기 위해서는 브라우저에 GWT 개발 도구(plugin)이 설치되어야 한다.
  • 표준 Java API
  • Java 6 VM
  • Servlet 2.5 Container
  • javax.net.URLConnection for URLFetch API
  • javax.mail for Mail API
  • JSR 107 for Memcache API : Key-value pair mapping
  • Application Service
  • 사용자 인증 및 권한 부여 : ServletFilter, Spring Security에서 활용 가능
  • Cron
  • 데이터 가져오기/내보내기 : Google Bigtable 기반 Database
  • 방화벽 데이터에 대한 액세스
  • 기타 환경
  • HTTP Session support
  • JDO/JPA for Database API : GQL (Google Query Language)
  • 제약 사항
  • Only 1000 files per application
  • A web request must respond in 30 senconds otherwise GAE throw DeadlineExceededException

Memcache

package com.jopenbusiness.gae.sample;

import java.util.HashMap;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;

public class Memcache {
	private static Cache cache = null;
	
	@SuppressWarnings("unchecked")
	public Memcache() {
		CacheFactory factory = null;
		
		try {
			factory = CacheManager.getInstance().getCacheFactory();
			cache = factory.createCache(new HashMap<String, Object>());
			cache.put("name", "value");
			cache.get("name");
		} catch (CacheException e) {
			e.printStackTrace();
		}
	}
}

Blobstore

  • Blobstore는 GAE/J에 대해서 과금을 할 경우에만 사용할 수 있다.
com.google.apphosting.api.ApiProxy$FeatureNotEnabledException:
The Blobstore API will be enabled for this application once billing has been enabled in the admin console.

Persistence

GAE for Java는 데이터 관리를 위해 JDO(Java Data Objects)와 JPA(Java Persistence API)를 지원한다. User 데이터 관리를 예제로 하여 이를 확인해 보자.

  • User.java
  • 저장할 데이터를 어노테이션을 사용하여 식별한다.
package com.jopenbusiness.gae.client;

import java.io.Serializable;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class User implements Serializable {
   private static final long serialVersionUID = 3438684493254005858L;

   @PrimaryKey
   @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
   private long id = 0l;
   @Persistent
   private String name = null;
   @Persistent
   private String username = null;
   @Persistent
   private String password = null;
   @Persistent
   private String email = null;

   public long getId() {
       return id;
   }
   public void setId(long id) {
       this.id = id;
   }
   public String getName() {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }
   public String getUsername() {
       return username;
   }
   public void setUsername(String username) {
       this.username = username;
   }
   public String getPassword() {
       return password;
   }
   public void setPassword(String password) {
       this.password = password;
   }
   public String getEmail() {
       return email;
   }
   public void setEmail(String email) {
       this.email = email;
   }
}
  • UserDAO.java
package com.jopenbusiness.gae.server;

import java.util.ArrayList;

import com.jopenbusiness.gae.client.User;

public interface UserDAO {
   void insertUser(User user);
   void updateUser(User user);
   void deleteUser(User user);
   ArrayList<User> selectUsers();
}
  • UserDAOJdo.java
  • PersistenceManager를 사용하여 데이터를 관리 한다.
package com.jopenbusiness.gae.server;

import java.util.ArrayList;

import javax.jdo.PersistenceManager; 

import com.jopenbusiness.gae.client.User;
import com.jopenbusiness.gae.server.PMF;

public class UserDAOJdo implements UserDAO {
   @Override
   public void insertUser(User user) {
       PersistenceManager pm = null;

       try {
           pm = PMF.getPersistenceManager();
           pm.makePersistent(user);
       } finally {
           pm.close();
       }
   }

   @Override
   public void updateUser(User user) {
       PersistenceManager pm = null;
       User tmpUser = null;

       try {
           pm = PMF.getPersistenceManager();
           pm.currentTransaction().begin();
           tmpUser = pm.getObjectById(User.class, user.getId());
           tmpUser.setName(user.getName());
           tmpUser.setUsername(user.getUsername());
           tmpUser.setPassword(user.getPassword());
           tmpUser.setEmail(user.getEmail());
           pm.makePersistent(tmpUser);
           pm.currentTransaction().commit();
       } catch (Exception ex) {
           pm.currentTransaction().rollback();
           throw new RuntimeException(ex);
       } finally {
           pm.close();
       }
   }

   @Override
   public void deleteUser(User user) {
       PersistenceManager pm = null;

       try {
           pm = PMF.getPersistenceManager();
           pm.currentTransaction().begin();
           user = pm.getObjectById(User.class, user.getId());
           pm.deletePersistent(user);
           pm.currentTransaction().commit();
       } catch (Exception ex) {
           pm.currentTransaction().rollback();
           throw new RuntimeException(ex);
       } finally {
           pm.close();
       }
   }

   @Override
   @SuppressWarnings("unchecked")
   public ArrayList<User> selectUsers() {
       PersistenceManager pm = null;
       String query = null;

       pm = PMF.getPersistenceManager();
       query = "select from " + User.class.getName();
       return (ArrayList<User>) pm.newQuery(query).execute();
   }
}
  • PMF.java
  • PersistenceManagerFactory가 자원을 많이 차지하므로 유일하게 관리 한다.
package com.jopenbusiness.gae.server;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

public class PMF {
   private static final PersistenceManagerFactory pmfInstance =
       JDOHelper.getPersistenceManagerFactory("transactions-optional");

   private PMF() {
   }

   public static PersistenceManagerFactory get() {
       return pmfInstance;
   }

   public static PersistenceManager getPersistenceManager() {
       return pmfInstance.getPersistenceManager();
   }
}
  • /src/META-INF/jdoconfig.xml
  • persistence-manager-factory를 정의 한다.
<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

  <persistence-manager-factory name="transactions-optional">
      <property name="javax.jdo.PersistenceManagerFactoryClass"
          value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
      <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
      <property name="javax.jdo.option.NontransactionalRead" value="true"/>
      <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
      <property name="javax.jdo.option.RetainValues" value="true"/>
      <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
  </persistence-manager-factory>
</jdoconfig>

Logging

import java.util.logging.Level;
import java.util.logging.Logger;

private final static Logger log = Logger.getLogger(Memcache.class.getName());

log.log(Level.WARNING, "Start Memcache");

SSO with Google Apps

  • 사용자가 접속할 때 보여줄 첫 화면
  • index.jsp
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8" language="java"%>
<%@ page import="com.google.appengine.api.users.User" %>
<%@ page import="com.google.appengine.api.users.UserService" %>
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
<%
UserService userService = null;
User user = null;
String domain = "jopenbusiness.co.kr";
String service = "http://www.jopenbusiness.co.kr/";

userService = UserServiceFactory.getUserService();
if (!userService.isUserLoggedIn()) {
 	out.print("<script type=\"text/javascript\">");
	out.print("window.document.location.href = \"" + userService.createLoginURL(request.getRequestURI()) + "\";"); 
	out.print("</script>"); 
	return; 
}

user = userService.getCurrentUser();
//user = (User) request.getAttribute("user");
if ((user == null) || (!user.getAuthDomain().equals(domain))) {
	out.print("<script type=\"text/javascript\">");
	out.print("window.document.location.href = \"" + userService.createLoginURL(request.getRequestURI()) + "\";");
	out.print("</script>");
	return;
}

out.print("<script type=\"text/javascript\">");
out.print("window.document.location.href = \"" + service + "\";");
out.print("</script>");
%>
  • 로그인된 사용자의 정보를 반환하는 화면
  • Google Apps의 각각의 서비스에서 로그아웃을 하면 Google Apps만 로그아웃됨
  • userService.createLogoutURL(loginURL)이 생성하는 URL로 로그아웃 요청을 하면 전체 서비스가 로그아웃됨
  • checkSSO.jsp
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8" language="java"%>
<%@ page import="com.google.appengine.api.users.User" %>
<%@ page import="com.google.appengine.api.users.UserService" %>
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
<%
UserService userService = null;
User user = null;
String domain = "jopenbusiness.co.kr";
String service = "http://www.jopenbusiness.co.kr/";
String loginURL = "http://www.jopenbusiness.co.kr/index.jsp";

userService = UserServiceFactory.getUserService();
if (!userService.isUserLoggedIn()) {
	out.print("{");
	out.print("errorCode:\"-1\",");
	out.print("errorMessage:\"Error: not login.\"");
	out.print("}");
	return;
}

user = userService.getCurrentUser();
//user = (User) request.getAttribute("user");
if ((user == null) || (!user.getAuthDomain().equals(domain))) {
	out.print("{");
	out.print("errorCode:\"-2\",");
	out.print("errorMessage:\"Error: authDomain not match.\"");
	out.print("}");
	return;
}

out.print("{");
out.print("errorCode:\"0\",");
out.print("errorMessage:\"OK\",");
out.print("authDomain:\"" + user.getAuthDomain() + "\",");
out.print("email:\"" + user.getEmail() + "\",");
out.print("userId:\"" + user.getUserId() + "\",");
out.print("nickname:\"" + user.getNickname() + "\",");
out.print("loginURL:\"" + userService.createLoginURL(loginURL) + "\",");
out.print("logoutURL:\"" + userService.createLogoutURL(loginURL) + "\"");
out.print("}");
%>

GAE for Biz

  • 기업용 Google App Engine
  • SQL을 지원하는 Database 제공 예정
  • 비용
  • 1인당 월 $8, 최대 $1000

Google Web Toolkit

GWT는 Java를 JavaScript로 컴파일 하여 브라우저에서 실행(AJAX Application)할 수 있도록 하는 도구로 성능적인 부분에서 최적화되어 있다.

  • 주의 : GWT를 사용하여 Eclipse에서 테스트하기 위해서는 브라우저에 GWT 개발 도구(plugin)이 설치되어야 한다.
  • 참고 문헌

개발자 매뉴얼

Query

  • /war/WEB-INF/datastore-indexes.xml

<?xml version="1.0" encoding="utf-8"?> <datastore-indexes autoGenerate="true">

   <datastore-index kind="ContactVO" ancestor="false">
       <property name="cloudName" direction="asc" />
       <property name="updateDatetime" direction="asc" />
   </datastore-index>

</datastore-indexes>

  • 참고 문헌

참고 문헌

  • Manual