Tue, Oct 1, 2019
Read in 11 minutes
Over the recent couple of months we created two brand new applications, Traveler-Portal (MyPage) and Insurance Manager (formerly Partner Dashboard / Partner Portal 2.0). Both apps were developed in parallel by a small size engineering team and as both are now in release candidate state or already in production, we’d like to introduce the new frontend development architecture we used to make this achievement possible.
While we have all kinds of different technologies in place to come up with a software product, there are a few cornerstones we’d like to highlight here. All of them, by themselves, are modern state of the art solutions we never utilized to such an extent before. Combined, they turned out to be a very robust and efficient platform to develop frontend applications on. Compared to our former approach, we adhere to better quality standards out of the box and have the potential to reduce the impact of API changes on the frontend, making the applications more scalable.
The main players are…
Let’s start by having a brief look at the single protagonists and find out why we like them so much.
With TypeScript in place it’s harder to write unsafe, potentially breaking code, and it actually forces developers to take more care for guaranteeing the application is always in the state it is expected to be. This results in overall better and leaner code, as one can now drop most of the, formerly required, defensive programming which attempts to prevent the app from crashing at runtime. Engineers no longer have to figure out if their implementations really work out and that the state is what it should be by trying the app at runtime, since it’s being assured upfront with TypeScript.
TypeScript also integrates well to the engineer’s IDE, offering handy helpers like code completion, linting, formatting etc. Needless to say, all this speeds up the development process quite a bit. TypeScript became a widely accepted industry standard when it comes to modern web development, on frontend and backend side. Consequently most of our other tools build on top of and make full use of it. Documentation and community support is also rich.
All the concepts described above are far from being revolutionary and backend developers will shrug or yawn over them. But in the frontend world they are for sure quite an impactful evolution.
React is the only not entirely new protagonist in our stack, it’s our frontend framework (technically it’s a library though) of choice in most of the current web applications for the last few years now. Still, React has also made some huge steps forward, especially in the last year, now being efficient and straightforward as never before and remains one of the most commonly used frontend frameworks for good reason.
What makes React a real powerhouse is the introduction of Hooks early this year. Hooks are techniques to access certain React functionality and behaviour in a very direct and established way. Hooks basically simplify React UI component structures, making code more readable and maintainable. The concept and usage of Hooks is widely appreciated and supported in the React ecosystem. Therefore, other React based 3rd party tools (like Material-UI and Apollo Client) have started to provide access to their APIs and functionalities in the form of Hooks as well.
Modern React lets our applications remain class and OOP free, we can keep the codebase lean, generate less boilerplate, get away with smaller bundle sizes and achieve overall better performance.
Fig.1. Traveler-Portal (MyPage) Login screen
Material-UI is a React based UI component library that implements Google’s Material Design visual language. The Material Design system is widely installed nowadays, when you use Google apps or e.g. Whatsapp. UI made with Material-UI will look quite familiar to you.
From an UI/UX point of view this library makes it possible to create user interfaces that look and work great out of the box in very little time. It offers a wide range of standard components like Buttons, Icons, Menus, Lists, Formfields, etc. but also utilizes its own color, typography, animation and others system. This provides the advantage of not having to maintain our own component library and provides a coherent UI/UX for our users. Additionally, it provides a common language to improve collaboration with our design team as Material Design is a well defined visual language.
Still, of course you can style and change the look and feel of everything either right in place, override specific default theme settings or create completely new custom themes. In Insurance Manager we already make use of the theming functionality, enabling the user to persistently change the applications’ color-scheme at runtime.
Fig.2. Theming in Insurance Manager
From an engineering point of view this library just works as well as it is documented (most of the time). Its major benefit is that it is now way easier for us to create a common look, feel and functionality throughout our applications and have better tools to create fully customizable and reusable components to be utilized in different applications. This would have been possible without Material-UI as well, but having it makes it a whole lot easier.
Worth to mention that the library is fully tree-shakable, meaning only the parts actually used in an application make it into the production bundle. Since this library is quite large, this approach ensures that we do not unnecessarily create large bundles. Apart from that we are now part of a community that starred it 50,000 times on Github.
GraphQL is another very beneficial addition to our frontend architecture. Within a GraphQL server, we can implement a dedicated API for our frontends and can communicate with it using the Apollo Client (see next section). Additionally, since this GraphQL server / API runs on node.js, we can map backend services and APIs to our Apollo GraphQL API instead of requiring backend development assistance to change existing APIs to fit the UI needs of the frontend applications.
The new GraphQL API in our setup sits in the middle between the frontend and the already existing REST API making requests to the later to trigger certain functionality or fetch data. Doing so, we do not necessarily have to take over and work with data structures the REST API provides, we can use the existing structure if it fits our needs, but if required we can easily restructure, reduce or enhance the data structure as required in the frontend. As long as the REST API offers functionality and data we need in any form, we can easily turn it into what best serves us. Additionally, instead of the REST API, we could theoretically communicate directly with other services in our backend ecosystem as well as databases, to enhance our GraphQL API.
Fig.3. Transformations of REST API data structures in the GraphQL schema.
What kind of data (structure) the frontend receives from the GraphQL API can be defined in a schema configuration. Here, we for example declare in what shape we want to retrieve data, e.g. a Certificate, a Claim or ProductGroup object, what kind of information such entities shall (not) contain. Doing so we never receive unnecessary data and can come up with those efficient data structures we want to work with on the frontend.
Those structural configurations can be further utilized to produce more robust code. We know exactly what data we get and what we might not get. Code to access data that’s possibly absent in an unsafe way would make TypeScript kick in, demanding a safer implementation. To not only be safe but stay safe we have the GraphQL code generator tool in charge, which generates frontend and backend TypeScript Types and Interfaces. With those generated declarations in place, GraphQL schema changes will not pass by implementations on both sides unrecognized.
Another cool feature of the Apollo GraphQL server are Subscriptions which make use of the Websocket protocol. This enables bidirectional client-server communication allowing the Frontend to be informed about data changes without manually refreshing the state. Our test implementations of the feature worked out well.
Fig.4. Live Update / Subscriptions in Insurance Manager.
The Apollo Client is basically a set of React components which can mutate (i.e. create, update or delete) data in the backend or retrieve, store and provide it to the UI components they are assigned to. In a nutshell we wrap our UI components in an Apollo Mutation or Query component which is given the GraphQL query that needs to be executed. Such a query could be translated to something like Fetch all certificates newer than X but each certificate shall only contain creation date, expiration date and customer e-mail information.
As soon as such a component is initially rendered by React, Apollo sends this query out to the GraphQL API, this calls our REST API, strips away the unwanted parts from the returned data and sends back the lean JSON response to the client only containing what we asked for. During and after the request the Apollo component rerenders the UI on updates, offering information about the loading state, and if there was a server error or it actually got the data to display. The Apollo Client also cares for caching state, assuring no unnecessary requests are made to retrieve duplicate data.
This technique is not only very elegant as we no longer require boilerplate fetching and state updating logic, it also frees us from the need of a global state that holds and propagates all application data through the applications to its recipients. Still, if component A already fetched data and another component B uses it as well, B gets it from the cache instead of fetching it again. All in all state management got way easier, quicker and safer with Apollo, very beneficial also in regard to the fact that many if not most bugs in frontend applications are resulting from flawed state management and finding/fixing them often is quite a chore.
Developing apps based on our new architecture only requires locally running a small React script that rebuilds the frontend application on change within a few seconds and the GraphQL server which also (re)starts in no time. The targeted backend exposing the REST API can either run locally as well or being deployed remotely, e.g. on a sandbox we set up or on our staging systems. This guarantees an always 100% utilizable backend environment to develop against.
Rebuilding and (hot) reloading both frontend application and GraphQL server is a fully automated process and a matter of seconds.
Fig.5. Insurance Manager Frontend development build time.
We are very convinced of the new stack and for sure will keep track of the constant changes and improvements happening to all those technologies. It puts us on the edge of innovation in terms of frontend development which is not a trivial task as the landscape evolves rapidly.
All in all, our goal is to increase the quality, scalability and robustness of our frontend applications and with this architecture we have made a step forward in this direction. Our approaches are by no means perfect as this is an unattainable goal, but nevertheless we continue to evolve our approaches as best practices become more apparent and we evolve our architecture to adapt to new requirements and features. From a development perspective it is a real pleasure to work with this stack as a lot of pieces play very nicely together.
We still have lots of frontend applications not using this stack and we will continue to support those, while looking for opportunities to migrate them over to the new stack or at least parts of the new stack. However, this depends on many factors, so no promises are made.