What an incredibly thorny problem. I have a dispatch class which dispatches complicated requests to our data store. However, the dispatch class does not know how to parse the request. It shouldn't have to. Unfortunately, the requests are complicated enough that persisting the request on the Web page is mandatory. This means that the dispatch class has to know what the parsed request looks like. But the form has the request, right? Why can't I just read in the form values, have the dispatch class cache them and then dispatch it? For various reasons, this is not much of an option (I'd have to sit down and walk you through the application and show you why this really doesn't work -- you'll have to trust me on this).
So I give this some thought. This is a RESTful application and the URLs are in the following form:
http://$domain/$path/$class_key/$method/$args
The args are essentially joined with slashes to make a clean URL. This means that a user can build a complicated request and bookmark it. The dispatch class doesn't know how to parse the reqeust but I could try to add extra args to put all form parameters in the URL before they're assembled by the request. Unfortunately that means they're being sent twice in the URL. I could attach them in a query string but they're still being sent twice. That defeats much of our purpose and complicates how the URL is being handled. So how can I get the dispatch class to pull apart the $args and know how to persist the complicated search form?
The data store accepts either a block of Perl code or a string. Depending upon which it accepts, it lexes the request and hands it off to a parser which builds an intermediate representation (IR) of the request which is then used to build the actual search request -- SQL in this case. I could have the data store accept a request that's already been lexed by the dispatch class and build the IR that way. Thus, the dispatch class would take the request, lex it, parse the lexed tokens and use that data to persist the search.
That would be bad. This violates separation of concerns. The dispatch class should know nothing about lexing or parsing. It merely dispatches requests to the data store and formats the results as XML. Then I remembered something I did months ago.
The data store used to contain all of the information for assembling the IR. However, this meant that the IR was tied to the data store for making database requests. Since the intention of the data store is to allow people to search anything, this means that LDAP searches can't use the IR already constructed. So I broke the IR out into a separate search class. This had huge benefits. I could build tests directly for the IR, it simplified the data store code and ensured that the IR could be reused if necessary.
Months later, I'm struggling to figure out the best way to persist a request sent from a REST application. I remember that the IR is now in its own class and all of the information I need is stored there. It's a bit odd, but now I'm setting it up so the store can, upon request, cache the IR. This allows the dispatch class when formatting the search results to query the IR and say "how did you get these results?" and easily persist the search information even though it still doesn't know how to parse it.
Out of all of the options available, this one was clean, neat, and reused components already put into place. Pulling the IR out of the data store months ago saved me a lot of work today and prevented some really bad design decisions being put into place.
--
Amusingly, once I thought of this strategy, I tried to think of the easiest way to implement it. My first run through, I toyed with the store code, figured out a way to do it and wrote up some quick code to handle it. It was really, really easy. Then I started writing the tests. The second I looked at my test class, I realized that the code I just wrote created a confusing API. I now have to back out that code and write the tests first to make sure that I really have an API that's easy to use.
I must say that working remotely forces one to be really disciplined. Before, if I was pair programming with someone or just having a programmer nearby to bounce questions off of, I could be a bit casual about my code and then fix things later as needed. Now, I have to focus very hard on coming up with good, solid designs that properly decompose problems into appropriate classes. I can't turn to another programmer months later and hope they remember what I did. It's pretty difficult, but I'm finding that I keep having to introduce new, complicated features but these features are easy to implement and test. The user interface is becoming rich, easy to use and intuitive. It's also very persistent despite the fact that we currently use no cookies.
The application also degrades very nicely. Can't handle XSLT? The server will do it for you. Is your javascript broken? The application gets a bit uglier, but it still works even though we're doing a huge amount of work on the client side.
Next up is going to be reimplementing some of the javascript as AJAX calls. This will allow us to have the Perl handle some of the heavy lifting and not require us to duplicate the logic in Javascript -- a problem that's plagued many an application.
-Dom