Dashboard as Microfrontend
- Description of the Feature
- The existing dungeon dashboard is a monolithic client that aggregates and displays inputs from several core services via its own backend. This monolithic design does not fit well with a microservice system in which the individual services are as self-sufficient as possible. In addition, there is the problem that backend business logic is implemented in the client (which can be seen from the fact that certain information is not updated in the client, if the corresponding tab is not in the foreground). For this reason, the dashboard is to be converted into a microfrontend architecture. There is only one "umbrella client" that loads and displays the microfrontends. Each microfrontend is assigned to a core service and displays information from this service or allows interactions with this service. with this service.
- Author
- Ahmad Bannout, Erdinc Gürsoy, Sascha Jannsen
- Implemented During
- WASP II "Microservices und Event-getriebene Architektur (MEA), SS2024, as topic (A5)
- Status
- ongoing
- Repositories
-
- https://gitlab.com/erdinc61/microfrontend-dummy (Branch main):
- (tbd.)
- Last Update
- 2025-02-04
Documentation for the Dashboard as Microfrontend
Situation Description:
- University project ⇒ limited time, constantly changing developers ⇒ Maintenance problematic
- Ensuring long-term functionality
- Ensuring a consistent UI
- Implementation of the necessary infrastructure
Criteria for Technology Selection (derived from the situation)
- What (special) requirements are there for our dashboard?
- Real-time data, more complex data through the map, many user interactions ⇒ Performance
- Maintainability, as simple as possible and as automated as possible
- Possibility of easy connection of player frontends
- Communication between the micro-frontends, will be necessary for the map
- Technology-independent development of the frontends
Technology Comparison
iFrame
iFrames are HTML elements that allow embedding another HTML page within an existing page.
Implementation:
- Creation of the Root Application: A simple HTML page with iFrames acting as containers.
- Embedding Micro-Frontends: Each Micro-Frontend application is loaded within an iFrame.
- Communication: Use
PostMessage
for communication between the iFrames.
Web Components
Web Components are a collection of technologies that enable the creation of reusable and encapsulated HTML elements, which can be used across any web application. They consist of three main technologies:
- Custom Elements: Allows the definition of custom HTML elements.
- Shadow DOM: Provides encapsulation of the DOM and styles, ensuring that the implementation of a Web Component is isolated from the rest of the page.
- HTML Templates: Enables the creation of templates for reusable markup structures.
Webpack 5 Module Federation
Module Federation is a concept and feature introduced in Webpack 5. It allows different web applications and microfrontends to share and import modules at runtime, without the need for these modules to be duplicated in each project. This can significantly improve the reusability and integration of modules across different projects.
Webpack is a widely used open-source module bundler tool for JavaScript applications. It processes modules with dependencies and generates static assets that can be efficiently loaded by browsers. Webpack allows developers to leverage modern JavaScript features and libraries while reducing the complexity of managing dependencies and builds.
Implementation:
-
Configure ESLint
{ "extends": ["next/babel", "next/core-web-vitals"] }
-
Configure the Remote Application
const NextFederationPlugin = require("@module-federation/nextjs-mf"); const { FederatedTypesPlugin } = require("@module-federation/typescript"); const federatedConfig = { name: "remote", filename: "static/chunks/remoteEntry.js", exposes: { "./Home": "./src/component/home.tsx", }, shared: {}, }; const nextConfig = { reactStrictMode: true, typescript: { ignoreBuildErrors: true, }, webpack(config, options) { config.plugins.push( new NextFederationPlugin(federatedConfig), new FederatedTypesPlugin({ federationConfig }) ); return config; }, }; module.exports = nextConfig;
-
Create a Component to Use in the Host Project
import React from 'react'; const Home = () => { return <div>Welcome to the Remote Home Component!</div>; }; export default Home;
-
Configure the Host Application
const NextFederationPlugin = require("@module-federation/nextjs-mf"); const { FederatedTypesPlugin } = require("@module-federation/typescript"); const nextConfig = { reactStrictMode: true, typescript: { ignoreBuildErrors: true, }, webpack(config, options) { const { isServer } = options; const remotes = { remote: `remote@http://localhost:3001/_next/static/chunks/remoteEntry.js`, }; const federatedConfig = { name: "host", remotes: remotes, shared: {}, }; config.plugins.push( new NextFederationPlugin(federatedConfig), new FederatedTypesPlugin({ federationConfig }) ); return config; }, }; module.exports = nextConfig;
-
Use the Exported Component in the Host
import React from 'react'; const RemoteHome = React.lazy(() => import('remote/Home')); const IndexPage = () => { return ( <React.Suspense fallback="Loading Remote Component..."> <RemoteHome /> </React.Suspense> ); }; export default IndexPage;
RSBuild
RSBuild is a framework specifically designed for the development of micro-frontend architectures. It offers several advantages, but also some disadvantages compared to other technologies for micro-frontend containers.
For more information, see: RSBuild Example
Single SPA
Single-SPA (Single-Single Page Application) is a micro-frontend framework that enables multiple micro-frontends to be combined in a single web application. It loads and renders individual micro-frontends at runtime as needed, ensuring they cooperate with each other.
- Application Registration: Each micro-frontend application is registered with Single-SPA and loaded based on routes and conditions.
- Lifecycle Hooks: Single-SPA uses lifecycle hooks (bootstrap, mount, unmount) to start, render, and remove micro-frontends.
- Routing: Single-SPA manages routing and directs navigation to the appropriate micro-frontends.
- Framework-Agnostic: Supports micro-frontends built with various frameworks like React, Angular, Vue, etc.
Implementation:
- Setup the Root Application: Create a root application with Single-SPA.
- Register Micro-Frontends: Each micro-frontend is registered as a separate application and loaded as needed.
- Communication Between Micro-Frontends: Use global event bus or shared state mechanisms for communication.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Root Config</title>
<script type="systemjs-importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js",
"react": "https://cdn.jsdelivr.net/npm/react@17.0.2/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@17.0.2/umd/react-dom.production.min.js"
}
}
</script>
<script type="systemjs-importmap">
{
"imports": {
"@single-spa/welcome": "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js",
"@MEA/root-config": "//localhost:9000/MEA-root-config.js",
"@MEA/React-MicroFrontend": "//localhost:8080/MEA-React-MicroFrontend.js",
"@MEA/React-MicroFrontend2": "//localhost:8081/MEA-React-MicroFrontend2.js"
}
}
</script>
</head>
<body>
</body>
</html>
index.js
<single-spa-router>
<nav>
<application name="@org/navbar"></application>
</nav>
<route path="settings">
<application name="@org/settings"></application>
</route>
<main>
<route default>
<h1>Hello</h1>
<application name="@MEA/React-MicroFrontend"></application>
<application name="@MEA/React-MicroFrontend2"></application>
</route>
</main>
</single-spa-router>
microfrontend-layout.html
```javascript
import { registerApplication, start } from "single-spa";
import {
constructApplications,
constructRoutes,
constructLayoutEngine,
} from "single-spa-layout";
import microfrontendLayout from "./microfrontend-layout.html";
const routes = constructRoutes(microfrontendLayout);
const applications = constructApplications({
routes,
loadApp({ name }) {
return System.import(name);
},
});
const layoutEngine = constructLayoutEngine({ routes, applications });
applications.forEach(registerApplication);
layoutEngine.activate();
start();
```
root-config.js
https://github.com/erdinc61/mfederation-test
Dynamic UI Composition
Dynamic UI Composition is an architectural approach where the backend acts as a gateway and dynamically assembles the UI components at runtime. This can be achieved through a Backend-for-Frontend (BFF) pattern or a gateway that integrates the various micro-frontends and presents them as a cohesive page.
Technology comparison
Technology | Advantages | Disadvantages |
---|---|---|
iFrames | - Easy Integration: Minimal effort to embed. - Isolation: Each application runs in isolation, avoiding conflicts. |
- Communication: Difficult and often cumbersome communication between the main application and the embedded content. - Performance: Higher resource consumption and potentially slower loading. - User Experience: Limited design options and poorer user experience. |
Web Components | - Standardized: Works in all modern browsers without additional libraries. - Reusability: Components can be used independently of the framework. - Encapsulation: Styles and scripts of a Web Component are isolated from the rest of the document. - Framework Independence: Can be used in any web application, regardless of the framework used. |
- Browser Compatibility in older browsers. - Boilerplate Code: Creating Web Components often requires more boilerplate code compared to frameworks like React or Vue, which can make development somewhat cumbersome. - Styling: Shadow DOM offers good isolation for styles but can also make sharing global styles difficult. Managing styles between Shadow DOM and the outer page can be complicated. - Performance: Initial Load Time: Using Shadow DOM and custom elements can increase the initial load time, especially when many components are rendered simultaneously. |
Webpack 5 Module Federation | - Performance: Efficient use of shared modules. - Integration: Seamless integration of micro-frontends, regardless of the framework used. |
- Dependency on Build Tools (Webpack 5). - Incompatibilities: Different versions of the same library can lead to unpredictable errors and compatibility issues. - Maintenance Effort: Maintaining the configuration and shared dependencies can be time-consuming and error-prone. - Scalability: Managing shared dependencies and configuration can become complex and difficult to scale with a large number of micro-frontends. |
Single SPA | - Flexibility: Supports various frameworks like React, Vue, Angular, and more. - Modularity: Each micro-frontend can be developed and deployed independently. - Efficient Loading: Loads micro-frontends on demand and can share common resources. - Good Developer Experience: Provides APIs and tools for easy management of micro-frontends. |
- Complexity: Can be more complex to set up and maintain than simpler approaches. - Isolating and managing styles between different micro-frontends can be challenging and lead to inconsistencies. |
Dynamic UI Composition | - Flexibility: Allows dynamic and context-dependent assembly of UI components. - Encapsulation: Backend handles the logic of composition, simplifying frontend development. |
- Complexity: Requires a complex backend infrastructure and increases overall system complexity. - Performance: Can introduce additional latencies when the backend dynamically assembles the UI. |
RSBuild | 1. Performance: - Optimized performance through efficient bundling and lazy-loading strategies. - Minimizes load time and improves user experience through asynchronous loading of modules. 2. Isolation: - RS Build offers strong isolation between different micro-frontends, preventing errors in one module from affecting others. - This enables a more robust and stable application. 3. Flexibility: - Supports various frontend frameworks and libraries, allowing developers to choose the best technologies for their needs. - Enables integration of existing projects without major restructuring. |
- Community and Support: Compared to established technologies like Webpack or Module Federation, the community around RS Build might be smaller. - Dependencies: Strong dependency on the RS Build infrastructure and provided tools, which could limit flexibility in choosing alternative solutions. - Potential issues with updating or migrating to new versions of RS Build. |
Our Technologies:
Webpack:
- Webpack: Module bundler for modern JavaScript applications.
- Webpack Module Federation Plugin: Enables module federation, where modules can be shared between different Webpack builds.
JavaScript Frameworks/Libraries:
- React
- Vue.js
- Angular
Development Tools
- ESLint
- Prettier
Styling
- CSS-in-JS
- SASS/SCSS
- Tailwind CSS
API Communication
-
Axios: Promise-based HTTP client for the browser and Node.js:
import axios from "axios"; const API_URL = "https://jsonplaceholder.typicode.com"; export const fetchPosts = async () => { const response = await axios.get(`${API_URL}/posts`); return response.data; };
-
React Query: For data fetching and state management in React applications.
You need to create a provider and client to use React Query
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactNode } from "react"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; const queryClient = new QueryClient(); interface ProvidersProps { children: ReactNode; } export function Providers({ children }: ProvidersProps) { return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ); }
React Query example:
import { useQuery } from "@tanstack/react-query";
import { fetchPosts } from "../services/api";
const PostsPage = () => {
const { data, error, isLoading } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
});
if (isLoading) return <div>Loading...</div>;
if (error instanceof Error)
return <div>An error occurred: {error.message}</div>;
return (
<div>
<h1>Posts</h1>
<ul>
{data.map((post: { id: number; title: string; body: string }) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
</div>
);
};
export default PostsPage;
State Management
- Redux: Centralized state management for React applications.
- RobotService -> WebSocket endpoint
- Planet-Service -> WebSocket endpoint
- Trading-Service -> WebSocket endpoint
Microfrontend Technologies:
- Single-SPA and Qiankun are ideal for complex microfrontend applications that need to integrate different frameworks.
- PuzzleJS and Piral are good for simpler, fast-to-develop microfrontends.
- Web Components are ideal for creating reusable UI components across different frameworks.
- Mosaic is best suited for large enterprises with complex requirements and the need for extensive support.
https://github.com/erdinc61/mfederation-test
Conclusion: Setting up the repositories was well documented and allowed for a smooth start. Expanding the project is easy and intuitive. You quickly get into the structure, and the loose coupling of the components is good. However, the update does not seem to be fully given.
Architecture:
Current Architecture:
The MSD dashboard queries the MSD dashboard backend service and the game log service at regular intervals. These services listen to the Kafka queue and process most, if not all, events (I have not compared every single event). The processed events are written to their databases, providing the dashboard frontend with information on robots, planets, scoreboards, and possibly also achievements (which represents an interesting extension possibility).
The MSD dashboard frontend allows the starting and ending of games as well as the creation of custom players via REST calls to the game service. If selected by the user, these custom players are started via the MSD dashboard Docker API.
Monorepos vs. Multi-Repos
What is a Monorepo?
A monorepo hosts multiple projects or components within a single repository. This approach promotes code and resource sharing, simplifies dependency management, and ensures consistency.
What is a Multi-Repo Approach?
A multi-repository structure involves housing individual projects or components in separate repositories. This provides autonomy to different teams working on different projects. This isolation allows for a focused and structured development process where teams can independently manage their codebase.
Monorepos
Pro:
- Easier coordination and synchronization: Frontend and backend can easily collaborate as both parts of the application are in the same repository.
- Consistent code quality and standards: Shared repository promotes uniform code standards and best practices.
- Simplified dependency management: Centralized management of dependencies reduces version conflicts.
- More efficient CI/CD pipelines: Integrated CI/CD pipelines enable consistent builds and releases for the entire system.
- Cohesion: The frontend serves as a complement to the backend and is not meaningful on its own, while the backend could also operate independently. Therefore, frontend and backend should be considered a unit and merged together to ensure efficient and better development and maintenance of the application.
Contra:
- Complexity and performance issues: Large monorepos can cause slow build and pull processes, and it’s easy to lose track.
- Scalability issues: Growing monorepos can be more difficult to scale and maintain.
- Size issue: Too large repositories can make it difficult for developers to get started.
- Version management: The shared repository leads to unclear version management.
Multi-Repos
Advantages:
- Isolation and independence: Issues in one project do not affect other areas.
- Flexibility in tool selection: Teams can use the tools and workflows best suited to their needs.
- Lower complexity and faster builds: Smaller repositories lead to faster build and test cycles.
- Easier entry: A smaller repository allows developers to get into the project more quickly and focus on specific aspects of the application. This makes understanding and editing the code easier, leading to more efficient and focused development work.
Disadvantages:
- Difficulty managing dependencies: Complex and error-prone dependency management.
- Increased administrative overhead: Multiple repositories require more coordination and management.
- Potential communication issues: Isolated work can lead to more effort.
https://www.gitkraken.com/blog/git-multi-repo-vs-git-mono-repo
https://www.thoughtworks.com/en-us/insights/blog/agile-engineering-practices/monorepo-vs-multirepo
Our Repo Strategy
Placement of Repos
The repositories are located in a dedicated area
within the “core-services.” We are following a multi-repo approach. This not only allows for centralized management but also facilitates coordination with the various microfrontends and other components. Most importantly, this approach significantly eases the maintenance and expansion of individual components.
Placement of Individual Components
- Microfrontends: Each microfrontend component is managed in its own repository. This allows for independent development, testing, and deployment of individual frontend parts.
- Design System: The design system is maintained in a larger, central repository. This ensures that all frontend components can access a consistent design and common UI components.
Deployment of a New Component
When deploying a new microfrontend component, the process follows these steps:
- The microfrontend is developed in its own repository.
- After completion and testing, it is deployed.
- It is then integrated into the container (shell), making it part of the overall application.
Necessary Changes in Various Repositories
- Core Services Repositories
- DevOps Repositories
- Docs-Repo
Naming the Feature Branch
For the development of new features, a specific branch naming convention is used:
- Feature Branch: MSD-{ticketnumber}-{tickettitle} (Example: MSD-10-Add_websocket_endpoint). This ensures clear assignment of branches to specific tickets and their descriptions.
For deploying the microfrontends, we have three options:
- Content Delivery Network, i.e., a server we rent (not a real option)
- Deployment via own Docker containers in which the frontends run
- Deployment via the respective backend service, which then provides the frontend via the standard URL, e.g., MapService would provide the map microfrontend via the simplest URL http://localhost/map:3000.
If we want separate repositories for the frontends, option 2 makes sense. Option 3 with separate repositories for the frontends is feasible but cumbersome.
The implementation of a new microfrontend would proceed as follows:
- Implement the code
- Configure e.g., Webpack Module Federation (plugin)
- Build (e.g., tsc compile or bundling of the files)
- Deploy the microfrontend via one of the above options.
- Integrate the microfrontend into the host/container frontend
Ideas for Map implementation
Approach I OREO-Concept
OREO-Concept Description
The OREO concept describes the mere layering of the Map-Frontend and the Robot-Frontend. These are completely independent of each other. As standalone components, we are already leveraging the advantages of micro-frontends here. And this with comparatively little effort.
OREO-Concept Advantages
- Independence: Each frontend can be developed, deployed, and scaled independently, allowing for greater flexibility and faster development cycles.
- Modularity: The separation of concerns ensures that each component can be maintained and updated without affecting the other, reducing the risk of introducing bugs.
- Scalability: Independent components can be scaled individually based on their specific needs, optimizing resource usage.
OREO-Concept Disadvantages
- Integration Complexity: Combining independent frontends can introduce complexity in terms of integration and communication between the components. This is limited with this approach.
- Overhead: Managing multiple independent components can introduce overhead in terms of deployment, monitoring, and maintenance.
OREO-Concept Naming
The name derives from the visual analogy of the two frontends as the cookies of an OREO, simply stacked on top of each other.
Approach II Narcos-Concept
Narcos-Concept Description
This approach involves the container, in this case, the Robot-Map container, determining the position of the robots. The robots are independent applications. The Robot-Map container determines the position of the robot’s planet based on the provided planetId and positions the robot accordingly.
In this approach, not only are the map and the planets independent of the robots, but each individual robot is also independent. This is a very complex approach that allows for very loose coupling. In our situation, this approach is overkill, and the benefits do not outweigh the effort required to implement it.
Narcos-Concept Advantages
- High Flexibility: Since each robot is an independent application, they can be developed, tested, and deployed independently.
- Scalability: Individual robots can be scaled independently according to demand and load.
- Maintainability: Changes to one robot do not directly affect other robots or the map, making maintenance easier.
- Reusability: Robots can be reused in different contexts or projects because they are independent.
- Technology Independence: Different robots can be developed with different technologies, allowing the best tools to be chosen for each task.
Narcos-Concept Disadvantages
- Complexity: Managing and coordinating many independent applications can be very complex.
- Performance Overhead: Communication between the independent units can lead to performance overhead.
- Deployment: Deploying many independent units can be more complex and requires a well-thought-out CI/CD pipeline.
- Debugging: Debugging can be more difficult as issues may arise in the interaction between the independent units.
- Initial Effort: The initial effort to set up the infrastructure and communication mechanisms is high.
Narcos-Concept Naming
This concept is reminiscent of Pablo Escobar, who commands his Narcos. These are independent units that are assigned to cities by him. Hence the name Narcos-Concept.
Narcos-Concept Implementation
An implementation of this concept can be found in this repository. More information is also available here: https://gitlab.com/sjannsen/MSD-Map-Approach-2
Approach III WW3-Concept
WW3-Concept Descripton
In this concept, the coordination of the Map-Frontend and Robot-Frontend is done through a shared state. The container application provides a map grid. Here, the planets can register and position themselves. Each planet represents its own frontend component. The robots, which are also independent frontend components, can use the state to find the position of the corresponding planet and position themselves accordingly. The difference from the Narcos-Concept lies particularly in the independent positioning of the components on a grid provided by the container. In the Narcos-Concept, these are positioned by the container. Unfortunately, within the given timeframe of the project, we were not able to implement this approach successfully. Of the three approaches, this is the most complex and requires the most effort.
WW3-Concept Advantages
- Independent Positioning: Components can position themselves independently on the grid, allowing for more flexibility and dynamic interactions.
- Modularity: Each planet and robot being its own frontend component promotes modularity and separation of concerns.
- Scalability: The approach scales very well as each component can be developed and deployed independently.
- High Fault Tolerance: Due to the loose coupling, the system has a high resistance to failures.
WW3-Concept Disadvantages
- Complexity: The approach is the most complex among the three, requiring significant effort to implement and maintain.
- Coordination Overhead: Managing the shared state and ensuring consistent positioning introduces additional overhead.
- Implementation Time: The complexity and effort required are causing an enormous amount of time needed to implement this approach, especially in comparison to the others.
WW3-Concept Naming
The name derives from the analogy of independent units in the military, which coordinate together in an operational area.
Connecting a Player Frontend
Example of dynamically loading frontends in Single SPA: https://gitlab.com/sjannsen/Single-SPA-Test
To load player-frontends the GameService needs to save the Name and the URL at the registration of a player. Under an endpoint he needs to provide a JSON of the following pattern:
[
{
name: '@MEA/React-MicroFrontend',
url: '//localhost:8080/MEA-React-MicroFrontend.js',
},
{
name: '@MEA/React-MicroFrontend2',
url: '//localhost:8081/MEA-React-MicroFrontend2.js',
},
]
This is all the container application needs to dynamically import the player frontends. Further details can be found here: https://gitlab.com/sjannsen/Single-SPA-Test#dynamic-integration-of-microfrontends
Where to go from here
We have presented and evaluated three possible approaches for building the MSD dashboard in micro-frontends. Approach I, in particular, can be realized with a reasonable amount of effort. The feature of integrating player frontends is also feasible with some effort, and this documentation provides starting points for it. As the next possible step, the MSD dashboard could be migrated to a micro-frontend technology like Single-SPA or similar. The current state of the map will initially remain unchanged. After that, the feature of integrating player frontends could be implemented. Finally, the map could be converted into micro-frontends. The advantages of micro-frontends are not particularly relevant in our situation from a pragmatic perspective, as we do not have business use cases where loose coupling is of particular importance, such as when outages are associated with financial loss or when we have many teams working simultaneously on an application. However, this would create a very realistic working environment, as found in many large companies like Rewe Digital, Zalando, etc.