Let me start with a question, how many software engineers do you know that enjoy Frontend development?
One question has always baffled me – Why is frontend universally unloved?
Let’s throw a few suggestions:
Is it the messy CSS and inability to vertically center a text in a box easily in 2019?
Is it the inconsistent browsers’ feature support and rendering?
Is it the “move this by 3 pixels to the left” expectation of people who’ve never done it?
Is it the tooling and the amount of frameworks required to do the simplest things?
Reality is, it’s all of the above.
The more people I ask, the more frequently I hear – I’ve just always been a backend developer.
I recently set up a pipeline for a web application from start to finish.
To illustrate where web app frontend development is at (September 2019), I put together this table.
If I told you, in my eyes, this is the absolute sensible bear minimum for a web app, that’s not from a standard template using Jekyll, WordPress or such. Would you believe me? Unlikely, but let me try to convince you.
Frontend is Complex
Looking at the image above, you can argue that I’ve included backend and SRE elements. Sure, but if you design a small project at home, you would still need to deploy it. If you want to store session data, you might opt in for Redis, instead of cookies. Flask is also Python-based, but for our illustrative purposes it’s not important. I agree the above table is not perfect. The point is – when is the last time you started a backend project and needed that much decision making in advance about frameworks that are not in the standard library. What confidence do you have the stack you’ve picked today will be the right one in 5 years time?
What makes frontend fundamentally different from backend is the amount of control over the moving parts which you need to integrate and make for a seamless user experience. You are also heavily limited by language of choice. You can create a User Interface in Python, but the ecosystem for that purpose is not there.
Frontend is an amalgam of multiple backends, teams, their design philosophies and system infrastructures. You are definitely not in complete control of the downstream. Yet you need to make it all seem as one coherent interface to the user. End goal being nobody realizing, one of the endpoints is 30 years old written in a language that nobody understands and another a 2 week old one that is not battle-tested.
You also need to think of security – HTTPS, SSO, 2FA, GDPR. I can continue with the acronyms for days.
Testing backend is certainly easier, you have a limited set of inputs, you confirm the outputs. Testing something that can have an infinite amount of mouse movements, states, rendering behaviors, screen resolutions, network inconsistencies and a random sequence of steps is another ball game. Can you guarantee that none of the endpoint have no side-effects? Which is probably why there’s so many testing frameworks. You can see the list on BestofJS and that one excludes integration testing.
Fragmentation hidden behind choice
Micro Frontends Design
A lot of the above issues exist, because UI development is this umbrella covering the entire backend. In many cases, backend teams wouldn’t have full understanding on how their endpoint is used in the user interface.
I strongly believe the future is Micro frontends. It’s the principles of microservices architecture extended to the frontend. Each team owns the entire pipeline, from data store, backend, endpoints up and to the displayed user interface component. ReactJS, for example, is well suited for this type of design. I’m better at doing diagrams than writing, so here’s a comparison between the current microservices and the new micro frontends designs.
Micro Frontends Design
Build-time vs Run-time integration
In all cases you need a platform application, which will bring everything together. It also includes basic elements, like header menus, about and FAQ pages. The question is how it will bring it all together.
This is when each team deploys their components as a part of a NodeJS package to the company’s artifactory and then the platform application imports them. The good part is that it deduplicates the dependencies when minified, but the teams’ deployments are dependent on the main app’s team release schedule.
My personal choice is build-time integration.
The main reason is to enforce consistency. The platform team will also be in charge of the general end-to-end testing, as well as enforcing uniform styling on the components if one team has not followed procedures. Complete autonomy sounds great, but also allows to get inconsistent user experience very fast and for things to spiral out of control. Secondly, even with run-time integration, you need to negotiate interfaces and state, so even though you can release any time, your feature still needs to live behind a feature flag or branch out. So in reality, you don’t really lose that much deployment autonomy. Also, with a good CI/CD pipeline, any component contributing team should be able to do a test branch deployment with the platform app and be able to test their component in real-world like scenario as part of the whole. Lastly, as you download the dependencies once, it reduces the overall application size.
Sharing data between components
This is the make or break piece of the puzzle. The most important responsibility of the platform application is the routing and state management. If you are using ReactJS, it’s quite well done with React-Router and Redux. Each component should have its own state, accessible by the parent app. The platform application should only pass the history to each component, to be able to read the URL parameters and push to the browser history. Any other interfacing should be done by passing handlers to the child components. The moment your child component needs too much state from the parent, you’ve designed something wrong.
Benefits & Drawbacks
- Smaller and separate codebases, allows for teams’ tech stacks evolve in their own pace.
- Decoupled and smaller deployments leading to smaller chance of breaking everything
- If one team gets the stack wrong, easier to migrate one piece than the entire frontend
- More independent teams and practices
- Single dependencies download (Build-time integration approach)
- More fragmented infrastructure, more moving parts to maintain
- Redundant data fetches, if the inter-component data share is not done correctly
- Code duplication
- Losing track of who has released what and when
- Multiple dependencies download (Run-time integration approach)
Micro frontends promotes the need for full-stack teams, which is a step towards avoiding the “I’ve always been a backend developer” situation. Nonetheless, this approach is not a panacea or a substitute for good design practices. For it to work well, you rely even more on a well defined CI/CD pipeline and responsibilities delegation.
I hope this post made some of the skeptics of frontend development a bit more optimistic or at least convinced a backend developer to try building a user interface component. If you got up to this point and enjoyed this post, please share!
Thanks for reading!