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:
- Config-Driven Discovery: The Shell Host (Main UI) dynamically discovers where each MFE is hosted via configuration.
- 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.shellentry 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
| Variable | Description | Format |
|---|---|---|
FRAMEWORK_M_MFE_REMOTES | Inline JSON or Base64 JSON of MFE remotes | {"app_name": "https://service.com/mfe/app/1.0.0/remoteEntry.js"} |
FRAMEWORK_M_MFE_REMOTES_FILE | Path 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
- The browser requests an asset:
GET /mfe/inventory/1.2.0/assets/main.jsfrom the Shell Host. - The Shell Host checks its remotes and sees
inventoryis remote. - The Shell Host fetches the asset from the Remote Service (e.g., internal K8s service).
- 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.
- Build the MFE:
cd libs/your-module/frontend && pnpm build:mfe - Verify assets: Ensure
remoteEntry.jsexists inlibs/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
- Frontend Customization - Learn how to build MFEs.
- Kubernetes Deployment - Production deployment guide.
- Macroservices RFC - Architectural details.