In this article, I will discuss the fundamentals of an emerging trend in application development: developing applications as microservices. I'll compare traditional app deployment to the new microservice model. and focus on the twelve factors to consider when creating microservices. In 2012, Heroku developers provided the crucial 12 parameters to be considered best practices while deploying applications to the web. You can reference them here.
A long time ago, say you had a web application that had to be deployed so that users could use it. You must first plan the development of your app, and then, after the coding part is completed, you must consider where you will deploy it. Traditional deployment requires a Windows or Linux server to deploy your program, as well as network equipment such as switches, routers, or firewalls to configure your environment.
When you've completed all of that, say your application is deployed on the server you purchased, and your application and that server are married for the rest of their lives. You will need to temporarily shut down your server if you need to upgrade software patches or restart it.
That is not ideal in today's society, as requirements are continuously changing. So, the development world has shifted to more agile methodologies, and DevOps approaches are becoming popular for operations. By writing your source code as microservices, you can easily manage and maintain your application, and changing infrastructure at any time is simple because our applications are deployed as reproducible containers.
The Twelve Factors
I discussed delivering applications as containers in the previous part; when designing microservices, there are many factors to consider to have a consistent code base that can be reused in different environments such as development, staging, and production. In this section, I will elaborate on and give information on each of the twelve factors:
First of all, you must have a centralized code base, such as (Github, or GitLab) for all of your developers to collaborate on without any conflict. Furthermore, it is a great practice to have one repository for each of your microservices. This allows you to readily distinguish and investigate bugs if they exist. So you'll have one code repository and deploy it to numerous environments like development, staging, and production.
You must adhere to two primary objectives in this section. The first is dependency declaration, and the second is isolation. This means that you must declare your application's code dependencies using dependencies manifest files like requirements.txt in Python and package.json in NodeJS. Because new developers can easily clone your source repo and then all they need is any particular programming language and package manager installed in their system, it is much easier for them to start and write code into current projects. The second crucial thing to remember is that every time you test run your code while developing, you must do so in virtually isolated environments on your workstation, because this way, you can easily know whether your code will function or not in any other system. You should never rely on system-wide package dependencies, and the recommended practice is to always use dependency declaration and isolation combined because each one is insufficient to satisfy the twelve-factor.
An application's configuration file contains everything that will differ between different environments, such as development, staging, and production, due to:
Different external services, such as databases or cache services, will be deployed in your development and production environments.
You will have unique domain names for each environment.
You will have different credentials for your databases or user access in different environments.
It is recommended that you do not hard-code your configuration in your code base and import it at run time as environment variables; instead, in Kubernetes, you must utilize multiple config maps or secrets files for the deployment of your different environments.
A backing service is any service that the app uses as part of its normal operation over the network, such as databases (such as MySQL or MongoDB), messaging/queueing systems (such as RabbitMQ), SMTP services (such as Postfix), and caching systems (such as Memcached).
These services may or may not be maintained by the same system administrators, and even if they are not administered by the same organization, the app should not distinguish between different resource environments since it should treat these services as attached resources. Resources can be added and removed from deployments at any time. For example, if the app's database is acting up due to a hardware problem, the app's administrator may start a fresh database server from a recent backup. The current production database could be disconnected and the new database attached without requiring any code changes.
Build, release, and run
Three stages are involved in converting a codebase into a (non-development) deployment:
The build stage is a transform that translates a code repository into a build, which is an executable bundle. The build stage fetches dependencies and generates binaries and assets using a version of the code at a commit provided at the build time.
The release stage combines the build stage's output with the deployment's current configuration. The generated release includes both the build and the configuration and is ready for use in the execution environment right away.
The run stage (sometimes referred to as "runtime") executes the program in the execution environment by starting an app's process. It can be Docker containers or Kubernetes deployments.
The twelve-factor app strictly separates the build, release, and run stages. Changes to the code, for example, are impossible to make at runtime since there is no method to propagate those changes back to the build stage. If you wish to make changes to your code, you must first go through the development process, then build your newly committed code and export a release. The runtime environment will then be changed to the appropriate release version; if there is an issue, we can easily roll back to the prior release version, which we know works great.
The app runs as one or more processes in the execution environment. Whether the developer runs the code on his or her local laptop in a virtually isolated environment, or in an execution environment with one or more processes, the code must behave in the same manner. Twelve-factor processes are stateless and share nothing. Any data that needs to be persistent must be kept in a stateful backing service, which is typically a database. The twelve-factor app never assumes that anything cached in memory or on disk will be available for a future request or job.
When any computer program is executed, it is represented by one or more processes. Web apps have adopted several shapes for process execution. PHP processes, for example, run as Apache's child processes. In such cases, the executing process(es) are only slightly visible to the app's creators. Processes in the twelve-factor app take major inspiration from the Unix process paradigm. By allocating each sort of work to a process type, the developer may structure their software to accommodate a wide range of workloads. HTTP requests, for example, may be handled by a web process, whereas long-running background operations may be handled by a worker process. When it comes to scaling out, the process model truly shines. The horizontally partitionable, share-nothing characteristic of twelve-factor app processes means that adding more concurrency is a simple and reliable operation.
Web applications are sometimes run within a webserver container. PHP applications, for example, may run as a module within Apache HTTPD, whereas Java applications may run under Tomcat.
The twelve-factor app is completely self-contained and does not rely on the runtime injection of a webserver into the execution environment to create a web-facing service. The web app exports HTTP as a service by binding to a port, and listening to requests coming in on that port.
The processes in the twelve-factor app are disposable, which means they can be started or terminated in a short period of time. This enables rapid scalability, rapid deployment of code or configuration changes, and robust production deployments.
Startup time should be kept to a minimum in processes. A process should take a few seconds from the moment the launch command is invoked until it is up and running and ready to receive requests or jobs. When the process manager sends a SIGTERM signal, the processes gracefully exit. A web process achieves a graceful shutdown by ceasing to listen on the service port (therefore refusing any new requests), letting any current requests complete, and then quitting.
Dev, Prod Parity
There may be differences between your environments ( development and production ) in terms of:
Time Gap: You finished your code in development a long time ago, and it can take weeks, if not months, to deploy your application code in a production environment.
People Gap: Developers work in a development environment, and once the application is released, it will be managed by other individuals such as operations professionals.
Tools Gap: Your project's tools, such as databases: In the development environment, you may prefer to utilize a lightweight SQLite, whereas the operation team will use Postgresql in production, and so on.
TO MITIGATE FOR THESE DIFFERENCES:
Time: The modern deployment world allows you to go to production after only a few hours, if not minutes, of development.
People: Organizations are going toward DevOps methodologies, in which development personnel will be involved and watch production deployments so that if something goes wrong, we can begin debugging straight away.
Tools: The twelve-factor app should never utilize a separate set of tools in production and development settings. You may indeed want to employ lightweight tools in your local environment due to resource constraints, but you should not.
Logs provide insight into a running app's activities. They are usually written to a file on disk (a "logfile") in server-based environments, however, this is simply an output format. Logs are a stream of aggregated, time-ordered events collected from all operating processes and backing services' output streams. Logs have no beginning or end point and flow indefinitely as long as the app is running. A twelve-factor app never has to worry about routing or storing its output stream. It should not try to create or manipulate log files. Instead, each running process writes its event stream to
stdout. The execution environment then manages these log stream collections and routes them to a final destination for analysis and long-term storage. Open-source log routers (such as Logplex and Fluentd) are available for this purpose.
Aside from serving routine web requests, developers will frequently be required to perform one-time administrative operations such as:
database migration tasks,
One-time scripts to perform a quick database fix.
One-time admin procedures should be run in the same environment as the app's normal, long-running processes. They run against a release and use the same codebase and config as any other process that runs against that release.
There are numerous advantages to developing your application following the best practices of 12 factors, including:
So, whenever you have the opportunity, you should constantly evaluate the 12 factors listed above.
To conclude, I discussed the differences between traditional app deployment and microservice deployment in my blog article. There are many more advantages to adopting the microservice design, and if you follow best practices when developing your application, it will be much easier to maintain and run your application in the long term. Thank you for taking the time to read this blog.