Maintenance Matters: Continuous Integration

David Eisinger, Development Director

Article Categories: #Code, #Front-end Engineering, #Back-end Engineering

Posted on

In this latest entry of our Maintenance Matters series, we'll discuss Continuous Integration: what, why, and how.

This article is part of a series focusing on how developers can center and streamline software maintenance. The other articles in the Maintenance Matters series are: Code Coverage, Documentation, Default FormattingBuilding Helpful Logs, Timely Upgrades, Code Reviews, Good Tests, and Monitoring.

As Annie said in her intro post:

There are many factors that go into a successful project, but in this series, we’re focusing on the small things that developers usually have control over. Over the next few months, we’ll be expanding on many of these in separate articles.

Today I’d like to talk to you about Continuous Integration, as I feel strongly that it’s something no software effort should be without. Now, before we start, I should clarify: Wikipedia defines Continuous Integration as “the practice of merging all developers’ working copies to a shared mainline several times a day.” Maybe this was a revolutionary idea in 1991? I don’t know, I was in second grade. Nowadays, at least at Viget, the whole team frequently merging their work into a common branch is the noncontroversial default.

For the purposes of this Maintenance Matters article, I’ll be focused on this aspect of CI:

In addition to automated unit tests, organisations using CI typically use a build server to implement continuous processes of applying quality control in general – small pieces of effort, applied frequently.

If you’re not familiar with the concept, it’s pretty simple: a typical Viget dev project includes one or more GitHub Action Workflows that define a series of tasks that should run every time code is pushed to the central repository. At a minimum, the workflow checks out the code, installs the necessary dependencies, and runs the automated test suite. In most cases, pushes to the main branch will trigger automatic deployment to an internal QA environment (a process known as continuous deployment). If any step fails (e.g. the tests don’t pass or a dependency can’t be installed), the process aborts and the team gets notified.

CI is a very tactical, concrete thing you do, but more than that, it’s a mindset – it’s your team’s values made concrete. It’s one thing to say “all projects must have 100% code coverage”; it’s another thing entirely to move your deploys into a CI task that only runs after the coverage check, so that nothing can go live until it’s fully tested. Continuous Integration is code that improves the way you write code, and a commitment to continuous improvement.

So what can you do with Continuous Integration? I’ve mentioned the two primary tasks (running tests and automated deployment), but that’s really just the tip of the iceberg. You can also:

  • Check code coverage
  • Run linters (like rubocop, eslint, or prettier) to enforce coding standards
  • Scan for security issues with your dependencies
  • Tag releases in Sentry (or your error tracking tool of choice)
  • Deploy feature branches to Vercel/Netlify/ for easy previews during code review
  • Build Docker images and push them to a registry
  • Create release artifacts

Really, anything a computer can do, a CI runner can do:

  • Send messages to Slack
  • Spin up new servers as part of a blue/green deployment strategy
  • Run your seed script, assert that every model has a valid record
  • Grep your codebase for git conflict artifacts
  • Assert that all images have been properly optimized

That’s not to say you can’t overdo it – you can. It can take a long time to configure, and workflows can take a long time to run as codebases grow. It can cost a lot if you’re running a lot of builds. It can be error-prone, with issues that only occur in CI. And it can be interpersonally fraught – as I said, it’s your team’s values made concrete, and sometimes getting that alignment is the hardest part.

Nevertheless, I consider some version of CI to be mandatory for any software project. It should be part of initial project setup – get aligned with your team on what standards you want to enforce, choose your CI tool, and get it configured ASAP, ideally before development begins in earnest. It’s much easier to stick with established, codified standards than to come back and try to add them later.

As mentioned previously, we’re big fans of GitHub Actions and its seamless integration with the rest of our workflow. Here’s a good guide for getting started. We’ve also used and enjoyed CircleCI, GitLab CI/CD, and Jenkins. Ultimately, the tool doesn’t matter all that much provided it can reliably trigger jobs on push and report failures, so find the one that works best for your team.

That’s the what, why, and how of Continuous Integration. Of course, all this is precipitated by having a high-functioning team. And there’s no GitHub Action for that, unfortunately.

The next article in this series is Maintenance Matters: Code Coverage.

David Eisinger

David is Viget's managing development director. From our Durham, NC, office, he builds high-quality, forward-thinking software for PUMA, the World Wildlife Fund, NFLPA, and many others.

More articles by David

Related Articles