-
Notifications
You must be signed in to change notification settings - Fork 13
Step 14 deploy to gae
Purpose: in this step we will see how to deploy you app to google's application engine cloud computing platform.
cd ~/devel/apps
git clone [email protected]:opensas/play-demo.git
git checkout origin/14-deploy_to_gae
Go to https://appengine.google.com/ (you wil obviously need a google account) and click in create application.
You might be asked to enter a mobile number, in my case (Argentina) I had to enter 541122223333, being 54 the argentina prefix, 11 the Buenos Aires prefix, and 2222333 the mobile number withour the leading 15.
Once you set up your app, download gae sdk 1.4 from here.
Take into account that gae needs a 1.6 java version.
Following the installation explained at setting up your development environment, unzip it to ~/devel/opt and create a symbolic link to it, like this (of course you can install it anywhere you wnat)
ln -s ~/devel/opt/appengine-java-sdk-1.4.0/ ~/devel/gae
Gae doesn't get along too well with JPA, so it's recommended to user another data layer. In our case, as it's recommended in play's documentation, we'll use siena.
Edit your dependencies file like this:
## Application dependencies
require:
- play -> crud
- play -> siena [2.0.0,)
- play -> crudsiena [2.0.0,)
- play -> gae 1.4
- provided -> DateHelper 1.0
repositories:
- provided:
type: local
artifact: "${application.path}/jar/[module]-[revision].jar"
contains:
- provided -> *
And from the command line run:
play deps --forceCopy --sync
This will tell play to install all the dependencies.
The --sync options deletes every file not recognized as a dependency, in our case it will remove the crud module and replace it with crudsiena. The --forceCopy option tells play to install every module in the application's directory, instead of installing it in the play framework installation folder.
Now we have to update our project to replace every reference to JPA with Siena.
So in our models we remove every import, the @Entity and @ManyToOne annotations, and we also have to create the findById method, like this:
[...]
public class Event extends EnhancedModel {
@Id(Generator.AUTO_INCREMENT)
public Long id;
[...]
public Event findById(Long Id) {
return all().filter("id", id).get();
}
[...]
Notice how we extend from EnhancedModel from siena package. Be sure to always import siena packages instead of javax.persistence.
The siena.EnhancedModel is just like the siena.Model, but it automatically adds the followgin helper method that is needed by siena:
public static Query<Event> all() {
return Model.all(Event.class);
}
Siena syntax for querying the datasource is a little bit different, so we'll have to do a couple of adjustments. Do the following replacements (we left commented out the original syntax)
controllers.Application
public static void list() {
Secure.loadUser();
//List<Event> events = Event.find("order by date desc").fetch();
List<Event> events = Event.all().order("-date").fetch();
render(events);
}
[...]
public static void nextEvent() {
//Event nextEvent = Event.find("date > ? order by date", new Date()).first();
Event nextEvent = Event.all().filter("date >", new Date()).order("date").get();
render(nextEvent);
}
[...]
@SuppressWarnings("unused")
@Before
private static void loadEventTypes() {
//renderArgs.put("types", EventType.find("order by name").fetch());
renderArgs.put("types", EventType.all().order("name").fetch());
}
also in controller.Secure replace
OAuth2.Response response = FACEBOOK.retrieveAccessToken(authUrl);
//User user = User.find("accessToken = ?", response.accessToken).first();
User user = User.all().filter("accessToken", response.accessToken).get();
if ( user == null ) {
user = new User();
As you can see, all these are trivial changes.
Siena does not automatically populate related objects, so you have to do it by yourself. Modify views/Application/list.html
<td>${event.id}</td>
<td>#{a @form(event.id)}${event.name}#{/a}</td>
<td>${models.EventType.findById(event.type.id)?.name}</td>
<td>${event.type}</td>
<td>${event.place}</td>
You also have to adjust the Bootstrap job
lib.utils.BootstrapJob
@Override
public void doJob() {
//Fixtures.deleteAllModels();
//Fixtures.loadModels("data.yml");
SienaFixtures.deleteAllModels();
SienaFixtures.loadModels("data.yml");
}
Don't forget to comment the db configuration in application.conf, in order for siena to use gae's datastore instead of the in memory h2 database.
conf/application.conf
# - fs : for a simple file written database (H2 file stored)
# db=mem
when starting the app you should see the following message
01:42:17,860 INFO ~ The backing store, /home/sas/devel/apps/play-demo/tmp/datastore, does not exist. It will be created.
Then map the /admin route to the crudsiena module, instead of the crud module
conf/routes
* /admin module:crudsiena
You can also modify /crudsiena-2.0.2/views.CRUD/layout.html tempalte to update jquery to the latest version.
And that's it, we converted our whole app from JPA to Siena.
When you run the gae application for the first time, a war folder will be created. Inside you'll find a WEB_INF folder with a appengine-web.xml file. Set the application name and version.
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>jugar-demo</applicatn>
<version>1</version>
</appengine-web-app>
Gae allows you to have many version running at the same time. Go to https://appengine.google.com/, choose your app, click on Versions, and there you can delete version as well as set the default one. To access a specific version you can prefix the url with the version, like this: http://2.jugar-demo.appspot.com
To deploy your app, just run the following command:
play gae:deploy --gae=/home/sas/devel/gae
You can optionally set GAE_PATH environment variable to omit --gae parameter.
Go ahead and test your app at gae. This application is available at http://jugar-demo.appspot.com/.
Gae module currently doesn't support play's jobs. But there's an easy workaround. Just create a cron.xml file to tell gae to run a specific task at regular intervals. For more info see here.
Save a file cron.xml in WEB-INF directory of your application (alongside appengine-web.xml) with the following contents:
<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
<cron>
<url>/load</url>
<description>Repopulate demo data every 30 minutes</description>
<schedule>every 3 minutes</schedule>
</cron>
</cronentries>
Our app is issuing an http request every 2 seconds to display the next event, and accordingly the action Application.nextEvent access the datastore to retrieve the info.
It's wise to cache this database access, not only from a performance point of view, but also because of google policy pricing.
So we'll create two private methods to get the next event and save it to the cache
[...]
private static Event getNextEvent() {
Event nextEvent = (Event) Cache.get("nextEvent");
if (nextEvent==null) {
nextEvent = refreshNextEvent();
}
return nextEvent;
}
private static Event refreshNextEvent() {
final Event nextEvent = Event.all().filter("date>", new Date()).order("date").get();
Cache.set("nextEvent", nextEvent);
return nextEvent;
}
then we'll have to update the methods that modify events to refresh cached next event
[...]
public static void delete(Long id) {
final Event event = Event.findById(id);
event.delete();
refreshNextEvent();
list();
}
[...]
public static void save(@Valid Event event) {
if (validation.hasErrors()) {
render("@form", event);
}
event.save();
refreshNextEvent();
flash.success("event successfully saved!");
list();
}
public static void nextEvent() {
final Event nextEvent = getNextEvent();
render(nextEvent);
}
public static void loadFromYamlFile() {
new BootstrapJob().doJob();
refreshNextEvent();
list();
}
[...]
that's it, our app is finnaly running on gae
Now we are going to Step 15 - next steps.