Skip to main content

UI Delivery & Microfrontends

Secure, high-performance serving of microfrontend assets with support for Module Federation, Web Components, and ES Modules.

Scope

This document covers the following packages and their interfaces:

LayerPackagesKey Files
Applicationpkg/v2/application/ui/ui_service.go, asset_server.go, cache_manager.go, sri.go, service.go
Sub-packagespkg/v2/application/ui/module_federation/container_loader.go, import_map_generator.go
Sub-packagespkg/v2/application/ui/web_components/component_registry.go
Sub-packagespkg/v2/application/ui/esm/module_resolver.go, import_analyzer.go
Domain Modelspkg/v2/domain/app/app.go (UI configuration)
Repositoriespkg/v2/domain/repository/asset_repository.go, app_repository.go
Presentationpkg/v2/presentation/ui/handler.go, compression.go
Infrastructurepkg/v2/infrastructure/encryption/Asset checksum validation

Overview

Based on pkg/v2/application/ui/, the UI engine provides:

  • Asset Serving: High-performance serving with in-memory caching and SRI verification
  • Module Federation Support: Webpack 5 Module Federation integration
  • Web Components: Custom element registration and serving
  • ES Modules (ESM): Native ES module support with import maps
  • Subresource Integrity (SRI): Cryptographic hash verification for browser security
  • Event-Driven Cache Invalidation: Automatic cache invalidation on app lifecycle events
  • Compression: Automatic Brotli/Gzip compression for optimal delivery

Service Architecture

Based on pkg/v2/application/ui/service.go:

Service Interface

type Service interface {
ServeAsset(ctx context.Context, appID uuid.UUID, appName, assetName string) (*AssetResponse, error)
GetModuleFederationConfig(ctx context.Context, appName string) (*ModuleFederationConfig, error)
GetWebComponentConfig(ctx context.Context, appName string) (*WebComponentConfig, error)
GetESMConfig(ctx context.Context, appName string) (*ESMConfig, error)
ListAssets(ctx context.Context, appID uuid.UUID) ([]*AssetMetadata, error)
ValidateAssetIntegrity(ctx context.Context, appID uuid.UUID, assetName string) (bool, error)
Start(ctx context.Context) error
}

Core Components

UIService Implementation (ui_service.go)

  • AssetServer: Handles asset serving with caching
  • CacheManager: In-memory cache with TTL and size limits
  • Event Bus Integration: Subscribes to app lifecycle and permission events
  • Repository Dependencies: Asset and App repositories

Initialization:

uiService := ui.NewUIService(
repos.assetRepo,
repos.appRepo,
infra.routeRegistry,
uiCacheManager,
eventBus,
logger,
)

Asset Serving System

Based on pkg/v2/application/ui/asset_server.go:

Asset Serving Pipeline

Request for Asset

Check In-Memory Cache

Cache Hit? → Return Cached Asset

Cache Miss:
1. Retrieve from repository (PostgreSQL)
2. Validate SHA-256 checksum
3. Generate SRI hash
4. Generate ETag (first 16 chars of SHA-256)
5. Store in cache
6. Return asset

AssetResponse Structure

type AssetResponse struct {
Content io.ReadCloser
ContentType string // MIME type
Size int64
SHA256 string // Checksum
ETag string // For conditional requests
CacheControl string // Cache directives
LastModified int64 // Millisecond precision
SRI string // Subresource Integrity hash
}

HTTP Headers

Response Headers Set:

Content-Type: <asset MIME type>
Cache-Control: public, max-age=31536000, immutable
ETag: "<SHA256 first 16 chars>"
X-Subresource-Integrity: sha256-<base64-hash>
Content-Encoding: gzip | br (if compressed)
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: *

Cache Management

Based on pkg/v2/application/ui/cache_manager.go:

Cache Configuration

Default Settings:

  • TTL: 5 minutes
  • Max Size: 100MB
  • Cleanup Interval: 5 minutes (background goroutine)

CachedAsset Structure

type CachedAsset struct {
Data []byte
MimeType string
SHA256 string
ETag string
SRI string
LastModified int64
CachedAt int64 // Unix milliseconds
}

Cache Key Format

"{appName}:{assetName}"

Examples:

  • todos-app:remoteEntry.js
  • analytics:main.js
  • auth-service:styles.css

Cache Operations

Get - Retrieves cached asset, returns nil if expired Set - Stores asset, evicts old entries if size exceeded DeleteByPrefix - Clears all keys matching pattern (e.g., todos-app:*) Clear - Flushes entire cache

Size-Based Eviction

When cache size exceeds limit:

  1. Sort entries by cached time (oldest first)
  2. Remove oldest entries until size < max
  3. Log eviction count

Subresource Integrity (SRI)

Based on pkg/v2/application/ui/sri.go:

SRI Hash Generation

GenerateSRIHash(content []byte) string
// Returns: "sha256-<base64-encoded-hash>"
// Example: "sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng="

Browser Usage

<script
src="/apps/todos-app/assets/remoteEntry.js"
integrity="sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng="
crossorigin="anonymous">
</script>

Security Benefits

  • Man-in-the-Middle Protection: Browser blocks execution if hash doesn't match
  • CDN Trust: Verify assets even when served from untrusted CDNs
  • Tamper Detection: Detects any modification to assets
  • Transmitted in Header: X-Subresource-Integrity header for client reference

Validation

ValidateSRIHash(content []byte, hash string) bool

Microfrontend Integration Modes

1. Module Federation (Webpack 5)

Configuration Endpoint: GET /apps/{appName}/federation-manifest.json

Config Structure:

type ModuleFederationConfig struct {
RemoteEntryURL string // URL to remoteEntry.js
ExposedModule string // Exposed module name
ModuleName string // App name
Metadata map[string]string
}

Implementation: pkg/v2/application/ui/module_federation/

Components:

  • ContainerLoader (container_loader.go): Manages Module Federation containers
  • ImportMapGenerator (import_map_generator.go): Generates ES Module import maps

Example Response:

{
"remoteEntryURL": "/apps/todos-app/assets/remoteEntry.js",
"exposedModule": "./App",
"moduleName": "todos-app",
"metadata": {
"version": "1.0.0",
"framework": "vue"
}
}

Shell Integration:

// Shell loads remote module
const container = await loadRemote('todos-app/App')
const component = container.get('./App')

2. Web Components

Configuration Endpoint: GET /apps/{appName}/web-component.json

Config Structure:

type WebComponentConfig struct {
TagName string // Custom element tag (e.g., "myapp-app")
ScriptURL string // URL to component script
Metadata map[string]string
}

Implementation: pkg/v2/application/ui/web_components/

ComponentRegistry (component_registry.go):

  • Registers custom elements with validation
  • Enforces Web Components spec (tag names must contain hyphen)
  • Supports component dependencies and observed attributes

Component Definition:

type ComponentDefinition struct {
TagName string // Must contain hyphen
ComponentURL string // JavaScript file URL
ShadowDOM bool // Use Shadow DOM
Dependencies []string // Required components
ObservedAttrs []string // HTML attributes to observe
}

Example Response:

{
"tagName": "todos-app",
"scriptURL": "/apps/todos-app/assets/component.js",
"metadata": {
"shadowDOM": "true",
"version": "1.0.0"
}
}

Shell Integration:

<!-- Shell mounts web component -->
<script src="/apps/todos-app/assets/component.js"></script>
<todos-app user-id="123"></todos-app>

3. ES Modules (ESM)

Configuration Endpoint: GET /apps/{appName}/esm-config.json

Config Structure:

type ESMConfig struct {
ImportURL string // URL to ESM entry point
Exports []string // Exported symbols
Metadata map[string]string
}

Implementation: pkg/v2/application/ui/esm/

Module Resolution (module_resolver.go):

  • Resolves bare specifiers (npm packages)
  • Handles relative imports (./, ../)
  • Supports absolute URLs and paths
  • Auto-normalizes file extensions

Import Analysis (import_analyzer.go):

  • Extracts import statements from JavaScript code
  • Builds dependency graphs
  • Detects circular dependencies
  • Uses regex: import\s+(?:[\w\s{},*]*\s+from\s+)?['"]([\w\-@/\.]+)['"]

Example Response:

{
"importURL": "/apps/todos-app/assets/main.js",
"exports": ["TodosApp", "TodosList", "TodoForm"],
"metadata": {
"type": "module",
"version": "1.0.0"
}
}

Shell Integration:

// Shell imports ES module
import { TodosApp } from '/apps/todos-app/assets/main.js'

Asset Types Supported

Based on pkg/v2/application/ui/models.go:

AssetMetadata Structure

type AssetMetadata struct {
Name string
MimeType string
Size int64
SHA256 string
UploadedAt int64
}

Supported MIME Types

JavaScript:

  • application/javascript
  • text/javascript

Stylesheets:

  • text/css

Markup:

  • text/html

Data:

  • application/json

Images:

  • image/png, image/jpeg, image/svg+xml, image/webp

Fonts:

  • font/woff2, font/woff

HTTP Presentation Layer

Based on pkg/v2/presentation/ui/handler.go:

Registered Routes

GET /apps/{appName}/assets/{assetPath...}           - Serve assets
GET /apps/{appName}/federation-manifest.json - Module Federation config
GET /apps/{appName}/web-component.json - Web Component config
GET /apps/{appName}/esm-config.json - ESM config

Asset Serving Pipeline

1. Parse Request
├─ Extract appName from URL
└─ Extract assetPath from URL

2. App Validation
├─ Verify app exists
└─ Verify app is INSTALLED

3. Service Call
└─ Delegate to UI service

4. Compression
├─ Detect if beneficial (threshold: 1KB)
├─ Choose algorithm (Brotli > Gzip)
└─ Apply if result smaller than original

5. Header Setup
├─ Content-Type
├─ Cache-Control
├─ ETag
├─ SRI
└─ Security headers

6. Response
└─ Return with appropriate encoding

Compression

Based on pkg/v2/presentation/ui/compression.go:

Algorithms:

  • Brotli: Levels 0-11 (preferred, better compression)
  • Gzip: Levels 0-9 (fallback, wider support)

Compression Strategy:

  • Threshold: 1KB minimum size
  • Quality Detection: Only compress if result smaller than original
  • Accept-Encoding: Respects client preferences
  • Content Types: Only compress text-based assets (JS, CSS, HTML, JSON)

Integration with App Registration

Based on pkg/v2/domain/app/app.go:

UI Configuration Structure

type UI struct {
Mode UIMode // federation | web_component | esm_component
EntryAssetName string // Asset name (e.g., "remoteEntry.js", "main.js")
ExposedModule string // Module to expose (federation mode)
RouteBase string // Base route for UI
SSR bool // Server-side rendering enabled
Navigation []*NavigationRoute // 3-level navigation
}

type UIMode string
const (
UIModeFederation UIMode = "federation"
UIModeWebComponent UIMode = "web_component"
UIModeESM UIMode = "esm_component"
)

App Struct Integration

type App struct {
ID uuid.UUID
Name string
State State
WebApp *WebApp // Contains UI config
}

type WebApp struct {
UI *UI
}

Asset Validation

The service validates:

  • Asset checksums match stored SHA-256 values
  • Asset MIME types are appropriate
  • App is in INSTALLED state before serving

Event-Driven Cache Invalidation

Based on ui_service.go:187-262:

Event Subscription Architecture

Event Types Monitored:

// Subscribe to app lifecycle events
eventBus.Subscribe("app.*", handleAppLifecycleEvent)

// Subscribe to permission invalidation events
eventBus.Subscribe("permission.invalidated", handlePermissionInvalidation)

Cache Invalidation Strategies

1. App Lifecycle Events (app.installed, app.uninstalled):

// Extract AppName from event payload
var payloadData struct {
AppName string `json:"AppName"`
}
json.Unmarshal(evt.Payload(), &payloadData)

// Invalidate all assets for app
cache.DeleteByPrefix(payloadData.AppName + ":")

2. Permission Invalidation:

// Conservative approach: clear entire cache
cache.Clear()

Error Handling

  • Continues event processing even if cache operations fail
  • Logs warnings for incomplete operations
  • Prevents cascading failures

Data Flow Diagram

┌─────────────────────────────────────────────────────────────────┐
│ HTTP Request │
│ GET /apps/{appName}/assets/{assetPath} │
└────────────────────────────┬────────────────────────────────────┘


┌──────────────────────────────────────┐
│ HTTP Handler (presentation/ui) │
│ - Parse URL path │
│ - Extract appName, assetPath │
│ - Validate app installation │
└────────────────┬─────────────────────┘


┌──────────────────────────────────────┐
│ UI Service (application/ui) │
│ - ServeAsset(appID, appName, path) │
└────────────────┬─────────────────────┘


┌──────────────────────────────────────┐
│ Asset Server │
│ 1. Check cache (CacheManager) │
│ 2. If miss: Retrieve from repo │
│ 3. Validate SHA-256 checksum │
│ 4. Generate SRI hash │
│ 5. Generate ETag │
│ 6. Cache asset │
└────────────────┬─────────────────────┘


┌──────────────────────────────────────┐
│ Asset Repository (PostgreSQL) │
│ - Retrieve asset contents │
└──────────────────────────────────────┘

┌────────────────────┴─────────────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌──────────────────────────┐
│ Compression │ │ Response Headers │
│ - Detect type │ │ - Content-Type │
│ - Choose algorithm │ │ - Cache-Control │
│ (Brotli/Gzip) │ │ - ETag │
└─────────────────────┘ │ - SRI │
│ │ - Content-Encoding │
└────────────┬───────────────┘ - Security headers │
│ │
▼ │
┌──────────────────────────────────────┐ │
│ HTTP Response │◄──────────┘
│ 200 OK + Compressed Content │
└──────────────────────────────────────┘

Error Handling

Based on pkg/v2/application/ui/errors.go:

Custom Error Types

type AssetNotFoundError struct {
AppName string
AssetName string
}

type ChecksumMismatchError struct {
Expected string
Actual string
}

type NoUIConfigError struct {
AppName string
}

type InvalidUIModeError struct {
AppName string
Expected string
Actual string
}

HTTP Error Responses

  • 400 Bad Request: Invalid asset path
  • 403 Forbidden: App not installed
  • 404 Not Found: App not found, asset not found, UI config not found
  • 500 Internal Server Error: Asset read failures, checksum mismatches

Security Features

  1. Subresource Integrity (SRI): Browser-enforced hash verification
  2. Checksum Validation: SHA-256 validation on every serve
  3. MIME Type Enforcement: Proper Content-Type headers prevent confusion attacks
  4. Immutable Caching: Long-lived caching prevents stale assets
  5. App Installation Check: Only serve assets from INSTALLED apps
  6. Security Headers:
    • X-Content-Type-Options: nosniff
    • X-Frame-Options: SAMEORIGIN
    • Access-Control-Allow-Origin: * (for public assets)

Performance Optimizations

  1. In-Memory Caching: Eliminates database lookups for hot assets
  2. Conditional Requests: ETag support for 304 Not Modified responses
  3. Compression: Automatic Brotli/Gzip for large assets
  4. Size-Based Eviction: Prevents memory exhaustion
  5. TTL-Based Cleanup: Background goroutine removes expired entries
  6. Immutable Cache Headers: Browser caches for 1 year

Code Reference Table

ComponentFileLinesTests/VerificationDescription
Service Interfaceui/service.go8-14ui/ui_service_test.goUI service contract
UIServiceui/ui_service.go25-60integration/ui_cache_invalidation_test.goEvent-driven cache invalidation
Asset Serverui/asset_server.go18-107ui/asset_server_test.go:15-200Cache lookup, SRI generation
Cache Managerui/cache_manager.go8-169ui/cache_manager_test.go:10-150TTL-based expiration, size eviction
SRI Hashui/sri.go12-45ui/sri_test.go:10-80SHA-256 hash generation
Container Loaderui/module_federation/container_loader.go9-129module_federation/container_loader_test.goModule Federation containers
Import Map Generatorui/module_federation/import_map_generator.go8-95module_federation/import_map_generator_test.goES Module import maps
Component Registryui/web_components/component_registry.go12-158web_components/component_registry_test.goWeb Components validation
Module Resolverui/esm/module_resolver.go10-120esm/module_resolver_test.goESM module resolution
Import Analyzerui/esm/import_analyzer.go8-87esm/import_analyzer_test.goDependency graph extraction
HTTP Handlerpresentation/ui/handler.go15-250integration/ui_e2e_test.goRoute mapping, compression
Compressionpresentation/ui/compression.go10-120Unit tested in handlerBrotli/Gzip support
Modelsui/models.go1-50Type definitionsAsset response structures
Errorsui/errors.go1-40Error handling testsCustom error types
Domain Appdomain/app/app.goUI structDomain modelUI configuration