Wednesday, December 24, 2014

Why did we need this to be a framework?

While this post will be about Spring, the java framework, I highly urge you to read this as more than a criticism of Spring. In fact, I think Spring+Jpa+rdbms is currently an unrivaled web development stack.

But I want to question what spring does, or at least used to, do for developers. And ultimately I want to question why developers won't do these things for themselves.

Dependency Injection

For the time being, lets put aside @Autowired. We're often told that Dependency Injection is a good thing, and the prevalence of Spring seems to support that. We're also often told that Dependency Injection is a pattern and not a framework, and Spring seems to fly right in the face of that.

Lets take a look at Spring's claim of decoupling, with regards to its xml-based dependency injection.

<bean id="mySpringComponent" 
    p:injected-ref="myOtherSpringComponent" />

<bean id="myOtherSpringComponent"
    p:injectedNumber="10" />

Of course this code is decoupled from Spring! I hope I'm not shocking anyone when I present the following code:

AlsoHowDoIUseThisOutsideOfSpring myOtherSpringComponent
    = new HowDoIUseThisOutsideOfSpring();

HowDoIUseOutsideOfSpring mySpringComponent = new HowDoIUseOutsideOfSpring();

Give some more realistic classnames here, and our example would be both clearer than the xml, and terser than the xml, and have something called "type safety" that's usually a big hit among java devs. Did I mention no startup performance where its doing xml parsing/analysis plus reflection?

Clearly, with this many drawbacks to the xml approach, it never would've gotten off of the drawing board. So lets look at some other reasons why the spring does things for you that you couldn't have done more easily yourself. For instance, the point of DI is to not call new on our services. Spring solved that for us, right?

Its a Design Pattern, Jim, But Not As We Know It

The only real important part of Dependency Injection is that the code which wires your dependencies is not your business logic. If the code that pulls your apps weight around reaches into global state, calls a static method, or uses a new, then you're not going to be able two swap out or reuse bitesized chunks of business logic without also rewriting and/or complicating your business logic.

So spring lets us have a main method that looks like:

ApplicationContext ctx =
    new SpringXmlApplicationContextFromClasspath("/blah/blah.xml");

This main method can be changed without complicating your business logic, and the xml config that can be changed without complicating your business logic. Seems pretty good. Otherwise we have a main method that looks like:

AlsoHowDoIUseThisOutsideOfSpring myOtherSpringComponent
    = new HowDoIUseThisOutsideOfSpring();

HowDoIUseOutsideOfSpring mySpringComponent = new HowDoIUseOutsideOfSpring();

Oh no! This is the exact same code example we had before! This seems to suggest our verbose xml solution is even clunkier than our verbose xml led us to believe.

I will note that we now have established an advantage of Spring xml config, which is that the xml can be changed by a user without compiling a fork of Main and reassembling your jar. Which is nice. Why would I try to take that from everyone?

Type Safety: A Love Story

Type safety is a spectrum. We've all done things with Objects than can't be done to any old object, suppressed casting warnings, and held perfectly typeable data in string form because we were in a situation where we simply weren't worried about it, for better or worse. Meanwhile, languages keep coming out that improve our type systems, with things like null-safe type systems, memory-safe type systems, and automatic extraction of impurity out of functions (though I can't seem to find the language which pioneered that one).

We don't need to abandon typesafety wholesale to allow truly dynamic configuration.

Class <?> clazz = Class.forName(properties.get("LoggerFactory"));
Constructor<?> constructor = clazz.getConstructor(Properties.class);
LoggerFactory loggerFactory = (LoggerFactory) constructor.newInstance(properties);

Now you can create any main class you want, to read any properties file you want, to dynamically construct any factories you want. Now you can simply include an implementation of LoggerFactory on the classpath and it can enjoy the benefits of a typesafety in the way it constructs the Logger, even though it can do so in absolutely any way it wants.

Alternatively, if you are lower on the I-care-about-typesafety spectrum and want to save yourself the classpath hassle and/or the compile step, you can implement an XMLLoggerFactory which uses the properties or the classpath to find an xml file that would probably look more or less like the spring files I was ripping on earlier. Nevertheless, you get to mix typesafe config with dynamic config.

And as for aspects such as transactions and discovering controller routes, I believe the approach all along should have been object-graph transformers, which traverse every object property recursively and replace them with wrapped instances implementing the new behaviours.

aspects.applyTo(mySpringComponent, myOtherSpringComonent);

In short, we don't need no stinking framework. With a little bit of diligence and some isolated libraries, we can do what spring does, in an arguably superior way. This qualifies dependency injection and aspect-oriented programming as design patterns. So why do we need a framework for us to adopt them?

Passing in Straw-Man Arguments

I hope I can more or less invalidate my own point here by pointing out that modern Spring has so many features which my approach wholly fails to address, such as autowiring private properties, circular constructor references, enforcing more-or-less declarative configs. But without these things, the original Spring probably would've been off as just a thin library with a standardized xml structure for loading factories by classname.

Spring's newest annotation-based config is leaps and bounds better than the XML solutions and the one that I offered as well. As are many other dependency injection frameworks out there: Guice and Dagger and others. All of these frameworks are about simplifying the code we write in the implementation of these design patterns, and should do little else.

Challenge Yourself as a Programmer

After all this, I still wonder why the early days of Spring ever inspired anyone. Or, more accurately, why so few people were moved by the design pattern and it took a framework which does mostly nothing in order to convince the masses.

Ultimately, I would say its that we don't challenge ourselves enough as programmers. Our threshold for appreciating new ideas and others' code is so low that we require completely new implementations of simple ideas in order to accept their power.

Challenge yourself to improve all of the simple things first. Sometimes good code can look like boilerplate code at first. Challenge yourself to discover and explore why others' suggestions have more merit than you might've first thought.

Not all advancements in programming are a crazy new framework, build tool, library, algorithm, or language. Challenge yourself to move forward in the simple ways too.

No comments:

Post a Comment