С недавних пор свое свободное время посвящаю реализации одной затеи в качестве платформы мы решили испоьзовать Google AppEngine для Java. И как оно обычно бывает с новыми и относительно неизвестными технологиями, с GAE это как оказалось – не исклбючение, появилось много проблем.
Проблемы в основном заключаются в сыроватости предоставляемых интерфейсов и органиченности их возможностей, думаю пока.
Начиналось все довольно спокойно – я установил SDK, поколдовал над рабочим окружением, что бы стало удобно работать и пустился в кодинг.
В моих планах было исследовать возможность работы Spring Framework 3.0.x, в частности меня интересует Spring MVC, а в качестве View – обычные старые добрые JSP. Со “спрингом” проблем особых не было, завелся, как говориться с пол пинка;).
Далее - в качестве слоя для работы с базой взор был остановлен на JPA. Почему – потому что знакома и относительно привык работать с этой технологией. Но вот тут-то и начались “приколы”. Да на первый взгляд все заработало, но. Перечень “па” с бубном:
- если вы используете Flex или GWT (как я) и хотите работать с объектами на стороне клиента, то одним из возможных подходов есть использование DTO объектов. В качестве конвертера объектов модели и объектов для клиентов я использую Dozer. И как оказалось в моем случае, из-за ограниченности в вариациях типов PrimaryKey, мне пришлось настраивать кастомные мапперы. В качестве PrimaryKey я использую предоставляемы Datastore тип Key, а клиент хранит его в виде строкового представления. До этого момента я получал сообщения об ошибках различного рода, связанные с ClassLoader-ом, с отсутствием поддержки типов ключей и т.д;
- отсутствие поддержки связей. То есть если вам необходимо создать отношение One-To-Many, Many-To-Many или Many-To-One – у вас не будет возможности это сделать стандартным подходом. Это делается путем сохранения в ссылки на объект через его Key и последующей выборкой в случае необходимости. Не очень удобно, но работает. Надеюсь этого будет не всегда;
- интересный нюанс с EntityManager.persist() или EntityManager.merge() – пока не вызовешь EntityManager.flush(), объект не будет сохранен. То есть если мне сразу же нужно вернуть сохраненный объект как результат выполнения метода, то я получу только то, что передал, хотя объект будет сохранен, но после.
- еще вылезла проблема с использованием Spring Forms тегов. Немного погуглив, нашел решение проблемы – добавить в декларацию JSP аттрибут isELIgnored=”false” и переопределить PropertyEditors для каждого типа, используемого в бине, представляющем форму.
Так же еще одной из задач, которую нужно было решить – это подъем локального GAE сервера (в качестве сервлет контейнера используется Jetty) совместно с запуском GWT в дебаг-режиме. Тут конечно пришлось попотеть, т.к. стандартный подход Google к структуре проектов и отсутствие нативной поддержки с помощью Maven вынуждает к этому. Проблемы в основном были опять же с определением что откуда должно грузиться и совмещение это с тем, как мне нужно. После продолжительных не замысловатых действий вида “добавить ресурс, удалить ресурс” у меня все получилось. Теперь мой проект стартует из распакованного war-ника, поднимает Spring-контекст и подтягивает изменения в GWT на лету без перекомпиляции – то, что надо. Правда еще бы хотелось править JSP , без необходимости их последующего копирования в дерево проекта, но тогда придется жертвовать “чистотой” исходников проекта. Далее привожу свой конфиг для запуска GWT приложения в GAE контейнере.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication"> <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS"> <listEntry value="/webapp-ui" /> </listAttribute> <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES"> <listEntry value="4" /> </listAttribute> <mapAttribute key="org.eclipse.debug.core.environmentVariables"> <mapEntry key="JAVA_OPTS" value="-Xms128M -Xmx512M -XX:MaxPermSize=256M" /> <mapEntry key="-Dappengine.sdk.root" value="D:\src\lib\gae-1.2.5" /> </mapAttribute> <stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector" /> <stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <sourceLookupDirector> <sourceContainers duplicates="false"> <container memento="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;javaProject name=&quot;restio&quot;/&gt;&#13;&#10;" typeId="org.eclipse.jdt.launching.sourceContainer.javaProject"/> <container memento="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;javaProject name=&quot;lib-controller&quot;/&gt;&#13;&#10;" typeId="org.eclipse.jdt.launching.sourceContainer.javaProject"/> <container memento="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;javaProject name=&quot;lib-domain&quot;/&gt;&#13;&#10;" typeId="org.eclipse.jdt.launching.sourceContainer.javaProject"/> <container memento="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;javaProject name=&quot;lib-model&quot;/&gt;&#13;&#10;" typeId="org.eclipse.jdt.launching.sourceContainer.javaProject"/> <container memento="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;javaProject name=&quot;lib-service&quot;/&gt;&#13;&#10;" typeId="org.eclipse.jdt.launching.sourceContainer.javaProject"/> <container memento="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;javaProject name=&quot;lib-service-impl&quot;/&gt;&#13;&#10;" typeId="org.eclipse.jdt.launching.sourceContainer.javaProject"/> <container memento="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;javaProject name=&quot;mvp4g&quot;/&gt;&#13;&#10;" typeId="org.eclipse.jdt.launching.sourceContainer.javaProject"/> <container memento="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;javaProject name=&quot;webapp-ui&quot;/&gt;&#13;&#10;" typeId="org.eclipse.jdt.launching.sourceContainer.javaProject"/> </sourceContainers> </sourceLookupDirector> " /> <listAttribute key="org.eclipse.jdt.launching.CLASSPATH"> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="com.google.appengine.eclipse.core.GAE_CONTAINER" path="3" type="4"/> " /> <!-- GWT 2.x feature - ability to start HostedMode within browser --> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry externalArchive="D:/src/repository/com/google/gwt/gwt-dev-oophm/2.0.0/gwt-dev-oophm-2.0.0.jar" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry externalArchive="D:/src/repository/commons-configuration/commons-configuration/1.6/commons-configuration-1.6.jar" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="com.google.gwt.eclipse.core.GWT_CONTAINER" path="3" type="4"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry externalArchive="D:/src/repository/name/webdizz/rest/io/lib-service/0.0.1/lib-service-0.0.1.jar" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry externalArchive="D:/src/repository/com/mvp4g/mvp4g/1.0-SNAPSHOT/mvp4g-1.0-SNAPSHOT.jar" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/gwt-widget/src/main/java" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/lib-domain/src/main/java" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/lib-service/src/main/java" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/gwt-base/src/main/java" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/webapp-ui/src/main/resources" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/webapp-ui/src/main/java" path="3" type="2"/> " /> <listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/webapp-ui/src/main/webapp" path="3" type="2"/> " /> </listAttribute> <stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.maven.ide.eclipse.launchconfig.classpathProvider" /> <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false" /> <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/1.6" /> <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.HostedMode" /> <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-startupUrl /view/ -war D:\tmp\restio\webapp-ui\webapp-ui -server com.google.appengine.tools.development.gwt.AppEngineLauncher name.webdizz.rest.io.ui.Engine" /> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="webapp-ui" /> <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.maven.ide.eclipse.launchconfig.sourcepathProvider" /> <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx512M" /> <stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="D:\tmp\restio\webapp-ui\webapp-ui" /> </launchConfiguration>
Хотел было прокомментировать код, но начав, понял – это не очень уместно т.к. названия он интуитивно понятен.
На это все, продолжим исследования…