Yegor Bugayenko wrote an amusing blog the other day entitled “SOLID is OOP for Dummies.” Well, if SOLID is OOP for dummies, I wonder if he’d agree with my assertion that the 12-factor app mantra is the dummies equivalent for cloud-native development?
I enjoyed Bugayenko’s article, although it seems like he took quite a bit of flack in the comments for it. But I completely agree with his premise. To me, telling software developers that their apps should follow SOLID principles is like telling a marathon runner that the best strategy for moving forward is to cyclically move one leg in front of the other. Sure, the statement is true, but does something so completely self-evident in its nature actually count as advice?
Revisiting the SOLID principles
Quite quickly, the SOLID principles are as follows:
· Have single responsibilities (S)
· Be object-oriented (O)
· Use polymorphism, or the Liskov Substitution Principle (L)
· Leverage interfaces (I)
· Abstract your code using the dependency inversion principle (D)
So I will see Bugayenko’s criticism of the five tenets of SOLID and raise him a similar criticism of the cloud-native world’s 12-factor app. (By the way, if you’re not familiar with all of the latest catch-phrases, Ken Owens provides a great definition of cloud-native in the article Tying Agile, DevOps, 12-factor apps and cloud native computing together)
Revisiting the 12-Factor App
For the uninitiated, these are the dozen tenets of cloud-native computing’s 12-factor App:
1. Codebase: One codebase tracked in revision control, many deploys
2. Dependencies: Explicitly declare and isolate dependencies
3. Config: Store config in the environment
4. Backing services: Treat backing services as attached resources
5. Build, release, run: Strictly separate build and run stages
6. Processes: Execute the app as one or more stateless processes
7. Port binding: Export services via port binding
8. Concurrency: Scale out via the process model
9. Disposability: Maximize robustness with fast startup and graceful shutdown
10. Dev/prod parity: Keep development, staging, and production as similar as possible
11. Logs: Treat logs as event streams
12. Admin processes: Run admin/management tasks as one-off processes
Seriously, do we really need to tell software developers to keep production, dev and staging environments as similar as possible, as app factor ten, dev/prod parity, instructs? Honestly, I can’t ever remember working on a project where the team said ‘hey, let’s make DEV and PROD completely different. Like, let’s use MongoDB in DEV, and DB2 in production.’
App factor one, using one codebase tracked in revision control, hardly seems like a revolutionary concept either, nor does it seem like a principle that any rational, cloud-native software development team would violate. Maybe if they were using MSD, the Masochistic Software Development methodology, they might spread their code across GIT, CVS, Clearcase and PVCS, but I can’t see anyone who wasn’t a sadist doing so.
Factor nine is outright comical. Has anyone ever actually sat down and written out a user-story or non-functional requirement describing how they wanted it to take a long time for the application to load, and they wanted general havoc to ensue when an application gets shut down? The 12-factor app’s factor nine, the disposability principle of maximizing robustness with fast startups and graceful shutdowns would imply that some software development teams weren’t aware that extended start-up times and hanging threads at shutdown were a bad thing.
Breaking the unbreakable
Quite honestly, there are tenets that I don’t even know how to violate. App factor four says backing services should be treated as attached resources. I don’t even know how I would write a cloud-native app that didn’t treat backing services as an attached resource. Isn’t that statement tautological in that just by definition of the fact that it’s a backend resource that it isn’t attached to your cloud-native application?
Maybe it’s because I’ve been developing on Spring and the Java EE platform for the last twenty years that some of these points seem rather superfluous. App factor three instructs cloud-native developers to store configuration details in the environment and not as a set of constants or if-then-else statements peppered into a variety of different classes throughout the code base. I honestly can’t see an experienced professional adding Java code that brackets every class with conditional statements that change the runtime behavior based on which environment is currently hosting the code. Furthermore, storing configuration outside of the application and abstracting away dependencies has always been a basic principle of Spring and Java EE. It’s exactly why resource references and JNDI bindings exist.
And if it’s not Spring or Java EE enforcing these best practices, it’s the application server itself doing so. App factor eleven states that logs should be captured by the execution environment and collated together with all of the other logging streams used by the cloud-native app. I honestly can’t remember a time when WebSphere didn’t do that. Maybe IBM can swallow up all of the little players in the cloud-native computing industry, create a new, cloud-native application server and show the industry how logging is done? That type of functionality has always been baked right into the application server runtime, even if you’re just using System.out calls instead of a proper logging framework like slf4j.
The 12-factor app does provide some food for thought, with most of what’s intellectually edible coming from the insistence that using a process, as opposed to threading, is the best way to scale. Although I would assert that this is really just a semantic argument, as opposed to one grounded in practice. While it’s true that traditional Java application servers ran one process with many threads, Java microservices tend to be single processed, and even within a microservice, there will be multiple functions that leverage threading, so it’s not really an either-or type of thing. If anything, factors six and eight, executing apps as processes and scaling out using the process model, really comes down to the assertion that monoliths are bad, and breaking up a monolith into smaller pieces is good. I think we’ve all heard that mantra being chanted enough lately. Yes, we get it.
Facebook shares and Internet memes
Each maxim of the 12-factor app credo reads to me like something that you’d put on one of those annoying, inspirational posters you find hanging in offices across the country. I can almost visualize a poster of an Italian sports car with the word DISPOSABILITY at the top, and the phrase “Maximize robustness with fast startup and graceful shutdowns” at the bottom. We do live in an age where in order to be consumed, every message must be delivered as a meme, or as something that can be shared as a Facebook post or delivered in a 140 character tweet. Maybe it’s the new developer, the social-media millennial, who the 12-factor app mantra is catering to?
Telling me to eat less and exercise more in order to lose weight is pretty useless advice. Telling me to pay my taxes on time and avoid interest and penalties is useless advice too. I mean, the statements are true, but it’s nothing I don’t already know, which makes stating these tautological statements a waste of time. You can frame these statements as advice, but they really aren’t advice if they provide nothing new, nothing of added value and nothing that’s particularly actionable. I can’t help but feel as though the entire discussion about building a 12-factor app falls into the same category. I wonder if Yegor Bugayenko would agree with me?
Here’s Bugayenko’s article: SOLID Is OOP for Dummies
Interested in more opinion pieces? Check out these:
- Why the Amazon S3 outage was a Fukushima moment for cloud computing
- Software ethics and why ‘Uber developer’ stains a professional resume
- It was more than user input error that caused the Amazon S3 outage
- Don’t let fear-mongering drive your adoption of Docker and microservices?
- Stop adding web UI frameworks like JSR-371 to the Java EE spec