Atomic-Based Code DesignPosted on by Richard Greene
Atomic design is a pattern pioneered by Brad Frost that advocates building up the visual elements of a webpage from small components that combine to make ever more useful and complex forms. We at Skyscanner have also found it to be a useful analogy for how small components of code functionality can be used to build up functional, integrated web applications. Here’s our experience of using atomic-based code design.
Describing and designing code in this way makes it easier for us to abstract out and re-use shared functionality, improve testability of code and identify dependencies so that we can build the cleanest and most maintainable code possible.
Atomic Design Terminology
In the design paradigm, atoms are the smallest possible unit of functionality, molecules are simple features built up from one or more atoms and organisms are full features made up from molecules and presented to the user. These could be a button, date-picker for a return trip, or a full search control with date, location and filters, respectively. Organisms are placed into templates, which map out where they appear in relation to each other, forming specific pages. From a developer point of view, we can map this onto the underlying code that makes these function and also on to pieces of application logic that have no component directly visible to the user.
In our Squad (Inspiration and Discovery) we inherited an existing codebase and went about expanding and updating it. As we went along we realised that we wanted to try breaking down the existing functionality into separate re-usable components. Part of the motivation for this was so that it would be easier to swap in and out different versions of components when we wanted to experiment with new and improved features. Another motivation was to take areas of functionality that were very similar in different places in the codebase and creat shared, reusable components for them so that they would have a single point for maintenance and testing. That way we could avoid bugs that were found and fixed in one area cropping up again or slipping by in similar areas that hadn’t picked up the original fix.
The first step was to pull out some simple, shared functionality, like our logging and server communication code. These were our first molecules, a few very basic pieces of functionality that we would re-use again and again across the code. They were composed of no more than a few functions (atoms) and gave us single points where we could add new functionality. For the logger, this gave us an easy way to add in logging to new channels, such as adding logging for Google Analytics or Facebook where it had been lacking before. For the server communication this would later allow us to experiment with substituting in more up-to-date data from local caches, on top of data from server storage, without the consuming applications ever having to know the substitution was being made.
Win Some, Lose Some
Our next step was to move across from Browserify to Webpack. Although similar in functionality, Webpack had some additional out of the box features that were useful to us. The ability to require CSS as well as functional code was very useful, and it also came with ability to inline SVGs as part of the build process rather than serving them up separately. With this done, we now had each of our main product pages effectively running as single large organism on their own pages.
Our first effort in putting together multiple separate organisms on a single page was with one of our newer pages, the map. We introduced a new component to give people a richer way to choose their specific flight dates after choosing their location from the map, a panel that let them see the pairs of dates they could fly out and back on. The two organisms used a shared bus to communicate with each other, a molecule that we added in at the page level. This allowed the panel to update its display based on changes to the selected location, messaged out from the map via the shared bus. We were able to add in a whole new component, displaying rich data to the user, with very little disturbance to the map code. The two organisms relied on shared expectations of data at the page level, communicating via the bus, but were otherwise ignorant of each other’s existence.
Our problem came when we tried to use the new panel component in place of the existing code on of our older pages, to see if it could duplicate the success it had shown being used with the map. We quickly found that to be used successfully in the older page, it had to be customised in a number of ways that differed from how we wanted to use it on the map. It needed to be embedded in some additional UI, which we were able to do by adding another simple molecule of functionality, adding some filters and visual padding. However, we also found that we needed to alter the layout, the styling and add in some additional small pieces of functionality that weren’t necessary when using it alongside the map.
The lesson we learned from this was that although organisms were discrete from each other they were often too dependent on their context in a given page to be fully reusable. A better way of looking at them was as a collection of molecules of functionality. The existing panel was a particular configuration of molecules, and one we could use as a template for building another organism in a new context, but was unlikely something we would re-use as a whole piece.
Onwards and Upwards
Our current work is now focusing on the styles associated with our web components and on applying atomic design principles more thoroughly across our existing code. Using Webpack’s ability to require styles like other library code we can create separate SASS files for each component and then combine them as needed. Instead of a single master style file, we build up and extract our overall stylesheet for any given page from the molecules we have pulled together to build it. To avoid problems with CSS ordering and specificity we are looking at adopting CSS Modules. With Webpack we can generate unique, locally scoped class names every time we consume styles for a molecule. The small amount of duplication this may cause is more than made up for by the ease of mixing and matching of different style components for our purposes.
For our existing code we are steadily decomposing the existing page-level applications into less tightly coupled molecules. In some cases, this is only a small change to the existing classes, in others it requires pulling out a lot of the implicit knowledge they have about the state of the rest of the page and making them require or access it explicitly. For one of our search controls, for instance, this allows us to pull out certain search fields to re-use in a different and more specific selection UI when the user has begun to narrow their search for destinations on a page. We can see clearly what components needs to function so that we can re-use them easily elsewhere. We can also see what depends on them and how they are connected to the other molecules around them for when we want to try putting new or updated molecule in their current place to test out new designs.
The more we break down our code into these building blocks, the quicker it is when we drop in new UI or UX on the page, when we update our data sources and when we want to try out or discard new pieces of functionality. If we want to try out a new service, we are better placed to put together a working prototype from the molecules we already have lying around, as our applications are designed to be broken down into pieces and rebuilt as needed.
Richard Greene is a Software Engineer working in Skyscanner’s Inspiration and Discovery Squad.