Tuesday, April 12, 2005

The bigger the phone, the better

I have just had the opportunity to play/work with two of the new Nokia smartphones: the 7710 and the Communicator 9500.

Now, first off, I would like to make it clear that these two phones are targeted to very different markets, but still share many communalities.

The 7710 is a 'cool' phone with stereo headphones, FM radio ( called Visual Radio), and is generally targeted as a general replacement for everything that ails you; the Communicator, is as many of you know, a all-in-one communication device for the business user who needs to email/surf the web and generally stay in touch around the world.

Friday, April 01, 2005

New job

Today is the 3rd day at my new job - and so far, it is a real break from my last gig (can a 2.5 year position be called a gig?) The code base is an order of magnitude cleaner than the last codebase, is driven by hibernate, spring, and struts and was done more or less correctly, with no massivly visible lacunae.

To all future managers of software groups - your team wants to make good code. Your team wants to make good code for a good reason, and it is not an abstract geeky reason, although the two are often correlated. Good code leads to fast development and fast learning.

At my last company, it took me several months to get up to speed with enough of the code base to make additions to the system. At my new company, it took me about 3 hours before I was coding and being productive. That is a lot of money the company saved by giving their coders time to fix broken code. Right now, my old company is desperately trying to train someone to even understand their system... good luck.

AOP and caching

I was recently developing a new AJAX based system for picking a drug from a long list of potentials (this sounds a lot more fun/dodgy/illegal than it really was). Specifically, there are two drop down lists - picking an element from the first select list makes the page look up the entry from the server.

In itself, this is rather unexciting, but i got a chance to code in what is IMHO a rather elegant manner:

I used JSON for the javascript-server client communication, and spring+hibernate for all server side interaction. Now, at this point, we are still talking a decent implementation. The part that I really liked was using AOP for method level caching.

Since the response time of the system needs to be as close to seamless as possible, I needed to make sure that round-trips to the database happened as infrequently as possible. There are, of course, two ways this can be accomplished: the first would be to code a caching mechanism at the service layer, and the second was to use AOP to put it in declaritavly after the rest of the code worked.

I looked around for a few minutes on the spring web site and found a couple of EHCache based method caches, stuck them into my working application, and presto: the time delay that had been due to database look-up was entirely removed. It actually seemed faster than normal javascript.

AOP, when used correctly, is a truly beautiful technology. On a side note, a friend and I did some performance testing of reflection in JDK 1.4 and 1.5. End result: don't worry about it, unless you are in a endless loop. I will post more about that later.

Wednesday, March 16, 2005

Spring is great; Spring sucks

In general, I have found Spring to be an invaluable framework for my development tasks. It provides fairly elegant APIs for most useful things, and does them well. Occasionally, however, you can get really stuck. I wasted several hours ( possibly days) trying to fix the following problem:
I upgraded to Tomcat 5.0.28 and somehow all my Spring applications stopped working. Specifically, i could not get a datasource to save my life, and this being a database driven web system, I was left in the proverbial mud (if that is indeed a proverb. I'm a software developper, not a sage).
It turns out that there is parameter ( little documented as far as I can tell) called "resourceRef". Now, I haven't really explored exactly what it does, but as far as I can tell, it simply appends "java:comp/env/" to the beginning of the jndi name. This in itself wouldn't have been such a big deal - and I would have probably thought to look there earlier, had the template file which I have been using ( copied from Spring, I believe) had the following text:


I have since removed that comment.
javax.naming.NameNotFoundException: Name jdbc is not bound in this Context
at org.apache.naming.NamingContext.lookup(NamingContext.java:768)
at org.apache.naming.NamingContext.lookup(NamingContext.java:151)
at org.apache.naming.SelectorContext.lookup(SelectorContext.java:136)
at javax.naming.InitialContext.lookup(InitialContext.java:347)
at org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.java:123)
at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:85)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:121)
at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:71)
at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:85)
at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:124)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1073)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:343)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:260)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:221)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:145)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:285)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:317)
at org.springframework.web.context.support.AbstractRefreshableWebApplicationContext.refresh(AbstractRefreshableWebApplicationContext.java:131)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:224)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:150)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:48)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3827)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4343)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:823)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:807)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:595)
at org.apache.catalina.core.StandardHostDeployer.addChild(StandardHostDeployer.java:903)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at org.apache.commons.beanutils.MethodUtils.invokeMethod(MethodUtils.java:216)
at org.apache.commons.digester.SetNextRule.end(SetNextRule.java:256)
at org.apache.commons.digester.Rule.end(Rule.java:276)
at org.apache.commons.digester.Digester.endElement(Digester.java:1058)
at org.apache.catalina.util.CatalinaDigester.endElement(CatalinaDigester.java:76)
at org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source)
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanEndElement(Unknown Source)
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
at org.apache.commons.digester.Digester.parse(Digester.java:1567)
at org.apache.catalina.core.StandardHostDeployer.install(StandardHostDeployer.java:488)
at org.apache.catalina.core.StandardHost.install(StandardHost.java:863)
at org.apache.catalina.startup.HostConfig.deployDescriptors(HostConfig.java:483)
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:427)
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:983)
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:349)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1091)
at org.apache.catalina.core.StandardHost.start(StandardHost.java:789)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1083)
at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:478)
at org.apache.catalina.core.StandardService.start(StandardService.java:480)
at org.apache.catalina.core.StandardServer.start(StandardServer.java:2313)
at org.apache.catalina.startup.Catalina.start(Catalina.java:556)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:287)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:425)

Wednesday, March 09, 2005

JSON and Spring

Not that this is necessarily the best place to post, but here it goes:

I can case you are trying to use Spring and JSON and happen to not be able to modify an existing servlet/jsp page to accomplish your goal, there is a work around:

public class NDCJSONSessionFilter extends OncePerRequestFilter {



protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {


HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;

// Find the JSONRPCBridge for this session or create one
// if it doesn't exist. Note the bridge must be named "JSONRPCBridge"
// in the HttpSession for the JSONRPCServlet to find it.
HttpSession session = req.getSession(true);
JSONRPCBridge json_bridge = null;
json_bridge = (JSONRPCBridge) session.getAttribute("JSONRPCBridge");

if(json_bridge == null) {
json_bridge = new JSONRPCBridge();
session.setAttribute("JSONRPCBridge", json_bridge);
}

WebApplicationContext context = WebApplicationContextUtils
.getRequiredWebApplicationContext(getServletContext());

IService service = (IService )context.getBean("service");

json_bridge.registerObject("Service", service);



}


}

-- this will allow you use JSON if you happen to be limited by something like by the use of velocity templates, etc.

This probably not useful to anyone else, but just in case. Oh, and does anyone know how I can put code in a blog??

Wednesday, March 02, 2005

Object-Relational mapping

You may have overheard the question before, whispered clandestinely between curious developpers. 'What is the point of using a object-relational tool - why not just use JDBC?' They ask. 'We know the database, and we can create the objects as we need them.' Let me posit some answers.

In order to address this question, we need to understand the full scope of a persistence layer.

1.) Transparent and/or low impact enabling of persistence on objects.
2.) Caching of objects
3.) Querying of objects
4.) Mapping of database to the objects and usually vice-versa

A lot of developers out there end up gradually evolving a home-grown and usual only partially functional persistence layer -- piece by piece, layer by layer.

At my current company, we have gone through 2 or 3 different persistence mechanisms over three years, with each one leaving a long trail of problems. This reflects general trends in software development over the past few years, although our codebase does seem to be a few years behind the industry-wide trends.

In the first stage, the code base was written using what is typically called a Table access mechanism, i.e., with a more or less 1-to-1 correspondence between the database structure and that of the data access objects. The relationships between tables are embodied in the SQL at the code level ( i.e. straight-forward JDBC for data retrieval and recording.)

The second generation (and, really the final generation, the third having never really received full traction in the company) comprises an evolutionary approach to persistence. This involves the following stages:

1.) Creating a set of classes similar to the classes above for loading/saving of individual classes
2.) Creating a single mechanism for loading of certain classes based on a unique ID structure (i.e. each class type has a corresponding ID type which is passed to the loader)
3.) Implementing caching for each loader.
4.) Getting the objects to manage their loading (e.g. have classes load database connections in the model)
5.) Creating a separate system for the saving of data.

Now, each of these evolutions came about as a new requirement came in, and the reason that a complete persistence system was never implemented was because it was never absolutely necessary to have a complete persistence domain at any one time. One can see, however, that in the end, the final result is a limited and rather poorly designed persistence mechanism. This, at least partially, illustrates why it is useful to think about the long-term picture before patching a limited system.

This addresses at least 3 of the properties listed above, but does not address the querying property. This came about later, when we were doing real-time analysis of data. Since we have a complex object model, including who created the data, who currently owned the data, etc -- altogether over 40 interconnected classes, ignoring sub-domain specific classes and data tables -- we often found ourselves performing complex queries across the datasets. The manner in which these queries were written down evolved over time, until they came to be written in a language that had a strange resemblance and HQL. Unfortunately, by the time this came about, we had written at least 3 different libraries for efficiently producing SQL queries, with the final version being only very slightly different from HQL.

The reasons that these changes were not foreseen (or perhaps just ignored) revolve a great deal around the politics of the company and its reluctance to use new technologies. However, at a deeper level it is a common mistake to evolve a design based on a small problem domain and not grasp the whole picture of where the code is heading. In our case, this had a great deal more to do with the quasi-burlesque view of our software as being a different from all other software projects, from the manner in which it was conceived, to its functionality, and finally to it capabilities, but these are topics for another day.

Since this is my first blog, I would appreciate comments ( if anyone happens to read this) with regards to the content: is this too little detail, is it stating the obvious, and does anyone really care? Did this help in the least bit to clarify why O/R layers are actually useful and not just some bizarre metadata-driven form of intellectual masturbation that allows architects to defend their constantly evolving realm?