Six Pillars of Frontend Development
Early 2019, I was speaking at the Tweakers Developers Summit about Frontend Architecture. This is where I introduced my "Four Pillars of Frontend Development". Later, I added two more pillars that I believe are essential for developer productivity and organisational agility. The below article I wrote in 2019, but I never bothered to publish it.
While I would describe my approach to Frontend Development differently these days, I still believe there's value in these pillars. Actually, they remain part of my vision, I just would tell the story differently. Watch this space for an updated description on my vision.
IntroductionAs development teams, we want to minimise the time we spend on framework updates and migrations, and maximise the time we spend on new functionality. We want standardisation, but at the same time, we want to be innovative and be able to quickly jumpstart a new application or experiment. We also may want to try new frameworks for internal projects. We want to be able to quickly scale from one to dozens of teams working on one application. These are difficult and sometimes conflicting requirements Senses had to fulfil. In order to stay sane, I came up with the Six Pillars of Frontend Development. These Six Pillars do not tell you anything about what popular Frontend Framework to use. The idea is to always stay as framework agnostic as possible.
The Six Pillars are: using TypeScript, building a framework agnostic Core library, using Webcomponents for our presentational components, practising Continuous Integration and performing Continuous Delivery/Deployment.
Pillar 1: TypeScriptTypeScript is a programming language developed and maintained by Microsoft. It is a syntactical superset of JavaScript and adds static typing to the language. In order to run TypeScript code in a web browser, it needs to be transpiled to JavaScript. In practice you hardly notice this transpilation step, as this is a very fast process that hardly needs any configuration.
While JavaScript, when used sensibly, is a fine language to develop Frontend Applications, for Enterprise scale development you want advanced tools to perform static code analysis to detect vulnerabilities and errors early, and you also want tools that help you refactor your code.
TypeScript makes all of this possible. When you use smart IDEs like Visual Studio Code or WebStorm, a developer's productivity will get a boost because these editors analyse your code while typing and give useful hints for code completion, and warn you of any errors way before it gets executed. Also, when you want to refactor code, like renaming functions, moving code around, or changing the signature of your methods, these editors are able to apply these changes to any references in your codebase.
Senses is written in TypeScript entirely.
Pillar 2: Core libraryA framework agnostic Core library is a collection of TypeScript classes that have no dependencies on any of the popular JavaScript frameworks, that contain functionality which can be reused in many different projects you develop.
In a Core library you want as little dependencies on third party libraries as possible in the first place. However, you should be pragmatic here and don't completely rewrite all kinds of useful third party libraries. The focus of your Core library is that it should outlive any hype. Just don't go and use the newest, coolest new invention.
The goal of your Core library is to have a rock solid foundation of your most common functionality. Once you have a stable version, it should not suffer from the next Open Source project that gets discontinued. It should be compatible with any framework you would ever want to use.
If you should desire to have a closer integration with a framework, you can add a layer on top of your Core library to make it play nice with your framework of choice. In Senses, we've extended all Senses Core classes specifically for Angular, calling it Senses Common, and integrating it with Angular's dependency injection system by making them @Injectable(). In Senses Core we've decided to make all asynchronous functions return Promises. The Angular ecosystem however works mostly with Observables. The extended, Angular specific version of each module simply turns all Promises into Observables. In short, all actual logic resides in Senses Core, we've just added a separate, thin layer of Angular integration by making all modules Injectable and let all asynchronous functions return Observables.
Pillar 3: WebcomponentsWhen we say Webcomponents, most of the time we actually mean components created using the Custom Elements specification that allow you to create new types of DOM elements. By now, all evergreen Browsers support the Custom Elements specification, and for older browsers, there are polyfills available, so you can safely use them in your projects. Apart from some caveats, you can use Webcomponents with all major frameworks.
Before Webcomponents were available, you had to implement all of your UI components using the technology you'd use for the rest of your project. When jQuery was all the hype, you'd create your UI with jQuery. If you decided to use AngularJS, you had to rewrite all of them to being AngularJS directives. Your UI library was always bound to the framework you would use for the rest of your web application. It's tempting to continue down this road by rewriting everything in Angular, React, Vue or whatever your framework of choice is these days.
As said, being as framework agnostic as possible is one of the ground rules. Being future proof is the main reason behind this. With Webcomponents, we finally have a future proof way of materializing our Design System. Now that Custom elements are part of the HTML standard (and bits of it part of the DOM standard), we can safely assume that as long as the web is based on HTML, we can count on Webcomponents to be a future proof solution.
At Rabobank, historically, our UI components were considered not to be part of our Frontend Platform, and therefore were officially not part of the Senses platform. In recent years however, teams have developed Senses Frontend Components (SFC) with this pillar in mind. On top of SFC, we also have Senses Building Blocks (SBB) which provides ready made composite components for common UI patterns. These too are written as Webcomponents.
Pillar 4: MonorepoA Monorepo is a version control setup, where many projects are stored in the same repository. It enhances the ability to share code, simplifies dependency management, but most importantly, it significantly helps with large scale, global changes in your code. I've already covered the reasons why we're attempting to use a Monorepo for Frontend Development at Rabobank in an earlier article. The pilot I described in this article actually never ended. In hindsight, we started our pilot phase too early, and we silently decided to keep the status "pilot" on our Monorepo efforts until after we release our first big Web Application (RBO) on Senses 2.0. Fast forward four years, and we still work in this monorepo setup without ever having made an official decision on it.
We've been experiencing quite some issues in our Monorepo, but little of these issues stem from the fact that we chose a Monorepo setup. For many people though, it's hard to see the difference between the Monorepo and the issues we see with CI/CD (more on that later). These two however are unrelated. Even in a multi-repo setup, we would have experienced flaky tests and deployment issues.
So using a Monorepo is the Fourth Pillar. As said before, the Six Pillars are not just about code, it's an approach to creating and maintaining Enterprise Frontend Development projects. The fundamental need to manage global changes across a Web Application that is built by numerous teams, requires the ability to access, modify, test and release all parts of it as one.
Global changes across multiple features, or even multiple apps, unfortunately happen in the Frontend world more often than in the backend world. This is on one hand due to the fact that breaking changes in third party libraries and frameworks still happen quite often, but on the other hand, you want design changes in your Web App to be applied to everything, immediately. Style changes in the middle of a customer journey are a terrible user experience.
Another important benefit of a Monorepo setup, is the ability to share features and libraries between applications. Of course this can also be achieved by building them as regular dependencies, but by performing trunk-based development in our Monorepo, dependencies are much easier manageable.
I have been asked on multiple occasions "What if it is decided that Senses is not going to continue with a Monorepo?". It would of course mean we'd have to redesign some elements of our setup to facilitate each feature being developed in their own repository. It will be a serious undertaking, but not impossible to do. It's just a technical challenge. The thing is, we'll be back in the situation where global changes and dependencies will be much harder to manage.
Pillar 5: Continuous IntegrationWith Continuous Integration (CI from hereon) we mean the practice of integrating all developers' code to test changes as early and often as possible. This can only be achieved by having meaningful, automated tests that cover most, if not all, code in the repository. I say 'meaningful', because all too often we see useless tests or tests that may work on a developer's local machine, but nowhere else...
CI is not a goal in itself, and there are multiple ways to implement it. By practicing CI, you provide a fast feedback loop for developers to know if their changes didn't break anything, and it is a prerequisite for Continuous Delivery or even Continuous Deployment. The fast feedback is essential for a developer's productivity. If the change a developer makes is only discovered right before, or even worse: right after a deployment, you need to pick up work that you thought you had finished already, frustrating the development cycle.
With the Six Pillars we intend to speed up Frontend Development, as well as improving the quality of our codebase. Therefore Continuous Integration using the Testing Pyramid is the Fifth Pillar.
Pillar 6: Continuous Delivery/DeploymentWith Continuous Delivery, we mean that after integrating all changes on the Master branch, a new build artifact is created, ready for deployment at any time. Deploying an application should be as simple as a click of a button, but since this is defined as a separate step, it's possible to manually intervene and perform any necessary validations on the working of your App.
Continuous Deployment means that every successful build is actually deployed to the production environment - automatically. It is actually the next logical step after Continuous Delivery, but a word of caution is in place here. If you want to continuously deploy all builds, you need to be able to trust your tests blindly. You need to have enough coverage of your tests, and you need to make sure your tests are reliable, meaning you weed out any false positives or negatives.
Within the 6 Pillars, you can choose whether you practice Continuous Delivery or Continuous Deployment. The choice depends on your requirements, your preferences, but also on the maturity of your testing infrastructure. But the minimal level is to practice Continuous Delivery, so you are able to release regularly, contributing to the fast feedback loop to developers.
At Rabobank, when we started out with the renewed Frontend Platform, we deemed our tests not good enough. We therefore started out with Continuous Delivery. Once we grew more and more convinced we could trust our tests, we moved to Continuous Deployment.
ConclusionAfter reading all this, you may get the feeling all of this has nothing to do with Frontend Development. Much of it is just general good practice on any kind of development. However, for backend development I would not necessarily choose a Monorepo setup (to name an example). The nature of a Single Page Application with many features in it, is such, that in production, it always turns into some kind of a monolith because it happens to run as one single application on the end user's machine. This makes it much more important to manage a Frontend Application as one: have a Single Version Policy enforced by the Monorepo, manage cross-feature (style) changes with ease using AST scripts, etcetera. That being said, Software Development principles like Continuous Integration are just as useful for backend projects of course.