...The architecture that actually predominates in practice has yet to be discussed: the BIG BALL OF MUD...A BIG BALL OF MUD is haphazardly structured, sprawling, sloppy, duct-tape and bailingwire, spaghetti code jungle...Why is this architecture so popular?...Can we avoid this? Should we? How can we make such systems better? | ||
-- Big Ball of Mud , by Brian Foote and Joseph Yoder |
Everyone knows the Big Ball of Mud. You've either written it (accidentally of course), you've had to read it, or you've had to refactor it. The Big Ball of Mud is disorganized, unwieldy code -- it blossomed in the late 1990's when the dotcom boom was in full force and there was great demand for fast application development, technology personnel was constantly in flux, and core technologies were changing regularly in order to accommodate issues of scalability.
Today the Big Ball of Mud behaves more like The Blob[1], growing larger as it consumes more application functionality, and threatening to swallow up good coding practices everywhere. Disorganized and poorly factored applications can be disastrous -- wasteful, expensive, and miserable to work with. Sometimes The Big Ball of Mud is so unwieldy it can't be worked with at all.
Fortunately, as web applications began to standardize on J2EE platforms (now known, and from here on referred to, as Java EE), design patterns for Java EE began to take shape (http://java.sun.com/blueprints/patterns). Frameworks such as Struts and Avalon gave more formal structure to web applications, and best practices began to emerge from, and inform, these frameworks. Spring's framework expanded on the concepts introduced in Struts and Avalon, adding its own twist in the form of its extremely lightweight Inversion of Control (IoC) container. Spring is different from other frameworks in that it can be used with virtually any Java component, including POJOs (Plain Old Java Objects), and can be leveraged in small pieces.
This chapter is about the Big Picture. We will explore the core concepts behind Spring and what it brings to Java EE Object Oriented methodologies. We'll also briefly summarize some of the other features of Spring that exist outside of the IoC container and illustrate how these features fit together, and tease apart.
A framework is essentially a skeleton, a structure around which the fleshing out of your application occurs. Frameworks tend to be built around a design pattern and consist of frozen spots – structural components which are unmodified by the developer -- and hot spots, the pieces that an application developer contributes [2]. In Spring, the hot spots are developer-contributed POJOs which are configured to run within the framework.
In this chapter we'll explore the context of Spring's most important component – the IoC container – and how Spring can lead you to better Object Oriented programming and eventually to new possibilities with Aspect Oriented Programming. The Spring framework can't make bad code good, but it can enforce good coding practices that make it easier for you and your fellow developers to cooperate in writing well-factored, reusable, readable, and manageable application components.
There is a lot of confusion about the definition of the IoC container – some equate it with a design pattern called Dependency Injection – but in reality IoC is much larger than dependency injection. The Spring IoC container enforces dependency injection in its various forms and employs a number of established design patterns to achieve this.
The main idea behind Inversion of Control as a concept is that component dependencies, lifecycle events, and configuration reside outside of the components themselves, and in the case of Spring, in the framework. This may sound a bit like giving up too much control, but in fact it can make your code more manageable, more testable, and more portable.
Before we discuss the Spring IoC container in detail, it's important to understand on the most basic level what the dependency injection pattern is and how it emerged in object oriented programming methodology. Spring's Inversion of Control framework is based on some best practice patterns – aspects of Spring IoC resemble and include the Factory and Observer patterns, and its most prominent feature is its implementation of a framework which enforces use of the dependency injection pattern.
The first reference to what would eventually become Dependency Injection appeared in 1994 in a paper by Robert C. Martin called “The Dependency Inversion Principle”.
In “The Dependency Inversion Principle” (or DIP), the author states the three defining factors of “bad code”:
According to Martin, interdependency causes these coding problems (we'll call them RFI for Rigidity, Fragility, and Immobility). To fix RFI issues in your OO code, DIP has two basic rules:
1. High level modules should not depend upon low level modules, both should depend upon abstractions.
In other words, high level modules – which contain your business logic and all of the important meat of your application – should not depend on lower level components. The reason for this is if these lower level components were to change, the changes might affect the higher level components as well. This is the defining concept behind dependency inversion, that the prevailing wisdom of having higher-level modules dependent on lower-level modules is in fact a bad idea.
2. Abstractions should not depend upon details, details should depend upon abstractions.
This is another way to say that before you begin coding to the abstraction – the interface or abstract class -- you should find the common behaviors in the code and work backwards. Your interface /abstraction should cater to the intersection between the needs of your business logic and the common behaviors of the lower level modules. You should also leave the details of how these behaviors are implemented to the implementation classes.
This simple example of a voting booth program shows a non-DIP compliant program.
package org.springbyexample.vote; public class VotingBooth { VoteRecorder voteRecorder = new VoteRecorder(); public void vote(Candidate candidate) { voteRecorder.record(candidate); } class VoteRecorder { Map hVotes = new HashMap(); public void record(Candidate candidate) { int count = 0; if (!hVotes.containsKey(candidate)){ hVotes.put(candidate, count); } else { count = hVotes.get(candidate); } count++; hVotes.put(candidate, count); } } }
In this example, the VotingBooth class is directly dependent on VoteRecorder, which has no abstractions and is the implementing class.
A dependency “inverted” version of this code might look a little different.
First, we would define our VoteRecorder
interface.
package org.springbyexample.vote; public interface VoteRecorder { public void record(Candidate candidate) ; }
And our implementing classes.
The LocalVoteRecorder
, which implements the VoteRecorder
interface:
package org.springbyexample.vote; public class LocalVoteRecorder implements VoteRecorder { Map hVotes = new HashMap(); public void record(Candidate candidate) { int count = 0; if (!hVotes.containsKey(candidate)){ hVotes.put(candidate, count); } else { count = hVotes.get(candidate); } count++; hVotes.put(candidate, count); } }
And the VotingBooth
class:
package org.springbyexample.vote; public class VotingBooth { VoteRecorder recorder = null; public void setVoteRecorder(VoteRecorder recorder) { this.recorder = recorder; } public void vote(Candidate candidate) { recorder.record(candidate); } }
Now the LocalVoteRecorder
class – the implementing class of the VoteRecorder
interface --
is completely decoupled from the VotingBooth
class.
We have removed all hard-coded references to lower level classes. According to the rules of DIP,
this is all we need to do in order to rid our code of RFI.
However, there is one problem with this implementation. We don’t have a main method. We definitely need one in order to run our application,
and somewhere in this main method we will need to instantiate the LocalVoteRecorder
.
By instantiating the LocalVoteRecorder
in our main
method, we would break Rule #1 of Dependency Inversion.
We have coded to the abstraction, we have integrated our changes, but our application would still have a dependency on a lower level class.
[1] A 1950's horror film featuring the big screen debut of Steve McQueen. The film stars an ever-growing, all-consuming amoeba-like monster that terrorizes a small Pennsylvania town.
[2] Pree, W. (1994) Meta Patterns – a means for capturing the essentials of reusable object-oriented design. Springer-Verglag, proceedings of the ECOOP, Bologna, Italy: 150-162
[3] The Dependency Inversion Principle, Martin, Robert C., .c 1994