Skip to main content

Distributed UI and Macroservices

Framework M supports a Progressive Decomposition strategy, allowing you to transition from a monolithic "Indie" deployment to a distributed "Macroservice" architecture without rewriting your frontend.

[!TIP] Pragmatic Scaling: You don't always need a paradigm shift (like a rewrite in a different language) to solve scaling. Sometimes, just decoupling your domains into independent macroservices—even if they share the same stack—is the right architectural move for team autonomy and de-risking your core.

Overview

In a distributed architecture, different parts of the UI (Micro-Frontends or MFEs) are served by different backend services. Framework M manages this complexity through two key mechanisms:

  1. Config-Driven Discovery: The Shell Host (Main UI) dynamically discovers where each MFE is hosted via configuration.
  2. Transparent Proxying: The Shell Host can optionally proxy asset requests to remote services, ensuring a single-origin experience for the browser.

[!NOTE] Shell Host Discovery: The main "Shell" application registration continues to use local framework_m.shell entry points. The distributed discovery is currently focused on MFEs (Micro-Frontends). The Shell Host acts as the central orchestrator for all discovered remotes.


1. MFE Discovery

By default, Framework M discovers MFEs via Python entry points in local packages. For distributed deployments, you can override or extend this via environment variables.

Configuration Options

VariableDescriptionFormat
FRAMEWORK_M_MFE_REMOTESInline JSON or Base64 JSON of MFE remotes{"app_name": "https://service.com/mfe/app/1.0.0/remoteEntry.js"}
FRAMEWORK_M_MFE_REMOTES_FILEPath to a JSON file containing remotes/app/config/mfe_remotes.json

Example: mfe_remotes.json

{
"inventory": "https://inventory-service.production.svc.cluster.local/mfe/inventory/1.2.0/remoteEntry.js",
"accounting": "https://accounting.cdn.com/mfe/accounting/2.0.1/remoteEntry.js"
}

When configured, the Shell Host will point the browser (via Module Federation) to these remote entry points instead of looking for them locally.


2. Transparent Proxying

When an MFE is hosted on a different domain or internal service, browsers may block requests due to CORS or mixed-content policies. Framework M provides a transparent proxy to solve this.

Enabling the Proxy

Set the following environment variable on your Shell Host:

FRAMEWORK_M_MFE_PROXY_ENABLED=true

How it Works

  1. The browser requests an asset: GET /mfe/inventory/1.2.0/assets/main.js from the Shell Host.
  2. The Shell Host checks its remotes and sees inventory is remote.
  3. The Shell Host fetches the asset from the Remote Service (e.g., internal K8s service).
  4. The Shell Host streams the response back to the browser.

[!TIP] Use the proxy for internal service-to-service communication in Kubernetes to avoid exposing every microservice to the public internet.


3. Deployment Level 5 (Distributed)

In a "Level 5" deployment, your architecture looks like this:

  • Shell Host: Serves the main Desk UI and proxies MFE requests.
  • Macroservices: Independent containers serving their own APIs and MFE assets.

Kubernetes Logic

# Shell Host Deployment
env:
- name: FRAMEWORK_M_MFE_PROXY_ENABLED
value: "true"
- name: FRAMEWORK_M_MFE_REMOTES
value: '{"billing": "http://billing-service:8888/mfe/billing/1.0.0/remoteEntry.js"}'

The browser only sees the Shell Host, but the logic is distributed across your cluster.


4. Best Practices

  • Caching: Enable a CDN or Varnish in front of the Shell Host to cache proxied MFE assets.
  • Versioning: Always include the version in your MFE remote URLs to ensure atomic updates.
  • Indie Mode: During local development, omit the remote configuration to fall back to local "Indie" mode (Python entry points).

5. Building a Macroservice (Provider Side)

To participate in a distributed UI, your standalone microservice must fulfill a few requirements.

A. Serve Static Assets

Your macroservice must be configured to serve its own MFE assets. When using framework-m-standard, the serve_mfe_assets route is automatically available if you use the standard app factory.

  1. Build the MFE:
    cd libs/your-module/frontend && pnpm build:mfe
  2. Verify assets: Ensure remoteEntry.js exists in libs/your-module/src/your_module/static/mfe/.

B. The Standalone Runner

Create a simple app.py in your macroservice app to initialize the framework in standalone mode.

# apps/inventory-service/app.py
from framework_m_standard.adapters.web.app import create_app

app = create_app(serve_static=True) # Ensure static serving is ON

C. Run via Uvicorn

Run your service as an independent process:

uvicorn inventory_service.app:app --host 0.0.0.0 --port 8888

Now, your service is ready to be registered as a remote by any Shell Host.

Next Steps