The MindTouch transition, which began in 2010, from an on-premise enterprise collaboration platform, to a product experience platform on an auto-scaling, SaaS infrastructure was completed with the launch of MindTouch 4. Not only is the MindTouch 4 user experience brand new, but the codebase that powers it bears little resemblance to the open source platform, MindTouch Core, that preceded it.
MindTouch moved to a rapid release schedule with the launch of our first SaaS product, MindTouch TCS. During a weekly ritual, application servers with bug fixes and optimizations are rotated into production. To our customers the rotation is completely transparent. The application servers carry zero state and session data resides in centralized locations accessible by any application server in the cluster.
If you think this sounds like high speed, agile software development – you’re right, it is!
Testing for Rapid Release
Testing becomes critical in a rapid release schedule. The luxury of months of testing, fixing, and re-testing for a product release do not exist. The user experience and all subsystems supporting it need to be tested every week. In addition to manual testing, we handle acceptance and full integration tests through a combination of browser automation tools and virtual machines (our approach is available on Slideshare). The MindTouch API, search engine, and other core services have considerable unit test coverage. Unit testing asserts the behavior of the smallest possible component of a software application, usually a function.
However, the foundation of the MindTouch frontend PHP application was still largely based on our open source offering, MindTouch Core. While functional tests could test the behavior of the entire framework, the behavior of an individual unit could not be tested. The inability to define and assert an individual unit’s behavior lowers confidence in the system.
The frontend relied considerably on singletons, statics, and global variables to access state and perform operations across the various concerns of the application.
The example above demonstrates how a user would log in to the system. The user class would be instantiated with a singleton. Inside the private user constructor, the session token would be fetched from the current web request, and given to the plug class (a web request builder and invocation library). Finally plug would request the user from the API via a localhost HTTP GET request.
The system works, its fast, and simple to follow. The problem lies with the fact that a test framework has zero access to all those decisions and function calls going on inside the singleton. How do I know the right behavior is occurring? As Miško Hevery wrote on Google Testing Blog, “Singletons are Pathological Liars”. In addition, the problems inherent with global state and testing is widely acknowledged.
Dependency Injection with MindTouch OpenContainer
MindTouch OpenContainer is a very simple PHP library for creating and auto configuring software components. Concrete classes are registered in OpenContainer by their interface, and their constructors in turn request the configured instances of other interfaces registered in OpenContainer (see the OpenContainer documentation on Github).
The inspiration to crack open the frontend’s singletons and expose the configuration of these instances came from the introduction of Autofac to our API services by MindTouch software architect, Arne Claasen. As the API grows and adds new features, the dependencies of these features are organized in the Autofac container taking a seriously complex burden off of the API engineers. Most importantly for testing, the dependencies can be mocked with fake instances that supply consistent testing data. OpenContainer, along with PHPUnit for unit testing and object mocking, brought these features to the frontend.
In this example, we create a user service. The user service is the gatekeeper for reading or writing user data. It is in the container, along with its dependencies, the HTTP plug library, a memcache library, and the current browser request.
Did I mention the frontend can also talk to Memcache for high speed access to cached data?
When the current user is requested, the user service gets the session token from the current web request, looks up the user in Memcache, and then falls back to the API in the event the data is not cached. Now we know exactly what components the user service is using to get the current user, because put them in the container, and the container gave them to it.
We can put any implementation of the interfaces in the container for the user service to rely on, and that means we can give it mock objects with pre-configured data or responses. Unit testing can finally be achieved!
Through OpenContainer, we can rapidly add frontend features, test the units that make up the feature, and leave the complex dependency management up to auto configuration. OpenContainer is available for your PHP projects on Github. If you don’t use OpenContainer or another Dependency Injection framework, please write your own!
For further reference, check out Google Testing Blog’s example of using Dependency Injection to avoid singletons.