Tech and Media Labs
This site uses cookies to improve the user experience.




Butterfly Container Script Type Safety

Jakob Jenkov
Last update: 2016-05-20

It is often claimed that dynamic reflection based dependency injection (DI), like Spring's XML-configurations or Butterfly Container Script, are not type safe, and therefore bad, error prone etc. Too often these claims are not backed up by any concrete cases where people have suffered from this, or even examples of common errors resulting from the lack of compile time checking in Spring's XML configurations or Butterfly Container Script. This text will dive into this discussion. Hopefully, at the end of this text you will be able to decide for yourself if the relaxed typed checking of Butterfly Container (or Spring for that matter) is a problem in your concrete case.


Static Types vs. Type Safety

Java is a statically typed language meaning that types (primtives and classes) cannot change at runtime. Because they cannot change at runtime the compiler can do a lot of static checking of types. For instance, the compiler utilizes knowledge of the static types to check if a parameter passed to a method is of the required type.

Static types primarily help developers with:

  1. Correctness - Limits error possibilities.
  2. Documentation - Shows other developers what types a given piece of code expects.
  3. Tool Assists - Features like code completion, refactoring etc. can be more powerful with static types.

When it is said that Springs XML format and Butterfly Containers script language are not type safe, what is typically meant is that the advantage of static types is lost, because the Java compiler cannot check the Spring XML files, nor the Butterfly Container Scripts.

Personally I think it is an overstatement to claim that Spring and Butterfly sacrifices type safety. It is not like you can all of a sudden let your Java class members, method parameters and variables be untyped. You still have to specify type. These types are still helpful for documentation purposes. Your IDE can also still help you with code completion, refactorings, JavaDoc etc. It is true that the Java compiler cannot check the XML and script files at compile time. It may also be true that your IDE cannot always refactor into the XML or script files (IntelliJ IDEA can to some extend). This makes it easier to make mistakes in the wiring of components, true. But both Spring and Butterfly Container will check the wirings eagerly at startup, minimizing the problem. The rest of the text will examine this closer.


Assembly vs. Execution Type Safety

Dependency injection is primarily used when assembling components, and usually only the major components of an application. Once wired together these components starts executing. When executing the components call methods on each other passing in parameter values and receiving return values. The vast majority of this execution code is injection free meaning type safety is kept in that part of the application. In my experience the execution parts of an application are usually a lot larger than the assembly parts, meaning the majority of most applications are perfectly type safe even when using DI for assembly. I'll even argue that you can unit test your way out of the lack of assemply type safety. I'll address that later in this text.

Here is an example of assembly and execution parts of an application:

You may use DI to wire a DAO to a business object (BO) and suffer a bit on assembly type safety. But, once the BO starts calling the DAO at runtime no DI is involved. Below is sketched an example of this:

BO.setDao(dao) //done using DI

BO.checkPersonIsValid(String personId){
  Person person = this.dao.readPerson(personId);

  //check if user is valid
  return true/false;
}

DAO.readPerson(String personId){
  Person person = null;
  ResultSet result = ...
  if(result.next()){
    person = new Person();
    person.setXXX(...);
    ...
  }

  return person;
}

In this example DI is used to inject a DAO instance into the BO instance. This is done at the beginning, by the method call BO.setDao(dao). But, notice how no DI is used inside the BO.checkPersonIsValid() or the DAO.readPerson() methods. It is plain, type safe Java.

You may argue that you could use the DI container to create the Person object and that this would break type saftey. However, I dont' see the benefit of doing so. The Person object is a simple data object (DO). I most likely never have to change implementation of the Person class. In addition most DO's are configured from data read in some system (database, legacy system etc.), so configuration of DO's is usually the responsibility of the DAO's, not the DI container.


Compile Time vs. Runtime Type Checking

Much of the cause for debate is the fact that script files (or Spring XML files) are not type checked at compile time, meaning that it is possible to specify an injection of a type B into A, when A really depends on C, not B. In Java this will result in a class cast exception at runtime (or some similar type error exception).

Type errors like these are a problem if they do not occur until the application has been running for hours. If they occur that late it can be hard to track down just exactly what caused the type error, and hard to bring the application into that state again to reproduce the error. Therefore, like with any other type of error, the earlier in the development process type errors can be caught, the better.


Application Startup Time Type Checking

With Butterfly Container (and Spring) the configuration of the container is typically read at application startup time. At this time the types are checked to verify that the specified injections are valid. This means that the application will fail early if an injection is invalid. So, basically the type checking has been deferred a bit, from compile time to application startup time. While this is theoretically a cause of annoyance to have to wait until startup to see if an injection is invalid or not, in my experience these type errors happen so rarely in practice that it isn't a big issue.


Build Time Type Checking

If you write unit tests to assure that your dependency injection container configuration is correct (and you do, right?), you are able to catch type errors already at build time.


Development Time Type Checking

To me (and many other developers) running the applications unit tests has become an integrated part of the development process. I write a bit of code, add a unit test for it (or write the test first, then code), and then I run the unit tests to check if the code still behaves as expected. If some of your unit tests checks the dependency injection containers configuration, type errors will be caught already here, in the development phase.


Summary

The DI configuration type safety debate mostly revolves around the fact that script and XML configurations do not provide compile time type checking. As I said earlier, the fact that you choose to use Spring XML or Butterfly Container Script to wire up your components, doesn't all of a sudden ruin tool support. Nor does it make your classes any harder to read. All types are still statically defined and referenced in your code.

DI is mostly used for component assembly. In my experience the assembly part of an application is usually only a fraction of the total application. The execution part is much larger. Therefore, type safety is usually only an issue in a fraction of the application.

Even if dependency injection containers like Butterfly Container and Spring may not provide compile time type checking, it does not mean that they do not provide type checking at all. Most often they will provide at least application startup time type checking. Provided you write unit tests to check the containers configuration you can pull the type checking forward to build time, and even development time. The type checking times are summarized below.

Compile Time
Unit Test Time - Development
Unit Test Time - Build
Application Startup Time

Jakob Jenkov




Copyright  Jenkov Aps
Close TOC