Demystify deployment and environment coupling of code
As a good practice, we keep a separate deployment instance=(qa=app.qa.com
) apart from main deployment=(production=app.com
), so that we can test our development on qa
instance before moving that code to production
instance
Adding a further level of safety, the code deployment to these different instances (qa
and production
) also goes from different git branches ( qa
and master )
For new code addition, we create a branch=(feature-1) from qa
and test it locally and if it works well on local, we merge feature-1 to qa
branch, now this qa
branch is used to deploy code to qa
deployment instance hereafter referred to as qa
environment, if this works well, we will move code from qa
branch to master branch and use code from the master branch to deploy to production
instance
In case of web development, both frontend and backend code follow the same code deployment approach, so we want frontend code on qa
instance to connect with backend API from qa
instance and similarly production frontend to call production backend API
This connection logic to the backend is part of frontend code
Thus the code that connects to backend API will be different for qa
and production instance of frontend, that means we can’t promote qa
branch to master branch through a simple pull request
We can solve this problem in frontend code by decoupling the backend API url from the frontend code logic and couple them through new deployment code at the time of deployment as per following coupling instruction
# deployment code == coupling instruction in plain English
1. if we want the code to run on qa
environment, pick the qa
credentials and build the code so that it runs well on qa
2. if we want the code to run on the production environment, pick the prod credentials and build the code so that it runs well on production
because credentials and code are decoupled and gets coupled only according to our coupling instructions defined above, we can safely do below two things
A1. promote the qa
branch to master
branch without worrying about overriding master credentials in the production branch, since they can coexist. (env.qa and env.prod)
A2. pull mastermaster
branch to qa
Thus we have explained the deployment logic used to ease the ongoing development of software
Now if in case, we are not able to decouple our credentials from code, then we have to MANUALLY take care that our coupling logic is intact while doing points A1 and A2 described above
Following is an example of one scenario and our solution approach
We have repository=Repo1 which contains page business.html
now route=/url1
in another backend, repo=API_SERVER is configured such that it renders business.html
in both environment qa
and prod
Now our requirement is that the pagebusiness.html
should submit the form to two different endpoints according to the environment qa
or master
and that is where credentials are coupled to code.
Now instead of jumping to the final solution let improve bit by bit
Generally, the coupling is defined in build.js, that means, build.js for qa
will contain credentials of qa
and build.js formaster
environment will contain credentials of master
, thus we can’t progress the build.js from qa
to master
branch seamlessly
Improvement 1
The first improvement can be to create two separate js files main.qa.js
and main.prod.js
and both will co-exist, we will just replace the reference in HTML as per the environment
Again we can’t do actions A1 and A2 seamlessly, because we will have to take care of proper reference in the HTML file
Improvement 2
Again we can go for improvement by passing env as query parameter from the API_SERVER like this /url1?env=qa
and in the HTML we can check the env value and load either main.qa.js
or main.prod.js
based on the value of env
like this
<script type="application/javascript">function loadScript(file) {var jsElm = document.createElement("script");jsElm.type = "application/javascript";jsElm.src = file;document.body.appendChild(jsElm);}let env = document.location.search.split("&").filter(x=>x.startsWith('env='))[0]?.split('=')[1];let url;switch (env) {case 'qa':url = '../js/main.qa.js';break;default:url = '../js/main.prod.js';}loadScript(url)</script>
Now we can do actions A1 and A2 seamlessly basically we have again tried to decouple the code with credentials with the help of query parameters from API_SERVER, because API server can actually send different parameters based on environment, since in our case it has that decoupling that we talked
Improvement 3
As explained in the first line of the post, our host URL are different namely app.qa.com
and app.com
so instead of relying on query parameter from API_SERVER, we can improve our coding logic as below
<script type="application/javascript">function loadScript(file) {var jsElm = document.createElement("script");jsElm.type = "application/javascript";jsElm.src = file;document.body.appendChild(jsElm);}const env = window.location.hostlet qaUrl = "../js/qa.main.js";let prodUrl = "../js/prod.main.js";let url;switch (env) {case "app.qa.com":url = qaUrl;break;case "app.com"url = prodUrl;}loadScript(url)</script>
With this improvement, we have coupling logic in HTML itself.
Let me know your views.