If you are new to the world of software, it's understandable that you'll feel overwhelmed by the amount of information you need to get started. This is especially true if you are a developer new to microservices and distributed systems.
Similar to the article “How to Define the Architecture for Scalable Applications”, we will investigate the parts of the 12-Factor App methodology, particularly those related to the operational aspects. We hope, by the end of this, you will have some insights that can help you in the journey to a real scalable application.
Streams, Streams, and More Streams
Despite being a fundamental concept in distributed computing, producer-consumer is often overlooked, especially in the field of logging. If logs are stored locally with an application or in temporary storage, you're set up for disaster. What if your microservice suddenly vanishes without a trace of what went wrong? That's why logs should be streamed in real-time and logged as recommended by the 12-Factor approach.
Each microservice focuses on logging its information to its output stream. Log aggregator tools like Fluentd or Splunk would step in to capture the log information and make it available to various consumers. This way, you'll never miss an important error message, ensuring you always stay in the loop.
By transferring the responsibility of receiving and storing logs to aggregation tools, the system becomes more flexible and decoupled. In this way, you provide the consumers with the ability to get different types of data in a single place, such as aggregated data, alerts, and statistics. So, say goodbye to logging headaches and welcome to a more efficient and reliable system.
The basic idea behind the 7th factor of this method is simple: services should be self-contained and listen to requests on a specific port, eliminating the need for an external web server. For example, a PHP application consists of only a few PHP and configuration files, and its execution depends on the runtime, so it doesn’t follow this concept because it needs an external web server at runtime. To be self-contained, a PHP application would need an embedded web server, although that’s not the most common way of working with PHP, it is doable.
Other technologies deal with this factor much more quickly, like Go. Go programs build into a single binary run to export an HTTP port, complying with the "be self-contained" motto.
In general, services that follow this idea are portable and move between different environments without any code changes. In addition to portability, the "port binding" factor also ensures that services are scalable and fault tolerant. By using a self-contained approach, each service scales independently of other services, and any failures are isolated to that service. This is important in microservices architectures, where each service needs to handle a high volume of traffic while remaining resilient to failures.
Admin Processes as First-Class Citizens in the Software Development Lifecycle
Think about all the administrative tasks your software needs to perform such as database migrations, data cleanup procedures, timed scripts, etc. As a developer, you may want to write a shell script and schedule it with Crontab. But as your software grows and scales, this approach can become a nightmare.
What if you have multiple instances of the software with different versions in different locations? Running administrative tasks within your software leads to data corruption if there's a time discrepancy between instances in different availability zones. And if your admin process was developed using an older version of the software, you could end up with a major headache!
The 12-Factor App methodology recommends that you implement these administrative processes as One-Off processes that can be run as microservices. They are also part of the application code in the repository and follow similar build, release, and run processes, thus ensuring they will run in an identical environment as the regular long-running processes of the application.
And when it comes to scheduled code execution, the rule of thumb is never to let it call the shots. Instead, expose an endpoint that allows you to initiate the execution. This way, you're always in control of the performance and timing of your administrative tasks.
When building a web application, it is common to rely on various services and systems such as databases, message queues, caching layers, and others. These services are collectively referred to as Backing services. In the traditional approach, applications tightly couple themselves to specific Backing services, making it challenging to switch between services or even upgrade to a newer version.
The 12-Factor App methodology takes a different approach, treating Backing services as attached resources. This means that applications should be designed to work in a way that makes it possible to switch between services or versions without affecting the application's code. To achieve this, applications should interact with Backing services through a well-defined interface, usually over a network, and the Backing service's connection information should be passed in as environment variables.
The benefits of this approach include improved flexibility and portability, making it easy to switch between different Backing services or use different services in different environments. It also enables you to scale the application independently of the Backing services, making it possible to add more instances of an application without worrying about increasing the load on the Backing services.
According to the 12-Factor App methodology, the operations for scalable applications will greatly increase their chance for success, when the folwing key aspects are observed:
- Streams are a great way to do logging
- Don’t use domain names or IPs for identification; use ports instead
- Your services must be self-contained and listen to requests without external web servers
- Administrative processes must be part of the Software Development Lifecycle (SDLC)
- Avoid letting the application itself decide when to run administrative code
- Treating Backing services as attachable components provides flexibility and scalability to the application
The 12-Factor App methodology is a great tool for any microservices developer. It helps you find the right solution to the problem you’re facing, avoid common mistakes, and use it as a guide when you are creating microservices. We hope you enjoyed this article. If you want to learn more about microservices, how to implement them, and how to avoid common mistakes, read our other blog posts.
- The Twelve-Factor App
- An illustrated guide to 12 Factor Apps | Enable Architect (redhat.com)
- 12 Factor App Principles and Cloud-Native Microservices
This article was written by João Pedro São Gregório Silva, Software Engineer at Encora. Thanks to Guilherme Carrenho, Daniel Franco, and João Augusto Caleffi for reviews and insights.
Fast-growing tech companies partner with Encora to outsource product development and drive growth. Contact us to learn more about our software engineering capabilities.