Frontend Domain Decomposition at Carta

Thomas Schmorleiz
Building Carta
Published in
5 min readApr 27, 2022

Co-authored with Elena Schneider and Manjeet Kaur

A single code repository gives you great value when you’re moving fast. Every line can call every other line, you can deploy frontend and backend changes in sync, and administration is easy.

But at some point, a single repository breaks. Even before we hit 1.2 million lines of JavaScript and TypeScript, we had major problems: slow releases, unclear ownership, and delayed bug fixes. We needed to move our components out of our monolith, product-by-product, but how?

Here’s how we grappled with that task. We now build PRs in five minutes, we’ve reduced our 1.5-hour releases to just three minutes, and our local hot reloading takes mere seconds.

The challenge of a shared codebase

Carta’s Employee Experience teams build products for the largest demographic at Carta. We worked for years in a monolithic repository with an extensive codebase of 2.7 million lines of Python and 1.2 million lines of JS/TS, which heavily impacted our developer experience. For instance, it took CI upwards of 30 minutes to create Docker images. The extensive build latencies often forced frontend developers to suffer long deployment delays — bad enough if they were creating end-to-end demo tests, but even worse if they were working on production hotfixes.

Latency impacted local development, too. Frontend code was so entwined with other teams’ code that hot reloading often took minutes. Frontend developers, usually those closest to design iterations and building out MVPs, had a hard time keeping up momentum.

At Carta, we release the monolithic application once or twice daily on average, taking one to two hours each, which impacts overall product velocity. Teams must often face the decision to either wait for the next day’s release (which occurs Monday to Friday) or run a full release themselves (which takes one to two hours of an engineer’s time). As you can imagine, this puts unnecessary pressure on engineers to not miss the day’s release, risking bugs and missed edge cases.

A shared codebase blurs lines of ownership: It encourages implicit dependencies and slows velocity when teams need to refactor or change component contracts.

Two options

We use React for most of our frontend code, segregated by product, and follow React’s standard rendering approach. Webpack 4 creates Javascript asset bundles using React’s centralized component file. The webpack-bundle-tracker webpack plugin generates a webpack-stats file to track product-to-bundle names. The django-webpack-loader library then consumes the webpack-stats file to add product-specific bundles to HTML files.

We considered two approaches: either extend the django-webpack-loader library to access remote webpack-stats files or upgrade to Webpack 5.

Option one: Extend the configuration for the django-webpack-loader library to access webpack-stats files generated in different remote locations. The django-webpack-loader library documented this process. It would be a purely additive change that wouldn’t impact other teams, which could allow us to move out of the monolith with ease. The main concern we had with this option was that the library was not actively maintained and the owner of the codebase was looking to pass on their responsibility to new maintainers. However, we at Carta were already heavily invested in this library, and several engineers were willing to support the changes we needed.

Option two: Upgrade from Webpack 4 to Webpack 5. Upgrading would allow us to leverage module federation to consume exposed features from the external codebase into our monolith. There was a lot of hype and information available in this direction, but Webpack 5 was in beta. Everyone at Carta consumed the Webpack config and shared its ownership. Upgrading to a new version would impact almost all Carta products and require a lot more time than we had. The risk of adopting a library in beta was too high.

We selected option one, but we’re monitoring Webpack 5 and will be prepared to adopt it when appropriate.

A three-part architecture

We divide our bundle architecture into three parts: repository, hosting, and integration.

Bundle repository: Frontend developers create bundles (frontend assets) using the Bundle Repository.

Bundle hosting: During deployment, a webpack-stats-service we wrote uploads bundles to an S3 bucket that hosts the artifacts generated from code in external repositories. As part of the upload process, the service notifies the monolith about the changes and availability of new assets. The django-webpack-loader’s documentation explains how to set up this configuration.

Bundle integration: When the monolith client requests a frontend asset bundle, it is either retrieved from an internal cache in the monolith backend or fetched from the S3 bucket via the webpack-stats-service. The django-webpack-loader determines the namespace and corresponding approach to retrieve the webpack-stats file.

This design enabled a move out of the monolith — not only for us, but for several other teams, too. They hooked onto our webpack-stats-service to deploy their assets into S3 buckets and piggybacked on our functionality to notify the monolith of new deployments. They just needed to extend the configuration of the django-webpack-loader to follow the format of our namespace.

Decomposing our frontend domain using the remote django-webpack-loader was the right decision. It brought those dramatic improvements in release time, build time, and hot reloading time. “Working in this decomposed frontend saves us at least a third of our development, build, and test time compared to the monolith,” said frontend engineer Vignesh Veeran. “Our productivity has skyrocketed and we’re not blocked by others’ work or projects due to implicit dependencies.”

But more important than faster builds and deployments was the improvement to our developer experience. The team was responsible for the quality of an entire repository, elevating their sense of ownership. According to Kingson Lo, a frontend Engineer working on Employee Platform and Stock-Based Financing, “After the decomposition, we have fewer interruptions, more sense of ownership, and quicker deployments. A huge quality of life improvement!”

There are still many ways our frontend team can improve both our build technologies and overall platform, and we are now turning our attention to Carta’s micro-frontend architecture, build tooling, and CI/CD pipelines. If you’d like to help us adopt new technologies and enhance our architecture, we’re hiring!

--

--

Thomas Schmorleiz
Building Carta

Senior Engineer Manager at Carta, creating more owners on the Employee teams. Previously, content moderation engineering at Kik.