I, and I hope everyone else, would like to be able to write scalable Spring code. Whoa--am I implying that Spring is not scalable? Well, Spring applications scale fantastically well when we run them in a scalable environment (think various PaaS solutions). But it is sometimes difficult to scale Spring applications when we increase the complexity of their code; when we need to have two (three, four!) slightly different versions of our application.
Let's explore the different meaning of the term scalable. What would it take to make the entire application's life scalable? What'd be involved in scaling the complexity of the source code, then building and packaging it and then, finally, running it.
To create applications that are easy to change and grow, we need to carefully dissect them into sharply defined components; components that do only one thing and do it well. Spring greatly simplified this task, because it manages the components, their life cycles and wires in the dependencies between the components. This allowed us to decompose our application into small components. Scaling such an application is simply an exercise in adding more components.
Initially, Spring applications usually included every component; the latest version of Spring (3.1) allows you to specify component profiles that allow you to decide which components are going to be created depending on the environment in which the application runs.
But there is another approach. Instead of decomposing your application into components (objects), we could think about decomposing our application into functions. There is a subtle, but very important difference. The traditional OO approach treats the components as atoms (in the original Greek meaning) in our system; these components just so happen to have methods that we call. And here is where the problem could lie. We are usually interested in the methods, the components are then just bags of methods, especially when the components are singletons! Another approach is to start treating the methods as the atoms in our system and to realise that we don't need instances of the containers. We can't do this in Java, but we can do just that in Scala.
Let's compare the typical code in Java versus typical code in Scala using a small
UserService component. Figure 1 shows its typical structure in Java.
Figure 1. Java components
We see that the
Notifier are interfaces and we have two implementations for each interface. This is good design: the interface represents the sharply defined component; Spring manages the lifecycle of the dependencies and performs the dependency injection of instances. But it seems that we're not after the instances in our code, we're after the functionality contained within them. Figure 2 shows a Scala approach to this problem.
Figure 2. Scala components
That's subtly different. We still have the "interfaces", but now the implementations of the interface (trait in Scala speak) remain traits, which means that we cannot instantiate them, but we can mix-in their functionality into any class. That's what the cryptic
this: PasswordCodec with Notifier => line means--it tells Scala that instances of
UserService must be constructed
with the dependencies mixed-in at compile time. This makes the Scala
UserService inaccessible to Spring. Spring would attempt to simply call its constructor, but we cannot instantiate the
UserService, because it would be missing the required functionality!
What can we do now?
Suppose you want to take the Scala route in your code; you want to try out a new cool language and you want to take advantage of being able to compose functionality rather than composing instances of objects. Right now, you can use Spring's Java configuration (see Figure 3).
Figure 3. Spring Java config
This approach gives you the function composition in today's Spring... and with the improvements coming to JavaConfig in Spring 3.2, this is almost a perfect solution.
What is missing from the almost-perfect composition of functionality? It does not deal well with modular systems; systems whose functionality is in different packages and these packages can be assembled in different ways at runtime. The approach I showed in Figure 3 resolves the dependencies at compile-time. So, if the
SmsNotifier and the
EmailNotifier live in different modules and we want to decide at deploy-time which modules make up the application, the code will not work.
It would be nice if we (read Spring!) could somehow detect which implementations of the functionality are available and automatically inject them. In other words, if we could write something like code in Figure 4.
Figure 4. Trait scanning
Imagine that you now have an application that defines its components and the dependencies of these components are functional dependencies rather than instance dependencies. And that you have a framework that detects what implementations of the functional dependencies are present and constructs the application appropriately. This, I believe, would give you true scalability; it will allow you to manage versioning of your application's components and give you the ability to introduce new behaviour gradually.