Does Spring scale in large projects?

The short answer is that it does not, from a development point of view. Actually it kind of scales better the less you use it. That sounds like something that would come from someone who hates Spring, but I don’t. The last three iterations of my project I actually created a lot of Spring xml-files, while supporting other developers when they removed Spring xml-files from other places in the codebase.

So what’s the story here? Simple answer again: You should have a good reason for doing the stuff you do. This reminds my of a simple strategy I had when Spring was up and coming: Spring is good, we’ll use it any way we can. I suspect this strategy for being wide spread, but without any good reason, except lack of alternatives. The motivation often being the need for an IoC container to support testability and transaction management.

So what happens if you apply this simple strategy to a large project? Being in the retrospective mode I will try to sum up some of the anti-patterns that emerged while applying “the standard approach” with Spring in a large project.

One class, one bean definition

Let use start with a classic example. You have hibernate in your stack and your application needs functionality for managing users. You map your table in the database to a user-entity-class, add a repository and a service to have your layering in place, and your application have what it needs to manage the data. The applicationContext could look like this:

<bean id=”userRepository”>
<property name=”sessionFactory” ref=”sessionFactory”/>
</bean>

<bean id=”userService”>
<property name=”userRepository” ref=”userRepository”/>
</bean>

Does this approach scale? Let’s say the codebase contains thousands of classes. I’m sure that Spring gladly will parse those thousands of bean definitions, and proxy them all if there are any advices around. Given the cpu, memory and time, the applicationContext will scale from a technical point of view. But that’s not the point, from a developers point of view this is a nightmare, because loading the applicationContext takes a serious amount of time. If daily development involves the applicationContext you could quickly have your own version of the #1 Programmer excuse for legitimately slacking off:

“My code is loading the spring-appctx”

The cure

So how do you remove this pain? Simple answer: do it like you did before, with code. If you have a good reason for a bean to be spring managed, keep it in there for now. If you argue that you will lose benefits of spring by taking beans out of the container I would recommend that you evaluate the cost it has on your test-code-test development cycle. When in need to speed up a test-code-test cycle, or a build for that matter, reducing dependencies to the applicationContext is often low hanging fruit. The new code could look like this:

public class UserRepository {
private SessionFactory sessionFactory;
public UserRepository(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}

public class UserService {
private UserRepository userRepository;
public UserService() {
}

public UserService(SessionFactory sessionFactory) {
this.userRepository = new UserRepository(sessionFactory);
}
.. .
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}

This simple approach reduces the applicationContext to the following

<bean id=”userService”>
<constructor-arg ref=”sessionFactory”/>
</bean>

I have kept the default constructor and setter for UserRepository on UserService, to keep the class testable.

One module, one spring-configuration

One characteristic of a large project is that it often consists of several modules, where some modules are applications and others are reusable service-modules. Often there is no need for remoting so the service module is simply added as a dependency to the applications that needs it.

Again we start with a classic scenario and look at a standard approach to integrate the services into one of the applications:

app-1-applicationContext.xml:
<beans>
<import resource=”service-module-a-applicationContext.xml”/>

So what could go wrong here? From the example you can see that the application has a explicit dependency to service-module-a. But since we a working on a large project we could have the case where service-module-a also has a dependency to service-module-b, since some of the services in module-a uses stuff from module-b:

service-module-a-applicationContext.xml:
<beans>
<import resource=”service-module-b-applicationContext.xml”/>

If you add the complexity of having multiple development teams spread out on the different modules you will experience that nested imports in Spring is a recipe for disaster, it scales one level at most. There’s no better way to loose control with your application than being downstream of spring-configuration-imports. Small changes in a upstream spring-config have the power to short circuit downstream clients. If the change happens in a module that is transitive to your application the job of finding the problem could be time consuming and frustrating at best.

If you have ever experienced how a circular dependency between bean definitions in a upstream module have randomly knocked out beans in the downstream module, you know what I’m talking about.

The cure

Simple ansvar: Don’t go there. Don’t import spring-configuration from upstream modules, especially those that don’t belong to your bounded context. As a client of a service you’re not interested in the details of what it takes to build a service. Neither are you interested in services that your application don’t use, there is no need to be exposed by the configuration needs of those services. The important thing a client should provide is the actual dependencies that is required to instantiate the services they need, such as a datasource or a prepared sessionfactory.

This means that the only modules that are allowed to have spring-configuration are applications, which actually matches well with the term applicationContext.xml.

The new code could look like this:

app-1-applicationContext.xml:
<beans>
<import resource=”app-1-app-code.xml”/>
<import resource=”app-1-module-dependencies.xml”/>

app-1-module-dependencies.xml:
<beans>
<bean id=”userService”>
<constructor-arg ref=”sessionFactory”/>
</bean>

By putting application code and module-dependencies in separate configuration files you have a separation between the two that lets you easily write tests that mock out all the dependencies.

Basically you have control, as opposed to import a world of configuration files under active development, never know what to expect.

Transaction management at the class-level

One of the selling points of Spring is that you have declarative transaction management, the freedom to start a transaction anywhere you want. The pain you can experience from this is that transaction boundaries makes it hard to reuse a service, or declarativ transactions in the code are just wrong. For some reason it actually happens that propagation level ‘never’ is used on a service, I suspect not from the right reasons. But this is a true partykiller if you want to reuse the service in a transaction. Anyway, how often do you need this freedom?

The cure

Simple answer: Less freedom, and no freedom at all at the service-layer, the service should expect an active transaction. Give the responsibility to the application, and define simple rules for when a transaction begins, and when it commits or roll back. If you are developing an application like spring batch this matches perfectly, since an essential part of that framework is tuning how much you do in a transaction. In a webapp you could go with a dead simple approach: delegate the responsibility to a servlet-filter, which means that the developers in most cases don’t have to think about it at all. Same with a webservice application, go with the filter. If simple is enough, go with the simple stuff.

Summary

It might be “old school” to use xml for spring configuration, now that you have annotations and component scanning, but we startet out with xml and stuck with it. I think the problems will remain the same, whether you use annotations or XML.

So does spring scale? I asked @miss_haugen at javabin today, she replied that there is nothing wrong with the framework, it’s often how it’s used that causes the problem. Good answer.

If you have any comments feel free to drop a line, and I will try to answer and possibly give more context, which is an essential part of any experience with any tool or framework.

This entry was posted in Spring. Bookmark the permalink.

Comments are closed.