Monorepo

Monorepo

Phil Copley is a Senior Software Engineer on the Applications Crew at FlightAware. He is currently the technical lead for the beta surface visualization and coordinator of the Web Competency Alliance.

We’ve been thinking a lot about velocity at FlightAware lately. How can we ship our products faster? How can we get customer feedback faster? Being able to ship quickly without compromising the stability of our products or platform is a competitive advantage.

One key strategy we’ve implemented as part of our broader technological transformation is a monorepository architecture for new applications and libraries. In this blog post, we’ll dive into how this decision came about, the technical challenges and benefits we’ve seen so far, and what’s coming up next.

How FlightAware is Built

"Organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations."

Melvin Conway

FlightAware is not exempt from Conway’s Law, and by extension our organizational structure lends itself to microservices and monorepos quite nicely. If you’ve read about our organizational structure before, not a lot has changed--we still have crews, wings, and alliances. But we’ve moved away from strictly “Web” or “Back-end” crews and toward more cross-functional crews. Right now our monorepo is mostly relevant to our Applications and Core Crews, both of which are made up of folks from our front-end web, back-end, operations, product, and design wings. Iterating on public user-facing applications as well as the platforms and APIs supporting them respectively, it makes sense we’d need to share a lot of code between the two crews but also between both front-end and back-end.

We started down the road of completely independent microservices and pretty soon began running into issues, especially around sharing code. A package update would sit in review while we coordinated releases of consuming applications as to not introduce inconsistencies. The same team would need to make changes in multiple packages and coordinate those deployments. We could see a future where different teams are writing the same logic in their language, or even the same language, just to own it and avoid this coordination. We needed a solution that would enable seamless code sharing and collaboration, regardless of the programming language, or the team, and most of all needed to avoid this blocking deployment coordination.

Enter: The Monorepo

Monorepositories are not a new concept. In the right circumstances they can give you many of the benefits of a monolithic application and microservices, while mitigating many of the downsides of both.

It’s important to understand the difference between a monolithic application and a monorepository containing many applications. A monolithic application is typically going to contain all of your data access code, all of your front-end code, all of your business logic, and all of your tests. Additionally, you’re almost always going to be deploying that in one block. Fix a bug with the padding on your logo? You’d better be comfortable redeploying all your database access and user management code at the same time.

A monorepository, while still holding all your code easily visible and accessible in a single place, will be able to deploy these pieces independently. Change the marketing page for one of your products? Just that application gets deployed. Change something that affects half your products? Only those products get deployed, but they get deployed independently. If you manage to deploy something that’s broken, you don’t take out your entire company’s product line but just the thing that’s actually broken. This is a huge benefit and greatly reduces the blast radius of production outages, letting us ship faster and more confidently at the same time.

Another benefit of a monorepo not typically found in a monolith is the support for multiple languages. FlightAware has four first-class languages: Rust, Go, Python, and TypeScript. While realistically almost all new front-end code will be in TypeScript, our back-end services are a healthy mix of all of the other languages, and we have a handful of other languages we support for very specific use cases as well. Because of this, being able to natively build, run, and test these different languages side-by-side is a definite plus.

Monorepo Tooling at FlightAware

FlightAware is using Nx for our monorepo tooling. Beyond simply hosting code in one git repository, Nx has tooling to quickly spin up new applications and libraries, built-in caching to prevent wasting time waiting for unchanged dependencies to build over and over again, and even offers cloud-hosted build services if you’re interested in that.

Nx Generators

Nx generators are CLI commands you can use to easily spin up new applications, new libraries, and change configuration for existing apps. For example, you can add TailwindCSS or change which testing library you’re using.

Nx also enables you to write custom generators, fulfilling one of our longer-term goals to have a suite of custom FlightAware generators for our common tasks, and for generating our basic application or library structure in new projects.

Targets & Shared Tooling

Nx has a concept of “targets”, which are scripts you can run for a given project, or multiple projects. These can be nx generators, custom generators, or just arbitrary command line scripts. For example, we launched our monorepo before Nx-release was stable, so we have a few custom targets to manage independently versioning our projects in GitHub.

nx-affected

As a software developer one of the most useful aspects of Nx to me is Nx-affected. This allows us to run specific targets only on projects that have changed, and projects consuming changed projects. So if you imagine the following contrived project graph:

Example Nx project with two libraries, three applications, and E2E test projects.

If you just make changes to product-1 you don’t want that to also trigger builds for the other products, your libraries, or waste time running e2e testing suites for unchanged applications. Likewise, if you make a change to your components library, you need to rebuild and retest everything that depends on it. Nx affected lets us do this quickly and has a lot of built-in features such as filtering. You can get all the affected projects but exclude e2e test projects, or you can get all the affected libraries but only if they contain a publish target. This gives you very fine-grained control over your build pipeline and lets you make sure you’re not wasting compute building or testing things you don’t need to.

Benefits and Results

Generated & Shared Code

There were many growing pains in this process. Migrating the first app from a standalone product to a monorepo app took about twice as long as we expected, and it took us a while to get the build pipeline to a stable point. However, after those initial hurdles, I think we’re at a point now where our velocity has increased beyond what it would have been using the earlier microservice approach. We’re shipping our beta surface visualization to production several times a week and we’re soon starting a spike to investigate moving more of our core libraries into the monorepo.

We also share all dependencies between all apps. Nx refers to this as an integrated monorepo. This means that we don’t need to worry at all about packages going out of date on seldom-used applications because they are using the same packages all the other projects are. If a package is updated, that project has been changed, so tests will run and the application will be redeployed.

Shared Deployment Tooling

Outside of the monorepo, coordinating initial setup of deployment tooling between our front-end, back-end, and operations teams could take as much as a two weeks depending on availability and other workload. With Nx, all that is taken care of and the infrastructure is already in place to automatically deploy an arbitrary number of new applications. Not only does in this include the basic of just getting an application into production, but right out of the gate we get PR deployments for every application starting with PR #1.

OSS: Giving Back

When I interviewed at FlightAware I was excited about the prospect of working for a company that contributes back to the Open Source community. We were able to open a pull request addressing an issue we ran into, and in the course of doing so realized other organizations had the same problem months prior.

This is not out of the ordinary, either. Even on my small team there are several of us who have opened public patches to open source software that we use every day. If it fixed an issue we’re facing there is no problem contributing that code back to the community.

Next Steps

Custom Generators

I touched on this earlier but Nx has the ability to implement custom generators for your applications. One of the first platform-level things we’ll be addressing with Nx is creating a suite of custom FlightAware generators. Eventually I expect this to be an experience much like create-next-app where you simply run the generator, answer a few questions, and you have a fully-integrated functional library or application ready to go.

More Apps

As more projects are moved into the monorepo, the argument for moving other projects there also increases. The benefits of being tightly integrated on the dependency and infrastructure side while remaining loosely coupled programmatically quickly outweigh the costs of any migration which so far have been minimal.

Your Help

If this sounds interesting to you we’d love to have you come join our team! FlightAware is hiring.

Phil Copley

Phil Copley

Phil Copley is a Senior Software Engineer on the Applications Crew at FlightAware. He is currently the technical lead for the beta surface visualization and coordinator of the Web Competency Alliance.

Show Comments
Back to home