Building Scalable Frontend with Composable Components

Building Scalable Frontend with Composable Components

The internet has become an essential part of our lives, with over 5.4 billion users – a staggering 67% of the global population. Since 2018, this number has skyrocketed by 45%, and the pace is only accelerating.

This explosive growth isn't just about numbers, it's about a fundamental shift in how we interact with the digital world. Users are becoming increasingly demanding, expecting seamless, intuitive experiences delivered at the click of a button. The rise of AI-powered content creation further fuels this demand, with users generating and consuming personalised content at an unprecedented rate.

This isn't just a technical hurdle, it's a strategic imperative for businesses to remain relevant and competitive. The future belongs to applications that can anticipate needs and deliver exceptional, personalised experiences, all while scaling rapidly.

However, the challenges present an exciting opportunity for those who embrace agility and composable solutions. We've entered the age of composable scalability, where modular, adaptable building blocks empower developers to craft future-proof applications. These innovative components don't just survive ever-changing user demands; they embrace them, constantly evolving and adapting to deliver experiences beyond expectation at an unprecedented scale.

Scalability in Atlassian’s Products

Atlassian's trajectory offers a compelling case study in scaling and evolution, vividly illustrated by its ever-expanding product suite. Founded in 2001, the company launched two flagship products: Jira in 2002, designed to streamline project management, and Confluence in 2003, fostering seamless collaboration and knowledge sharing. Far from remaining static, Atlassian has consistently shown remarkable flexibility and agility in adapting and scaling its offerings to meet the evolving needs of its users.

Jira’s evolution over the years exemplifies this adaptability. To date, there are seven versions of Jira, each designed for specific workflows and industry requirements. Jira Service Management, launched in 2013, was created for IT service desks. Jira Work Management, introduced in 2020, was developed for business teams. This diversification is a direct result of Atlassian's commitment to adapting its products to meet the modern worker where they are and anticipate their future needs.


Atlassian's scaling prowess extends beyond features and functionality, deeply ingrained in its ability to support a vast and ever-growing user base. Atlassian proudly serves over 260,000 customers worldwide, with 85% of customers adopting its cloud platform as of 2023. This widespread adoption underscores Atlassian's expertise in data scalability, confidently managing the demands of diverse workflows and massive data volumes with unwavering reliability and performance.

What is Scalability?

“When a company scales, its products and tools need to scale right along with it.”

Here's a closer look at the three key dimensions of scalability that we prioritise to ensure Atlassian tools evolve with your business:

  • User Scalability: Ensures seamless collaboration for teams from 10 to 35,000, maintaining high performance and speed, exemplified by a customer managing 10,000 IT projects with 35,000 users in Jira.
  • Feature Scalability: Enables progression from simple tasks to intricate projects with scalable workflows, adapting tools for evolving processes and methodologies. Demonstrated by Spotify's management of over 50,000 Confluence pages for global teams.
  • Global Scalability: Facilitates collaboration across borders and time zones with consistent performance and localized content, adhering to international data laws for global security and compliance, as seen with Atlassian's usage by over 225,000 customers across 190 countries.

As products scale, they demand multidimensional scalability: operational efficiency for growing workloads, strategic alignment with evolving markets, technological advancement to stay competitive, ecosystem integration for broader IT compatibility, and enhanced security against emerging threats and compliance demands.

Scaling products involves addressing different aspects at various times and with varying intensity. While there are well-known strategies for scaling backend aspects like APIs, databases, and hosting services, scalability in the frontend presents its own unique challenges. How does this concept of scalability translate to the frontend of product implementation? This question often leads to exploring different approaches and solutions specific to frontend development.

Scalability in Frontend

Scalable front-end components in web development refer to the elements of a web application that can efficiently adapt and perform under varying conditions, primarily in two key aspects:

  1. Feature Scalability: This involves the capacity of components to accommodate an increasing number of features without compromising performance or maintainability. As new features are added, the codebase grows, and components must be designed to handle this growth. Modular designs, Lego style, enable the easy integration of new functionalities and the efficient modification of existing ones.
  2. Data Scalability: This pertains to the ability of components to manage and present varying volumes of data effectively. Whether it's handling data from multiple APIs or dealing with large datasets from a single source, scalable components should be able to process, render, and display this information in a performant manner. This might involve strategies like pagination, lazy loading, or efficient state management to ensure that the user experience remains smooth regardless of the data load.

In summary, scalable front-end components are those that can grow in terms of features and data handling capacity while maintaining high performance, usability, and code manageability. This adaptability is crucial in today's dynamic web environment, where user needs and technological capabilities are constantly evolving.

Scalable Component Design

To build applications that can grow and adapt, implementing core guiding principles is pivotal. These principles provide a foundation for teams to develop components that are scalable, maintainable, and performant. They act as a blueprint, ensuring solutions evolve sustainably as products and their data requirements expand.

Guiding Principles

Principle 1: Strive for self-contained components. Each component should include everything it needs to function properly, including its code, data, and any necessary assets.

Rationale:

  • Enhanced Flexibility and Replaceability: Self-contained components decouple functionality, allowing for features to be easily modified, replaced, or rebuilt without impacting other parts of the system.
  • Boosted Agility and Development Speed: Developers can work on and deploy isolated components independently, streamlining development cycles and improving time-to-market for new features.

Implications:

  • Modular Design: Developers should create components that are modular, managing their state and dependencies through well-defined interfaces.
  • Data Communication: Components should be designed to receive context or data in a predictable manner, maintaining their functional integrity across various scenarios.
  • Composability and Responsibility: The design of self-contained components should facilitate the assembly of complex views while preserving each component's individual responsibility.

Principle 2: Unified way of Data Handling.

Rationale:

  • Enhanced Data Consistency: A unified approach ensures data integrity and consistency across all components, reducing errors and inconsistencies.
  • Improved Component Interoperability: Components can seamlessly collaborate and exchange data with a shared understanding of data structures and formats.
  • Simplified Development and Maintenance: Developers can focus on core functionalities without constantly adapting to varying data representations.
  • Optimised Performance: Unified data management can facilitate efficient caching, synchronisation, and data manipulation, leading to performance gains.

Implications:

  • Standardised Data Schema: Implement a standardised schema for data representation, ensuring all components adhere to this uniform structure when dealing with common data types like user information.
  • Consistent Data Access: Ensure all components access and utilise data in a consistent manner, relying on a single, defined schema or interface for each data type.
  • Component Design and Integration: Design components with the expectation of a consistent data format, facilitating easier integration and interaction within the broader system.

Principle 3: Promote Component Discoverability

Rationale:

  • Faster Development and Composition: Discoverable components allow developers to quickly find and integrate existing components into complex views, accelerating development and fostering flexible UI creation.
  • Enhanced Platformisation and Reuse: Easy component discovery facilitates sharing and reuse across teams and projects, contributing to platformisation efforts and maximising code efficiency.

Implications:

  • Adherence to Standards: Developers should adhere to established guidelines and standards to ensure components are understandable and easily discoverable across teams.
  • Isolated Deployability: Components should be deployable individually and capable of being viewed or interacted with in isolation, similar to Atlaskit components.
  • Comprehensive Documentation: The purpose, communication interfaces, and usage instructions of components must be thoroughly documented to enhance their discoverability and usability.

Principle 4: Enforce unidirectional data flow and parent-child isolation

Rationale:

  • Unidirectional data flow: Developers should focus on unidirectional data flow and parent-child isolation to promote component flexibility, maintainability, and testability by minimising dependencies and preventing unintended side effects.

Implications:

  • Data Flow Optimisation: Components should only receive data and actions from their parent through well-defined props, avoiding direct access to the parent state or methods.
  • Controlled Interactions: Unidirectional data flow simplifies managing component interactions and prevents unintended side effects by clearly defining how data flows between components.
  • Enhanced Testability: Unidirectional data flow allows components to be easily tested in isolation by focusing on their inputs and outputs.

Examples from Atlassian Products

Few examples of how we, at Atlassian, follow these guiding principles to create Scalable frontend components.

Issue Fields

At the core of Jira's functionality are the Issue View, Issue Create, and Issue Transition features. These are powered by a variety of Issue Fields such as the Project Field, Assignee Field, Reporter Field, Due Date Field and many more. Jira provides a vast selection of fields, over 80 different types of fields, enabling users to tailor their interfaces to their specific requirements.

Each Issue Field is designed to be intuitive: they present a read-only view on the Issue View by default, which can be transformed into an editable state with a simple click. Conversely, during Issue Creation or Transition, these fields are editable from the outset. The fields themselves are diverse in their design, accommodating a range of inputs from text fields to selections made via dropdown, radio buttons, checkboxes, and cascading selects, along with date-time pickers. The Assignee Field, for instance, is enriched with avatars for each option and a typeahead feature to swiftly locate people.

To prevent redundant development and maintain a consistent user experience, our approach focuses on creating modular Issue Fields as discrete components. They encompass all necessary functionalities internally, such as:

  • Data Fetching: Each field is responsible for retrieving its own data, ensuring that it always displays the current information.
  • Validation: Built-in validators check the data entered by users to maintain data integrity.
  • Error Handling: Fields autonomously manage errors, providing immediate feedback to users.
  • Data Schema: Fields maintain their own schema, dictating the structure of their data.

Breaking down further, each field type caters to specific interactions: Edit Only for data entry, View Only for display, Inline Edit for dynamic toggling, and Form Field for structured input.

/* This is a basic skeleton example of Edit Only Select Component 
*  with its query to fetch options 
*/
const SingleSelectEditOnlyField = () => {
  const suggestionsData = useLazyLoadQuery<SingleSelectEditViewQueryType>(
        graphql`
            query singleSelect_suggestionsQuery(
                $id: ID!
            ) {
                node(id: $id) {
                    ... on JiraSingleSelectField {
                            edges {
                                node {
                                    value: id
                                    label: value
                                    optionId
                                    isDisabled
                                }
                            }
                        }
                    }
                }
        `,
        { id: props.fieldId },
    );
    
    return (
        <Select
            options={suggestionsData.node.edges}
        />
    );
}        

As shown in the above example, the SingleSelectEditOnlyField defines the Data Schema and the UI associated with it, within the component.

Individually, these components function effectively for their intended purpose. Collectively, they form a composable architecture that aligns with the scalability and performance needs of Jira. This design allows for individual components to be developed, maintained, and enhanced independently, underpinning the seamless and efficient evolution of Jira's capabilities.

As shown in the above example, the SingleSelectEditOnlyField defines the Data Schema and the UI associated with it, within the component.

Individually, these components function effectively for their intended purpose. Collectively, they form a composable architecture that aligns with the scalability and performance needs of Jira. This design allows for individual components to be developed, maintained, and enhanced independently, underpinning the seamless and efficient evolution of Jira's capabilities.

Validation Engine

The Validation Engine stands as a cornerstone in data migration processes, meticulously designed to scrutinise and validate the diverse landscapes of data transfer before initiation. It is equipped with a dynamic range of Validation Checks, Reporting, and Remediations, each attuned to the specific requirements of different migration projects. With the growth of our product suite, the intricacies of these migrations intensify, necessitating a Validation Engine that can dynamically scale and adapt to these intricate ecosystems.

To meet these demands, the engine is built on the principle of composability. Each component is self-contained, ensuring easy integration and scalability. This modular design allows the Validation Engine to evolve alongside our products, maintaining efficiency and reliability even as migration challenges become more complex.


The architecture of the Validation Engine is an example of the principle of composability in complex systems. Central to this architecture is the Validation Engine Core Component, which operates as the orchestrator, integrating configurable modules to suit the varied data migration needs of the feature teams.

This core is where the selection and configuration of modular Validation Checks take place. Teams can choose from an array of checks, such as space conflicts or public access validations, and couple them with corresponding Remediation and Reporting components.

In practice, this means that feature teams can compose a version of the Validation Engine that precisely matches their feature’s requirements. The engine's modular components not only streamline the validation workflow but also scale with the product, adapting effortlessly to the increasing complexity of data migrations. This modular and scalable approach underpins the engine's capability to support our evolving product suite while preserving performance and reliability.


In the above two examples, each element is crafted to perform autonomously yet integrates seamlessly when synchronised with others. This integration offers unmatched versatility — components can be modified or interchanged without disrupting the system's overall functionality. Such adaptability is key to the system’s longevity and is instrumental in supporting the increasingly complex needs of our Products, ensuring that the engine remains an integral asset for our migration teams.

What next?

As the digital landscape transforms with interconnected devices, virtual worlds, and personalised experiences, user expectations will soar. Applications must be more than functional; they need speed, adaptability, and the ability to meet individual needs. This demands a fundamental change in how we build products.

The answer lies in composability and performant scalability. Building applications from pre-tested components unlocks agility and innovation. Imagine apps constructed like Lego sets, with features that fit together seamlessly, evolve easily, and perform flawlessly as they grow.

Composability empowers rapid innovation, real-time adaptation, and faster value delivery. Performant scalability ensures applications handle increasing data and user demands without sacrificing responsiveness. This keeps our creations robust, future-proof, and adaptable.

In this dynamic future, we must adopt these building blocks for a better digital experience. Let's prioritize composability and performant scalability – not just for technical prowess, but to create adaptable, maintainable, cost-effective, and future-proof applications. By doing so, we create a world where technology bends to our needs, not the other way around.

As we look towards the digital future, let us remember: Scalability isn't just about size; it's about agility, efficiency, and the freedom to evolve alongside our users. By embracing composability and performance, we can build applications that not only survive the future but thrive in it.

Sujata Garera

Digital Creative Designer | Researcher | Prompt Engineer | Staff Engineer

1 周

Interesting read Prithveesh Goel. When we consider validation with respect to data integrity, do these checks kick in at both the point of data entry and data storage to ensure integrity is preserved throughout the flow of a data point in the system ?

回复
Sanjay Singh Bisht

Frontend Engineer 1 at Amazon

6 个月

This is pretty good read. Just one query: don't you think composing components can make it difficult to pass props sometimes. E.g. if hierarchy increases props drilling situation may occur. Second, when we compose components into parent and child there can be situations that parent has multiple children but props passed from parent are not required by all children and in that case heavy computation children will re-render unnecessarily. Third, composability between parent and child will be direct and their relation will not depend upon abstractions right so I suppose it violates dependency injection principle. Let me know your views on the same.

Mrityunjaya Shukla

startups enthusiast

6 个月

Pretty useful insights , the guiding principles will help the developers for sure. Shared this article with the team...nice article Prithveesh Goel , thanks

回复
Yash K.

Software Engineer at Nike | AWS cloud, Spring Boot, Java, Microservices

6 个月

Considering unified data management, if components collaborate and share data, would that not act as a bottleneck for other components in cases such as locks where in one components bombard db instances with multiple hits?

回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了