Top 3 Things Charter Learned About Reusable Designs and Components
This is a guest post by Geoffrey Cullins, Sr. Director, Portal App Development at Charter Communications.
Modern web app development is complicated. Even when you go it alone, you’re faced with innumerable decisions. Which libraries and frameworks will you choose? How will you structure your code? What syntax rules will you follow? How will you build and deploy the app?
Oh, yeah, you also need to design it. For a browser on a desktop machine. And a tablet. And a phone. Lot’s of different types of phones, actually. Don’t forget about accessibility. Oh, and SEO. And so on…
We’re not usually a lone wolf, however. We work as cross-functional teams including design, product, engineering, test, dev ops, etc., and adding more cooks to the kitchen isn’t a recipe for reduced complexity. Throw in more than one app and, well…you get the picture.
At Charter, we develop and support over 100 independent applications across our entire organization. In addition to browser-based web apps, we also build native applications for phones (e.g. Android & iOS), set-top boxes (the interface on your cable box), and 3rd party devices (e.g. smart TVs, video streaming devices, gaming consoles, etc.). When you consider all of the people involved in designing, defining, and developing such a large, collective effort, it quickly becomes clear that you need a systematic way of injecting some sanity. Otherwise, things could spiral into a disjointed, inconsistent, and confusing experience for your development teams and, most importantly, your end users.
We needed ways to maintain design and functional consistency while reducing redundant effort. The tools we added to our toolbox needed to support those overarching goals with the least amount of overhead and maintenance. To answer the challenge, we’ve implemented solutions that maintain consistency and reusability for every step of the SDLC, while remaining flexible so the dev teams can make the technology choices that are the best fit for their needs. For our Account and Support web applications we chose Angular, with all of the benefits it gives us out-of-the-box that enable us to best leverage our approach to reusability and scalability.
What problems are we trying to solve, and what solutions have we implemented (so far)?
Problem: How do we maintain consistency of UI/UX design across many applications?
Solution: Produce a UI/UX design component system to serve as the ancestral source of visual elements that are included in consuming applications.
Let’s go fly a kite!
Kite is the UI/UX design component system we developed that helps the application teams stay aligned. While not revolutionary, we took an established pattern and tweaked it to make it more flexible and available for an unknown variety of applications. The following image illustrates Kite at a high level:
Design components are created and published using vanilla JavaScript. These are then ported to framework-specific, shareable components that rely on the ancestral vanilla implementation for future changes.
But, porting takes a long time, right?
In our case, Angular made the consumption and exposure of these a breeze. We rely heavily on Angular’s opinionated structure of modules and components to produce easily consumable and composable elements with predictable look and behavior. It allows us to directly instantiate our vanilla implementations and then sprinkle some Angular dust on top to enable things like transclusion and importing specific CSS requirements, while getting the benefit of any bug fixes made in the future to the vanilla implementation.
Problem: How do we reduce duplicate efforts for similar cross-application business requirements across separate, unique applications?
Solution: Identify similar requirements during the design and definition stage, and build shareable components in a way that accommodates the unique use cases.
For example, multiple web apps use exactly the same header treatment, except for the logo and some copy. In another example, all web apps use essentially the same modal, but with variable copy, buttons/actions, forms, etc. More complex shareable components contain other, smaller shareable elements, such as buttons, form elements, pop-up help, etc.
Leveraging Angular’s directives and dependency injection features, and adhering to Angular’s well documented and widely adopted best practices for application structure, enables us to build highly flexible, independent components that can accommodate differences between use cases while maintaining consistency for core look, feel, and functionality. Used in conjunction with our Kite design system, we have created a library of consistent, composable components that are available for consumption by any Angular applications we build and deploy to production, whether they’re customer-facing or for internal use.
Problem: How do we force the separation of application-specific business rule logic from visual component UI/UX code?
Problem: How to maintain consistent application of best practices and overall architecture across many distinct business applications?
Solution: Both of the above problems are solved, in-part, by our move to a monorepo for source control of both larger applications as well as shared componentry.
In order for an application or shared component to be included in the monorepo, it needs to adhere to the standards established by Angular’s documented best practices. We make heavy use of linting rules, robust documentation, and automation during the code review and deployment process to keep ourselves in-check.
So, what’s next?
The approaches we’ve take up to this point have had a big impact on the speed of development and quality of the web applications on which we work. But, there’s still a lot we can do to get even better. Some open problems and potential solutions on our plates include:
Problem: How do we expose shared components to consuming applications that live outside of the monorepo?
Proposed solutions:
- Automate the export of component distribution files to a separate repository for consuming applications to add as a dependency of their own;
- Evangelize the monorepo, with the goal of encouraging more teams to migrate their applications into it.
Problem: How do we steamline change management for shared components (i.e. what needs to be done, who will do it, how will it be communicated, how will it be tested, how will it be consumed, etc.)?
Proposed solutions:
- Automated pipeline jobs that run unit tests of any component-consumers that also live within the monorepo;
- Automated pipeline jobs that create test tickets for any component-consumers that also live within the monorepo;
- PR/MR reviews by a select group of senior-level Engineers;
- Specific distribution lists to which changes will be communicated.
Problem: How do we manage the flow of updates for shared dependencies/components?
Proposed solutions:
- Each shared component should be treated as a mini Angular application with its own set of dependencies that need to be recognized by consumers (both within and without monorepo);
- Regularly scheduled “patch” (update) days to make releases predictable.
What do you think?
We know there is a long road ahead to keep refining and improving, and would love to hear from you! What are your approaches? What do you think of ours? What are challenges you’re facing for which you haven’t found a solution that’s “just right?” Let us know in a comment on this post!