In this post today, after a few days reading about GWT and playing with the application created in Part 1, I decided to also try to develop another application with GWT + Grails, but this time without using the plugin. Thus, I could even decide if I prefer with or without the plugin. This post will show you how to create a solution composed of two separate applications, one GWT and the other Grails, communicating through the exchange of data using JSON.
Separating Grails and GWT in two distinct applications
As described in Part 1, if you do not use the plugin and have two separate applications (one other GWT and Grails) instead, this means having to deal with the Same Origin Policy (SOP) in development time. In production it may be simpler. You could always find a way to get the HTML, CSS and JavaScript generated by the GWT compiler into the grails project (you could just copy and paste from one side to the other). But in development we have to deal with SOP in a way more productive than having to copy and paste from one side to another. The simplest way I found was using a ProxyServlet, originally created by Jason Edward, and then evolved by Matt Raible. The source of this class I got from the project Implementing OAuth with GWT created by Matt Raible. It is simple to use, and solves the problem easily. What it does in our context is:
- GWT application on the client side makes a request to the server;
- GWT application on the server side receives this request and forwards it to the Grails application
- Grails application receives this request and responds JSON
- GWT application receives this JSON response and forwards it to the GWT client
Thus we need not bother with SPO.
Why do we need all this? After all, why do we want to have two separate applications (one other GWT and Grails)? Well, that ends up being a personal decision of each one, ok? But for me, I find it interesting to follow the standards of each project. A GWT project, as recommended, should be organized in a way, with a particular folder tree, etc. It may not be followed, but it is a recommended standard. So, why not speak the same "language" in chat rooms, discussion forums, new developers on the team, etc?
In addition, separating the two projects leads to an interesting reflection on the conceptual architecture of rich applications for the web (RIA - Rich Internet Applications). By separating, you are literally, physically, implementing the SOFEA architecture (Service Oriented Front End Architecture). SOFEA was very well presented in the article Life Above the Service Tier (worth the read. The authors now have a group on this subject and have a website at: ThinServerArchitecture.com).
In SOFEA applications, the whole User interface logic runs on the client side, in our case, the browser. The View and Contoller layers will have a project just for them (the GWT project). This is the most decoupled Front-End as it gets. This is interesting in my opinion, because then your server application turns out to have the sole responsibility to provide services for the Front-End (User Interface - UI). It seems like we're back to the concept of client x server, and somehow, we are, but a little different because it is a thin client, where only the user interface logic and flow of navigation control are on the client side. All other business rules are and should be on the server.
Two other reasons for this separation into two applications are (these are from Matt Raible's blog Packaging SOFEA Applications for Distribution):
- It may be simpler to work different teams each in a different project
- Each project can be versioned independently
Well, after all these reasons, the fact is: I created a new application that consists of a GWT app responsible for the Front-End, and a Grails app for the Back-End (providing services, remember SOA?). Here things start getting even more cohesive and decoupled. Interesting results ....
Communication using JSON
For the communication between the Front-End and Back-End, I chose to use JSON. A grails app can easily generate a JSON response, while GWT can easily consume JSON. So, being a lightweight protocol to exchange data, and easily served and consumed by the GWT and Grails, I think JSON is a good choice. But if you prefer to work with XML, or other proprietary format, the decision is yours.
Therefore, to create this new application, simply create a new GWT project, and another Grails project.
Application Front-End (GWT)
I'm using Eclipse with the plugin for GWT, therefore, to create a new project just click to create new GWT project in Eclipse. I created this GWT project called rockgwtfront, with the package com.rockgwt.front
The Eclipse wizard creates an EntryPoint class, the XML Configuration Module, and also creates an RPC service implementation. But we do not need this service for this example, therefore, you can delete the package com.rockgwt.front.server and package com.rockgwt.front.shared, and can also delete files GreetingService.java and GreetingServiceAsync.java from com.rockgwt. front.client package. Then change the class Rockgwtfront.java to not use this service anymore. In the end, I got the EntryPoint class below:
package com.rockgwt.front.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONException;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
public class Rockgwtfront implements EntryPoint {
public void onModuleLoad() {
final Button sendButton = new Button("Get Hendrix songs");
final FlexTable songsTable = new FlexTable();
// We can add style names to widgets
sendButton.addStyleName("sendButton");
sendButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
String hendrixSongsServiceUrl = GWT.getHostPageBaseURL();
if(!GWT.getHostPageBaseURL().contains("rockgwt"))
hendrixSongsServiceUrl += "rockgwt/";
hendrixSongsServiceUrl += "hendrix/songs";
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, hendrixSongsServiceUrl);
builder.setTimeoutMillis(10000);
builder.setCallback(new RequestCallback() {
@Override
public void onResponseReceived(Request request, Response response) {
if (response.getStatusCode() == 200) {
JSONValue v = JSONParser.parse(response.getText());
JSONArray songs = v.isArray();
if(songs != null){
updateBacklogItemsTable(songsTable, songs);
}
else{
throw new JSONException("O retorno nao veio como um objeto de Projeto");
}
} else {
onError(request, new RequestException(response.getText()));
}
}
@Override
public void onError(Request request, Throwable exception) {
Window.alert(exception.getLocalizedMessage());
}
});
try {
builder.send();
} catch (RequestException e) {
Window.alert(e.getLocalizedMessage());
}
}
});
RootPanel.get("sendButtonContainer").add(sendButton);
RootPanel.get("songsTableContainer").add(songsTable);
}
private void updateBacklogItemsTable(FlexTable songsTable, JSONArray songs) {
songsTable.clear();
for (int i=0; i<songs.size(); i++) {
JSONObject item = songs.get(i).isObject();
songsTable.setWidget(i, 0, new Label(item.get("name").isString().stringValue()));
songsTable.setWidget(i, 1, new Label(item.get("album").isString().stringValue()));
}
}
}
Note that the class above uses a RequestBuilder to perform the request to the server. RequestBuilder is a GWT class that can be used to make remote requests. With it, the requests are always asynchronous, ALWAYS. Here there is a callback object that will be called when the request comes back from the server. Then, this callback can do whatever it takes with the response received. In this case, I parsed the JSON response to work with the data returned. At the end, I just take this data and present them in a table, that's all.
The GWT application expects a JSON return in the following format:
[( "name": "Hey Joe", "album": "Are You Experienced"),
( "name": "Bold as Love", "album", "Axis: Bold as Love")]
Therefore, it is necessary that the Grails application returns a JSON exactly in this format above.
You must add the JARs needed by the ProxyServlet quoted above to ~/rockgwtfront /war/WEB-INF/lib. So, add commons-fileupload-1.2.1.jar, httpclient-4.0.1.jar, and httpmime-4.0.1.jar to the lib (in the future we can manage this dependencies with Maven). Create the ProxyServlet class. You can download the OAuth 1.3 and see the ProxyServlet there. Next, you must configure the web.xml to let it know that ProxyServlet exists. I mapped this proxyServlet for it to be run for every /rockgwtback/* request. See below:
<web-app> <servlet> <servlet-name>RockServletservlet-name> <servlet-class>com.rockgwt.front.servlet.ProxyServletservlet-class> <init-param> <param-name>proxyHostparam-name> <param-value>localhostparam-value> init-param> <init-param> <param-name>proxyPortparam-name> <param-value>8080param-value> init-param> <init-param> <param-name>secureparam-name> <param-value>falseparam-value> init-param> servlet> <servlet-mapping> <servlet-name>RockServletservlet-name> <url-pattern>/rockgwt/*url-pattern> <servlet-mapping> <welcome-file-list> <welcome-file>Rockgwtfront.htmlwelcome-file> <welcome-file-list>
<web-app>
Note that the servlet-mapping shows how the url should be requested by the GWT client. (/ rockgwt/*).
For the Grails project, you can create a new app using the command line, or you can create a new one using Eclipse's STS (SpringSource Tool Suite). I created this grails app calling it rockgwtback. For this app, I created a controller called HendrixController that has the action "songs." This action returns a list of songs from Hendrix. See below:
package com.rockgwt.back.controller
class HendrixController {
def index = { }
def songs = {
println "chegou"
render(builder: 'json') {
songs = array {
song name: '1) Hey Joe', album: 'Are You Experienced'
song name: '2) Bold as Love', album: 'Axis: Bold as Love'
}
}
}
}
Also changed the name of the application within the application.properties putting app.name = rockgwt
That's it!
Thus, we have a system running with Grails and GWT, independent of each other, but together forming our application. Here we miss a way to merge the two applications in production. Probably I would do it with Maven, so I have a single app in production.
interesting stuff!! Thanks for sharing!
ReplyDeleteIf your going to be accessing JSON based back end services with GWT, the best way to do it is using RestyGWT from http://restygwt.fusesource.org/
ReplyDeleteIt allows you to use async service interfaces annotated with JAXRS annotations to access your back end rest services. It also makes it easy to share DTO java objects between the client and your server.
@Hiram
ReplyDeleteThanks a lot for your comments. I did not know RestyGWT and it looks good, thanks.
But I guess it is a nice option if you are not using Grails right? Cause if you are using Grails, then I guess you loose some goodies from RestyGWT, not sure... I am gonna have to try that.
Cheers
Does SpringSource Tool Suite works well with Grails, GWT, SmartGWT? I heard it doesnot sice GRails and GWT directrctory structures are different.
ReplyDeleteSTS works nice with GWT since it is Java. The Grails plugin for Eclipse (wich STS has) is quite rudimentary right now IMHO. For Grails support, IntelliJ IDEA is much nicer. The different directory structures is not a big deal. If you work with two disticnt projects, one for GWT e another for Grails, each one can have its structure. If you have only one project, then you can adapt GWT directory structures and configure your own.
ReplyDeleteCheers
Thanks for this interesting article.
ReplyDelete@Felipe: I love to work in IDEA for Grails projects. However, for some odd reason IDEA on the Mac does not see the GWT_HOME variable and I haven't figured out how to solve this problem. Any hint would be greatly appreciated.
Peter
This comment has been removed by the author.
ReplyDelete(EDITED) Thanks, it's really nicer way to structure a grails/gwt application.
ReplyDeleteBut the critical point - way to merge it for production. Maven?
Are there any other ways? We can have some property e.g. {backendUrl} and change it before building GWT site to production or have prod value there but override it when running in dev hosted mode.
Thanks!
Hi Misha
ReplyDeleteyou can just set this URL in Config.groovy inside a Environment block.
Example in Config.groovy:
environments {
development {
backendUrl = "http://localhost:8080/myapp"
}
production {
backendUrl = "http://www.mydomain.com/myapp"
}
}
Right?
Cheers
Hi Felipe
ReplyDeleteI suppose this property should be in GWT side, not in Grails server-side.
I have to admit: I am not a GWT specialist. But one simple solution is to define in the Grails server side as I pointed out, and then you could just have a hidden property in your html. This hidden property would have a dynamic value. Something like:
ReplyDeleteinput type="hidden" value="${backendUrl}"
So, in GWT you can easily get this value from this hidden field.