Pemberly at LinkedIn

Coauthors: Sarah ClatterbuckMark Pascual, Chad Hietala, Eugene O’Neill

When setting out to re-imagine our flagship app and desktop web experiences last year, we wanted to make a move to a more modern technology stack. We were falling behind in a few areas and wanted to address the following points of concern:

Responsiveness
Modern web apps are typically heavily client-rendered to provide the user with a more responsive user experience. The design team wanted to create page transition experiences, which are not possible in traditional web pages. When a traditional web page is requested, the browser loads it suddenly and jarringly as the response is fulfilled. Conversely, in a client-rendered app, transitions can be made to elegantly fade or slide in, because the Document Object Model (DOM) of the existing page is being replaced rather than a new page being requested from the server.  Also, we wanted our desktop member experience to be more app-like and aligned with the native mobile app experience both for consistency between platforms and snappy-feeling interactions.

State management
In our patterns for addressing JavaScript in web pages, we had great conventions around widgets and DOM manipulation. However, where things got really complicated was when we needed to manage state. There were a number of hand-rolled solutions to managing state along with several imports of various libraries to do the same. This led to a fracturing of our frontend ecosystem and an inability to provide adequate developer tooling for developing with these more complex tool sets.

Developer productivity
In addition to creating an easy way to manage state, we also wanted to provide greater developer productivity. This included the ability to go from writing code and tests to deploying in several hours. We also wanted to bring together our fractured frontend ecosystem into a single set of conventions that all the teams developing long-lived stateful apps would follow. In order to get to the level of productivity desired, we also needed a robust set of tooling for creating dev and production builds and writing unit and acceptance tests for JavaScript.

The solution: Pemberly

We decided to build up a solution, dubbed Pemberly, combining open source software we were already using in the mid-tier (Play) along with a web framework (Ember) for JavaScript that would deliver strong conventions, easy ability to manage state, a robust build and testing methodology, and the delightful, app-like user experience we were targeting.

pemberly1

API server
As we were going to support sending data directly to the browser, we needed a way to make our API open to HTTP requests on the internet—of course with all the proper handling of user authentication. We decided to build an API server that could be shared by web and mobile clients for a consistent experience across platforms; in other words, to build once for the UI and use in multiple clients. In order to provide the appropriate filters and access control, we leveraged our Play mid-tier architecture. Typically, our Rest.li APIs are available in the data center via D2, a protocol for discovery of services by clients. The API server transforms these requests to standard JSON and exposes them to the internet. In addition to security and internationalization capabilities, the API server provides conveniences like mocking and replay of responses for writing robust unit tests of the APIs.

Base Page Renderer
The next piece of the architecture is the Base Page Renderer. The Base Page Renderer provides the head of the document and early flush capabilities. It also allows the app to load in several modes: Vanilla (browser does the heavy lifting), SSR (server-side render for DOM complete), and BigPipe  (the ability to let the browser boot the Ember app while simultaneously streaming data and DOM to the browser by delegating that work to separate Node processes, which are running the Ember app).

Ember app
Ember is our browser-based application framework. Ember provides a set of robust conventions for developing apps and managing data and state within the apps. It also provides an ecosystem of tools including a CLI and a rich unit, integration, and acceptance testing framework. Ember runs all the business logic for the app after the initial page load is brokered from the Base Page Renderer. Consistent entities and API writes are managed through Ember Data. We are also pushing the bounds of what can be done inside the Ember ecosystem with the new Glimmer2 rendering engine and loading engines lazily or eagerly in production.

ArtDeco CSS library
The last page of our ecosystem includes ArtDeco, which is our internal library for consistent styling (and sometimes interaction) of web page elements. Built on top of Eyeglass, a Sass extension ecosystem, ArtDeco is distributed as an Ember CLI add-on, seamlessly integrating into the Ember build tools. This final layer gives us a consistency of user experience and visual styling across different products with a high level of re-use in the CSS layer.

Along with re-imaging the user experience of our flagship app, we’ve also completely re-imagined the tech stack from CSS down to the APIs. We are incredibly proud of our ability to iterate quickly on our apps as well as to contribute back to a significant number of open source projects as we learn and evolve our ecosystem.

Acknowledgments

Huge thanks to the following, who helped us arrive at this architecture: Anand Bolini, Mark Pascual, Eugene O’Neill, Chad Hietala, Chris Pettitt, Krati Ahuja, Marius Seritan, Chris Eppstein, Prateek Maheshwari, Josh Fleming, Ashit Gandhi, and Nash Raghavan—and to the Ember community for providing us incredible support on our journey.