Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sharing data and state between micro-apps via Redux? #107

Closed
jaredwilli opened this issue Aug 16, 2017 · 17 comments
Closed

Sharing data and state between micro-apps via Redux? #107

jaredwilli opened this issue Aug 16, 2017 · 17 comments

Comments

@jaredwilli
Copy link

Hi there @joeldenning,

I came across this project and think it's pretty awesome. It seems to function very closely to how we want to do things at my company. My UI team has been talking more about how we can create a system that can work very much like how this does.

We would, however, require the ability to share data between the loaded micro apps somehow, and would also like to be able to manage the state as well. I may have missed it in the docs, but is this something that is possible currently, or do you know if there is anyone doing anything similar?

We want to be able to develop the different parts of our currently very monolithic application as smaller independent apps that can co-exist and communicate together through some kind of state management (redux) or other kind of interface.

Would love to hear your thoughts on this.

Thanks!
Jared

@joeldenning
Copy link
Member

Happy to hear you're considering single-spa or at least looking at building a system that operates similarly. Redux-like state management is not built into single-spa itself, but you can definitely use redux or any other state management system in addition to single-spa. The way you would do this is to have redux in a common dependencies bundle and then have all the child applications import the redux store and use it inside their application.

How to share state between single-spa child applications is a common question, and one that I think there still is room for innovation and improvement. At Canopy, we use single-spa in production with 14 child applications, and we have a few techniques for sharing state. We also adopted a convention that we like that simplifies state management.

A simplifying convention

At Canopy, we have only one "main content" child application that is active at any time. There are still multiple applications active at once, but only one of them is rendering the main content that is scrollable. The rest of them (so far) just render fixed position menus. As you're navigating around, the menus might stay in place when the main content applications are switching off. So in the screenshot below, communications-ui is the main content application that is scrollable, and primary-navbar, contact-menu, and workflow-ui are supplementary applications that just are fixed menus.

screen shot 2017-08-18 at 12 09 08 pm

Canopy techniques for sharing state

We chose not to have a global redux store or state manager at Canopy. What we have preferred is to have each of the child applications manage their own state and be self encapsulated. We were worried that a global store would lead to weird situations where the order in which you navigate around to applications matters. In other words, we wanted to avoid where navigating to App1 and then to App2 would result in a different global state (and bugs) than navigating to App2 and then to App1. Another reason we have liked doing things that way is that we deploy the child applications separately and it is nice to have a blast radius of each deployment. We were worried that having a global state manager would create situations where you couldn't really deploy one of the child applications without worrying about how it interacted with the other apps and the global state.

As an alternative, we use the following state sharing techniques:

  • Having a shared API layer that can cache api objects. What we realized is that a lot of the state that needs to be shared between child applications is just API data. The UI-state that isn't in the API is much less commonly shared. So we built an ajax library that allows child applications to fetch data from the server like normal, but sometimes the data is actually cached and an ajax request isn't really made. This way, the child applications don't even need to know if any other child application is fetching the same data, they just make the API request like normal and that ajax library takes care of not making too many ajax requests to the same endpoint. This solves a surprisingly high percentage of our data sharing needs.
  • Apps that export observables to other apps. For this one, we choose an owning application for a piece of UI state and then we have that application export an observable for that data. All other applications subscribe to the observable, but can't change the values in it. We don't use this very often, but have found a few use cases where we like it. It's not quite a global store since there is an owning application for the state, and we have also found it pretty simple to debug/interact with because only the owning application updates the data.
  • App1 imports a function from App2 and calls it. What we like about this is that it is explicit and easy to follow where the data is originating and where it's going.

Anyway, that is how we do it at Canopy. But like I said above, I think this is an area where there could be more innovation. If you guys find a better way to do things, we'd be happy to hear what you've done!

@ShayHaluba
Copy link

ShayHaluba commented Aug 26, 2017

Hi @joeldenning,

I have some questions about your state sharing techniques:

  • Shared API layer - where does it store the cached data? and how is it accessible from all child-apps?

  • Apps that export observables to other apps - is it being exported due the global window object? otherwise how child-apps has access to this observables?

  • Did you consider post messages protocol for establishing communication between child-apps?

It would be great if you add to the examples repository an example of this technics.

Thanks 👍

@joeldenning
Copy link
Member

Shared API layer - where does it store the cached data? and how is it accessible from all child-apps?

The shared api layer is accessible to all child apps via an import statement. We have a javascript module called "fetcher" which any child application can import. In our case, we are using SystemJS as our module registry, but this would also work with webpack, assuming there is one master webpack config for everything. So example code would look like this:

// In our case, fetcher is a module registered to SystemJS. But module resolution could also be done by webpack if you're using that.
import {getWithSharedCache} from 'fetcher';

// Get this api object, but share it with all other child applications. Once the user navigates away from user 1's settings page, clear the cache.
// Our specific implementation returns an observable, but it could just as easily return a Promise.
getWithSharedCache('/api/users/1', location => location.href.startsWith('/users/1/settings'))
.subscribe(...)

Apps that export observables to other apps - is it being exported due the global window object? otherwise how child-apps has access to this observables?

We do not put the observables on the window, but instead make them exported values from the javascript module for the child application. That way, other child applications can access the observable just with an import.

// child app 1, which exposes an observable
import {Observable} from 'rx'; // The rx library is an implementation of observables

// Single-spa lifecycles, as per normal for a child application
export function bootstrap(props) {...}
export function mount(props) {...}
export function unmount(props) {...}

// Observable for others to import
export const heightOfNavbar = Observable.just(4);
// child app 2, which needs the height of the navbar
import {heightOfNavbar} from '../path-to-child-app-1/child-app-1.js';

heightOfNavbar.subscribe(
  height => console.log('height', height)
);

Did you consider post messages protocol for establishing communication between child-apps?

My understanding of postMessage is that it allows for communication between two different window objects (which are usually iframes or browser tabs opened up from a parent tab). Assuming that my understanding of postMessage is correct, I don't think that it will work for communicating between single-spa child applications because all of those child applications are sharing the same window object (since they are all in the same browser tab and there are not usually inside of iframes).

It would be great if you add to the examples repository an example of this technics.

I agree! I just created #112 for this. Hopefully we'll find a good way to help others understand their options and how to implement inter-app communication. Contributions accepted, of course.

@blittle
Copy link
Contributor

blittle commented Aug 29, 2017

Another option would be to use a simple pub/sub event emitter. We have preferred observables because you can control if your subscription includes events that happened in the past before your subscription started (useful when two apps are mounting and you can't guarantee the load order).

@joeldenning
Copy link
Member

Closing -- feel free to reopen if there are more questions.

@abelkbil
Copy link

@me-12 @joeldenning Hope https://github.com/arkency/event-bus can do the inter spa communications. I have a doubt on socket connection from app one will be closed if the app is unmounted ? , Hope that is okay ?

@me-12
Copy link
Contributor

me-12 commented Jan 23, 2018

@abelkbil Have a look at my example app which uses redux for communication between apps: https://github.com/me-12/single-spa-portal-example Maybe that's helpful to you.

@joeldenning
Copy link
Member

@abelkbil It looks like eventing-bus allows you to unregister a single subscription or even unregister all subscriptions. If you unregister your subscriptions during the unmount lifecycle, your unmounted applications should no longer receive messages on the event bus once they are unmounted.

Let me know if I'm misunderstanding or if you have more questions! @me-12's example repo is a good place to look, too!

@abelkbil
Copy link

@joeldenning Thanks I understand eventing-bus subscription model.

@ghost
Copy link

ghost commented Dec 26, 2018

You might want to use something like postal.js
https://github.com/postaljs/postal.js

we have been using this for sometime now in production without issues

@nchandran030
Copy link

Hi Joel this is nitheesh I am using single spa application concept for my project here i am having 6 independent application all the applications are in Angular now I want to pass the data between two independent application can u please help me here.

https://github.com/nchandran030/single-spa-main-application

@joeldenning
Copy link
Member

Hi @nchandran030 - could you clarify what you need help with?

@joeldenning
Copy link
Member

Ah I see now that your comment is a duplicate of #552 (comment). I will respond there, instead.

@SENTHILnew
Copy link

@nchandran030 Take a look at my repo https://github.com/SENTHILnew/micro_spa_intercom
similiar way you can create a redux store and pass its ref to microapps through custom props

@smrutiwankhade
Copy link

Hi Team,

Actually I am facing issue while accessing the another application from container app. It gives me CORS issue while loading the application itself.

@joeldenning
Copy link
Member

CORS issues are resolved in the normal way - adding an Access-Control-Allow-Origin header to the server hosting the files. See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors

@shrinidhiurala
Copy link

You might want to use something like postal.js
https://github.com/postaljs/postal.js

we have been using this for sometime now in production without issues

it'll be great if you can share an example code for how to use it with single-spa? Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants