Better Together: Cross-Functional Collaboration at the Intersection of Design Systems and GraphQL APIs
By Amanda Olsen, Sam Combs
Part 1: The GraphQL Double-Edged Sword
A deep dive into how coupling design system components with GraphQL schema through collaborative cross-functional workshops can reduce sprawl, ensure consistency, and enhance productivity at both the UI and API layers.
While the supergraph architecture has been a game-changer - enabling the creation of large-scale GraphQL deployments that connect distributed microservices and multiple decoupled frontends - schema governance at scale remains a huge challenge.
A big part of this problem is what we call the “GraphQL double-edged sword.” GraphQL makes it really easy to fetch the exact data you want; however, without implementing an intelligent and intentional approach, teams are highly likely to produce duplicate queries and schema - leading to API sprawl - and duplicate or near-duplicate UIs - leading to inefficiency and inconsistent user experiences.
This phenomenon is further exacerbated in siloed environments where multiple teams end up shipping similar yet divergent UIs, resulting in inefficiency and inconsistent user experiences. Such UI code sprawl is highly likely to lead to bugs and build up tech debt.
In this article, we explore a mock case study to paint a detailed picture of the problem and we hypothesize a radically new approach to cross-functional collaboration on both design systems and GraphQL schema design. This approach puts in place guardrails to prevent duplication while enabling quick iteration on new experiences. These practices aim to guarantee both UI and schema consistency, while protecting against the trap of unwinding bad patterns in production.
Context
Our radically new approach comes out of a particular context and is therefore best for this context. While surely many of our ideas can be applied to other contexts, you should know that what we’re proposing assumes the following:
- We are powering multiple client platforms across multiple business domains.
- We leverage the server-driven UI (SDUI) architectural pattern to minimize client-side logic and facilitate consistency between client platforms.
- We use GraphQL federation to hydrate clients with data via a unified GraphQL API that aggregates numerous microservices.
- We operate in a large organization with many distributed teams.
What about Design Systems?
As per recommended best practices, server-driven UI with GraphQL greatly benefits from the use of a design system.
The problem is that, despite the existence of authoritative resources on design systems in general, the intersection of design systems and GraphQL APIs is still pretty much uncharted territory.
To make things even trickier, what belongs in a design system and what doesn’t seems to vary depending on who you ask. Then, there’s the fact that design systems evolve slower than the products they support, which is a feature and not a bug.
While design systems certainly bring enhanced consistency and reduction of boilerplate across organizations, the fact that they are slow to evolve can create bottlenecks in the software development lifecycle. This is similar to the challenge GraphQL platform teams face trying to push forward the best possible evolution of the federated GraphQL API.
Here is our central question: what if we approached these perceived bottlenecks as opportunities for closer collaboration on schema design that could improve the reusability of both frontend components and the associated GraphQL fragments and schema?
Atomic vs. Pattern Components
The first step to such closer collaboration requires that we agree on the types of components that exist within our design system, in the context of GraphQL schema design.
Taking the cue from Brad Frost’s atomic design methodology, we posit the atomic component as the smallest unit in a design system. For our purposes, let’s define it as follows:
An atomic component is a UI component that cannot be broken down into smaller parts and reassembled in another way.
Here are a few examples from the Amazonia system:
![][image1]
However, in contrast to Brad Frost’s hierarchy of atoms, molecules, organisms, templates, and pages, we propose to introduce only one more type of component besides the atomic one, that is, the pattern component. Let’s define it as follows:
A pattern component is composed of atomic components and built to enable a user to complete a task or solve a problem.
Consider the following examples of Amazonia pattern components:
![][image2]
Atomic components enable site-wide consistency of UI across business domains and client platforms (both internal and customer facing.) By combining these atomic components into pattern components, we unlock user experiences.
Note: The user doesn’t go to Amazonia to “click a button” (an interaction with an atomic component); instead the user goes to Amazonia to “book a trip to Las Vegas” (an interaction with a pattern component).
Thus, atomic components are mechanical realities, while pattern components represent product realities that carry meaning to the user. The differences between these libraries—the atomic component library and the pattern component library—helps us determine who needs to be at the relevant schema design workshops.
Why only Atomic and Pattern Components?
Before we answer this question, let’s first delineate the larger context.
More and more organizations are adopting a GraphQL-native approach. This unlocks the ability to codify the contract between data consumers and data providers. What used to be a polyglot system of BFFs, with unpredictability and a lack of standards between systems, has been modernized and replatformed into massive unified APIs all feeding into a single supergraph endpoint.
Unified data APIs have unlocked a more intelligent architecture that allows a demand-oriented approach. This means that product teams, as well as specific client consumers, can ask for just the data they need.
The demand-oriented approach can be put in practice in a very agile way by using server-driven UIs, meaning that clients (web, iOS, Android) can get updated versions of experiences without having to change a line of code. As the backend data and experience services evolve more functionality, this is immediately available to users across platforms.
What’s worth noting is that design systems and the new GraphQL-native unified API have much in common: both promote consistency and efficiency across business domains, with design systems focusing on user experience consistency and the unified API on data delivery consistency.
Now, by limiting design systems to just two levels, i.e., atomic and pattern components, we can map design system concepts 1:1 to schema concepts. This means that product-focused design decisions can be codified as a data contract between the frontend clients and backend data providers.
We believe that such coupling, together with a simplified design system hierarchy, is going to result in a number of benefits.
First, limiting to two levels increases the maintainability of the system. Too many layers can result in dependency hell as updates in one layer trigger a ripple of necessary updates across all levels and consumers. This can result in outdated versions of the design system being deployed due to lack of time to address the tech debt to update.
Further, atomic components’ data requirements can be modeled into shared GraphQL types, easily reusable by backend data service teams to use as building blocks for richer, product-specific user experiences. These combinations of atomic components constitute pattern components.
For frontend teams, atomic components can have GraphQL fragments associated with them, for easy reuse across experiences, all without having to reinvent the data shape each time.
Moreover, pattern components can be used to delineate query boundaries on a page. Being made up of atomic components, their data structures are easy to assemble from the shared library of schema, similar to how frontend experience teams easily assemble design system atomic components to build UIs.
While it can be tempting to fashion one massive GraphQL query per page, this quickly becomes a performance problem at scale. Limiting queries to the pattern component level not only makes page performance better, it makes it easier for engineers and designers alike to reason about these components and their queries.
Without a system like this, many very similar queries often emerge, and sometimes whole services and teams are spun up to provide data shapes that already exist in a slightly different format in the graph.
Mock Case Study
We’ve been fascinated with the evolution of both design systems and unified GraphQL APIs. Let’s now explore a mock case study where we look at some divergence in Amazonia’s user interface across product domains, and see what happens when we marry design systems with GraphQL via collaborative cross-functional workshops.
UI Divergence at Amazonia
Below, you can see two variants of a product page at Amazonia.com; one for a Property and one for an Activity.
![][image3]
It’s worth noting that, although these pages are continuously evolving, they were originally created before Amazonia began adopting its design system and the supergraph. Currently, the teams at Amazonia are working to consolidate experiences to streamline user interactions, reduce operational complexities, and leverage efficiencies.
Such a brownfield scenario means that:
- Both the experiences and the underlying GraphQL layer need to be continuously evolved, rather than rebuilt from scratch;
- Given the sheer scale of the organization and the fact that experiences require collaboration between product, design, and engineering functions, we need to put some coordination and planning into place.
But how do we pull this off without getting overwhelmed by analysis paralysis so often associated with upfront design?
Our north star is a unified, coherent customer experience served by an intelligent architecture which prioritizes reusability and composability, enabling new user experiences to be quickly crafted, tested, and delivered.
Too much variation in similar user experiences across product domains can negatively affect sales as well as diminish productivity for engineers and designers, who may be building duplicative elements due to lack of governance.
With the above in mind, let’s examine the variation in these product overview components and how they’re being fetched.
![][image4]
Current State
The starting point for unifying the overview component variations should definitely be a discussion between product and design teams. Perhaps one variation aligns more closely with what has been defined at the design system level, or maybe there’s data indicating superior performance from a UX perspective.
Regardless of the particulars, let’s assume that the result of this discussion is an overall preference for the Property variation. From this perspective, we can observe that:
![][image5]
- The ‘ratings’ atomic component should be reused for consistency.
- The ‘features’ component should follow the ‘Popular amenities’ layout.
- The ‘overview’ component should be a subheading.
- The ‘map’ atomic component should be reused for consistency.
In this scenario, product and design agree with each other that promoting more consistency in the experiences is beneficial. This, of course, affects the UI code and rightly so; by consolidating these components, we can increase reusability and eliminate redundant frontend code.
But what about the GraphQL schema? Unsurprisingly, there’s inconsistency and a lack of reusability here, too. Let’s compare the queries powering the different overview variations side by side:
![][image6]
First, the elements marked green indicate an overlap between the queries which presents an opportunity for either reuse or abstraction. It appears that consolidating these queries into one could be especially low lift in the case of the Property and Packages overview components, since the only issue there is inconsistent naming that causes bloat in the schema (marked orange):
- guestRating vs. rating
- numberOfReviews vs. totalReviews
- isRefundable vs. canBeRefunded
- amenities { vs. includedAmenities {
In the case of the Activity overview, things become messier. Here, too, we can notice naming inconsistencies causing bloat (orange), but there’s also a bigger problem (marked red):
![][image7]
It seems that fields have been named for each feature. Such a constrained approach doesn’t support reusability and calls for a redesign in the schema.
Clearly, the task of unifying experiences involves not just the design and frontend code, but also the GraphQL layer. But what if there was a way to simultaneously address both issues and, in the process, implement guardrails to prevent schema bloat and promote reusability?
Applying Design System Thinking to GraphQL through Collaborative Workshops
Let’s assume that, at the design level, the Property overview component will serve as a blueprint for the consolidated Product overview pattern component that is going to be incorporated into the design system.
As we know, every pattern component needs to be powered by atomic components to ensure experience consistency:
![][image8]
The atomic components are the reusable building blocks designed to form any kind of pattern component or UI recipe that product stakeholders may require. So what if we co-located GraphQL fragments with these atomic components to promote not just frontend code reuse but also query and schema consistency and reuse?
To achieve this, we need to first hold a cross-discipline workshop, where:
- we discover and zero in on the demand for data,
- all parties agree on consistent naming.
The idea for this kind of workshop goes back to Sam’s GraphQL Summit talk introducing the concept of schema storming. The gist of this technique lies in collaboratively annotating UI mockups to define data requirements, which greatly facilitates subsequent query and schema design. By incorporating Amanda’s experience and insights on working with design systems and GraphQL at the enterprise level, we tried to adjust the schema storming format to a large org scenario.
In the following sections, we’re going to explore how these collaborative workshops could possibly help improve a design system, establish intelligent schema governance, consolidate divergent experiences, and prevent tech debt.
Atomic Components Schema Storm
Since atomic components are the foundational building blocks of our experiences, we need to bring together key stakeholders from across disciplines. This includes:
- Designers from the design system team
- Engineers from web, iOS, and Android
- Members of the GraphQL platform team
- Key product managers with cross-org context
Often designers, engineers, and product people are only together in the room during all-hands meetings or outages. We are proposing to pre-emptively gather these critical stakeholders and discuss data requirements at even the atomic component level, and codify these learnings into GraphQL schema that serves as the source of truth for the system. Any consumers of these atomic components will, by default, adhere to the guidelines established in the schema storming session.
If we collaborate on data requirements early, we avoid fighting similar issues in multiple places (i.e. we only make these decisions once) and we decrease the likelihood of future tech debt.
Let’s now try to imagine what the schema storming workshop would look like for the icon with text atomic component.
Icon Component![][image9]
The basic premise of the workshop is really simple. Using a whiteboard tool like Miro, the relevant stakeholders need to to discuss the data expectations of the icon component and record them as sticky notes placed on the design system screen cap:
![][image10]
Let’s unpack what they could eventually arrive at:
- The icon needs a name that maps to one of the icons available in the design system.
- The icon has an accompanying text.
- The icon has a configurable size.
- To support visually impaired user experiences, an ariaString is provided for screen readers.
- To support persistence of specific variants in a content management system, an id is provided.
Note that the data requirements concern both the visible elements and the non-visible ones, such as accessibility. At Amazonia, designers are responsible not only for the visible design but also for the audible design, making them crucial for this atomic component schema storm session.
Apart from listing the requirements as sticky notes, this is the time to zero in on the naming of these elements. This is a key element of this exercise. The idea is that if all stakeholders are on the same page about what’s called what early on, it will protect the teams from inconsistencies and bloat.
Let’s assume now that everyone agrees that there are no more stickies to be added here. Having contributed to the UI annotation, the designers can then leave the meeting, because, in the second part of the session, the frontend and backend/platform engineers are going to get particular about the type definitions.
Essentially, they need to discuss the return types and nullability of the various fields. In this case, the “Icon with Text” UI component only requires fields with scalar return types, and all types are required, so the work of creating the schema (in SDL form) is easy. A graph relationship naturally forms and is easy to express as a reusable type definition in GraphQL’s Schema Definition Language (SDL).
![][image11]
Link with Icon Component
Let’s now explore another atomic component schema storm example, this time for the link with an icon component.
![][image12]
Again, we start by gathering the right people to discuss the data requirements and record them as sticky notes.
With links, there’s much more than meets the eye:
![][image13]
Note that this time, in addition to design and engineering stakeholders, we likely need someone from product to align on the analytics part.
Similar to the previous example, the engineering stakeholders should eventually arrive at an SDL expression of reusable type and enum definitions.
![][image14]
Ultimately, the GraphQL types defined in these atomic component schema storming sessions should be incorporated into a shared schema library for consumption by backend experience teams. Diligence in defining visible content, accessibility, analytics, and configuration requirements at the atomic component level ensures that all of these aspects are integrated into the shared schema library. As a result, there’s no need to reinvent these requirements each time the teams build something.
Backend teams can now develop with confidence knowing that when they use an atomic component they will not be missing critical elements required for consistent UX across platforms.
Co-locate GraphQL Fragments with Atomic Components
Type definitions feeding the shared schema library are just one part of the equation. On the frontend, we still need to define GraphQL fragments and co-locate them with the atomic components in the design system code library.
Here’s what this could look like for the “Icon with Text” and “Link with Icon” atomic components:
![][image15]
![][image16]
While this may seem like a lot of work to do for each atomic component, it’s fairly easy to plan and iterate on, one component at a time.
Meanwhile, the resulting wins are immense:
- The frontend developer experience is greatly enhanced during the process of composing pattern components because all atomic component schema requirements come for free from the design system.
- We decrease the chance that developers add unneeded GraphQL types; because schemas are frequently very large, developers often add types without first looking to see if the needed type already exists. This leads to duplication and frequently undesired variation.
- We reduce the chance of over/under-fetching because each GraphQL fragment is associated directly and only with the component it supports. (This is as opposed to a common practice which is to write and maintain a GraphQL fragment without reference to the specific component it supports. Making this worse, this fragment frequently supports more than one component, each which has different needs, so as we update data to support one component we forget about the other component and create over/under-fetching in the other component.)
- Coupling data with the component it powers surfaces downstream effects at the right time: if a data field is removed from a GraphQL fragment, it will be clear that the UI element which displays that data also needs to be removed. Conversely, if a UI element is added to the component, it will be clear that a data field to support that element needs to be added.
- We can more-easily fuel design mocks for pattern components, because the majority of data requirements are already known before a fully-fledged design is even produced.
- We no longer have to repeatedly wrestle with accessibility, analytics, and configuration. All too often these requirements surface as bugs in production. Shifting these concerns left lets us figure it out once, and then repeatedly reuse.
The above wins are all made possible by attaching data requirements to the atomic design system components. But to actually realize these wins in production, we need to compose these data-aware atomic components into larger experiences, which we call pattern components.
Join us in Part 2 to see how to put this into action!
—---
Better Together: Cross-Functional Collaboration at the Intersection of Design Systems and GraphQL APIs - Part 2 of 2
A deep dive into how coupling design system components with GraphQL schema through collaborative cross-functional workshops can reduce sprawl, ensure consistency, and enhance productivity at both the UI and API layers.
In part 1, we discussed the relevance of design system thinking in GraphQL schema design. And in fact, we attached GraphQL fragments to our atomic components. These fragment shapes were created in cross-functional workshops, ensuring the result would be sufficient for practically any future use case.
Now that we have leveled up our design system with data-aware atomic components we will compose them into experiences which we call pattern components—the part of the UI which begins to become meaningful to the end user.
Pattern Component Schema Storm
Let’s imagine that we’re now in a situation where:
- We have already gone through the schema storming process for numerous atomic components (preferably all of them).
- We have co-located GraphQL fragments with said atomic components.
Previously, we stated that one of our goals is to consolidate the divergent overview components into more-consistent experiences. To achieve this, we want to create an overview pattern component that’s going to serve all possible overview component variants.
Let’s now explore what this could look like by applying the schema storming workshop format at the pattern component level.
First, we need to collect all the overview component variants that we want to consolidate. This process will vary, but it generally focuses on finding similar experiences across different business domains and client platforms, such as web, Android, iOS, etc.:
![][image17]
At the same time, we have to select and invite the relevant workshop attendees. In this case, it’s going to be:
- Platform stakeholders: web, iOS, Android, and GraphQL.
- A design representative who is well-informed about all product lines and client platforms in question.
- Specific product stakeholders who are most likely to be affected by the experience consolidation.
Two things need to be noted here.
First, this involves cultural change. Just as the emergence of federated GraphQL has required engineers to think more globally (because the supergraph needs to support the entire UI across all platforms and across all UI concerns), now design and product stakeholders also need to adopt a global, cross-platform, and cross-product mindset. Since our scenario uses a design system, we assume Design would play a crucial role as the guardians of a unified user experience.
Second, the product stakeholders’ involvement is likely to depend on the experience divergence. In our example, the Activity overview component is most inconsistent. It is up to a discussion with the relevant product representative to decide if:
- They are on board with the consolidation motion.
OR - They do require and can justify a non-standard component, taking into account the impact of an alternate UI component on the frontend code and GraphQL schema.
Variation Discussion
The first two questions that the attendees of the pattern component schema storm workshop need to discuss are:
- What level of divergence between pattern component variations is unacceptable? (Think of the Activity overview component controversy above.)
- How exactly should the target variations vary? (This will help us to reliably define the requirements for a global Product overview pattern component that can support all variations.)
Collective agreement on these two aspects should allow the attendees to arrive at the foundation for the consolidated Product overview pattern component in the form of a wireframe:
![][image18]
The resulting wireframe of a Product Overview pattern component is made up of boxes that represent atomic components. This simple diagram captures the agreed-upon consistency as a semi-formal output of the variation discussion.
Naming Discussion
With the wireframe mockup consisting of atomic components complete, the focus of the workshop can shift to collaboratively defining expressive names for the various visible data fields that will power the component in question.
By composing our pattern component with atomic components, we get a portion of the naming and data requirements for free, but we still need to define what to call each atomic component in the context of this particular pattern component. Securing agreement on naming between product, designers, and engineers before a line of code is written will ensure the work is done right the first time around. Unwinding poorly named schema concepts in production is very painful, and sometimes impossible.
Once a name that accurately expresses the function of the atomic component within this pattern component is agreed upon, it can be easily tracked in the workshop using a yellow sticky. Similar to the Atomic component schema storm earlier, yellow stickies will represent visible data requirements (which will map to GraphQL fields, as we will explain later).
Early on in the discussion, it’s also helpful to agree on a good name for the component itself, keeping reusability in mind. Let’s add the component name, accompanying input field, as well as the visible atomic component field names:
![][image19]
As the diagram takes shape you can see a data graph forming. And because the various styles and colors of visual annotation map to GraphQL concepts 1:1, it’s very easy to convert this diagram to GraphQL Schema Definition Language for the backend server systems, as well as GraphQL Operations (in this case a query) for the front end.
![][image20]
Combining atomic components to make pattern components sometimes means that the same atomic component will be used multiple times in different contexts within the same pattern component.
Earlier, we discussed the benefits of getting solid on naming in a component especially when there are multiple instances of the same atomic component.
For example, in our mock Product Overview pattern component, the designers plan to use two “Heading” atomic component instances, one for the product title, and one for the summary.
The pattern component schema storm enables all relevant stakeholders to agree on exactly what the various aspects of the component are called. In this case, the top “Heading” should be called the “title” whereas the second Heading would be for the “summary”. You can surely imagine how many synonyms there are for “title” or “summary.” By deciding early on this ubiquitous language, time is saved, and consistency within teams is created.
![][image21]
It’s worth noting that GraphQL thinks of schema as a shared language, or “ubiquitous language” as Eric Evans says in the link above. Without schema storming, in which the different disciplines are brought together, how does a technical organization produce this shared language? We believe the schema storming effort, at both the atomic and pattern levels, allows us to realize one of greatest potential benefits of GraphQL: “a common product language that engineers, designers and all stakeholders can use to discuss and iterate on complex concepts” (source).
Groupings
Sometimes certain atomic components are used in conjunction with one another to help the user meet a goal. These groupings can also represent lists of atomic components. Because we’ve standardized our design system on two levels (Atomic and Pattern), it can help to have some form of hierarchy within the pattern component level.
These groupings help define the function of entire sections of a component, and by taking the time to define them carefully, the engineers in the room get to keep track of the product and design teams’ intent by designing an expressive schema.
We will represent the groupings with gray boxes around atomic components, and we will give these groupings a descriptive name with a gray sticky:
![][image22]
These groupings map 1:1 to GraphQL Type Definitions. Being able to link an on-screen component to its underlying data type visually is a powerful tool. When assembling atomic components into pattern components, designing the schema is quite easy, because so much work has already been done by the atomic component schema storm.
Let’s update our SDL and our client query to see how the groupings apply. Note the use of square brackets “[ ]” to denote lists:
![][image23]Optionality, Nullability, and Error State Discussion
Next up, we need to:
- Define which elements of the pattern component are optional variations to be displayed only for certain product lines.
- Identify the required parts of the component. These would be parts of the UI that if there were no data, the user would be shown an Error message.
- Discuss desired error states.
Optionality
Because the goal of this pattern component is to serve as many business use cases as possible, it is helpful to indicate which sections of the UI are optional. For instance, for some product lines there might not be certain details sections. Another product might not show a price on its overview component.
Deciding on the acceptable variation between two similar user experiences before any code is written means that developers can start with a more clear understanding of the requirements. After all, that’s what we are doing here: “requirements modeling.”
Let’s see how optionality is represented in our diagram:
![][image24]
Requiredness, Nullability, and Error State Discussion
Now that we have defined the acceptable variations, it is helpful to discuss which parts of the component are a must have–that is, if the data for these atomic components does not come back, then the component should not attempt to render, but instead a predetermined error experience should render.
GraphQL responses have the ability to deliver partial successes as well, and the more stakeholders can get on the same page about what kinds of alternative user experiences should be supported the better.
In the case of our mock Product Overview component, our stakeholders have aligned on only two must-have fields to be able to display the component at all: the “heading” and the “summary”.
We will mark those with a red outline to keep track of the decision.
By now, the technical folks in the room can see a pattern. Each new discussion item and associated diagram annotation actually secretly represents an element of the GraphQL specification. And because of the nature of GraphQL as a contract between clients and servers, it also serves as the common language (and a common type system through the use of codegen tools) for frontend and backend engineering teams.
In the GraphQL world, getting nullability and error states right is hard. It takes discussions and time for alignment. By securing this alignment in a workshop setting, we set the engineers up for success with a well-formed contract for their work to adhere to.
When error shapes are known early on in the process, an ergonomic and future proof schema can be developed. For instance, by using a union return type for our experience query, we can potentially load multiple error or partial-render states, depending on the availability of data, and all of this can be done server-side. This pattern enables gradual onboarding of clients to the new error shapes as well, with confidence that the operation will be backwards compatible with previous app versions.
Let’s see how the discussion on requiredness and error state preferences affects our diagram and the resultant SDL + client operation:
![][image25]
Hidden Fields, Accessibility and Analytics
Now that we have field names, groupings, optionality, and error states discussed, all that’s left is discussing any non-visible data required to power this component. Often frontends will require special fields to connect native accessibility APIs. Similarly, analytics data and interaction tracking may require special data fields.
Let’s annotate any non-visible fields with black sticky notes. We can also use groupings (which map to GraphQL types) where it makes sense to gather common items in the structure.
We will also introduce the idea of an Option at this stage. In GraphQL we know these as Enums. But for non-technical stakeholders, discussing an accepted list of options for a given data field is easy to reason about. We can map these to enum value definitions and get our schema for free!
Product may ask for a field or two which records when our component has finally come into view so that we can track how often this component is viewed. This is usually called an “impression”. (In pattern components which have more user interaction than this one, Product may ask for analytics to be recorded in response to various clicks or other user interactions.)
Design may ask for an ARIA string which represents the entire component. This is because they are aware of the flow of the entire page within which this component sits and they know that a screen reader user needs to know when this component starts. This particular pattern component does have a heading but the content of that heading will only be the name of the Lodging property or the Activity or the Package. Instead, design wants the visually impaired person to know that this component is an “overview” so an entirely new hidden field is needed which says (for example) “Overview of Hyatt Regency La Jolla” rather than simply “Hyatt Regency La Jolla”. A sighted user can tell where this component starts and ends but a visually impaired user loses all of that data. In turn, because we have named this field well, i.e. “airaHeading”, the frontend developer knows that this is the first field which should appear in the DOM of this pattern component.
Let’s check out how our hidden data requirements show up on our diagram:
![][image26]
From Sticky Notes to GraphQL Schema Definition Language
At this point in the workshop, all that’s left is for the engineering stakeholders to take the diagram and map it to GraphQL schema concepts. Note that, in this thought experiment, we are working in an organization that has already done schema storming on the atomic design system component level. This means much of the work of figuring out the details of data requirements has been completed already.
Armed with all of the details collaboratively figured out with design and product, the engineering stakeholders can now continue the good work on their own and move on to design the schema.
Now this is where the magic happens. By using arrows to denote type membership, we can easily make a graph out of the stickies of requirements in our diagram:
![][image27]
This graph easily maps to backend service SDL and frontend GraphQL operations:
![][image28]
Now we can start to see the real power of a UI annotation system that maps to GraphQL schema concepts. The sticky notes used to get alignment of key non-technical stakeholders on correct naming can now be used to directly create schema that represents the data requirements.
The yellow and black stickies give us our field names. Red boxes indicate nullability. The green sticky is the query entry point itself. Now, we also start to see some of the compounding benefits from having Atomic Components schema requirements previously discussed and codified.
For server side engineers, they get the data shapes for free for any atomic components via the shared components schema library. Likewise, client engineers will get a GraphQL fragment for free from the design system for each atomic component.
The schema design process for a pattern component is actually quite straightforward. So many of the details of the requirements of the atomic components don’t have to be thought of over and over every time a new pattern component is minted. Instead, developers can quickly create new pattern components to serve new use cases, and have confidence that developers are adhering to best practices for hydrating and displaying atomic components on both the server and the client sides.
Now that we’ve gone step by step through each stage of the pattern component schema storm, let’s zoom out and see the finished diagram:
![][image29]
Using the strategies discussed in previous sections we can easily transform the diagram annotations into schema shapes. The diagram supports the following schema concept mappings:
![][image30]
Let’s now take a look at the entire schema output from our pattern component schema storm. The following image breaks out the details of each UI annotation and how it maps to schema:
![][image31]
Thanks to our diligence in thoroughly discussing a number of concerns across product, design, and engineering teams, the schema now practically writes itself.
Let’s sum up the wins here:
- Atomic component schema shapes are defined in advance, saving time and guaranteeing correct implementation and consistency.
- The “groupings” indicated with gray boxes in the whiteboard perfectly map to nested GraphQL types or lists.
- We know which types (indicated with yellow boxes) shouldn’t affect the UI when returning null (these are the optional variations).
- Bare minimum fields to display this component are decided, and marked non-nullable in schema, meaning if these don’t come back, the error state is triggered.
- The error state is codified in the schema before any code is written and the union return type makes error handling easy.
- If design had not previously considered how they wanted the error state(s) to look, this process guarantees that they will be reminded and they will design those UIs. (It is possible the designer wants nothing to display at all in an error state, but it is important that the frontend engineer is not forced to guess that this is the case and instead receives explicit instructions from the designer.)
Thanks to extensive cross-functional collaboration using the visual medium of a pattern component wireframe, the challenging task of schema design has become fairly straightforward.
One Query to Rule Them All
The wins of schema storming with design system components don’t end at the schema language definition. Let’s take a look at the query that’s going to power the consolidated Product overview pattern component with data:
![][image32]
The green boxes indicate the atomic component schema shapes. Just like with the SDL, it turns out that a large chunk of work has already been done in the design system at the atomic component level, ensuring reusability and correct implementation.
The black box represents fields discovered in the pattern component schema storm that pertain directly to the pattern component, in this case accessibility and analytics concerns. Rather than being an afterthought, these have been well-thought out before a line of code is written.
The red box indicates the union return type that facilitates error handling.
And finally the red circle at the top is the piece of data that allows our one query operation to support multiple variants. This input type expects a Product ID to be passed in. Upon receipt of this information, the backend generates different data to respond with. For example, in our case the backend will pass back either a property, packages, or activity data set.
But the biggest win of all is that we now have one, universal query that can power all of the pattern component variations, eliminating schema bloat, query duplication, and component bloat:
![][image33]
Essentially, what we’re proposing here as a best practice is a many-to-one relationship between the UI and the schema. In our experience, too often this relationship is in practice one-to-one, which creates bloat and inconsistency in both the schema and the UI.
Observing that certain components are similar unlocks the opportunity to remove this bloat and ensure consistency. By using a visual medium to collaboratively determine the data demand of related UI elements and how they vary between one another, we can greatly facilitate the design of a clean and precise schema, as well as produce leaner UI code.
Final Words
The consensus among the GraphQL community is that the successful introduction of this new layer in the stack requires cultural change in organizations.
We believe that one of the biggest blockers to the desired cultural change stems from the fact that GraphQL platform teams can often be too far removed from the design/product/business side, perhaps even siloed towards the backend. Without intelligent governance and collaboration across disciplines, individual teams’ unchecked contributions to the graph can mess things up, leaving the platform team scratching their heads, as they weren’t involved in the feature request in the first place.
Since GraphQL is not just a technical concern, but is an indispensable ingredient to products and/or business processes, what’s needed then is more cross-functional collaboration to inform schema design earlier on. To this end, we’re proposing schema storming as a collaborative workshop format that aims to facilitate healthy discussions between product, design, and engineering.
Just like developer tooling can support technical schema governance, a collaborative, workshop-driven process is THE tool for the GraphQL platform team to drive organizational governance, curate a healthy, scalable supergraph, enable leaner UI code, and establish themselves as a core part of enterprise architecture.
About the Authors
![][image34]
Amanda Olsen
Senior Frontend Engineer
Amanda has a passion for efficiency and high-level thought. As a Frontend Engineer for 17+ years, she has had to learn to think from the user’s perspective while coding/building systems. What threw a curveball into all of this was the rise of GraphQL and also of design systems, occurring at about the same time. She scoured the internet for how to appropriately relate these two and came up empty handed. Upon meeting Sam at the GraphQL Summit, many concepts came together and they decided to construct this article, which would have been the article she was looking for.
![][image35]
Sam Combs
Senior Full-Stack Engineer
Sam was exposed to computers at a very young age, sparking a passion that he can now proudly call his livelihood. He cut his teeth as a full stack developer building organic farm management solutions which gave him experience in working with large data sets and creating UIs for non-technical users. Introduced to GraphQL in 2018, Sam currently works at Xolvio, the official Apollo GraphQL professional services partner, working with large enterprises like Wayfair, Ford Credit, and more.
Unsure if this image should be used at all or where.