This is the start of a series of blog posts on building Java-based apps following the 12 factor best practices. I have a number of reasons for wanting to write this blog series. First, I want to sharpen my knowledge of 12-factor app design, no matter how much experience you have there is always room for growth and this seems like a good place to invest my time. Second, it is one thing to learn best practices in terms of theory, it is another to learn those best practices in the context of a particular technology. It makes the concepts concrete and I find this very helpful in the learning process. For this reason, I’ve decided I want to look at how one might build a 12-factor app in Java. I realize that 12-factor apps involve more than a programming language (i.e. change management software, logging software, CI/CD tooling, etc…) but let’s start with Java as our baseline technology and go from there.
The first thing I did in preparation for writing this post was to go to the 12factor.net website to remind myself of what those 12 factors were. I highly recommend that you take a look at this website as it is a fantastic starting point for understanding this better. I then asked myself what is the goal of performing each of the 12 factors in terms of delivering and maintaining software and I came up with the following.
“The goal of following 12 factor best practices is to build software that makes it easy and fast to change; resources, functionality, and tooling through the delivery and support and maintenance of an application.”
As a part of this thought process, I concluded that each of the 12 factors falls into one of 3 main objectives for a solution that would help you build an app to achieve the previously stated goal. These 3 objectives are process interoperability, source code decoupling, and change management. By reaching each of these objectives you make your solution much more likely to support easy and quick changes in resources, functionality, and tools. Here is a diagram that brings these thoughts together on a single page. I also reserve the right to make changes to this initial analysis as I think this through more but I believe that this is a good starting point to build from.
An initial observation is that by achieving Source Code Decoupling in your software you can much more easily achieve Process Interoperability. Further, when you combine Process Interoperability with Change management then you have a solution that can easily and quickly handle change in terms of its resources, functionality, and tooling.
Source Code Decoupling
Each of the factors grouped in this objective in one way or another separates the source code from a dependency that would tightly couple that code to something else. For example, Config externalizes the application configuration in a way that is specific to the environment in which the application is intended to run. Another example is that processes within the solution should not maintain their own in-memory state, rather it should state should always be persisted. In both of these cases, we are externalizing something that could be handled internally by the process to an external party whether it be a 3rd party database or a configuration management solution. The same is true for the rest of these to some degree.
This objective of process interoperability is only possible if you have sufficiently decoupled your source code. In my opinion, process interoperability consists of first the ability to run different instances of the same or different processes concurrently such that one does not cause issues with the other. Second, a process is able to stop whether expected or unexpected in such a way that is quick and does not hinder another instance of the same or different process to be brought up. If these two things are true about your processes then they can interoperate without interfering with each other. There are huge advantages to this namely that your application can easily and robustly scale. There are also some big challenges with this kind of approach, one being managing data consistency across process instances.
This third objective, combined with Process interoperability really makes all of this come together. For each of the factors grouped in this objective, the goal is to put in place mechanisms that help you manage changes within the two key aspects of your build namely your Source Code and your Configuration. For example, a codebase helps coordinate updates made to source code across team members in such a way that one person’s work does not hinder or break another person’s work. It also provides a means for tracking changes through a deployment process to ensure that when you finally deploy a change into production you just know that nothing will break. If there wasn’t the tracking and other capabilities provided by a source code control management solution then this would be virtually impossible.
In the goal statement, there are a number of key concepts that should probably be addressed. By building a solution that follows 12 factor best practices you will be in a much better place to achieve a solution that is easy and fast to change in terms of the following.
A solutions resources can include many things but what immediately comes to mind is things like Database Technologies, Logging Software, Messaging Technology, etc…. These are called backing services in 12 factor lingo and a solution that can swap these out without having to change the code (with some change to config) is well on it’s way to adding value to an organization.
The functionality of a solution is primarily found in the logic implemented in the source code. A 12 factor solution is one where, as many dependencies as possible that could be found within the source code are externalized so that the only thing that really has to be considered when making a change to the logic is the source code itself. Abstracting away things like logs, ports, DB connections, credentials, etc… from the source code reduces the chances of breaking something when the source code itself is changed.
The tooling of a solution is key throughout the delivery, support and maintenance of a software solution. The biggest advantage I see here is tooling used for supporting and maintaining the software such monitoring tools for logging, performance, security, etc…. By following 12 factor best practices (i.e. externalizing config) it becomes much easier to change the tools you use to keep your solution running.
Quick and Easy Change
The whole point is that you can make changes to any of these 3 things in your solution quickly and without much effort. By having processes that are able to run concurrently and can be easily shut down and started up without impacting other processes you can do many different things that would be otherwise difficult. For example, A B testing where you slowly but reliably introduce a change into your software that is tested in one process first before rolling it out to all processes thus reducing the scope of the impact if the new functionality does not have the effect you are looking for. You are also able to handle increases in demand on your solution by starting more processes when traffic increases and spinning them down when it reduces, etc….
My plan for this series is to take a look at each of the 12 factors individually and try and understand how one might implement the best practice under consideration if you were writing a solution in Java. Each factor may not necessarily have to do with Java directly but, as I stated in the beginning I think this will give us an opportunity to be more concrete in how we look at things as we move forward. My hope is that I can improve my own knowledge of the best practices and maybe offer some value in helping others do the same.
If your company is looking to build software and you need help doing so, software development is our bread and butter. Please feel free to request a free quote to speak with us further about how we can help you successfully build the software that your company needs.