CI/CD — A Purpose Exploring Approach — Part 1
While the tooling for Continuous Integration and Continuous Delivery (CI/CD) is widely known, its purpose is often not fully understood. This blog post will explore the motivations behind CI/CD and how it can help improve your software development and delivery process. With a better understanding of CI/CD, you can more effectively utilize it to streamline your workflow and deliver better quality software faster. By the end, you’ll better understand how CI/CD can benefit your software projects.
Since I felt the entire content was too much for one blog post, I decided to split it into three better consumable parts. So have fun reading the first one, which primarily illuminates the topic of CI.
Before I start, we have to clarify one thing: there are many names for the primary branch in Git, like Main, Mainline, Trunk, Master, and many more. In this blog post, I will address it as the Mainline.
What is CI?
Continuous Integration (CI) is a development practice that seeks to keep everyone in sync with each other throughout the entire software development lifecycle. To achieve this, CI tools checkout code from a repository and run various verifications such as compiling, tests, code style, code packaging, and so on. The idea is that this process should be done at least once per day, with more frequent verifications being encouraged. This verification process produces an artifact that can be used in end-to-end or similar tests before being stored in a registry for final deployment. The importance of Continuous Integration lies in its ability to ensure that the deployed version is exactly the same one that went through our set verifications. Continuous transparency is guaranteed as everything is version controlled and can be traced back from production to the original commit it originated — making Continuous Integration invaluable for any project.
Why do we use CI?
Using CI, teams can drastically reduce the risk that new code changes do introduce bugs or break existing functionality. Additionally, by integrating code changes early and often, developers can identify and resolve conflicts between code changes made by different team members more efficiently. CI also enables teams to continuously build and test their code, which can help to ensure that the software is always in a releasable state. This can help to reduce the time and effort required to get new features and updates to customers. Additionally, it can help prevent large numbers of bugs or issues from accumulating in the codebase, making it more difficult and time-consuming to fix. Overall, CI can help to improve the quality of software, increase efficiency and collaboration among developers, and reduce the time it takes to get new features and updates to customers.
CI — Tool vs. Behavior
To determine whether your team is adopting CI, it is essential to focus on their behaviors rather than the tools they use. This can be accomplished by asking three simple questions proposed by Jez Humble: Do you check in to the Mainline once per day? Do you have a suite of tests to validate your changes? When the build is broken, is it the number one priority for the team to fix it? The answers to these questions show how well CI is ingraining itself into people’s minds as an unbreakable habit, forming the core culture of automation in software delivery. These questions hit the heart of having a solid CI process — behavior, not just tools. Moreover, they give you solid practical advice for understanding your company’s use of CI beyond the basic output of a tool.
Do you check in to the Mainline once per day?
When working on a project, checking in to the Mainline at least once daily is essential. You must ensure that the code integrates properly; the longer you wait, the harder it will be. Long-lived featured branches are clearly breaking this principle and making integration more complex and time-consuming than it needs to be. Regular check-ins should be part of your team’s workflow, and make sure everyone is kept up to date with the latest changes.
Do you have a suite of tests to validate your changes?
Syntactic code integration is simply not enough to ensure that the changes you make are safe and won’t break the system. We must go further and validate if our changes haven’t altered the overall system behavior. That’s why having a suite of tests available to run before committing any changes is important — it ensures that we don’t cause unintentional damage and gives us a complete picture of the system’s integrity.
When the build is broken, is it the number one priority for the team to fix it?
When the build is broken, it should be the team’s first priority to fix it. All other check-ins not contributing to fixing the build should be stopped, as this will reduce complexity and save time in fixing any issues. Use Pull or Merge Requests to guard your Mainline so that build failures can be prevented from occurring in the first place. If a broken build occurs, focus on fixing it as quickly as possible — it will save you hours of debugging nightmares later on.
Why is it Hard to be a Good CI Citizen?
Mostly, the struggle of being a good CI citizen is this: do you check in to the Mainline once per day? But why? Generally, you need to establish some precondition when starting a project to support regular merges to the Mainline.
Avoid long-lived feature branches
Branching models need to be considered carefully when selecting the best development flow. No long-lived feature branches should exist, as Trunk-based Development is much better suited for the purpose of integrating often with the Mainline. You should either commit your development straight to the Mainline or have short-lived feature branches that are merged quickly. No project is too big or small for this approach, and its advantages make it well worth considering.
Team must come up with some mechanism to potentially release software with “unfinished” features
As a team, we must develop a system for potentially releasing software with unfinished features; otherwise, it is impossible to integrate daily with the Mainline. The main issues associated with this are interfaces like UI or API implementations. One possibility is using fully-fledged feature toggles or an alternative, such as API annotations that deactivate endpoints in production or implementing simple if-conditions. Whatever solution we choose, understanding our system’s architecture and users’ demands is paramount to ensure that our final product meets their expectations.
When and how should features finally be enabled
When planning our development process, it is essential that it is well-tailored to the task at hand. Therefore, we have to come up with a solution for enabling new features for our customers. We can support this with tooling like an interface to enable feature toggles in production. Besides tooling, we also have to think about how we want to integrate feature activation into our process. This could be done with a simple solution like a sub-ticket of the User Story(s) related to the new feature as a reminder for the final activation. These practices ensure that the result fits into the system correctly and offers reliable tools to get the job done efficiently. Ultimately, putting some thought into how your processes work together can save time and effort — so make sure to commit time to consider them.
Developers must have the ability to break down complex features in packages
This sounds easier than it is. In reality, developing the ability to break down complex features into packages requires a particular skill set crucial for daily integration with the Mainline. This is a skill that must be developed and honed through practice. Many developers do not see the benefit of this process, arguing that they are faster when they don’t break down complex features in packages. However, they often ignore the impact this has on the team’s performance as a whole. This approach has been proven time and time again to be beneficial for efficient development in many organizations.
Discipline
Discipline is an essential concept in any work or team setting. If one team member doesn’t play by the rules, it can create an unhealthy atmosphere that affects the entire team. If everyone isn’t abiding by agreed-upon principles, expecting optimum performance or results makes no sense. Establishing firm disciplinary practices helps ensure that everyone fully participates and plays their part with dedication and motivation toward a shared goal.
Conclusion
A purpose-exploring approach to Continuous Integration and Continuous Delivery helps us to understand these concepts better. Additionally, it is important to remember that CI/CD is not just a tool but a set of behaviors we need to adopt to succeed. Changing our habits and workflows can be challenging, but it is worth it in the end when we see the benefits of a more streamlined and efficient process. In Part 2, I will dig deeper into Continuous Delivery.