Software development—whether with a team or as an individual—benefits from predictable workflows. Many developers have inherited a mess of code, poorly managed files, or things that are difficult to maintain. We don't want to contribute to that mess, so we need a plan. This development and deployment strategy comes from an evolution of work with dozens of talented developers that I've had the privilege to work with.
While Gitflow is referenced here, I would highly suggest reviewing:
- This documentation by Atlassian on the usage of Gitflow.
- A successful Git branching model, a document capturing the foundation of Gitflow.
- The Github project page for gitflow, which also has a supporting application to help automate some processes.
Whether a website or any digital application, it's important to have places to create and test. Then we have the live application. A production website is no different than an application in an app store. The development and deployment strategy is the same for both. For websites there are useful tools to help create, share, and maintain consistent configurations and deployments, like Ansible, Jenkins, Docker, and Vagrant.
These separate environments—development, testing, and production—are quite valuable to the software lifecycle. This reminds of a quote, as software is a creative work:
Art is never finished, only abandoned. ~Leonardo da Vinci
Let's keep building, improving, and maintaining this useful thing.
Backups and testing restoration
It's great to have these designated environments to keep things in an appropriate place, but it's also important to have ways to go back to previous states. Backups serve that purpose. There are several ways to configure backups solutions that should be considered. It is equally important to verify that these backups are functional. I usually recommend a quarterly review (if restoring by-hand) or an automated verification.
This investment can be summarized by yet another quote:
An ounce of prevention is worth a pound of cure. ~Benjamin Franklin
A development environment is where work is traditionally done. This can be either a local or a shared environment. There can be multiple copies of this environment, so long as there's a clear way to synchronize changes between them all. Development environments often have additional tools for debugging and profiling software to better understand possible issues. These environments, therefore, are not quite like the live environment. Hardware resources and supporting applications may also differ. In an ideal situation, however, this should be minimized (e.g. try to use the same version of PHP, MySQL, and other dependencies).
Consider this the
dress rehearsal... thus the name. These environments should be virtually identical to the live, production environments, and are primarily used for testing functionality and/or content before going live. All hardware and software should be the same as a live environment. With websites, this is usually accomplished by using the same server configuration with the same hardware. While with native applications, such as mobile apps, this requires multiple staging environments to simulate the target devices. Once a new version of an application needs to be staged, a copy of the live data and files is usually pulled to facilitate more real-world testing. The fewer differences between staging and production the better.
Time for the big show! Dress to impress! This is the live website or the application that a main audience will use. All the work done up to this point should have been well tested for the acceptance criteria defined before development began... That was done early on, right?
Want to change functionality? Back to the development environment. Want to add content? Well, go right ahead! Don't know the difference or feel like the line is blurred? Be sure to define these things before working on the application.
Most folks want to get right to work. Good! A well-defined workflow shouldn't interfere with productivity, but streamline the process and add a very valuable layer of safety. Things like version control are critical for moving through development and referencing changes through time. This is very important overall and quite critical if your code is maintained by more than one person at any point (plan for it).
For the purpose of this strategy we'll reference Git and Gitflow. Git is a version control system and Gitflow is a popular branching model. What is branching? It's a way of working on separate tracks of code changes that can be shared and merged with other branches as needed. This branching model lends itself well to working in the aforementioned environment types and with a deployment strategy with those environments.
Development (or develop) branch
This main development branch—often called the develop branch—is the trunk of the tree, where all work pivots. In single developer projects, this is often sufficient for the majority of the development work. While with teams, this is where all the team's work converges. This branch contains the full history of all code changes in the project.
When working on a particular set of functionality, or a
feature, look no further than a feature branch. These branches are great for focused efforts, even if they're just experimental ideas. Since Git is decentralized, there is no problem with multiple developers working on the same feature branch. The bus factor, however, may warrant considering sub-feature branches per developer to help capture larger efforts in a central remote repository.
Work is done! Maybe. Time to test it out with a mirror of the live application to make sure. Here comes a new release branch. Code changed shouldn't change here, but sometimes documentation (e.g.
README.md) might be updated, then merged back into the develop branch as needed.
Often release branches are named sequentially for easy reference in testing documentation, ticket, and issue queues. E.g.
release/r-1234 is the 1,234th release. Another option is a coordinated effort with semantic versioning with a suffix release. E.g.
release/2.x-alpha3 is the third
alpha release of the second major version of the application. Be sure to document this strategy to get consistency and predictability by all that participate. Remember that these releases can be disposed if they aren't needed for future reference.
Live (or master) branch
If all went well in a release branch, this approved code is ready to go live. Throw a party!
Only stable, well tested code should go live, so make sure those commits are solid. That heart monitor is working properly, correct? It was tested, yes? Okay, sure, we aren't building life support systems, but many of these applications do have some major money behind their success or failure, so we'll take this seriously nonetheless.
Commits to this master branch are
gold releases and should typically use Git tags with well defined versions. Semantic versioning is a well documented, popular strategy for versioning. E.g.
2.12.0 is the twelfth minor release of the second major release. Again, document this strategy to get consistency and predictability by all that participate.
We've all been there. There's an emergency and time for best practices or due diligence is lost to urgency. Enter the hotfix branch. A development environment moves to the master branch, then creates a new hotfix branch for this urgent work. Once the work is completed it should be tested, of course. If things look good, this work can be merged into both the master and develop branches to allow normal work and operations to continue. Any conflicts in develop must be resolved promptly to allow normal work to continue.
Time to put all these pieces together!
Branches align with environments in a logical way:
- Development environments: ANY branch can go here to help facilitate the movement of code between branches and to allow review for various reasons. Most of the work in this environment is done on a feature branch or the main develop branch. This is also a great place to work on a hotfix branch.
- Staging environments: A release branch should be the only branch in this environment. This should be thought of just for holistic testing or testing with stakeholders. This is not a place for code changes.
- Production environments: A place for a version of the code from the master branch. It's fine to move between versions, as these should all be well tested commits, ideally tags with version for easy reference and movement. It's important to be mindful of using backups if moving backward in versions, however. Sometimes new content is incompatible with old code.
Merge requests for feature branches
Working with lots of code requires some coordination. Merge requests can be helpful for teams that have a designated team lead to review the work of the group and bring things together in a safe, consistent, and compliant manner. These merge requests can usually be created in a platform like Gitlab or Github when code is completed in a feature branch and ready to coalesce with the main develop branch. These systems often provide notifications to the appropriate team members, if configured properly.
Internal QA testing
Ready to start testing? Great! This can be done in the main develop branch or optionally with a release branch. Pull a full backup from the production environment to use for testing.
While individual components should be tested as they are created and finalized, holistic testing is also quite critical. Sometimes functionality or styles might be changed by one component, but unknowingly change another part of the application that would go unnoticed without some visibility. Well established acceptance criteria can help confirm if something works as expected or looks right in target browsers/devices. User stories and design mockups can help establish this acceptance criteria, for example, and should be defined before development begins. That was done, right? Automated tests can help verify that functionality continues to operate properly with each code change and is highly recommended for larger, more complex projects.
Once things are verified internally—for sanity sake if nothing else—it's time to involve the stakeholders and/or a sample audience. Again, pull a full backup from the production environment to use for testing. A release branch should be created and referenced in any triaged issues. It is important to review the testing triage process, scope, and acceptance criteria with the people doing this testing. Be sure to provide ample time estimates for testing, review of feedback, and resolution. Provide deadlines for feedback that are reasonable, extending only if needed.
If everything goes well in testing, merge things back into the develop and master branches. Be sure to pull backups in the production environment, just in case things need to be restored.
Ongoing support and emergencies
Sometimes unknown scenarios occur and software requires adjustment. Whether an unexpected input, a deliberate security exploit, or an unhandled logical condition, these things happen with all applications and require immediate attention in some cases. A hotfix branch can be created from the master branch, work can be performed as described earlier. Once tested and ready, a new version can be created for the master branch. E.g.
2.12.1 is a patch release, based on the concept expressed in semantic versioning. These changes should also be merged with the develop branch.
Crack a beer and get back to work.