Step 11: Branching the Code

5.0 version
Maintained

Branching the Code

There are many ways to organize the workflow of code changes in a project. But working directly on the Git master branch and deploying directly to production without testing is probably not the best one.

Testing is not just about unit or functional tests, it is also about checking the application behavior with production data. If you or your stakeholders can browse the application exactly as it will be deployed to end users, this becomes a huge advantage and allows you to deploy with confidence. It is especially powerful when non-technical people can validate new features.

We will continue doing all the work in the Git master branch in the next steps for simplicity sake and to avoid repeating ourselves, but let’s see how this could work better.

Adopting a Git Workflow

One possible workflow is to create one branch per new feature or bug fix. It is simple and efficient.

Describing your Infrastructure

You might not have realized it yet, but having the infrastructure stored in files alongside of the code helps a lot. Docker and SymfonyCloud use configuration files to describe the project infrastructure. When a new feature needs an additional service, the code changes and the infrastructure changes are part of the same patch.

Creating Branches

The workflow starts with the creation of a Git branch:

1
$ git branch -D sessions-in-redis || true
1
$ git checkout -b sessions-in-redis

This command creates a sessions-in-redis branch from the master branch. It “forks” the code and the infrastructure configuration.

Storing Sessions in Redis

As you might have guessed from the branch name, we want to switch session storage from the filesystem to a Redis store.

The needed steps to make it a reality are typical:

  1. Create a Git branch;
  2. Update the Symfony configuration if needed;
  3. Write and/or update some code if needed;
  4. Update the PHP configuration (add the Redis PHP extension);
  5. Update the infrastructure on Docker and SymfonyCloud (add the Redis service);
  6. Test locally;
  7. Test remotely;
  8. Merge the branch to master;
  9. Deploy to production;
  10. Delete the branch.

All changes needed for 2 to 5 can be done in one patch:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
--- a/.symfony.cloud.yaml
+++ b/.symfony.cloud.yaml
@@ -15,8 +15,13 @@ runtime:
 build:
     flavor: none

+variables:
+    php-ext:
+        redis: 5.3.1
+
 relationships:
     database: "db:postgresql"
+    redis: "rediscache:redis"

 web:
     locations:
--- a/.symfony/services.yaml
+++ b/.symfony/services.yaml
@@ -2,3 +2,6 @@ db:
     type: postgresql:11
     disk: 1024
     size: S
+
+rediscache:
+    type: redis:5.0
--- a/config/packages/framework.yaml
+++ b/config/packages/framework.yaml
@@ -7,7 +7,7 @@ framework:
     # Enables session support. Note that the session will ONLY be started if you read or write from it.
     # Remove or comment this section to explicitly disable session support.
     session:
-        handler_id: null
+        handler_id: '%env(REDIS_URL)%'
         cookie_secure: auto
         cookie_samesite: lax

--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -8,3 +8,7 @@ services:
             POSTGRES_PASSWORD: main
             POSTGRES_DB: main
         ports: [5432]
+
+    redis:
+        image: redis:5-alpine
+        ports: [6379]

Isn’t it beautiful?

“Reboot” Docker to start the Redis service:

1
2
$ docker-compose stop
$ docker-compose up -d

I’ll let you test locally by browsing the website. As there are no visual changes and because we are not using sessions yet, everything should still work as before.

Deploying a Branch

Before deploying to production, we should test the branch on the same infrastructure as the production one. We should also validate that everything works fine for the Symfony prod environment (the local website used the Symfony dev environment).

First, make sure to commit your changes to the new branch:

1
2
$ git add .
$ git commit -m'Configure redis sessions'

Now, let’s create a SymfonyCloud environment based on the Git branch:

1
$ symfony env:delete sessions-in-redis --no-interaction
1
$ symfony env:create

This command creates a new environment as follows:

  • The branch inherits the code and infrastructure from the current Git branch (sessions-in-redis);
  • The data come from the master (aka production) environment by taking a consistent snapshot of all service data, including files (user uploaded files for instance) and databases;
  • A new dedicated cluster is created to deploy the code, the data, and the infrastructure.

As the deployment follows the same steps as deploying to production, database migrations will also be executed. This is a great way to validate that the migrations work with production data.

The non-master environments are very similar to the master one except for some small differences: for instance, emails are not sent by default.

Once the deployment is finished, open the new branch in a browser:

1
$ symfony open:remote

Note that all SymfonyCloud commands work on the current Git branch. This command opens the deployed URL for the sessions-in-redis branch; the URL will look like https://sessions-in-redis-xxx.eu.s5y.io/.

Test the website on this new environment, you should see all the data that you created in the master environment.

If you add more conferences on the master environment, they won’t show up in the sessions-in-redis environment and vice-versa. The environments are independent and isolated.

If the code evolves on master, you can always rebase the Git branch and deploy the updated version, resolving the conflicts for both the code and the infrastructure.

You can even synchronize the data from master back to the sessions-in-redis environment:

1
$ symfony env:sync

Debugging Production Deployments before Deploying

By default, all SymfonyCloud environments use the same settings as the master/prod environment (aka the Symfony prod environment). This allows you to test the application in real-life conditions. It gives you the feeling of developing and testing directly on production servers, but without the risks associated with it. This reminds me of the good old days when we were deploying via FTP.

In case of a problem, you might want to switch to the dev Symfony environment:

1
$ symfony env:debug

When done, move back to production settings:

1
$ symfony env:debug --off

Warning

Never enable the dev environment and never enable the Symfony Profiler on the master branch; it would make your application really slow and open a lot of serious security vulnerabilities.

Testing Production Deployments before Deploying

Having access to the upcoming version of the website with production data opens up a lot of opportunities: from visual regression testing to performance testing. Blackfire is the perfect tool for the job.

Refer to the step about “Performance” to learn more about how you can use Blackfire to test your code before deploying.

Merging to Production

When you are satisfied with the branch changes, merge the code and the infrastructure back to the Git master branch:

1
2
$ git checkout master
$ git merge sessions-in-redis

And deploy:

1
$ symfony deploy

When deploying, only the code and infrastructure changes are pushed to SymfonyCloud; the data are not affected in any way.

Cleaning up

Finally, clean up by removing the Git branch and the SymfonyCloud environment:

1
2
$ git branch -d sessions-in-redis
$ symfony env:delete --env=sessions-in-redis --no-interaction

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.