Front-end Microservices at HelloFresh

Pepijn Senders
HelloTech
Published in
7 min readAug 15, 2017

--

About 2 years ago, one of our developers put the idea of front-end microservices into our heads. After probing him a bit on how he would like to accomplish this, he didn’t have a satisfying answer. His idea was to make a platform made up out of <iframe>s, but we didn't want to get into the ugly world of <iframe>s for our main website. It would be okay for internal tools, but not for the face of the company.

Now, after a couple of years’ thought, we have a solution. It gets pretty close to what we envisioned a front-end microservice architecture to be and, since the rest of the world is also getting in on the front-end microservices train, we thought we should show off our solution as well.

Alice on her way to find the perfect front-end microservice architecture

Defining a front-end microservice

Firstly we had to define what a front-end microservice is, so what is a front-end microservice exactly?

Our definition was as follows (again we’re not sure if this is the right definition, it’s just our definition):

A front-end microservice listens on one or more HTTP hooks (endpoints) and serves the page HTML and links to scoped CSS and JavaScript.

The technology should be free to choose; there should be no external critical dependencies. This way we can serve React apps on one page, plain HTML on the other, and maybe a Vue app on another page.

Pretty simple so far. We also came up with the following additional requirements:

  • It should have an isolated deployment cycle.
  • It should be able to run in an isolated environment.

And something a bit more difficult, since we want the experience for our end-users to be the most optimal:

  • The HTML should be server side rendered.
  • The response time should be as fast as possible.

The design process

We need a couple of parts to make this front-end microservices thing work. The first part we needed was actually something we already had. It’s not the most optimal solution and it’s pretty hard to configure, but it worked. Meet entry, an NGINX server listening on the HelloFresh domain. This routes traffic to via consul, so it’s easy for us to launch new servers to serve these pages by simply adding a new location block:

Entry server configuration for our the HelloFresh shop

As you can see we also intercept our errors here (404s and 5xxs), which are then proxied to other front-end microservices that serve the error pages 💪.

All the different services happily drinking tea together

HelloFresh front-end microservice jargon

Of course we’re not going to call everything a “front-end microservice”. For the sake of saved keystrokes, we came up with some HelloFresh specific jargon for our architecture:

  • Fragment (aka florp): The server serving the entire HTML bulk, CSS etc. — essentially a full page
  • Particle (aka glorp): Shared parts of the page that need to be loaded synchronously
  • Tag: Asynchronously loaded shared parts of the page

The back-enders were a bit confused by our ultra cool names; they had to come up with their own variations. I noted them in between the parentheses.

The fragment

Honestly, this name came to be because we initially thought to go with Zalando’s approach. It didn’t turn out this way, but we kept the name. This is a service that runs under one of these entry server locations and serves small SPA’s.

Currently we have only one kind of fragment running, but soon we will have some more kinds to serve legacy pages.

This non-legacy fragment is built up out of an isomorphic React application, that is served through an Express server. It’s like any other isomorphic react boilerplate, but with some of our own specific needs built in. One of these needs is the integration of the particles.

The particle

The particle is a bit special and tailored to our needs. We built the particle to create applications that are shared across our platform, but also need to be rendered on the server. A simple example is our header and footer, which are simple React applications. The cool thing here is that the parent application can be anything, since the particles are not dependent on the application that includes them; all they need is an element to bind to. The particle server returns JSON that contains the following:

  • The HTML that is server side rendered.
  • The critical CSS that comes with this.
  • The state of the application at that point in time.
  • Link to the script file to mount the application

So an example return would be:

{
"html": "<div id=\"react-root\">Test</div>",
"css": "#react-root{color:green}",
"initialState": {
"country": "DE"
},
"script": "https://assets.hellofresh.com/dist/my-particle.hash.js"
}

The fragment will basically request this particle, write the HTML and CSS and when the application is loaded mount the particle. There’s a lot more to it than that — a bit too much to talk about in this blog post. Watch this space for a future article dedicated to this glorp technology.

The tag

The tag is also something of our creation. We felt the need for some very small — but still a bit more complex than easy jQuery and CSS — applications that are triggered asynchronously on the page. Consider a modal that you want to load asynchronously — perhaps on a button press — and is not necessarily tied to a route. Using a tag instead of a particle, loading of these scripts is deferred, so doesn’t affect page load.

These are implemented as small React applications that expose a window function to initialise them. For example, to initialise the sign up tag we built:

window.signUpTag({
bindToElement: document.body,
});

This function will then load the React application and mount it to the element.

How it all ties together

Will it blend!? Well we think so:

Simplified view of the current infrastracture

In the flow above, a client sends a request for one of our React pages. If the CDN doesn’t have a recent cached copy of this page, it will direct the request to our entry server which will then forward it to one of our fragments. I drew the RAF (recipe archive fragment), the FF (funnel fragment) and the MDF (my deliveries fragment) here to portray a part of our infrastructure. The fragments in turn will request parts (particles) of the page from the gateway. These particles will return the HTML and CSS needed for the fragment to return a full render. As fragments are cached by the CDN the experience is lightning fast ⚡️.

Here’s another view of the same flow:

The road travelled

We’ve now been working with this architecture since the Summer of 2016. Luckily the way we built it, it allows us to constantly evolve this architecture. We’re able to iterate on it really quickly due to a project manager we built in-house, called 🐙. Now, after almost a year, we’ve gathered a lot of experience and data on how this approach has worked for us.

Development velocity

At first, we found development speed to be a bit slow. Since this approach requires you to own your entire project E2E, the learning curve can be steep. But after cresting the curve we were able to push out projects like never before; the new HelloFresh shop for example took 3 people 4 weeks to roll out. Our developers also claim increased confidence and breadth of knowledge after adopting this approach.

Tiago didn’t do a lot

Isolation and code freedom

At the beginning of this project, one aim was to be able to write projects in Vue, React, or Angular and to have it work seamlessly with the rest of the website. While this is possible, in practice everyone has adhered to our React boilerplate so far. Of course, the fact that we have the possibility is already a big win in flexibility and future-proofing.

Error tracing

Fragments’ logging is unified, consistent, and fairly verbose. With all the applications following our boilerplate, we’ve gained a lot of speed in triaging issues as they arise, which helps us to fix issues much more rapidly.

Development

This win is probably the most noticeable. While previously we had this huge monolithic application that was unmaintainable and nigh-on unbootable in a local environment (shortly described in our previous blog post here), now each project has its own server, and its isolated dev environment. All the external dependencies can be configured, so you can talk to the live, staging, or your own environment to debug problems.

Credits

Credit goes to Tom Söderlund blog post, wherein he describes the approaches other companies have taken to front-end microservices. I think it’s also important to showcase all the benefits we got from this approach at HelloFresh.

I couldn’t have built this without the help of a lot of my colleagues. I need to thank all the front-end developers, who hung in there during many intense patch periods, the front-end leadership team for being critical about the process and the implementation, Joanne Wong for making the beautiful graphs in this blog post and last but not least to Nuno Simaria for giving us the freedom to develop this infrastructure next to an already full roadmap.

Looking for a new job opportunity? Become a part of our team! We are constantly on the lookout for great talent. Help us in our mission of delivering fresh ingredients to your door and change the way people eat forever.

--

--