|
LATEST POSTS
Friday, July 30, 2010
Executive Summary: Use a formula in an NHibernate mapping to facilitate searching the entire string, “LastName, FirstName”, for a User object. Will see how long this Executive Summary thing lasts. Getting tired of people wasting my time by posting comments saying I’m wasting their time. (I’m also working on an idea for curbing “Smells like fail” comments as well but it’ll involve some serious changes to your browser. Or, based on the average age of people that say things like that, a call to your parents to discuss how much time you spend on Facebook.) When I’m not Google Web Toolkittin’, I have a nice side project that I use to keep my .NET skills sharp, keep one foot in the door, and whatever other reason I can think of to avoid saying the real reason, which is “pay the bills”. Because one thing start-ups ain’t got a lot of is stable (read: any) income. In said project, I have a page with an auto-suggest feature to search for users. I.e. you enter some text, and it finds any users with the entered text in the name and displays them in a dropdown. I’d show a screenshot but in the time between when I developed it and when I wrote this, the feature was dropped. The mechanics of the auto-suggest might be the subject of another post but I doubt it because it’s been covered to death (though not so much in ExtJS which is what we’re using). I’m going to talk about what happens in the back-end. That is, how do I get the data from the database with NHibernate. We’re using Linq to NHibernate so my first pass was straight-forward: public IList<User> Search(string searchText) {
var session = NHibernateSession.Current;
return ( from w in session.Linq<User>()
where w.FirstName.Contains(searchText) || w.LastName.Contains(searchText)
select w).ToList();
}
This works exactly as one would expect. If the user enters "will", it will display "William F. Buckley" and "Ted Williams" and "William 'Wild Bill' W. Williamson" in the search results. Or rather, it will show "Buckley, William F.", "Williams, Ted", and "Williamson, William 'Wild Bill' W." because that's how we're displaying our search results.
And to facilitate that display, we have a Name property on the User object:
public string Name {
get { return LastName + ", " + FirstName; }
}
Problem is that this search doesn't cover a common scenario. What if the user types 'Williams, T'? This would be a natural thing to do. They want Ted Williams, so they start typing Williams. The search results are too big and they are showing items in the "Last, First" format so it makes sense to keep typing and try to narrow it down further.
The code above will return zero results for such a search. Really what we want is to search the Name property, like so:
public IList<User> Search(string searchText) {
var session = NHibernateSession.Current;
return ( from w in session.Linq<User>()
where w.Name.Contains(searchText)
select w).ToList();
}
Which doesn't work either because Name isn't a database field and as yet, NHibernate is not able to parse formulas in your properties and convert them into SQL or Criterion.
But NHibernate *does* allow formulas if you describe the formula to it in the mapping. We're using Fluent NHibernate (assuming it hasn't been merged into the NHibernate project yet and completely replaced mapping files, which it should be):
public class UserMapOverride : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.Map(x => x.Name).Formula("LastName + ', ' + FirstName");
}
}
And update the Name property in the User object accordingly:
public virtual string Name { get; private set; }
Now, our Search function works the way I want.
Kyle the Formulaic
Friday, July 30, 2010
Bug tracking probably means something different to you than it does to the Hillbilly but if it gets you half as excited as it gets me, be sure to wander on over to http://youtrack.codebetter.com. As a sister-aunt service to http://teamcity.codebetter.com, we’re now offering a free issue tracking service to OSS projects, courtesy of the gentle folk at JetBrains. Hadi Hariri has more details on how to sign up. Despite doing all the work, he also graciously thanks me for all my hard work and if replying to emails with “Good idea” and “Let’s do it!” and “If you think I’m supporting all these &*%$ whiners and their ‘issues’ then you’d better add ‘Hadi’s Personality’ to the project list ‘cause you got issues of your own” is all it takes to get gratitude these days, then I’m owed a whole lotta appreciation. Happy bugging! Kyle the Etymologist* *Yes, yes, I know. I’m trying to be subtle here.
Wednesday, July 28, 2010
Executive Summary: With all of our experience in Microsoft technologies, why did we choose Google Web Toolkit over other technologies for our start-up? Almost six months ago, I described my initial reaction to Java coming from ten years in the Microsoft world. At the time, I tastefully glossed over the reason we ended up using Java, or more specifically, Google Web Toolkit saving the discussion for a time when all I really wanted to do was make sure there were no gaps in the list of months in my Archive. Plus the original post inspired would-be blog-reviewer, Dinesh Gajjar, to comment on the lack of content, a position that leads me to believe he’d only recently started reading my blog. In any case, one of his concerns was that I didn’t explain “why I chose what” which I will assume means “why I chose GWT” (and apologies, Dinesh, if by “what”, you really meant “to waste time that could be spent writing comments on other people’s blogs”). When first deciding on how to proceed, our default was .NET, which led to some obvious options. MVC seemed a no-brainer and since I was involved in Sharp Architecture, my first reaction before we even finished the conversation was to run the Sharp project template. “But,” says we, “we’re pragmatic programmers. Why do we automatically reach for .NET? This is *our* project!” Thus explains why my resume says I have two days of experience with Ruby. I played with it for (almost) that long before we changed our minds again. As we discussed the nature of the application, we discovered it would rely *very* heavily on JavaScript. So we wanted something that would facilitate that. At the time, we were also a little apprehensive about the learning curve with Ruby. Not that we wanted to shy away from it. But this wasn’t some fictitious client’s money we were playing with. It was hours. “Learning on the client’s dime” had a more ominous effect when the client’s dime was my dime. So we ventured back to .NET being familiar territory and telling ourselves “it’ll help us get this out the door faster.” We also looked seriously at OpenRasta because it seemed to facilitate our view of having the server serve up resources rather than the default view of serving up pages. Our intent was to have a single page and dynamically modify the various pieces view JavaScript. We weren’t looking forward to managing history support for something like that but we felt it offered the best user experience, which is more important than our nagging little technical problems. One nagging aspect to this approach was testing the JavaScript, something I had little experience with. I did some smoke tests with WatiN and was encouraged a little but it always felt a little uncomfortable, especially trying to run the tests on a CI server. And both my partner and I (admittedly, he more than I) were concerned about the costs of long-term maintainability and were pretty adamant on making the app testable and following a strong focus on quality while we built it. WatiN didn’t give us that warm fuzzy feeling and I don’t believe we looked at Selenium at the time. Around this time, my partner discovered Google Web Toolkit which looked tailor-made for the type of application we wanted to build: A single page with various pieces updated based on user clicks. And you could write it all in Java, which was easily testable. And better yet, it had history support built in. So we both did smoke tests and decided it was the way to go. To summarize, it gave us (in order of importance): - an AJAX application with a good user experience
- history support (also important for user experience)
- easily tested
- strong community and many resources
In retrospect, it seems there is a fine line between “pragmatic” and “indecisive”. There’s a good chance that not all of our decisions were the right ones, just the ones we made at the time with the information available to us. As I reflect on it, maybe we dismissed Ruby too soon. Or maybe we could have built the app faster in .NET and still made it maintainable. Or maybe or maybe or maybe… Truth be told, writing this post is about all the reflecting I’ve done on it. Because I have no regrets using GWT whatsoever. It’s lived up to its promise. Yes, we’ve run into issues but none that have been insurmountable and certainly no more than we would with .NET (can’t speak for Ruby). More importantly, it’s because we use Java/GWT that we have the team that we do and, with all due respect to the awesome team I worked with on my first Livelink project a couple of years ago, it is by far the best team I’ve been in in my twelve years of consulting. And this from a guy who gets along with everyone (almost). Kyle the Indefinite
Wednesday, June 23, 2010
Quick PSA today on automating tasks in web apps that don’t have keyboard shortcuts. I had to migrate about 110 stories from one AgileZen project to another. As yet, AgileZen’s API is read-only so after confirming that the eldest offspring was unwilling and the youngest (being seven months old) was unable, I set up doing the task manually. (Side note: The reason for the migration is that we were originally using BaseCamp. All our stories were stored as tasks for our beta version and version 1. We had originally moved all the V1 stories into a fake phase for AgileZen just for the sake of getting them out of BaseCamp. At the time, I glanced through the AgileZen API documentation and had mistakenly concluded that I’d be able to migrate them to a new project programmatically when the time came.) Copying the stories wasn’t too onerous a task. They were all in the same phase and didn’t have any details assigned to them so it was about fifteen minutes of cut and paste from one browser to another. Deleting the original stories proved a little more monotonous. They can only be deleted by opening the details, clicking Delete, then clicking OK. After doing this manually about five times, I started exploring other options. On Chrome, I have Vimium installed. It gives you Vim navigation keyboard shortcuts, similar to Vimperator for Firefox. With it, when you can “click” links by pressing f, then typing the appropriate code corresponding to the link. That means I can handle the clicking involved to delete a story from the keyboard. I also have AutoHotKey, which is designed to automate keystrokes and mouse clicks. I use it to create keyboard shortcuts like Win+n for notepad and Win+g to open the hillbilly community forums website and an alternate way to close an app without using Alt-F4. Between the two of them, I came up with this quick ‘n dirty script to delete stories: #q::
Delete()
return
Delete() {
Send f
Sleep 250
Send s
send h
Sleep 1000
Send f
Sleep 250
Send a
Send f
Sleep 500
Send f
}
Now, when I press Win+q, the following happens:
- Press f to tell Vimium I’m going to navigate to a link
- Pause a quarter of a second; without this, the script goes too fast for Vimium
- Enter the key code for the story I want to delete. Note that this is always the top story in the phase so the code is always the same as long as there is at least one story in the phase
- Pause a second to give the browser time to navigate to the story
- Press f again for Vimium navigation and pause
- Enter the key code for the delete button
- Pause half a second to give the confirmation dialogue time to appear
- Press f again for Vimium navigation
At this point, I enter the key code for the OK button manually just to be on the safe side.
Pausing for a second to let the browser navigate to the story details is a minor hack. I’m pretty sure I could get AutoHotKey to wait until the title of the window changes or something else more accurate. But I have to balance out the time it takes to figure this out versus the time it takes to do it manually…ignoring the time it took to write this post, of course.
Kyle the Served
Tuesday, June 22, 2010
I have a new mistress and her name is Rietveld. I call her Veldy or sometimes, in the throes of passion, RV. But only because I have no idea how to pronounce Rietveld out loud. In a recent post, I talked about our switch to the code review tool. And by recent, I mean less than a month so it’s been a torrid affair but I can tell she’s the code review tool I’ve been waiting my whole career for. In that post, I described our old process and the proposed new one. Now, a few weeks later, I can begin my mission to make sure every man, woman, and child knows about the tool whether they do code reviews or not. Our process is the same as outlined in the previous post but I’ll mention some side notes here just to ensure there’s new content in an attempt to keep potential trolls at bay. The absolute greatest feature by far of the tool is how it enables asynchronous and disconnected reviews. By “disconnected”, I mean I don’t need to have the code with me. (You still need an Internet connection.) I just finished a ten day trip to London* and performed at least half a dozen reviews during that time on my wife’s laptop. Everything I need is included in the review page: the old code, the new code, and any comments by other reviewers or the initiator. There are a couple of things this has enabled. First, I can do code reviews any time I have a free moment. I whipped off at least four this morning. Before I hit the sack for the evening, I’ll often open Rietveld up and bang out one or two just to end on a high note. It’s rare that I spend more than half an hour on any of them. More often, it’s closer to fifteen or twenty minutes. If you’re wondering why we have so many reviews, that’s the second “enabled thing”: It’s so easy to do them, we can now review everything. We even have our own process swimlane for stories that are to be code reviewed. I.e. a story can’t be marked done until it’s passed the review. Ditto for bugs. The reason this works? Because it’s trivial to start a review and it’s almost trivial to do them. At this given moment, we have fifteen active code reviews. I expect at least three of them to be closed in the next twelve hours. But I’m also expecting two more to be created in that same time. And because it’s asynchronous, I don’t even need to finish a review when I start it. Any comments I’ve made that session are stored as drafts until I publish them. I’ve done this a couple of times: Hit some code that required more thought than I was able to give at the moment so I just shut down the review and moved on to the next one. There is a nice side effect that’s come from this process. Our team consists of one junior developer, a senior developer, and me. Because the comments are persisted and because we don’t allow stories to continue until the reviews are complete, the reviews have become a good source of training and mentoring for the junior dev. She will submit a review for a story, launch a review, and then update the code several times addressing issues that were brought up during the review. Sometimes the comments are trivial, like “make sure the indentation is consistent”, sometimes it involves a fairly sizeable chunk of work, like “add unit tests to expose the bug”. During this time, she can submit further patch sets and Rietveld can show you the diffs between them and the previous patch set. All the while, we (and by “we”, I almost always mean “the senior dev”) are providing on-the-job mentoring that’s relevant and contextual. “Code review” no longer holds a connotation of six people in a room for hours being kept conscious by whatever substance passes for coffee these days. Now, it’s become a relatively benign task, like verifying we have unit tests. Story’s done; launch a review and move on to the next one. So if you meet the requirements (use SVN, Hg, or Git; can create an app on AppEngine), you’d make an old-ish Hillbilly mighty happy iffen you’d check it out. Kyle the Renewed
Wednesday, June 02, 2010
For me, code reviews have traditionally been one of those aspects of software development that fall into the “good idea; bad execution” category. It’s something that I know we’re supposed to do but when someone says, “let’s do a code review”, my first reaction is usually to make a cross with my fingers and start yelling “THE POWER OF CHRIST COMPELS YOU!” at them. Unfortunately, our team is scattered across three time zones these days so that tactic is less effective than it has been in the past. Which means we’ve had to address the real problem: how do we make code reviews useful? Our first attempt was to do them the tried-and-almost-true way. That is, we scheduled a code review meeting on Wednesdays and I’d send out notices to the other developers on Monday saying we’re reviewing these five classes. I sent the notes out on Monday to give people plenty of time because it wouldn’t be realistic to expect them to do the reviews an hour before the meeting starts now, would it… Historically, code review meetings that I’ve attended/led have usually followed a familiar pattern. To summarize: Meeting starts Coding Hillbilly: Okay folks, I’m pumped to get started. There are some interesting things going on in this code and some awesome comments. Let’s take an easy one to start off: “This code is pretty coupled to the implementation. We should create an event bus to manage the communication between the various presenters.” I agree and let me tell you at length why I say that… End of Hour One CH: I’m going to have to cut off your overview of dependency injection frameworks, Arbuckle. You’re right, I think that will help us with the internationalization of our config files. But we’ve only covered three issues so far. The rest of them should go much quicker though. Let’s take a look at the next comment…”This should be split out into a REST service”… End of Hour Two CH: Moving on…”We shouldn’t need a separate interface for this. It’s just extra complexity. Just create a new instance of the implementation in the constructor.”………<sigh>…Agreed. Next… End of Hour Three CH: Next…”Move the hard-coded database connection string out of the vie---“…you know what? The rest of this code looks good to me. Any objections if we accept it as-is? Great. See you all next week. In this little slice of life, I’ve also ignored an important aspect of code reviews with remote teams. In an attempt to keep comments as close to the code as possible, they are done as //TODOs inline with the code. And to avoid annoying merge issues, this means everyone needs to do the actual reviewing at different times. So there’s a lot of “Okay, I’m done. Have at ‘er” IMs thrown about. As much fun as this is, I decided to give Rietveld a look. I stumbled across it when I noticed Philippe Beaudoin, the creator of gwt-platform, was using it for his project. It was easy to pick his brain about it because, y’know, he’s on our dev team. Some notable tidbits about Rietveld. It’s based on Mondrian, the internal code review tool used by Google. And it was created by Guido van Rossum. As a general rule, if the creator of a piece of software has also written a widely used programming language, it’s worth a quick eval. But it was the workflow of Rietveld that drew me in. This is our new code review process: - A developer initiates a code review by selecting a starting revision and an ending revision. The diff between the two is submitted for review to the selected reviewers
- The reviewers look at the diffs (either as a side-by-side diff or, if you prefer a challenge, a unified one) and draft comments inline with the code on the web app (i.e. not in the code in the repository itself)
- When a reviewer is done drafting comments, she publishes them to other reviewers and the initiator
- The initiator reviews the comments, makes changes if necessary, and publishes updated code diffs in response to the comments (again, if necessary)
- The initiator and other reviewers can keep adding comments back and forth. The comments are stored in a discussion format similar to how they appear in GMail.
- Once all comments have been addressed (either through code or discussion), the code review is marked closed.
Here are the parts I love about this: The reviewed code is a diff We don’t need to review an entire class every time. We just look at what’s changed from one revision to the next. So we can focus on the functionality for a specific bug or user story. This makes it easier to implement a policy like “User stories must be code reviewed before they can be marked complete.” At the beginning, this will be a pretty moot argument since diffs will include a lot of new classes. But even still, we can see *all* the changes that have been made for a particular story, including that “minor” change you made to the GetUsersTimeZone function in the global DateUtils class. Review comments are an ongoing discussion The comments are threaded in the interface and capture more of the discussion than our previous process, where one developer would add a //TODO and the rest of the discussion was done at a meeting. And speaking of the code review meeting… NO CODE REVIEW MEETING! This is by far, my favorite aspect of Rietveld. Once launched, code reviews are done asynchronously. Reviewers add comments whenever it is convenient (within a reasonable timeframe, of course). Other reviewers can chime in when they want. Once published, comments are immediately seen by other reviewers and can be commented upon further. For a disparate team like ours, this feature alone is pure 100 proof homemade moonshine. And finally: The code review isn’t done after the meeting In our previous process, the code review was all but forgotten after the meeting. There was no inherent follow-up process to make sure the changes had been made. Here, the code review remains active until it is explicitly closed. I.e. once all comments have been addressed to the team’s satisfaction. Now to implementation details. Rietveld is a Django app running on Google App Engine. There’s a publicly hosted implementation of it at http://codereview.appspot.com. There, you’ll find a truckload of OSS apps registered for it (and in fact, Rietveld is open source itself if you feel like looking at Python code written by the creator of Python). Nice thing about that is that you can see examples of code reviews in progress. If, like us, you are leery about publishing your reviews publicly, you have at least two options that I know of to host it yourself. One is to create an application for yourself on App Engine and host it. There are instructions to do that and I’m sure they work. We didn’t try them because the other option is much easier. If you have a Google Apps For Your Domain account, you can add it directly from the Google Apps Marketplace. One click and it’s added to your account and only you and your domain users have access to it. From what I’ve read, Rietveld works with Subversion, Mercurial, and Git repositories. From experience, they can be private repositories as well as public ones. It’s probably too early in the process to be extolling the virtues of our new code review process. At this stage, Rietveld is kind of a rebound girlfriend for us because of the pain we felt with the old process. But early indications are that it will be a big hit. If not, I suppose we could print out the code and fax comments back and forth… Kyle the Encoded
Wednesday, May 26, 2010
We’ve hired two new developers to our team, effectively tripling our development staff. So I’m celebrating the alleviation on my workload with some posts that are essentially going to be documentation for the junior developer. I hope she has a sense of humour when it comes to sample code as she reads this… I’ve long forgotten what the traditional method is for making RPC requests between the client and server in GWT applications. In a traditional web application, that’s what XMLHTTPRequest was for until jQuery et al came out and was nice enough to make it readable. For GWT applications, the calls are called RPC requests which I think is just a fancy name for “our proprietary JSON format” based on what I’m seeing in Firebug*. I have a vague recollection of having to register URLs for every single type of request you wanted to make to the server. In our case, we’re using gwt-platform which contains a built-in dispatcher for managing calls to the server, all filtered through a single URL. The implementation is based heavily on gwt-dispatch with some minor tweaks. (Side note: Not only are we using gwt-platform, we’ve hired the guy who built it. In his spare time, he builds 3D physics-based animation frameworks.) How it works is like so: - Client code gets an instance of a DispatchAsync object (almost always using dependency injection)
- Client executes a command on the dispatcher, registering a callback function
- Server receives the command, processes it with a handler, then returns a result
- Callback function executes using the result from the server
That’s all well and good and the description satisfies my “why use regular language when developer-speak will do?” gland. Now let’s try again a little less like I’m trying to compensate for something: - Even though they are in the same project, you still need to think of the application in three pieces: a client, a server, and objects that travel back and forth between them
- When you’re working in the client, you’ll need to communicate to the server to do one of two things: get data to display, or process data for storing.
- When you do need to communicate with the server, you instantiate an object called a command or action. This can be confusing at first because it’s an object that doesn’t really represent a “thing”. Rather it represents a verb (aka. an action). Examples: GetClientList, SaveOrder, FindSuitableMate. These actions contain all the data the server will need to process it. For example, FindSuitableMateFromFamilyTree may contain properties for the various search criteria, like HairColour and MinimumAge and NumberOfUndisputedSharedRelatives.
- This action instance is sent to the server through an object called a dispatcher (think: Danny Devito’s character on Taxi, but more pleasant). Because the call is made asynchronously, we need to tell the dispatcher what to do when the server responds. So we give it a function to call when it’s done. The function will take a parameter that is provided by the server. It is typically named the same as the action with Result appended to it. E.g. GetClientListResult, SaveOrderResult, FindSuitableMateResult.
- On the server, we have a handler for each action that we could potentially receive. A handler is an object that does all the heavy lifting for a particular action. For example, FindSuitableMateHandler would react to a FindSuitableMate action. It would examine the command and perform a search of the database based on the criteria provided in the FindSuitableMate object it received.
- After the handler executes, it creates an appropriate result object. In our above example, FindSuitableMateHandler would create a FindSuitableMateResult object and set one of its properties, e.g. SearchResults, to the list of found items from the database.
- The callback function we created in step 4 gets executed using the result object created by the server. Typically, this will do client-y type stuff like update a list of items on a page or display a message to the user.
That’s the crux of the RPC mechanism in GWT as I see it. I’ve skimmed over much of the details, like how handlers are registered. But this post is about two weeks overdue as it is. Kyle the Commanded * By “Firebug” I actually mean Chrome developer extensions but despite the fact that I’ve moved off Firefox, Firebug just sounds cooler.
Saturday, May 01, 2010
Before I get started, I should point out I may have a shaky definition of the term “multitenancy”. I haven’t exactly studied up on it but I’ve skimmed some blog posts and I hope it means what I think it means. In our Google Web Toolkit application, we’re going to have multiple clients but only a single instance of the app and only a single data store. Multiple instances of the application and the data store aren’t feasible as we are deploying to Google App Engine. That makes it sound like Google App Engine dictated the solution but it was more of an “icing on the cake” thing. Even if we were doing it in SQL, we were going to do it in one database. I think one reason was to make it easier to aggregate data but I hope there were more reasons because that one is kind of lame. After all, we could aggregate all the data we want into a separate database for reporting purposes. We’d like decent URLs as well, like http://wholesaleroadkill.com/store/clients/crittersnstuff and http://wholesaleroadkill.com/store/clients/macysHairSalon. This isn’t as easy as it sounds in GWT because the default behaviour is to load the app and manage all transitions as AJAX calls. If you need to bookmark something, it’s done with a hash tag. Some examples: - http://wholesaleroadkill.com/#clientList
- http://wholesaleroadkill.com/#inventory
- http://wholesaleroadkill.com/#category;mammal
I like to compare the type of applications you build in GWT to GMail. Once you hit the home page, you stay there. There isn’t really the concept of navigating to a new page, just refreshing sections of the existing page. Furthermore, GMail supports multi-tenancy to some degree. If you use Google Apps For Your Domain, your home email URL is http://mail.google.com/a/wholesaleroadkill.com. After that, navigation is done the traditional way (e.g. #sent, #drafts/123123123, #inbox, etc.) Problem is, as far as I know, GMail isn’t a GWT app. And even if it is, it’s not open source so how they configure it to be a multi-tenant application is for me to find out. I’m told that multi-tenancy is on the radar for GWT but the radar sounds pretty big so I’m not holding out hope. So I did find out *a* way to achieve what I want, through modifications to the web.xml config file. By default, the home page for your GWT application is <appName>.html. It’s a physical HTML file located in the war folder. It contains everything needed to load the JavaScript that powers the app. In ordered to set up the URLs, I modified web.xml to redirect requests to this file, like so: <web-app>
<!-- other stuff -->
<servlet>
<servlet-name>Multitenant</servlet-name>
<jsp-file>/myapp.jsp</jsp-file>
</servlet>
<servlet-mapping>
<servlet-name>Multitenant</servlet-name>
<url-pattern>/myapp/customer/*</url-pattern>
</servlet-mapping>
</web-app>
With this in place, I can navigate to /myapp/customer/freddystaxidermy and /myapp/customer/thelmasgunemporium and have it serve up myapp.jsp. MyApp.jsp is almost an exact duplicate of MyApp.htm with one change I’ll get to later.
Don’t let the use of session scare you. Done the right way (and they make it very hard *not* to do it the right way), it’s actually using the Google App Engine data store to manage the session. From what I can gather, it’s actually the recommended way of managing state in a Google App Engine application because of the speed and scalability of App Engine.
There are some considerations to this approach that I don’t know I’ve thought through completely. The big one is security. Every call needs to be qualified somehow to indicate which customer is making it. I.e. which URL launched the request. For the moment, I’m doing that by making a call in the main page that sets a session variable based on the URL. This kinda reeks of being very open to allowing someone smarter than I am to make a call with a fake URL and impersonating someone else. At the moment, that’s still a pending todo.
Back to the change I made to MyApp.jsp. We had a relative reference to our CSS stylesheet:
<link type="text/css"
rel="stylesheet"
href="myApp.css">
This doesn’t work because it’s now looking for myApp.css in the wrong place so I changed this line to:
<link type="text/css"
rel="stylesheet"
href="<%=request.getContextPath( )
%>/myApp.css">
Now it properly references myApp.css at the proper level of the application.
This brings up an interesting aspect to this set up. There are still certain things, like myApp.css, that are accessed globally. We are using gwt-platform, which uses a modified version of gwt-dispatch for server-communication. All requests are routed through http://wholesaleroadkill.com/myapp/dispatch regardless of where it originated. I’m sure it can be configured so that requests are routed through http://wholesaleroadkill.com/myapp/customer/freddystaxidermy/dispatch but that hasn’t crept high enough on our priority list yet.
Note: I’ve had this post ready to go for a few weeks now. Typing it out made it sound more hacky than it did when I figured it out in the first place. So consider this an appeal for alternatives or tweaks.
Kyle the Let
Wednesday, April 28, 2010
My marketing skillz aren’t the greatest. Which is probably why two of my three favorite sections of Brownfield Application Development in .NET are available free. The one that isn’t is chapter 1 which I like because it opens with a sample brownfield scenario. It was the place where Donald and I could be most creative. I’ve attached my own homegrown excerpt from that chapter but I’m not entirely sure what the official procedure is for doing that so grab it before I get in trouble. The second section I’m proud of wasn’t written by us. It’s the foreword by David Laribee. He was our first choice to write the foreword for a few reasons. First, he was there at the beginning providing encouragement to write the book in the first place. Second, his writing style jibes with ours. And third, we knew he’d make us look good. That third reason is why I’m proud of it because he came through in spades. The third part I like is pretty much all of chapter 6 which is freely available on the book’s site. It’s unassumingly titled “Defect management” which sounds about as exciting as a discussion on Russia’s economic policy. Well, it turns out Russia’s economic policy can be pretty entertaining when you look at it from the right angle. As we wrote and re-wrote the chapter, a funny thing happened. A theme emerged. I’d heard of themes before from, y’know, legitimate writers and stuff. It never occurred to me that I might actually create one, however inadvertently. We wrote the first draft of the chapter pretty much as you’d expect a chapter on defects to be written. Then we forgot about it until the next pass. By that time, we’d finished the first draft of the book and had a better sense of how the whole thing should flow. And chapter 6, coming as it did at the end of part 1, seemed like a logical place to try our hand at being inspirational. In some respects, a brownfield project is defined not only by its defects but by the team’s attitude toward them. A large backlog of defects sends a message to the team: These defects aren’t the most important thing in our workload. Maybe the team (and by team, I mean developers, QA, client, project managers, etc.) had reasons that seemed good at the time when the defects started piling up. New features needed to be pushed out. The defect was going to be addressed after rewriting the UI layer. It was due to a defective third-party web service. But at some point, a prioritization decision was made before the developer started a new task. And unless the developer started picking off defects, the decision was: something else is more important than every single defect in this list. There’s a psychological barrier to fixing defects. They’re boring. It’s code we’ve already written and don’t want to look at again. New features are more fun. And more profitable. There’s always the risk that we’ll submit the fix and it’ll come back again and that we’ll be saddled with this defect for the rest of our natural lives. Customers often learn to live with defects so who are they hurting really? I’m in the early stages of a start-up and to date, I’ve been the sole developer. In the next month, the development team will triple and today, we had a discussion on team culture. As a solo developer, it’s much easier to be lax when it comes to letting quality slide. After all, who else is looking at the code? Don’t we have *real* deadlines to meet? But such is the thinking that leads to brownfield applications and it is *not* something that we are willing to stand for in our greenfield project. We are placing a high priority on code quality. Not because we want to blog about how cool it is to write good code and how superior we feel because of it. But because we believe very strongly that high quality code will save us a boatload of money in the long run in ease of maintenance and flexibility. You can see how a discussion on defects can lead to one on culture and the priority you place on quality. I believe in the benefits of TDD and BDD and mocking and IoC containers and OR mappers and all the other things the kids are developing with these days. But what good is all that if, when defects are found, I ignore them? Worse yet, what does that practice tell the two new people coming in, one of whom is a junior developer? Seems a little too hippy-critical for my tastes. So while we started out talking defects in chapter 6, these kinds of issues started bubbling to the surface. What type of culture do you want to install in the team? How can you achieve and maintain a zero defect count? How do you place a high priority on quality while maintaining a decent pace on productivity? It’s been a while since I re-read the chapter so I hope the answers to some of these questions are in there. But the part I’m proud of most is the fact that we even asked the questions. Kyle the Morphed
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent
my employer's view in any way.
Copyright © 2010 Kyle Baley. All rights reserved.
|
|
|
CATEGORIES
|
|
LATEST POSTS
POPULAR POSTS
|
|
ARCHIVE
| July, 2010 (3) |
| June, 2010 (3) |
| May, 2010 (1) |
| April, 2010 (5) |
| March, 2010 (4) |
| February, 2010 (8) |
| January, 2010 (2) |
| December, 2009 (3) |
| November, 2009 (2) |
| September, 2009 (5) |
| August, 2009 (4) |
| July, 2009 (2) |
| June, 2009 (5) |
| May, 2009 (6) |
| April, 2009 (5) |
| March, 2009 (6) |
| February, 2009 (2) |
| January, 2009 (6) |
| December, 2008 (5) |
| November, 2008 (2) |
| October, 2008 (5) |
| September, 2008 (9) |
| August, 2008 (5) |
| July, 2008 (7) |
| June, 2008 (6) |
| May, 2008 (11) |
| April, 2008 (13) |
| March, 2008 (13) |
| February, 2008 (12) |
| January, 2008 (19) |
| December, 2007 (16) |
| November, 2007 (8) |
| October, 2007 (23) |
| September, 2007 (15) |
| August, 2007 (8) |
| July, 2007 (6) |
| June, 2007 (11) |
| May, 2007 (19) |
| April, 2007 (14) |
| March, 2007 (3) |
| February, 2007 (4) |
| January, 2007 (7) |
| December, 2006 (5) |
| November, 2006 (9) |
| October, 2006 (11) |
| September, 2006 (14) |
| August, 2006 (11) |
| July, 2006 (15) |
| June, 2006 (8) |
| May, 2006 (10) |
| April, 2006 (12) |
| March, 2006 (3) |
| February, 2006 (7) |
|
|
|