6 Communication patterns

This chapter covers:

  • Examining user interface communication patterns to exchange events between micro frontends
  • Inspecting ways to manage state and discussing the issues of shared state
  • Illustrating how to organize server communication and data fetching in a micro frontends architecture

Sometimes user interface fragments owned by different teams need to talk to each other. When a user adds an item to the basket by clicking the Buy button, other micro frontends like the mini basket want to be notified to update their content accordingly. We’ll take a more in-depth look at this topic in the first part of this chapter. But there are also other forms of communication going on in a micro frontends architecture, as you can see in figure 6.1.

In the second part of this chapter, we’ll explore how these types of communications play together. We’ll discuss how to manage state, distribute necessary context information, and replicate data between the team’s backends.

Figure 6.1 An overview of different communication mechanisms in a typical micro frontends architecture. The frontend applications in the browser need a way to talk to each other. We call this UI communication . Each frontend fetches data from its own backend , and in some cases, it’s required to replicate data between the backends of the teams .

6.1 User interface communication

How can UIs from different teams talk to each other? If you’ve chosen good team boundaries, you’ll learn more about how to do it in chapter 13. There should be little need for extensive cross-UI communication in the browser. To accomplish a task, a customer is ideally only in contact with the user interface from one team.

In our e-commerce example, the process the customer goes through is pretty linear: finding a product, deciding whether to buy it, and doing the actual checkout. We’ve aligned our teams along these stages. Some inter-team communication might be required at the handover points when a customer goes from one team to the next.

This communication can be simple. We’ve already used page-to-page communication in chapter 2--moving from the product page to another team’s recommendation page via a simple link. In our case, we transferred the product reference, the SKU, via the

URL path or the query string. In most cases, cross-team communication happens via the URL.

Figure 6.2 Three different forms of communication that can happen between the different teams' UIs inside a page

If you are building a richer user interface that combines multiple use cases on one page, a link isn’t sufficient anymore. You need a standard way for the different UI parts to talk to each other. Figure 6.2 illustrates three common communication patterns.

We’ll go through all three forms of communication with a real use case on our product page. We’ll focus on native browser features in the examples.

6.1.1 Parent to fragment

The introduction of the Buy button on the product page resulted in a considerable amount of tractor sales over one weekend. But Tractor Model, Inc. has no time to rest. CEO Ferdinand was able to hire two of the best goldsmiths. They’ve designed special platinum editions of all tractors.

To sell these premium edition tractors, Team Decide needs to add a platinum upgrade option to the detail page. Selecting the option should change the standard product image to the platinum version. Team Decide can implement that inside their application. But most importantly, the Buy button from Team Checkout also needs to update. It must show the premium price of the platinum edition. See figure 6.3.

Figure 6.3 Parent-child communication. A change in the parent page (selection of platinum option) needs to be propagated down to a fragment so it can update itself (price change in the Buy button).

Both teams talk and come up with a plan. Team Checkout will extend the Buy button using another attribute called edition. Team Decide sets this attribute and updates it accordingly when the user changes the option:

  • Updated Buy button

    tag-name: checkout-buy

    attributes: sku=[sku], edition=[standard|platinum]

    example: <checkout-buy sku="porsche" edition="platinum"></checkout-buy>

Implementing the platinum option

The added option in the product pages markup looks like this.

Listing 6.1 team-decide/product/fendt.html

...
<img class="decide_image"
  src="https://mi-fr.org/img/fendt_standard.svg" />
...
<label class="decide_editions"> 
   <input type="checkbox" name="edition" value="platinum" />     
   <span>Platinum Edition</span> 
</label>
<checkout-buy sku="fendt" edition="standard"></checkout-buy>     
 ...

Checkbox for selecting the platinum option

Buy button has a new edition attribute

Team Decide introduced a simple checkbox input element for choosing the material upgrade. The Buy-button component also received an edition attribute. Now the team needs to write a bit of JavaScript glue-code to connect both elements. Changes to the checkbox should result in changes to the edition attribute. The main image on the site also needs to change.

Listing 6.2 team-decide/static/page.js

const option = document.querySelector(".decide_editions input");    
const image = document.querySelector(".decide_image");              
const buyButton = document.querySelector("checkout-buy");           
 
option.addEventListener("change", e => {                            
  const edition = e.target.checked ? "platinum" : "standard";       
  buyButton.setAttribute("edition", edition);                       
  image.src = image.src.replace(/(standard|platinum)/, edition);    
});

Selecting the DOM elements that need to be watched or changed

Reacting to checkbox changes

Determining the selected edition

Updating the edition attribute on Team Checkout’s Buy-button custom element

Updating the main product image

That’s everything Team Decide needs to do. Now it’s up to Team Checkout to react to the changed edition attribute and update the component.

Updating on attribute change

The first version of the Buy-button custom element only used the connectedCallback methods. But custom elements also come with a few lifecycle methods.

The most interesting one for our case is attributeChangedCallback (name, oldValue, newValue). This method is triggered every time someone changes an attribute of your custom element. You receive the name of the attribute that changed (name), the attribute’s previous value (oldValue), and the updated value (newValue). For this to work, you have to register the list of attributes that should be observed up front. The code of the custom element now looks like this.

Listing 6.3 team-checkout/static/fragment.js

const prices = {
  porsche: { standard: 66, platinum: 966 },        
  fendt: { standard: 54, platinum: 945 },          
  eicher: { standard: 58, platinum: 958 }          
};
 
class CheckoutBuy extends HTMLElement {
  static get observedAttributes() {                
    return ["sku", "edition"];                     
  }                                                
  connectedCallback() {
    this.render();                                 
  }
  attributeChangedCallback() {                     
    this.render();                                 
  }                                                
  render() {                                       
    const sku = this.getAttribute("sku");          
    const edition = this.getAttribute("edition");  
    this.innerHTML = `
      <button type="button">
        buy for $${prices[sku][edition]}           
      </button>
    `;
    ...
  }
}

Added new prices for platinum versions

Watching for changes to the sku and edition attribute

Extracted the rendering to a separate method

Calling render () on every attribute change

Extracted render method

Retrieves the current SKU and edition value from the DOM

Renders the price based on SKU and edition

NOTE The function name render has no special meaning in this context. We could have also picked another name like updateView or gummibear.

 

npm run 10_parent_child_communication

Now the Buy button updates itself on every change to the sku or edition attribute. Run the preceding code and then go to http://localhost:3001/product/fendt in your browser and open up the DOM tree in the developer tools. You’ll see that the edition attribute of the checkout-buy element changes every time you check and uncheck the platinum option. As a reaction to this, the component’s internal markup (innerHTML) of it also changes.

Figure 6.4 You can achieve parent-child communication by explicitly passing needed context information down as an attribute. The fragment can react to this change.

Figure 6.4 illustrates the data flow. We propagate changed state of the outer application (product page) to the nested application (Buy button). This follows the unidirectional dataflow 1 pattern. React and Redux popularized the “props down, events up” approach. The updated state is passed down the tree via attributes to child components as needed. Communication in the other direction is done via events. We’ll cover this next.

6.1.2 Fragment to parent

The introduction of the platinum editions resulted in a lot of controversial discussions in the Tractor Model, Inc. user forum. Some users complained about the premium prices. Others asked for additional black, crystal, and gold editions. The first 100 platinum tractors shipped within one day.

Emma is Team Decide’s UX designer. She loves the new Buy button but isn’t entirely happy about how the user interaction feels. In response to a click, the user gets a system alert dialog, which they must dismiss to move on. Emma wants to change this. She has a more friendly alternative in mind. An animated green checkmark should confirm the add-to-cart interaction on the main product image.

This request is a bit problematic. Team Checkout owns the add-to-cart action. Yes, they know when a user successfully added an item to the cart. It would be easy for them to show a confirmation message inside the Buy-button fragment, or maybe animate the Buy button itself to provide feedback. But they can’t introduce a new animation in a part of the page they don’t own, like the main product image.

OK, technically they can because their JavaScript has access to the complete page markup, but they shouldn’t. It would introduce a significant coupling of both user interfaces. Team Checkout would have to make a lot of assumptions about how the product page works. Future changes to the product page could result in breaking the animation. Nobody wants to maintain such a construct.

For a clean solution, the animation has to be developed by Team Decide. To accomplish this, both teams have to work together through a clearly defined contract. Team Checkout must notify Team Decide when a user has successfully added an item to the cart. Team Decide can trigger its animation in response to that.

The teams agree on implementing this notification via an event on the Buy button. The updated contract for the Buy-button fragment looks like this:

  • Updated Buy button

    tag-name: checkout-buy

    attributes: sku=[sku], edition=[standard|platinum]

    emits event: checkout:item_added

Now the fragment can emit a checkout:item_added event to inform others about a successful add-to-cart action. See figure 6.5.

Figure 6.5 Team Checkout’s Buy button emits an event when the user adds an item to the cart. Team Decide reacts to this event and triggers an animation on the main product image.

Emitting Custom Events

Let’s look at the code that’s needed to make the interaction happen. We’ll use the browser’s native CustomEvents API. The feature is available in all browsers, including older versions of Internet Explorer. It enables you to emit events that work the same as native browser events like click or change. But you are free to choose the event’s name.

The following code shows the Buy-button fragment with the event added.

Listing 6.4 team-checkout/static/fragment.js

class CheckoutBuy extends HTMLElement {
  ...
  render() {
    ...
    this.innerHTML = `...`;
    this.querySelector("button").addEventListener("click", () => {
      ...
      const event = new CustomEvent("checkout:item_added");        
      this.dispatchEvent(event);                                   
    });
  }
}

Creates a custom event named checkout:item_added

Dispatches the event at the custom element

NOTE We’ve used a team prefix ([team_prefix]:[event_name]) to clarify which team owns the event.

Pretty straightforward, right? The CustomEvent constructor has an optional second parameter for options. We’ll discuss two options in the next example.

Listening for Custom Events

That’s everything Team Checkout needed to do. Let’s add the checkmark animation when the event occurs. We won’t get into the associated CSS code. It uses a CSS keyframe animation, which makes a prominent green checkmark character (✓) fade in and out again. We can trigger the animation by adding a decide_product--confirm class to the existing decide_product div element.

Listing 6.5 team-decide/static/page.js

const buyButton = document.querySelector("checkout-buy");      
const product = document.querySelector(".decide_product");     
buyButton.addEventListener("checkout:item_added", e => {       
  product.classList.add("decide_product--confirm");            
});                                                            
product.addEventListener("animationend", () => {               
  product.classList.remove("decide_product--confirm");         
});                                                            

Selecting the Buy-button element

Selecting the product block where the animation should happen

Listening to Team Checkout’s custom event

Triggering the animation by adding the confirm class

Cleanup--removing the class after the animation finished

Listening to the custom checkout:item_added event works the same way as listening to a click event. Select the element you want to listen on (<checkout-buy>) and register an event handler: .addEventListener("checkout:item_added", () => {...}). Run the following command to start the example:

npm run 11_child_parent_communication

Go to http://localhost:3001/product/fendt in your browser and try the code yourself. Clicking the Buy button triggers the event. Team Decide receives it and adds the confirm class. The checkmark animation starts.

Figure 6.6 Child-parent communication can be implemented by using the browser’s built-in event mechanism.

Using the browser’s event mechanism has multiple benefits:

  • Custom Events can have high-level names that reflect your domain language. Good event names are easier to understand than technical names like click or touch.

  • Fragments don’t need to know their parents.

  • All major libraries and frameworks support browser events.

  • It gives access to all native event features like .stopPropagation or .target.

  • It’s easy to debug via browser developer tools.

Let’s get to the last form of communication: fragment to fragment.

6.1.3 Fragment to fragment

Replacing the alert dialog with the friendlier checkmark animation had a measurable positive effect. The average cart size went up by 31%, which directly resulted in higher revenue. The support staff reported that some customers accidentally bought more tractors than they intended.

Team Checkout wants to add a mini-cart to the product page to reduce the number of product returns. This way, customers always see what’s in their basket. Team Checkout provides the mini-cart as a new fragment for Team Decide to include on the bottom of the product page. The contract for including the mini-cart looks like this:

  • Mini-Cart

    tag-name: checkout-minicart

    example: <checkout-minicart></checkout-minicart>

It does not receive any attributes and emits no events. When added to the DOM, the mini-cart renders a list of all tractors that are in the cart. Later the team will fetch the state from its backend API. For now, the fragment holds that state in a local variable.

That’s all pretty straightforward, but the mini-cart also needs to be notified when the customer adds a new tractor to the cart via the Buy button. So an event in fragment A should lead to an update in fragment B. There are different ways of implementing this:

  • Direct communication --A fragment finds the fragment it wants to talk to and directly calls a function on it. Since we are in the browser, a fragment has access to the complete DOM tree. It could search the DOM for the element it’s looking for and talk to it. Don’t do this. Directly referencing foreign DOM elements introduces tight coupling. A fragment should be self-contained and not know about other fragments on the page. Direct communication makes it hard to change the composition of fragments later on. Removing a fragment or duplicating one can lead to strange effects.

  • Orchestration via a parent --We can combine the child-parent and parent-child mechanisms. In our case, Team Decide’s product page would listen to the item_added event from the Buy button and directly trigger an update to the mini-cart fragment. This is a clean solution. We’ve explicitly modeled the communication flow in the parent’s system. But to make a change in communication, two teams must adapt their software.

  • Event-Bus/broadcasting --With this model, you introduce a global communication channel. Fragments can publish events to the channel. Other fragments can subscribe to these events and react to them. The publish/subscribe mechanism reduces coupling. The product page, in our example, wouldn’t have to know or care about the communication between the Buy button and the mini-basket fragment. You can implement this with Custom Events. Most browsers2 also support the new Broadcast Channel API,3 which creates a message bus that also spans across browser windows, tabs, and iframes.

The teams decide to go with the event-bus approach using Custom Events. Figure 6.7 illustrates the event flow between both fragments.

Figure 6.7 Fragment-to-fragment communication via a global event. The Buy button emits the item_added event. The mini-cart listens for this event on the window object and updates itself. We use the browser’s native event mechanism as an event bus.

Not only does the mini-cart need to know if the user added a tractor, it also must know what tractor the user added. So we need to add the tractor information (sku, edition) as a payload to the checkout:item_added event. The updated contract for the Buy button looks like this:

  • Updated Buy button tag-name: checkout-buy attributes: sku=[sku], edition =[standard|platinum]

    emits event:

    name: checkout:item_added

    payload: {sku: [sku], edition: [standard|platinum]}

Warning Be careful with exchanging data structures through events. They introduce extra coupling. Keep payloads to a minimum. Use events primarily for notifications and not to transfer data.

Let’s look at the implementation of this.

Event bus via browser events

The Custom Events API also specifies a way to add a custom payload to your event. You can pass your payload to the constructor via the detail key in the options object.

Listing 6.6 team-checkout/static/fragment.js

...
const event = new CustomEvent("checkout:item_added", {
  bubbles: true,                                       
  detail: { sku, edition }                             
}*);
this.dispatchEvent(event);
...

Enables event bubbling

Attaches a custom payload to the event

By default, Custom Events don’t bubble up the DOM tree. We need to enable this behavior to make the event rise to the window object.

That’s everything we needed to do to the Buy button. Let’s look at the mini-cart implementation. Team Checkout defines the custom element in the same fragment.js file as the Buy button.

team-checkout/static/fragment.jsListing 6.7

...
class CheckoutMinicart extends HTMLElement {
  connectedCallback() {
    this.items = [];                                          
    window.addEventListener("checkout:item_added", e => {     
      this.items.push(e.detail);                              
      this.render();                                          
    });                                                       
    this.render();
  }
  render() {
    this.innerHTML = `
      You've picked ${this.items.length} tractors:
      ${this.items.map(({ sku, edition }) =>
        `<img src="https://mi-fr.org/img/${sku}_${edition}.svg" />`
      ).join("")}
    `;
    ...
  }
}
window.customElements.define("checkout-minicart", CheckoutMinicart);

Initializing a local variable for holding the cart items

Listening to events on the window object

Reading the event payload and adding it to the item list

Updating the view

The component stores the basket items in the local this.items array. It registers an event listener for all checkout:item_added events. When an event occurs, it reads the payload (event.detail) and appends it to the list. Lastly, it triggers a refresh of the view by calling this.render().

To see both fragments in action, Team Decide has to add the new mini-cart fragment to the bottom of the page. The team doesn’t have to know anything about the communication that’s going on between checkout-buy and checkout-minicart.

Listing 6.8 team-decide/product/fendt.html

...
<body>
  ...
  <div class="decide_details">
    <checkout-buy sku="fendt" edition="standard"></checkout-buy>
  </div>
  <div class="decide_summary">                     
    <checkout-minicart></checkout-minicart>        
  </div>                                           
  <script src="http://localhost:3003/static/fragment.js" async></script>
</body>
...

Adding the new mini-cart fragment to the bottom of the page

Figure 6.8 shows how the event is bubbling up to the top. You can test the example by running this command:

Figure 6.8 Custom Events can bubble up to the window of the document where other components can subscribe to them.

Dispatching events directly on window

It’s also possible to directly dispatch the Custom Event to the global window object: window.dispatchEvent instead of element.dispatchEvent. But dispatching it to the DOM element and letting it bubble up comes with a few benefits.

The origin of the event (event.target) is maintained. Knowing which DOM element emitted the event is helpful when you have multiple instances of a fragment on one page. Having this element reference avoids the need to create a separate naming or identification scheme yourself.

Parents can also cancel bubbling events on their way up to the window. You can use event.stopPropagation on Custom Events the same way you would with a standard click event. This can be helpful when you want an event to only be processed once. However, the stopPropagation mechanism can also be a source of confusion: “Why don’t you see my event on window? I’m sure we’re dispatching it correctly.” So be careful with this--especially if more than two parties are involved in the communication.

6.1.4 Publish/Subscribe with the Broadcast Channel API

In the examples so far, we’ve leveraged the DOM for communication. The relatively new Broadcast Channel API provides another standards-based way to communicate. It’s a publish/subscribe system which enables communication across tabs, windows, and even iframes from the same domain. The API is pretty simple:

  • You can connect to a channel with new BroadcastChannel("tractor_channel").

  • Send messages via channel.postMessage(content).

  • Receive messages via channel.onmessage = function(e) {...}.

In our case all micro frontends could open a connection to a central channel (like tractor_channel) and receive notifications from other micro frontends. Let’s look at a small example.

Listing 6.9 team-checkout.js

const channel = new BroadcastChannel("tractor_channel");     
const buyButton = document.querySelector("button");
buyButton.addEventListener("click", () => {
  channel.postMessage(                                       
    {type: "checkout:item_added", sku: "fendt"}              
  );                                                         
});

Team Checkout connects to the central broadcast channel.

They post an item_added message when someone clicks the Buy button. In this example we send an object, but you can also send plain strings or other types of data.

Listing 6.10 team-decide.js

const channel = new BroadcastChannel("tractor_channel");    
channel.onmessage = function(e) {                           
  if (e.data.type === "checkout:item_added") {              
    console.log(`tractor ${e.data.type} added`);            
    // -> tractor fendt added                               
  }                                                         
};

Team Decide also connects to the same channel.

They listen to all messages and create a log entry every time they receive an item_added.

At the time of writing this book, all browsers except Safari support the Broadcast Channel API. 4 You can use a polyfill 5 to retrofit the API into browsers without native support.

The biggest benefit of this approach compared to the DOM-based Custom Events is the fact that you can exchange messages across windows. This can come in handy if you need to sync state across multiple tabs or decide to use iframes. You can also use the concept of named channels to explicitly differentiate between team-internal and public communication. In addition to the global tractor_channel, Team Checkout could open its own checkout_channel for communication between the team’s own micro frontends. This team-internal communication may also contain more complex data structures. Having a clear distinction between public and internal messages reduces the risk of unwanted coupling.

6.1.5 When UI communication is a good fit

Now you’ve seen four different types of communication, and you know how to tackle them with basic browser features. You can, of course, also use custom implementations for communicating and updating components. A shared JavaScript publish/subscribe module which all teams import at runtime can do the trick. But your goal when setting up a micro frontends integration should be to have as little shared infrastructure as possible. Going with a standardized browser specification like Custom Events or the Broadcast Channel API should be your first choice.

Use simple payloads

In the last example, we transferred the actual cart line-item ({sku, edition}) via an event from one fragment to another. In the projects I’ve worked on, we’ve had good experiences with keeping events as lean and straightforward as possible. Events should not function as a way to transfer data. Their purpose is to act as a nudge to other parts of the user interface. You should only exchange view models and domain objects inside team boundaries.

The need for intense UI communication can be a sign of bad boundaries

As stated earlier, when you’ve picked your team boundaries well, there shouldn’t be a need for a lot of inter-team communication. That said, the amount of communication increases when you are adding a lot of different use cases to one view.

When implementing a new feature requires two teams to work closely together, passing data back and forth between their micro frontends, we have a reliable indicator of non-optimal team boundaries. Reconsider your boundaries and maybe increase the scope, or shift the responsibility for a use case from one team to another.

Events versus asynchronous loading

When using events or broadcasting, you have to keep in mind that other micro frontends might not have finished loading yet. Micro frontends are unable to retrieve events that happened before they finished initializing themselves.

When you use events in response to user actions (like add-to-cart), this is not a big issue in practice. But if you want to propagate information to all components on the initial load, standard events might not be the right solution.

6.2 Other communication mechanisms

So far, we’ve focused on user interface communication, which happens directly between micro frontends in the browser. However, there are other types of data exchange you have to solve when you build a real application. In the last part of this chapter, we’ll discuss how authentication, data fetching, state management, and data replication fit into the micro frontends picture.

6.2.1 Global context and authentication

Each micro frontend addresses a particular use case. However, in a non-trivial application, these frontends need some context information to do their job.What language does the user speak, where do they live, and which currency do they prefer? Is the user logged in or anonymous? Is the application running in the staging or live environment? These necessary details are often called context information. They are read-only by nature. You can see context data as infrastructure boilerplate that you want to solve once and provide to all the teams in an easily consumable way. Figure 6.9 illustrates how to distribute this data to all user interface applications.

Figure 6.9 You can provide general context information globally to all micro frontends. This puts common tasks like language detection in a central place.

Providing context information to all micro frontends

We have to answer two questions when building a solution for providing context data:

  1. Delivery --How do we get the information to the teams’ micro frontends?

  2. Responsibility --Which team determines the data and implements the associated concepts?

Let’s start with delivery. If you’re using server rendering, HTTP headers or cookies are a popular solution. A frontend proxy or composition service can set them to every incoming request. If you’re running an entirely client-side application, HTTP headers are not an option. As an alternative, you can provide a global JavaScript API, from which every team can retrieve this information. In the next chapter, we’ll introduce the concept of an application shell. When you decide to go that route, putting the context information into the application shell is a typical pattern.

Let’s talk about responsibility. If you have a dedicated platform team, it’s also the perfect candidate to provide the context. In a decentralized scenario with no platform team, you’d pick one of the teams to do the job. If you already have a central infrastructure like a frontend proxy and an application shell, the owner of this infrastructure is a good candidate for also owning the context data.

Authentication

Managing language preferences or determining the origin country are tasks that don’t require much business logic. For topics like authenticating a user, it’s harder. You should answer the question, “Which team owns the login process?” by looking at the team’s mission statements.

From a technical integration standpoint, the team that owns the login process becomes the authentication provider for the other teams. It provides a login page or fragment that other teams can use to redirect an unauthenticated user towards. You can use standards like OAuth 6 or JSON Web Tokens (JWT) to securely provide the authentication status to the teams that need it.

6.2.2 Managing state

If you’re using a state management library like Redux, each micro frontend or at least each team should have its local state. Figure 6.10 illustrates this.

Figure 6.10 Each team has its own user interface state. Sharing state between teams would introduce coupling and make the applications hard to change later on.

It’s tempting to reuse state from one micro frontend in another to avoid loading data twice. But this shortcut leads to coupling and makes the individual applications harder to change and less robust. It also introduces the potential that a shared state could get misused for inter-team communication.

6.2.3 Frontend-backend communication

To do its work, a micro frontend should only talk to the backend infrastructure of its team, as shown in figure 6.11. A micro frontend from Team A would never directly talk to an API endpoint from Team B. This would introduce coupling and inter-team dependencies. Even more important, you give up isolation. To run and test your system, the system from the other team needs to be present. An error in Team B would also affect fragments from Team C.

Figure 6.11 API communication should always stay inside team boundaries.

6.2.4 Data replication

If your teams should own everything from the user interface to the database, each team needs its own server-side data store. Team Inspire maintains its database of manually crafted product recommendations, whereas Team Checkout stores all baskets and orders the users created. Team Decide has no direct interest in these data structures. They include the associated functionality (like recommendation strip or mini-cart) via UI composition in the frontend.

But for some applications, UI composition is not feasible. Let’s take the product data as an example. Team Decide owns the master product database. They provide back-office functionality, which employees of The Tractor Store can use to add new products. But the other teams also need some product data. Team Inspire and Team Checkout need at least the list of all SKUs, the associated names, and image URLs. They have no interest in more advanced information like editing history, video files, or customer reviews.

Both teams could retrieve this information via API calls to Team Decide at runtime. However, this would violate our autonomy goals. If Team Decide goes down, the other teams wouldn’t be able to do their job anymore. We can solve this with data replication.

Team Decide provides an interface that the other teams can use to retrieve a list of all products. The other teams use this interface to replicate the needed product information regularly in the background. You’d implement this via a feed mechanism. Figure 6.12 illustrates this.

Figure 6.12 Teams can replicate data from other teams to stay independent. This replication increases robustness. If one team goes down, the others can still function.

When Team Decide’s application goes down, Team Inspire still has its local product database it can use to serve recommendations. We can apply this concept to other kinds of data.

Team Checkout owns the inventory. They know how many tractors are in stock and can estimate when new supplies arrive. If another team has an interest in this inventory data, they have two options: replicate the needed data to their application, or ask Team Checkout to provide an includable micro frontend that presents this information directly to the user.

Both are valid approaches that have their benefits and drawbacks. Team Decide can choose to replicate the inventory data if they want to build business logic that builds upon it. As an example, they might want to experiment with an alternative product detail layout for products that will run out of stock soon. To do this, they must know the inventory in advance, understand Team Checkout’s inventory format, and build the associated business rules.

Alternatively, if they just want to show the inventory information as simple text as part of the Buy button, UI composition is much more comfortable. Team Decide doesn’t have to understand Team Checkout’s inventory data model at all.

Summary

  • Communication between different micro frontends is often necessary at the handover points in your application. When the user moves from one use case to the next, you can handle most communication needs by passing parameters through the URL.

  • When multiple use cases exist on one page, it might be necessary for the different micro frontends to communicate with each other.

  • You can use the “props down, events up” communication pattern on a higher level between different team UIs.

  • A parent passes updated context information down to its child fragments via attributes.

  • Fragments can notify other fragments higher up in the tree about a user action using native browser events.

  • Different fragments that are not in a parent-child relationship can communicate using an event bus or broadcasting mechanism. Custom Events and the Broadcast Channel API are native browser implementations that can help.

  • You should use UI communication only for notifications, not to transfer complex data structures.

  • You can resolve general context information like the user’s language or country in a central place (e.g., frontend proxy or application shell) and pass it to every micro frontend. HTTP headers, cookies, or a shared JavaScript API are ways to implement this.

  • Each team can have its own user interface state (for example, a Redux store). Avoid sharing state between teams. It introduces coupling and makes applications hard to change.

  • A team’s micro frontend should only fetch data from its backend application. Exchanging larger data structures across team UIs leads to coupling and makes applications hard to evolve and test.


1.See http://mng.bz/pB72.

2.At the time of writing this, Safari is the only browser that hasn’t implemented it: https://caniuse.com/#feat=broadcastchannel.

3.See http://mng.bz/OMeo.

4.Broadcast Channel API--Browser Support: https://caniuse.com/#feat=broadcastchannel.

5.Broadcast Channel API--Polyfill: http://mng.bz/YrWK.

6.See https://en.wikipedia.org/wiki/OAuth.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.240.21