Human beings, who are almost unique in having the ability to learn from the experience of others, are also remarkable for their apparent disinclination to do so. | ||
--Douglas Adams |
The book is now officially over, but because you bought the whole album, we’ll throw in an additional bonus track for you.
The bonus track is a recording of two developers, Guillaume (G) and Dierk (D), who work together on a full-blown web application in Groovy. By eavesdropping on their conversation and looking at the code they produce, you’ll witness the evolution of the application from first ideas until deployment.
G and D work for ACME Software, an independent software vendor. Their boss wants each product to be accompanied by an “interactive tutorial.” At least, these were his last words before heading for the golf course. While leaving the room, he grumbled something about “needed by Monday morning” and “only highest quality accepted.”
In which our heroes are given an assignment, make a bold decision, install Grails, and create their first page.
It’s Friday afternoon, right after lunch, when Guillaume enters Dierk’s office.
G: | Hi! |
D: | Hi, Mr. G. What’s up? |
G: | Didn’t you hear the boss? We have to do something about the “interactive tutorial.” |
D: | He can’t be serious. I’m not gonna spend the weekend on this. |
G: | Me neither. We need to find the quickest way to make this happen tonight. |
D: | You mean “quick and dirty”? Not with me. |
G: | No, quick and clean. I suggest we use Grails and see how much we can achieve this afternoon. |
D: | Grails? What’s that? |
G: | Grails is a web application framework. |
D: | Oh no! Not another one. I’ve seen so many of them. They all claim to do everything in no time. Please spare me another disappointment. |
G: | Well, all I’ll promise is that it will be fun working with it. I’ve used it in some other projects, and it worked well. I even think there’s a good chance we’ll have something running by this evening—and if we don’t, what have we got to lose? |
D: | I wouldn’t follow anyone but you after this pitch. [laughs] Okay then. We’ll give it a try. What’s next? Download and install? |
Guillaume takes a seat next to Dierk. They start working together at Dierk’s machine, sharing the same keyboard, mouse, and monitor.
G: | Point your browser to http://grails.codehaus.org. Downloading and installing is explained there. We will go for the latest version, which is 0.3.1 at the moment. |
Dierk opens the browser and navigates to the web site.
D: | Okay, it needs Java 1.4+ and Ant as prerequisites. I’ve got them already. Strange that it doesn’t need Groovy to be installed. [raises right eyebrow] |
G: | Grails comes with the embeddable groovy-all.jar included to avoid any version conflicts. |
D: | Wise decision. Now how about installation? Aha—setting a |
G: | That means we are building Grails from the sources. Isn’t that neat? |
D: | Neat or not, it’s a bit odd. But hey—as long as it works, it’s fine with me. |
G: | That was it for installation. |
D: | Eh—no. Wait. We’re gonna need a database. |
G: | Yep. That’s included. |
D: | And this will be a web application, right? So we also need a web server. |
G: | No, that’s all included. Grails comes with everything you need for development. |
D: | That can’t possibly be right. In the end, we’ll have to deploy it on our corporate SphereLogic webserver and the DBacle/2 database! That’s all very special. How could we ever develop against a different environment? |
G: | Well, first of all, Grails will produce a full J2EE-compliant web application as a web archive file. We can throw that into any compliant server. Second, Grails uses Hibernate to take care of the database mapping. That means we have a huge variety of databases that we can choose from. |
D: | Pretty impressive. Now, how do we start? |
G: | Go to any directory you like, and open a command shell. |
D: | I’ll take one that’s already under version control. |
G: | [nods] Sure. Now create a new application by typing grails create-app |
G: | and enter our application name when asked. I think Tutor would be appropriate. |
D: | It’s created a lot of directories. Any idea what they all do? |
G: | Yes, it looks like this. [produces table 16.1] |
Table 16.1. Directory structure below the Tutor application directory
Directory | Content | ||
---|---|---|---|
grails-app | The grails-specific part of the web application | ||
conf | Configuration data sources and bootstrapping | ||
controllers | All Grails controllers; initially empty | ||
domain | All Grails domain classes (models); initially empty | ||
i18n | Message bundles for internationalization | ||
services | All Grails service classes; initially empty | ||
taglib | All Grails tag libraries | ||
views | All Grails views (GSP or JSP); initially empty | ||
layouts | All sitemesh layouts | ||
grails-tests | All Grails unit tests; initially empty | ||
hibernate | Optional Hibernate configuration and mapping files | ||
lib | Additional libraries as jar files | ||
spring | Spring configuration file(s) | ||
src | |||
groovy | Additional Groovy sources; initially empty | ||
java | Additional Java sources; initially empty | ||
web-app | Web application document root directory | ||
css | Resource directory for Cascading Style Sheets | ||
images | Resource directory for images | ||
js | Resource directory for JavaScript files | ||
WEB-INF | J2EE meta information (web.xml, and so on) | ||
classes | Target for compiled classes; initially empty | ||
tld | Resource directory for compiled tag libraries |
D: | Gosh, it’s lucky you had that table with you. I see that the layout of the grails-app directory suggests that Grails obeys the good-old Model-View-Controller (MVC) separation or even enforces it. |
G: | Yes, we will see that throughout the whole project. In general, the model is made by the domain objects, which drive the whole process. |
D: | It’s created a whole bunch of files, too. |
G: | Those are defaults for our application so that we can start right away. |
D: | You mean we can start the application without having done anything? |
G: | Yes, we can. [takes the keyboard] Go to the application directory cd tutor |
G: | and run the application. grails run-app |
D: | That’s a heck of a lot of console output. Is anyone meant to understand that? |
G: | Well, we’re running at warning log level per default. I bet you’d be glad if anything went wrong. |
D: | Now it waits and says: |
G: | That’s the Jetty web server, which is included in the distribution. Grails has generated a full-blown J2EE web application and started the server on it. |
D: | Very helpful. No tinkering with server configuration files. That’s a big plus. And where is it running now? |
G: | http://localhost:8080/tutor/. |
D: | [grabs the keyboard] Here we go. [figure 16.1 is displayed] |
G: | Okay, the setup works. |
D: | Yes, that’s a good installation check. How did you know the URL? |
G: |
|
D: | I see. And now we hack away some static HTML pages to create a prototype? |
G: | Why would we want to write static HTML pages? Don’t you want to see real data? |
D: | Of course, eventually, but surely a few dummy pages come first, don’t they? |
G: | No. If we were going to do any more coding right away, we would create domain classes, but I think we should lay out some plans first. |
D: | [nods] Does that mean you’re calling a coffee break? |
G: | Or tea. 15 minutes after lunch is the perfect time for this. |
In which our friends learn of requirements and dream up a schema.
Guillaume and Dierk are standing at a round table next to a whiteboard in the coffee corner, with mint tea and cappuccino.
D: | This can’t be a coincidence. |
G: | What do you mean? |
D: | We’re working with Grails and standing at a round table... |
G: | Oh, please. This isn’t Camelot. |
D: | [grins] Okay then. What about the “interactive tutorial”? |
G: | I guess the tutorial needs to consist of at least some text and code examples... |
D: | ... that we need to create and display. |
G: | Yes. We have authors who create a tutorial and users who read it. To make the authors’ lives easier, it would be nice if they could post tutorial pages through the web application. |
D: | You mean like posting to a blog or a wiki? |
G: | Yes, exactly. We’ll find out what works best. We’ll also need a tree-like structure for the tutorial entries to organize the tutorial and show a table of contents. |
D: | |
G: | I talked with our boss about that before. He has the idea that logged-in users should be able to see what tutorial elements they’ve already worked through so that they can concentrate on the new ones. |
D: | Hm, that’s a bit tricky. A user can scan through the material without reading much of it. That shouldn’t count as “reading.” |
G: | Maybe the user clicks a button, indicating that they have visited the page. |
D: | Hm, sounds doable—with considerable effort. |
G: | We don’t have much time, so we will follow the simplest possible route. Let’s see what we have. [sketches figure 16.2 on the whiteboard] |
In which Guillaume creates the first domain class, and Dierk is astonished by the scaffolding and testing capabilities of Grails.
Back at Dierk’s machine, he unlocks the screensaver.
D: | Now, how do we start implementing the domain model? Are we creating POJOs?[1] Do we have to follow any conventions? |
[1] Plain Old Java Objects. |
Both, actually. [grabs the keyboard] But Grails gives us all support we need. Let’s first create the grails create-domain-class [input] Enter domain class name: author | |
D: | Ah, you entered the class name in lowercase? |
G: | Yes, but that’s only because I’m a lazy typist. |
D: | And Grails says it has created two classes for us: |
G: | That’s a scaffolded domain class and a corresponding unit test to give us something to start with. |
D: | There’s not a lot there, really. Is that it? |
G: | That’s all it takes to start with. Grails will inject |
D: | You mean we should make it return something that makes sense to a user rather than to the computer? |
G: | [nods] Exactly. Also, every such class will automatically be persistent—that is, backed by the database and managed by Hibernate. |
D: | Aha, then let’s create the database, the tables, the schema, and the mapping descriptor. |
G: | No, no, no! That’s why I said “automatically.” That’s all done for us behind the scenes. There’s really nothing we have to do. |
D: | Wow! Very impressive. But how about the |
G: | That’s as simple as this. [edits the file to make it listing 16.2] |
And no mapping at all? | |
G: | As I said—it’s all automatic. Grails follows the “convention over configuration” paradigm, which makes all this possible. |
D: | I would like to see something tangible before we proceed with the domain model. Can we look at the database? |
G: | Yes, we can. We can even look at it through the web application. |
D: | You’re kidding! We don’t have a web application. |
G: | We don’t have a web application yet, but we will have one in a minute. See here: grails generate-all [input] Enter domain class name: author |
D: | Lots more screenfuls of output, I see. And this is creating a web application? |
G: | Start the server with grails run-app |
G: | and point the browser to http://localhost:8080/tutor/author. We need to add “author” to the URL because that is our domain class of interest. [browsing around, the screens in figure 16.3 are displayed] |
D: | You mean we can also test the web application automatically? |
G: | Yes, and it’s remarkably easy. Look here. We’ll create the webtest, then scaffold the author tests, and finally run it. Creating the webtest support is a one-liner: grails create-webtest |
G: | This fetches Canoo WebTest if necessary and installs it. The download size is pretty big. Luckily we have good network connectivity. |
G: | Now scaffold a webtest for the grails generate-webtest [input] Enter domain class name: author |
G: | And now we run it: grails run-webtest |
D: | [as figure 16.4 is displayed] Ah, it brings up a test report. That’s pretty. Does it work by clicking through the web application on my behalf? |
Guillaume leans back and folds his hands.
D: | We’re going to scaffold |
G: | Yep. |
D: | [starts typing] grails create-domain-class [input] Enter domain class name: tutorialEntry |
D: | All fine. Now scaffolding views and controllers? |
G: | We could do that, but the views are scaffolded from what’s available in the domain classes, so we should complete them first. |
D: | Okay, |
G: | Add a property for every attribute that we defined in our design: |
D: | Do we need to declare property types? |
G: | Yes. Grails uses the type information for building the model. Having the type information available is a big plus. |
D: |
|
G: | Of course. |
D: | And |
G: | [grabs the keyboard] Here we go. [edits |
Example 16.3. Domain class TutorialEntry
class TutorialEntry { String title TutorialEntry parentEntry String text Author author String toString() { title } }
D: | |
G: | Go ahead. |
D: | Piece of cake. [starts typing] grails generate-all [input] Enter domain class name: tutorialEntry |
D: | Now starting the server... grails run-app
[wait-forever]: |
D: | ... and open the browser and create a |
G: | Every |
Guillaume opens the browser at the URL http://localhost:8080/tutor/author and creates an Author
.
G: | Now head over to http://localhost:8080/tutor/tutorialEntry/create. [figure 16.5 is shown] |
D: | Wow! We already have the core of the application. And it’s working. We have the full lifecycle for authors and tutorials. The views are still not what we really want, though. |
G: | Yes. Scaffolding is only to get you started and provide you with something that you can build on. It’s not uncommon to progressively replace all scaffolded artifacts in the course of the project. |
D: | Then let’s see what we can do about the |
In which our heroes create some sample data, change a view in GSP, and gain wiki-like formatting using a tag library.
G: | We create our domain objects programmatically. Look here.[ |
D: | You create your objects with references and such, and when you say “save()” it all goes to the database? |
G: | Isn’t that nice? That’s the typical Groovy style of having a smart configuration. Just imagine if this were XML like in other systems. Then we wouldn’t be able to define any amount of example data as we do in the little |
D: | Well, then let’s display it. [types] grails run-app |
D: | Now see what we have in the |
G: | I think the scaffolded list view of tutorials shows too many details. It should only show titles, like in a table of contents, but with indentations. |
D: | Okay. How do we change it? |
G: | It’s all here. [opens views/tutorialEntry/list.gsp] |
D: | Ah, the directory structure reflects the URL of the view. Very convenient. |
G: | At least for the standard views as scaffolded. There are alternatives like creating them dynamically, but for our little thing here the standard way is all we need. |
D: | And this GSP is the Groovy version of JSP? |
G: | Yes, only better. [grins] Look at the snippet here that renders the list of tutorials. [opens the file, showing listing 16.5] |
D: | Looks familiar. I know nothing about GSP, but I can guess that this renders a table row for each |
G: | Yes. And don’t forget the corresponding table header cells. |
D: | Done. Now restart the server and see what it looks like? |
G: | No, no. We don’t need to restart the server. Just save the file and reload the browser page. |
D: | Wonderful—very convenient. I like this micro-iteration development. |
G: | Yes. Instant feedback is really helpful. You do a little change and verify the result without losing your concentration. This is what gets you into the flow of programming. |
D: | I know what you mean. When everything seems to just fit. |
G: | That reminds me—our list view doesn’t fit yet. It doesn’t show indentation. |
D: | Hm, we need to indent each title as much as its nesting depth counted from root. |
G: | And that’s equivalent to the number of parents it has. |
D: | Ah, you mean we count the parents and indent by that number? But that’s a full-blown tree algorithm that will require a lot of work! |
G: | Not necessarily. Let me try something. [grabs the keyboard—edits the file to listing 16.6]. |
D: | |
G: | Yes, the same as you would do in Java for JSP but in Groovy. The |
D: | It works well, and the output looks nice, but it has a smell. I think a view should never contain logic, and this view does. |
G: | I’m not so sure. We could easily refactor this code into the |
D: | But I don’t like the mix of logic here. It doesn’t look well factored. I agree it’s view logic, but it should go to a separate place. |
G: | If that’s the only thing that makes you feel uneasy, then we can move it into a template. Templates are little view pieces that are reused with changing data. |
D: | Like a row template used for each |
G: | In fact, that is a good name. We make a new file tutorialEntry/_row.gsp and put all the |
D: | The leading underscore is a naming convention? |
G: | That separates it from the normal views and suggests that it doesn’t produce a full page but only a part of it. |
D: | And the |
G: | Yes, via the <g:each var="page" in="${tutorialEntryList}"> |
G: |
<g:render template="row" collection="${tutorialEntryList}" /> |
G: | This will pass all entries of the |
D: | I like that much better. It clearly shows that the extracted logic affects only one line. |
G: | It also makes the view logic reusable. Any view that needs to render a |
D: | What other views need improvement? Ah, the detail view should present tutorial entries in a more readable format. I assume it resides in views/tutorialEntry/ show.gsp. |
D: | This looks all very comprehensive. Rearranging the fields a little should be all that’s necessary. |
G: | I have an idea. In addition to having our authors possibly provide HTML tags in their contribution, wouldn’t it be nice if they could use some wiki-like markup for the simple cases? We could then display the transformed markup on this page. |
D: | Hm, sounds complicated. |
G: | For a start, we allow some simple markup: treating newlines as breaks, two newlines as a paragraph, and support bold, underline, and italic styles. |
D: | That’s still a lot of work—more than we can do in a scriptlet. |
G: | Yes. That’s a job for a taglib. |
D: | Hm. [shakes head] I did some taglib stuff with JSP—lots of configuration and not a lot of fun. |
G: | I’ll show you Grails’ no-configuration solution. Go to the taglib dir, and create a new file WikiTagLib.groovy. |
D: | And now? |
G: | We implement the intended tag as a closure property with the respective name. How about “wikify”? The tag would have a |
D: | And how do we pass the result back? |
G: | We add it to the |
Pretty slick. All plain Groovy. But how do we use it? | |
G: | In show.gsp we replace ${tutorialEntry?.text} |
G: | with <g:wikify text="${tutorialEntry?.text}"/> |
D: | That’s all logical. I guess we now have to declare our |
G: | No, no. That’s all done automatically. Save the files, and reload the page, [David Copperfield gesture] et voilà, there it is. [figure 16.7 appears on the screen] |
In which Dierk and Guillaume take control of the application and ask searching questions of their database.
D: | As a Tutor user, I would like to go through the tutorials in sequence. How about providing Previous and Next links in the detail page? |
G: | Yes, you’re right. That’s missing. But how do we determine which tutorial entry is previous or next? |
D: | We could guess from the containment in the parent/child relationship or from conventions about the title. |
G: | That’s all too shaky. We need to have that in the model itself. After all, the page sequence is a central part of any tutorial, right? |
D: | But changing the model is always critical. |
G: | Not necessarily. Let’s add this line to TutorialEntry predecessor |
G: | and then restart the server. |
D: | Changing the domain class requires a restart? |
G: | Not always. Simple changes don’t. Grails tries to avoid making you restart unless it’s absolutely essential. However, my personal style is to always do it after changes of the domain classes—just in case, you know. |
D: | Fair enough. I guess we should also provide our bootstrapped |
G: | Ah, right. Thanks. I would have forgotten about that. |
D: | Okay. All done. The server is restarted. |
G: | Then let’s change the show.gsp to include Previous and Next buttons. I’d say we will use menu buttons for this. [edits file to listing 16.8] |
Example 16.8. Snippet of views/tutorialEntry/show.gsp introducing Previous and Next menu buttons
<span class= "menuButton"> <g:link action= "index">Home</g:link></span> <span class= "menuButton"> <g:link action= "previous" id="${tutorialEntry?.id}">Previous </g:link></span> <span class= "menuButton"> <g:link action= "next" id="${tutorialEntry?.id}">Next </g:link></span>
D: | |
G: | When we click the Previous button, the |
D: | That means we have to extend the |
G: | You see all the scaffolded actions in it? Does it look familiar? |
D: | They are implemented as closures and assigned to properties—the same way we defined tags for the WikiTagLib. Looks like a pattern to me. |
G: | [smiles] Seems to be more than a coincidence at least. [takes the keyboard] Let me try this. [makes the controller resemble listing 16.9] |
Example 16.9. Previous
action in the TutorialEntryController
def previous = { def entry = TutorialEntry.get( params.id ) if (entry.predecessor) { entry = entry.predecessor } else { flash.message = "Top of tutorials reached." } redirect(action: show, id: entry.id) }
D: | I don’t understand a single line. |
G: | Well, we first fetch the current |
D: | Is |
G: | Kind of. It “gets” you the instance from the database or from the cache. Chances are that when we reach this point, the instance is already in the cache. |
D: | And |
G: | So it is. |
D: | Then we work with the object references as usual. That’s okay. What is the |
G: | The flash is a scope that lives until the next request and is used for relaying information from one controller call to the next. It is mostly used in situations like this, for relaying messages such as information, warnings, errors, and so on. In the scaffolded views, you will find references to the flash scope to find out whether there are any messages to be shown. |
D: | Aha—and finally we redirect to the |
G: | Exactly, possibly showing the flash message if there is no predecessor. |
D: | After the explanation, it makes sense. |
G: | I hope so. The |
D: | What’s different? |
G: | Look closely at the second line. |
D: |
|
G: | No, we didn’t, but Grails is smart enough to know what we’re after. That’s a dynamic finder method made from the domain class information. This expression is roughly equivalent to this pseudo SQL statement: SELECT * FROM TutorialEntry AS te WHERE entry.predecessor.id = te.id |
D: | Why do you say “roughly”? |
G: | Because strictly speaking, SQL has no notion of “entry” that we used here. Behind the scenes, Hibernate is doing all the work of providing us with nice object-oriented query facilities and optimization. Grails makes them available to us in the most convenient fashion. |
D: | That is really amazing, but also very unfamiliar. Is there a reference about all these dynamic methods? |
G: | Yes, it’s all on the web under http://grails.org. |
D: | I know what to read over the weekend, then. Are we finished with the Tutor application? |
G: | Not yet. The “interactive” part is still missing. We have no model about our users, yet, and how they visit |
D: | That sounds like we should have a coffee break for another modeling session. |
G: | Good idea. |
Guillaume and Dierk walk over to the coffee corner.
In which Guillaume and Dierk discover a new entity in their midst.
The coffee corner’s whiteboard still shows figure 16.2.
D: | We should add the new |
G: | [nods silently] |
D: | And we will have a |
G: | [nods silently] |
D: | You’re so quiet. What are you thinking about? |
G: | About the relation between |
D: | Why? One |
G: | It’s not as simple, because many |
D: | Ah, I see. You’re right. If we ask a |
G: | The rule of thumb is that when you hit a many-to-many relation, you have missed an important concept in your design. |
D: | Aha. So we know at least there’s something missing. Any hint how to find it? |
G: | The trick is to picture an object that encapsulates the whole many-to-many relationship. If we had an object that takes |
D: | Eureka! I have it! That’s a |
G: | Good job! That makes sense. Every time a |
D: | And the relations become much simpler. Many |
G: | And many visits can be stored for the same |
D: | In other words, we have split the original many-to-many relation into two many-to-one relations. How exciting! We invented a whole new world-changing pattern! |
G: | I don’t like pouring water in your cappuccino, but this pattern isn’t new at all. It’s as old as relational databases—if not older. |
D: | Still, I think we did a really good job modeling the domain. I’m impatient to see how that looks in code. |
In which Guillaume and Dierk ask their users to introduce themselves, and allow them to record their visits.
Back on Dierk’s machine.
D: | Scaffolding |
Example 16.11. Scaffolded and customized User
domain class
class User { String name String toString() { name } }
D: | And here’s the |
Example 16.12. Scaffolded and customized Visit
domain class
class Visit { User user TutorialEntry entry String toString() { user.toString() + ' : ' + entry.toString() } }
G: | The user needs some device to log in. |
D: | To begin with, they could choose their name from the list of users. Ah, wait—we have scaffolded the user |
G: | How should that work? |
D: | We add a new action button to each table row like so: <span class="actionButton">
<g:link action="select" id="${it.id}"> That's me </g:link>
</span> |
G: | I see. And we add a |
D: | ... to the scaffolded |
G: | Okay, let me do it. [takes the keyboard] We need get hold of the selected |
D: | |
G: | It would be more accurate to say that it can be used like a map. |
D: | And if everything is okay, we forward to the |
G: | Yes, via the |
D: | The error handling seems a bit too much. Can it really be that a bad |
G: | You’re right. It’s unlikely, but you never know. Call me paranoid, but in controllers with all the redirections and chaining that might happen, I prefer to keep the code defensive. |
D: | You’re the boss. What’s next? |
G: | Users need some device to mark a |
D: | Let me do that GUI stuff. [keyboard switch] I will add this line to the <span class="button"><g:actionSubmit value="Visited"/></span> |
G: | If we click this, it will be handled by the |
D: | Let me try to implement this. I will keep it as defensive as you like it. |
G: | Good. You create the corresponding |
D: | And I added a convenience feature in the last line. After the button click, there is no point in staying on the same page. We go directly to the next one. |
G: | Your users will love you. |
D: | I hope so. Are we done? |
G: | Almost. We still need to provide a filtered view for the |
D: | That concerns views/tutorialEntry/list.gsp. I’d say the simplest solution is to add the filtering functionality as menu buttons in the top row. [does so to make listing 16.15] |
Example 16.15. Snippet of menu button section in tutorialEntry/list.gsp for providing filtering views
<span class="menuButton"> <g:link controller="user" action="list"> <%= (session?.user) ? "${session.user.name}": 'Log in' %> </g:link> </span> <g:if test="${session?.user}"> <span class="menuButton"> <g:link action="listVisited">List visited</g:link> </span> <span class="menuButton"> <g:link action="listUnvisited">List unvisited</g:link> </span> </g:if>
G: | I see you’re going to like GSP. |
D: | Is that too much? |
G: | You’re doing a little more than we need. First, you’re displaying the current user. |
D: | Or “Log in” if there is no user in the session yet. The link behind that button goes to the user list view. |
G: | And then two filtering buttons to restrict the current view to only the visited of the unvisited links. |
D: | But only if the user is known. Otherwise there is no point in showing them. |
G: | Very user-friendly. But none of this will work until we have the |
D: | Can you help me with this? |
G: | Sure. Remember, it’s all plain Groovy object work. [edits the controller to listing 16.16] |
In which Guillame and Dierk protect themselves from the forces of evil (well, evil input), deploy the application to a live system, and go their separate ways.
G: | Yep. Finally, I’d like to go over field validation to protect the application from bad user input. |
D: | I would normally complain that this is too much work, because we have to go over all the affected actions and views, but I guess you have another ace up your sleeve? |
G: | Well, I haven’t—but Grails has. It allows easy declaration of constraints in your model that are automatically used for validation. Let’s take the |
D: | Looks like we have to write some tricky conditionals and database access code here. |
G: | Or a simple def constraints = { name(length: 5..15, blank: false, unique: true) } |
D: | Marvelous! And how does it work on the user interface? |
G: | Let’s try to add a user that already exists and see what happens. [tries it, resulting in figure 16.9] |
D: | Really slick and very intuitive. Can you tell me what kind of validations are available? |
G: | There’s a fairly long list in the online docs. So far, I’ve been able to do everything I needed, even regular expression matches. |
D: | That’s fine. Regular expressions should at least cover everything we need to validate on the syntactic level. But what if I want to have other error messages? |
G: | Then you can adapt the file i18n/defaultErrorMessages.properties or provide your own localized message bundle. It all goes through the standard Java way of internationalization. |
D: | Perfect. I’d say we are ready for take-off. |
G: | So would I. |
Dierk leaves to contact the database administrator and returns with a paper sheet of the configuration details.
D: | Here we go. I owe him a beverage. |
G: | [smiles] We’ll have one later. |
D: | Okay, I’ve fixed up the configuration. What was next? Creating the web archive? |
G: | Yes. Here it is: grails prod war |
G: | We now have the tutor.war web archive ready for deployment. |
D: | I know how this goes. The server has a web interface where we can upload the file and start the application. That’s easy. Just a minute. [uploads the file] Okay. Done. |
G: | Let’s do some simple click-through testing to verify it’s fine. Open the browser to http://groovy.canoo.com/tutor. |
D: | Looks okay. [clicks through the application] Perfect. |
G: | Gimme a high five. [clap] |
D: | Okay. Let’s invite Martin and call it a day. |
Guillaume, Dierk, and the database administrator Martin assemble in the nearest beer garden.
D: | The work with you and Grails was a ton of joy. Thanks for pointing me at it. I think we did an awesome good job this afternoon. |
G: | Hm, we could have done a little better. |
M: | I saw your application and used it a little. Really nice. Others need more than a week to achieve anything comparable. |
G: | Thanks, but I would have liked it better if we’d worked in a more test-driven style. You know: writing unit tests and functional tests as we go along. |
D: | You’re demanding too much. That’s hardly possible. |
G: | With all the scaffolded tests, it’s quite easy. |
M: | And you are testing from a user’s perspective? |
G: | Yes. Those are the so-called webtests. They are testing the view. We did a little of that. But we can also have unit tests for our models and controllers. |
D: | But we tested the views all the time, because we were constantly switching between coding and using the application. |
M: | Sounds like an agile programming thing. |
D: | And the models have no methods at all, so there’s no point in testing them. |
G: | True; they could have had some methods, though. |
D: | I agree that testing the controllers automatically would have been beneficial. They contain the beef of the application logic. |
M: | Testing controllers is always tricky. You have to set up a server environment for that, because controllers rely on request parameters, session information, and such. That’s an awful lot of work. |
G: | Actually, it’s simple. In Grails, all the information is in common maps that you can pass into the controller method when testing. |
M: | Hm, okay. Then it should be easily possible. |
D: | I was surprised so many times this afternoon that I believe everything. |
G: | [laughs] |
M: | You must have had some real fun today. |
D: | You bet. It felt like everything was just falling in place. |
G: | Well, almost. Remember how we discussed the design of the domain model? That was the crucial part. |
M: | The drawings I saw in the coffee corner? They didn’t look overly complicated. |
G: | |
D: | Yes. I think my first attempt would have been to go for more bidirectional references. Would that have been possible at all? |
G: | Of course. It’s also straightforward. The online docs show how to do it. |
M: | How about database performance? I certainly don’t want you to drag down my db when your application becomes popular. [twinkles] |
G: | We still have room for optimization, whether by using bidirectional references or by optimizing Hibernate usage with direct mappings. However, I expect at least 90% of our database accesses to hit the Hibernate cache anyway. |
M: | Perfect. I’ll look into the database logs to see what you produce, anyway. |
G: | Thanks. I would have asked you for that favor otherwise. [grins] |
D: | What are we going to do on Monday? I’d like to do some more work with Grails. |
G: | We’ll write some more tests first. After that, I’ll say it would make sense to hook in security. |
D: | That is possible? |
G: | Of course; Grails is pure J2EE, and we can use every feature of that platform: administration, operation surveillance, logging, load balancing, failover, and so forth, and also security. There is no need to reinvent the wheel just because we’re groovy. |
M: | I can help you with the security setup. |
G: | Great! |
D: | The little wiki-like markup we implemented in the WikiTagLib made me think we’re re-inventing the wheel, though. Would it be possible to include something like the Radeox Wiki engine? |
G: | Good idea! Yes, of course, that’s possible. We can use any Java library we fancy. We throw its jar file in the lib directory and rework the WikiTagLib to use that functionality! |
M: | You have so many options that virtually anything is possible. |
D: | Friends, [stands up and raises his glass] I’m afraid the party is over, and I have to leave now. It was an honor to work with you. I know that working with me has not always been easy. I appreciate your forbearance when overlooking my little mistakes. Thank you very much for spending your precious time with me. I hope we will soon meet again and share the joy of dynamic programming. |
Cheers!
18.227.102.50