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

Top-level routing #21

Closed
andrixb opened this issue Nov 27, 2018 · 8 comments
Closed

Top-level routing #21

andrixb opened this issue Nov 27, 2018 · 8 comments

Comments

@andrixb
Copy link

andrixb commented Nov 27, 2018

Hi there,

I hope I'm writing in the right section (otherwise I'll move it wherever it belongs).

I've been trying to implement the microservice architecture but even though I managed to run two different applications on the same page, I've been struggling a bit to get how to implement a proper routing.
In the website and in the videos I watched you talk about a top-level routing. Has this to be implemented in NGINX (for instance)?
How should I use this model if I need to create routes at the component level, like inner/sub-routes?

Many Thanks,

A.

@dhruvaldarji
Copy link

My team and I are also working on the same. We aren’t at the stage yet where we can split our monolith yet, but this question has been on our mind as well.

@adrw
Copy link

adrw commented Nov 27, 2018

With https://github.com/square/misk-web, our initial approach has developed into two idioms for sub-routes.

1. Sub-routes can only be extensions of the url domain of the component

Component Component URL Domain Allowed Sub-Routes
<foo-component/> /foo/ /foo/*
<bar-component/> /category/bar/ /category/bar/*

2. Components can not have overlapping url domains

Assume in the following example that components are added in the order of the table rows.

Component Component URL Domain Url Domain
<foo-component/> /foo/ Valid
<bar-component/> /foo/bar/ Invalid
<baz-component/> /category/baz/ Valid
<bip-component/> /category/bip/ Valid
<category-component/> /category/ Invalid

@ChristianUlbrich
Copy link

Lay down your burdens you need to rethink, what URLs have been originally been used for - to link to an application at a certain state.

In the time of SPAs we abused the URL for all sort of state-related things in our apps - we used it to route to a certain state of the app (by associating URLs with components). If we were to navigate to another state of the app, we triggered fake URL changes. However as time passed by, we figured out, that the state of a SPA needs special care and thus we all craved a dedicated state management (being it Redux, Mobix, ...).

If you look at the typical things the router in SPAs is doing right now, it becomes clear, that this is just an awkward mechanism to change the state of the app. But for changing the state of an app we do not need a router - we can just dispatch the corresponding actions and have generic router-less state router component listen for the state changes and thus load components accordingly.

If you see it that way you will solve the main problem - different components competing for using the URL (by using a router) as their sole source of state initialization. If all components are decoupled from the URL and are only using an in-memory based native state management aware routing, they will now all happily live together on the same page. For communication between components you have to use either DOM events or attributes.

How about "deep-linking" into micro frontend based apps? Depending on your micro frontend architecture - you might have a glue app (we call it the frame in our architecture) that is orchestrating all the components. As all components have been decoupled from the URL, the glue app can now utilize it again to (re)store the state of each component. The frame knows about the state changes between components and can encode this global state in the URL. Usually the state is to big to be kept in the URL anyway and thus it is feasible to store the serialized application state either locally or remote and associate it with a GUID / token that allows for restoring - i.e. once the frame has been passed the GUID it uses it to restore the state of all components.
It can do so, by either replaying all actions against all components or by storing not the actions but the GUIDs of the local state of components itself and let them handle the restoring of their inner local state by themselves.

That is the generic idea; we are using it in production by replacing the Angular Router in a Angular Elements app with a native NgRx aware (but still somehow compatible version) URL-less one. That way we can load multiple Angular Elements apps on one page.

@ChristianUlbrich
Copy link

@adrw I have to give your micro frontend architecture a try. Thanks for posting it! If I understood it correctly you have certain conventions what URLs (and sub paths) are mapped to certain components. This is a viable choice, but it needs coordination between teams to avoid naming collisions. Ultimately it weakens the isolation of components and might lead to problems in scaling - i.e. when certain conventions cannot be enforced easily any more (read: third party supplied components).

In our (above) approach we simply remove the dependency on the URL altogether, while still maintaining the possibility of deep-linking into app state. This allows for a better isolation of components by avoiding potential naming collisions on the URL.

Of course, GUID-based URLs are not so pretty(TM) and might scare away SEO magicians; but people simply do not memorize URLs any way. Even the most "SEO-optimized" URL will end up either as a bookmark or an entry in the browser history and therefore we have no problem with "ugly" URLs.

@daKmoR
Copy link

daKmoR commented Jan 5, 2019

Even the most "SEO-optimized" URL will end up either as a bookmark or an entry in the browser history and therefore we have no problem with "ugly" URLs.

I would disagree... I personally use individual URLs very often. Especially with the autocomplete of modern browsers

Example:

so by just selecting the appropriate URL I can go to where I need to be...
If it was all just

that experience would be rather different.

so I agree storing the full state in the URL is not possible for an application... but certain entry point should still have their easy human-readable path...

Unfortunately, I don't have a ready and good alternative solution... I guess it should be a combination of a "critical path" + data.
e.g. https://my-app.com/foo/bar?dataId=lasdflgue32343
How to best do this while still decoupling most data from the url and working with multiple teams who can give "hints" on what their feature component's url should be... I'm also not sure

@brion-fuller
Copy link

@ChristianUlbrich I really like the idea of separating state from the url. This is something I have tried to solve and still haven't found a good solution for.

I see the same problems as above. I do agree that URL is critical in the User Experience as this is a functionality commonly used and known to shortcut to desired pages. But how does the frame know when a components state should be referenced in the url someway?

Lets say I have a site and a team is working on a Users component and they want to display user "10". This is easy with state as we will see a request to get user "10" and then the component will populate. Now the problem comes in on refresh, lets say the clients wants user "11" and just replaces the url "/user/10" => "user/11" how is this translated and known?

@yavuztor
Copy link

@brion-fuller , I have faced the same challenge in one of my recent projects. We were trying to encapsulate all ui for a micro service in single web component (custom element) and had a shell application to compose these elements in some fashion. One requirement for us was that each view should be addressable by url to allow bookmarks.

The way we solved the problem was to expect a view attribute in each web component and parse this value in the web component to show a specific view. For instance, we had something like <title-app view="/view/1"></title-app>. So, it is much like routing, but instead of playing with the URL, the component simply expects it to be passed in through the view attribute. Now, this was the first piece of the puzzle. It allowed us to instantiate any view in that web component simply by passing an attribute. No URLs so far.

In the shell application that composed these components, we applied a simple heuristic for routing: /app/:componentName/**:view. More specifically, every url that started with /app/, we expected the next segment to be the componentName. So, if it was /app/title-app, we would create and append the title-app component to the DOM. The remainder of the url was passed into the view attribute. For instance, the url /app/title-app/edit/3 would create an element like <title-app view="/edit/3"></title-app>.

This allowed us to serve up our web components in a shell application, but also able to use them in other web applications that works differently. For instance, we used some of these components in a ASP.Net web forms application, without making any changes.

@andrixb
Copy link
Author

andrixb commented Apr 26, 2019

Hi there,
I actually solved that issue by taking inspiration from this repo:

https://github.com/me-12/single-spa-portal-example

He does reverse proxying through Webpack. It's actually unnecessary, you can just pass the URL where the entrypoint.js files are hosted.
Hope that help

@andrixb andrixb closed this as completed Apr 26, 2019
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

7 participants