Cannibalizing the Monolith: A Micro-Application, Component-Driven Approach to Web Development

Part 2: Creating a Standalone Micro-Application

Zachary Maybury
DraftKings Engineering
5 min readSep 29, 2020

--

If you haven’t already, please check out Part 1, where we discussed our approach to shipping our first React/Redux components to production.

Our React + Redux based approach to upgrading our legacy website had gained a good deal of momentum. Our web team was able to rapidly apply updates across our site and we quickly developed the confidence to take on a bigger challenge, replacing an entire legacy user flow with a new, standalone single-page application (SPA). We chose to opportunistically pair this desire with a top ask on our product roadmap: rebuild all our user payments page, flows, and integrations.

A view of the DraftKings Secure Deposit Single Page Application
The DraftKings Secure Deposit single-page application. Built to rapidly deploy new payment providers and flows, dramatically improve performance, and provide critical security improvements.

Application Compartmentalization

While we had some success in transitioning a good deal of client code to extensible React components, we had still not achieved compartmentalization at the application level. Every web request was still ultimately backed by our monolithic web application servers, allowing degradations in unrelated parts of the application server to affect the performance of our web page. We began to sketch out a new type of application architecture for our Deposit SPA, a completely client-side site, backed by S3, and powered completely by RESTful API requests through our application gateway. This structure would move our application completely off of our legacy MVC web servers, allowing our initial application page load to be completely powered by S3. By utilizing separate buckets per environment, we also achieved the compartmentalization we desired.

An overview of our cloud-native architecture for our Deposit SPA code
Leveraging S3 for hosting our HTML and JS assets allowed us to dramatically reduce the complexity of our architecture. All application data is subsequently served from REST APIs through our application gateway.

We chose to leverage the knowledge we had gained from our component integration and settled again on client-side React + Redux. Given the importance of a payments page to any eCommerce business, we wanted to build an experience that was not only intuitive and modern, but performant. We decided we wanted to start from a new base HTML page, which would serve as the distribution channel for our new single page application. Unlike our initial integration, which leveraged existing web application servers and internal services to determine what HTML and Javascript to return on a page request, we chose to route all requests for our deposit page to return a static HTML file hosted in S3, which we generated at build time. This file contained all the markup and styling required to render our initial page, as well as the script to include our SPA code. Deploying a new release to production simply required us to copy the new Javascript code to S3 and overwrite the existing HTML file with our newly generated one. The updated HTML would be returned on subsequent requests, pulling in and executing our latest Javascript. At this point, our SPA would take over, making initial data-hydration calls to our API gateway and uplifting the remaining parts of the user experience.

The End-to-End sequence of a webpage request, utilizing S3 for HTML and SPA code distribution.
Site load flow: Requests for our Deposit Page fetch an HTML file from S3, which controls what JS code to fetch and execute. The browser loads this code from S3 and executes it, initializing the application. From this point forward, the SPA handles everything client-side, including initial data hydration and subsequent data requests.

In order to keep the experience snappy, we wanted to be able to cache as much of the client code as possible, while still maintaining the ability to release new code to our customers on demand. We had already decided to use Webpack for bundling our code and generating the base HTML for our page, so we were able to utilize build plugins to generate content hashing for our bundled files. We ended up with 2 types of build artifacts from this design:

  1. Content hashed Javascript assets that could be aggressively cached on clients and our CDNs
  2. A lightweight HTML file that allowed for straightforward deployment
Example difference between 2 versions of our build-generated HTML file, referencing the latest content hashed javascript for web clients to pull and cache.

Through this design, we were able to ensure that a new production deploy of our application would utilize the latest, content-hashed JS bundles referenced in the new HTML and that we could aggressively cache our JS code to improve future visit performance. Clients only had to download JS chunks that were new or had changed, saving unnecessary round-trips for returning customers.

With this new distribution strategy, the mechanics of shipping a new build of our code to production became even more straightforward than our initial component integration.

  1. New JS code is copied to S3. We content-hash each JS file to ensure we are able to set a long max-age cache-control header, reducing unnecessary network traffic on the client.
  2. New HTML is copied to S3, which references the JS code we just copied to S3.
  3. Subsequent requests to our web servers receive the new HTML
  4. Web clients fetch and execute the new code, pulling any new JS content from S3 and initializing our SPA.
Build and Deploy Flows for new Deposit SPA changes
Build and Deploy Flows for new Deposit SPA changes

From this point forward, our distribution mechanisms were in place and the development process looked very similar to our component based approach, even leveraging some generic components we had broken out into a component library. With React Router handling all of our client-side routing needs, we were able to quickly build and integrate this new single page application, dramatically improving one of our most critical user flows.

More Apps, More Products

Stay tuned for Part III of this series, where we will show how DraftKings Engineering has built upon these principles to create new Node.JS server-side applications for our market leading Online Sportsbook and Casino web products.

--

--