Application Marketplace
The marketplace feature provides complete application lifecycle management, enabling apps to be registered, installed, uninstalled, and managed within a multi-tenant environment.
Scope
This document covers the following packages and their interfaces:
| Layer | Packages | Key Files |
|---|---|---|
| Application | pkg/v2/application/marketplace/ | marketplace_service.go, service.go, commands.go, responses.go, errors.go |
| Domain Services | pkg/v2/domain/service/ | dependency_resolver.go, route_conflict_detector.go, certificate_validator.go, asset_validator.go |
| Domain Models | pkg/v2/domain/app/, pkg/v2/domain/dependency/, pkg/v2/domain/lifecycle/, pkg/v2/domain/route/ | app.go, dependency.go, state_machine.go, registry.go |
| Infrastructure | pkg/v2/infrastructure/authz/, pkg/v2/infrastructure/health/, pkg/v2/infrastructure/ratelimit/ | tuple_manager.go, checker.go, limiter.go |
| Repositories | pkg/v2/domain/repository/ | app_repository.go, dependency_repository.go, route_repository.go, asset_repository.go |
| Presentation | pkg/v2/presentation/grpc/, pkg/v2/presentation/graphql/ | marketplace_server.go, GraphQL resolvers |
| Proto Definitions | easy.proto/v2/go/manifest | manifest.proto (settings schema) |
Overview
Based on pkg/v2/application/marketplace/, the marketplace system orchestrates:
- App Registration: Apps authenticate with certificates and declare capabilities via manifests
- App Installation: Dependency resolution, route registration, and permission allocation
- App Uninstallation: Graceful removal with impact analysis
- Dependency Management: HARD and SOFT dependencies with cycle detection
- Route Management: API/UI route registration with conflict detection
- Permission Management: Fine-grained authorization via OpenFGA tuples
- Lifecycle Events: Integration with event bus for state notifications
Architecture
Layered Structure
Presentation Layer (gRPC/GraphQL)
↓
Application Layer (marketplace_service.go)
↓
Domain Layer (app/, lifecycle/, dependency/)
↓
Infrastructure Layer (repositories, events, routes)
Core Components
marketplaceService (pkg/v2/application/marketplace/marketplace_service.go:24-51)
type marketplaceService struct {
// Repositories
appRepo repository.AppRepository
assetRepo repository.AssetRepository
dependencyRepo repository.DependencyRepository
routeRepo repository.RouteRepository
// Domain services
stateMachine lifecycle.StateMachine
certValidator service.CertificateValidator
assetValidator service.AssetValidator
routeDetector service.RouteConflictDetector
dependencyResolver service.DependencyResolver
// Application services
settingsService settingsService
// Infrastructure
routeRegistry route.Registry
tupleManager *authz.TupleManager
rateLimiter ratelimit.Limiter
healthChecker health.Checker
eventBus event.Bus
// Telemetry
logger telemetry.Logger
}
Dependencies & Interactions:
- → Settings Service (lines 54-58): Registers/validates app settings, deletes on deregistration
- → Route Registry (in-memory): Fast lookup for proxy routing
- → Route Repository (PostgreSQL): Persistent route storage
- → Tuple Manager (OpenFGA): Permission grants/revokes
- → Event Bus (RabbitMQ): Publishes lifecycle events
- → Dependency Resolver: Validates dependencies, checks install preconditions
- → Route Conflict Detector: Validates route patterns before registration
- → Health Checker: Registers/unregisters app health monitors
- → Rate Limiter: Configures API and per-route rate limits
Service Interface
Service (pkg/v2/application/marketplace/service.go:9-36)
type Service interface {
RegisterApp(ctx context.Context, cmd RegisterAppCommand) (*RegisterAppResponse, error)
InstallApp(ctx context.Context, cmd InstallAppCommand) (*InstallAppResponse, error)
UninstallApp(ctx context.Context, cmd UninstallAppCommand) (*UninstallAppResponse, error)
DeregisterApp(ctx context.Context, cmd DeregisterAppCommand) (*DeregisterAppResponse, error)
GetApp(ctx context.Context, query GetAppQuery) (*app.App, error)
ListApps(ctx context.Context, query ListAppsQuery) (*ListAppsResponse, error)
RestoreRoutesFromDB(ctx context.Context) error
}
App Registration Flow
Based on marketplace_service.go:100-270:
Process Steps
| Step | Function | File:Lines | Description |
|---|---|---|---|
| 1 | Create app entity | marketplace_service.go:104 | app.NewApp() with certificate and manifest |
| 2 | Create aggregate | marketplace_service.go:107-113 | Wrap app with validators and state machine |
| 3 | Execute domain validation | marketplace_service.go:116 | agg.Register() validates certificate, assets |
| 4 | Validate navigation | marketplace_service.go:124-135 | Check UI navigation structure (max 3 levels) |
| 5 | Check existing app | marketplace_service.go:138-157 | Handle re-registration if app exists |
| 6 | Validate dependencies | marketplace_service.go:160-164 | Check dependency existence, detect cycles |
| 7 | Persist app | marketplace_service.go:167-169 | appRepo.Create() |
| 8 | Persist dependencies | marketplace_service.go:172-178 | dependencyRepo.Create() for each |
| 9 | Persist assets | marketplace_service.go:181-188 | assetRepo.Store() for each asset |
| 10 | Register settings | marketplace_service.go:190-208 | settingsService.RegisterSettings() if present |
| 11 | Grant permissions | marketplace_service.go:210-221 | OpenFGA tupleManager.GrantPermission() |
| 12 | Register routes | marketplace_service.go:224-252 | In-memory registry + database persistence |
| 13 | Publish event | marketplace_service.go:255-259 | eventBus.Publish(AppRegistered) |
Re-registration Pattern
If app already exists (handleReregistration, lines 274-469):
- Permission Sync (lines 295-322): Calculates diff using
diffPermissions(), revokes old, grants new - Route Update (lines 324-375): Deregisters old routes from registry/DB, registers new ones
- Certificate Update (lines 378-379): Updates app certificate and webapp
- State Transition (lines 383-389): Transitions UNINSTALLED apps back to REGISTERED
- Database Update (lines 392-394): Persists updated app
- Dependency Sync (lines 397-406): Deletes old dependencies, creates new ones
- Asset Sync (lines 408-419): Deletes old assets, stores new ones
- Settings Sync (lines 422-452): Deletes old settings, registers new ones
- Event Publishing (lines 455-458): Publishes AppRegistered event
Atomicity: Registry registration happens first, rolled back if database persistence fails (lines 243-245, 366-368)
Example Manifest
{
id: 'todos-app',
name: 'Todo Management',
version: '1.2.0',
dependencies: {
hard: [
{
appId: 'auth-service',
version: '>=2.0.0 <3.0.0'
}
],
soft: [
{
appId: 'analytics',
version: '>=1.0.0'
}
]
},
routes: [
{
pattern: '/api/apps/todos/**',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
scopes: ['todos:read', 'todos:write']
}
],
permissions: {
required: ['users:read'],
exposed: ['todos:read', 'todos:write']
}
}
App Installation Flow
Based on marketplace_service.go:504-645:
Process Steps
| Step | Function | File:Lines | Description |
|---|---|---|---|
| 1 | Retrieve app | marketplace_service.go:506-519 | Find by AppID or AppName |
| 2 | Validate dependencies | marketplace_service.go:526-528 | dependencyResolver.CheckInstallPreconditions() |
| 3 | Validate settings | marketplace_service.go:531-548 | Check required settings are configured |
| 4 | Get existing routes | marketplace_service.go:551-559 | Fetch for conflict detection |
| 5 | Create aggregate & install | marketplace_service.go:562-575 | agg.Install() checks route conflicts |
| 6 | Update database | marketplace_service.go:578-580 | Persist INSTALLED state |
| 7 | Configure route permissions | marketplace_service.go:594-602 | OpenFGA SetRoutePermissions() |
| 8 | Configure rate limits | marketplace_service.go:605-607 | API-level and per-route limits |
| 9 | Register health checks | marketplace_service.go:610-626 | Independent health monitoring |
| 10 | Publish event | marketplace_service.go:630-633 | eventBus.Publish(AppInstalled) |
Dependency Resolution
From pkg/v2/domain/service/dependency_resolver.go:
CheckInstallPreconditions validates:
- All HARD dependencies are in INSTALLED state
- HARD dependencies are healthy (passing health checks)
- Returns
DependencyNotInstalledErrororDependencyUnhealthyErrorif preconditions fail - SOFT dependencies only generate warnings
App Uninstallation Flow
Based on marketplace_service.go:648-776:
Process Steps
| Step | Function | File:Lines | Description |
|---|---|---|---|
| 1 | Retrieve app | marketplace_service.go:650-668 | Find by AppID or AppName |
| 2 | Check dependents | marketplace_service.go:670-690 | Unless force flag set |
| 3 | Impact analysis | marketplace_service.go:671-689 | CheckUninstallImpact() - find HARD dependents |
| 4 | Create aggregate & uninstall | marketplace_service.go:693-706 | agg.Uninstall() transitions state |
| 5 | Update database | marketplace_service.go:709-711 | Persist UNINSTALLED state |
| 6 | Deregister routes | marketplace_service.go:714-752 | Remove from registry and database |
| 7 | Unregister health checks | marketplace_service.go:742-751 | Stop health monitoring |
| 8 | Publish events | marketplace_service.go:755-764 | AppUninstalled + PermissionInvalidate |
Force Uninstall
When Force: true flag is set (lines 670-690):
- Bypasses dependent check
- Allows removal even with HARD dependents
- May break dependent apps (use with caution)
Dependency Management
Based on pkg/v2/domain/service/dependency_resolver.go:
Dependency Types
HARD Dependencies
- App CANNOT function without them
- Must be INSTALLED before dependent can install
- Uninstalling requires checking dependents
- Blocks installation if missing
SOFT Dependencies
- App CAN function without them
- Provides enhanced features if available
- Generates warnings if missing
- Does not block installation
Dependency Data Structure
From pkg/v2/domain/dependency/dependency.go:
type Dependency struct {
AppID uuid.UUID // App with dependency
DependsOnAppID uuid.UUID // App being depended on
Kind Kind // HARD or SOFT
VersionConstraint string // Optional: ">=1.0.0 <2.0.0"
CreatedAt time.Time
}
Version Constraints
Follows semantic versioning:
1.2.3- Exact version>=1.0.0- Minimum version>=1.0.0 <2.0.0- Range~1.2.0- Patch updates allowed (1.2.x)^1.2.0- Minor + patch updates allowed (1.x.y)- Empty string - Any version
Cycle Detection Algorithm
Uses Depth-First Search (DFS) with recursion stack:
- Build dependency graph from all apps
- Start DFS from each unvisited node
- Track visiting path in recursion stack
- If node already in current path → cycle detected
- Returns entire cycle path for debugging
- Enforces max depth of 50 to prevent infinite loops
Example Cycle:
If App A depends on B, B depends on C, C depends on A:
DFS detects: [A → B → C → A]
Returns: CyclicDependencyError with full path
Installation Order
Uses Kahn's topological sort algorithm:
- Build reverse dependency graph
- Find nodes with no incoming edges
- Process queue, removing edges
- Output nodes in dependency order
- Ensures dependencies installed before dependents
Route Management
Based on pkg/v2/domain/route/:
Dual Registration Pattern
Routes registered in two places:
-
In-Memory Registry (
routeRegistry.Register())- Fast lookup for proxy routing
- Restored from database on startup via
RestoreRoutesFromDB()(lines 935-980) - Implementation:
pkg/v2/domain/route/registry_inmem.go - Thread-safe: Uses
sync.RWMutexfor concurrent access
-
Persistent Storage (
routeRepo.Create())- Durability across restarts
- Source of truth
- PostgreSQL storage
Conflict Detection
Based on pkg/v2/domain/service/route_conflict_detector.go:
Pattern Types:
PatternTypeExact:/api/items(exact match)PatternTypePrefix:/api/items/**or/api/items/*PatternTypeParameterized:/api/:id/itemsPatternTypeWildcard:/api/*/itemsPatternTypeRegex:regex:^/api/.*
Conflict Rules:
- Routes conflict only if HTTP methods overlap
- Exact patterns conflict with overlapping prefixes
- Parameter patterns conflict if same structure
- All new routes checked against existing AND each other
Error Example:
type RouteConflictError struct {
ConflictingRoute string // "/api/todos/** conflicts with /api/todos/items"
}
RegisteredRoute Structure
From pkg/v2/domain/route/route.go:
type RegisteredRoute struct {
ID uuid.UUID // Unique route ID
AppID uuid.UUID // App owning route
AppName string
BasePath string // e.g., "/api/apps/todos"
UpstreamBaseURL string // http://todos-bff.svc:8080
StripBasePath bool // Remove BasePath before forwarding
IsPublic bool // Skip auth if true
RouteSpecs []*Spec // Fine-grained routing rules
Permissions []string // Required OpenFGA scopes
RateLimit *config.RateLimit // API-level limits
HealthCheck *config.HealthCheckConfig
}
Permission Management
Based on marketplace_service.go:210-221, 594-602:
Permission Sync During Re-registration
Uses diffPermissions() (lines 473-501):
func (s *marketplaceService) diffPermissions(oldPerms, newPerms []string) (toRevoke, toGrant []string)
- Calculates permissions to revoke and grant using set difference
- Prevents unnecessary OpenFGA tuple operations
- Logged for audit trail (lines 297-301)
OpenFGA Integration
Registration (lines 210-221):
for _, perm := range cmd.WebApp.APIConfig.RequiredPermissions {
err := s.tupleManager.GrantPermission(ctx, cmd.Name, perm)
// Continues on error (non-blocking)
}
Installation (lines 594-602):
for _, routeSpec := range foundApp.WebApp.APIConfig.Routes {
routeID := fmt.Sprintf("%s%s", BasePath, Pattern)
err := s.tupleManager.SetRoutePermissions(ctx, routeID, routeSpec.Scopes)
}
Tuple Management
From pkg/v2/infrastructure/authz/tuple_manager.go:
GrantPermission(ctx, app, permission)- Creates OpenFGA tupleRevokePermission(ctx, app, permission)- Deletes tupleSetRoutePermissions(ctx, routeID, scopes)- Sets per-route scopes- Failures logged but non-blocking (don't fail operation)
Lifecycle Events
Based on pkg/v2/domain/event/app_events.go:
Event Types
app.registered- App authenticated and manifest providedapp.installed- App transitioned to INSTALLED stateapp.uninstalled- App removed from INSTALLED stateapp.deregistered- App completely removed from systempermission.invalidate- Permission cache invalidation event
Event Publishing
// After registration (marketplace_service.go:255)
evt := event.NewAppRegistered(newApp.ID, newApp.Name)
s.eventBus.Publish(ctx, evt)
// After installation (line 630)
evt := event.NewAppInstalled(foundApp.ID, foundApp.Name)
s.eventBus.Publish(ctx, evt)
// After uninstallation (lines 755, 761)
evt := event.NewAppUninstalled(foundApp.ID, foundApp.Name)
s.eventBus.Publish(ctx, evt)
// Permission cache invalidation (line 761)
invalidationEvt := event.NewPermissionInvalidateEvent(...)
s.eventBus.Publish(ctx, invalidationEvt)
Non-Blocking Pattern:
- Event publishing failures logged as warnings
- Operations succeed even if events not published
- Prevents marketplace from blocking on event bus
Commands and Responses
Commands
From pkg/v2/application/marketplace/commands.go:
type RegisterAppCommand struct {
Name string
Certificate *app.Certificate
WebApp *app.WebApp
Dependencies []*dependency.Dependency
SettingsSchema *pb.SettingsSchema
}
type InstallAppCommand struct {
AppID uuid.UUID
AppName string // Alternative to AppID
}
type UninstallAppCommand struct {
AppID uuid.UUID
AppName string // Alternative to AppID
Force bool // Force uninstall even if dependents exist
}
type DeregisterAppCommand struct {
AppID uuid.UUID
AppName string // Alternative to AppID
}
Responses
From pkg/v2/application/marketplace/responses.go:
type RegisterAppResponse struct {
AppID uuid.UUID
App *app.App
EventsEmitted []string
}
type InstallAppResponse struct {
AppID uuid.UUID
App *app.App
RoutesAdded int
EventsEmitted []string
}
type UninstallAppResponse struct {
AppID uuid.UUID
App *app.App
RoutesRemoved int
EventsEmitted []string
}
Error Handling
From pkg/v2/application/marketplace/errors.go:
// AppAlreadyExistsError - app already registered (lines 9-16)
type AppAlreadyExistsError struct {
Name string
}
// InvalidCommandError - command validation failure (lines 18-25)
type InvalidCommandError struct {
Reason string
}
// InvalidStateError - operation invalid for current state (lines 27-35)
type InvalidStateError struct {
CurrentState app.State
Reason string
}
// HasDependentsError - cannot uninstall app with dependents (lines 37-45)
type HasDependentsError struct {
AppName string
Dependents []string // Names of dependent apps
}
Error Handling Patterns
Non-Blocking Failures (don't abort operation):
- Permission grants/revokes (lines 213-220, 304-322, 314-321)
- Event publishing (lines 256-259, 631-633, 756-758, 762-764)
- Health check registration/unregistration (lines 617-626, 743-750)
- Asset/dependency deletion during re-registration (lines 398-399, 410-411)
Blocking Failures (abort operation):
- App persistence errors (lines 167-169, 392-394)
- Dependency validation failures (lines 161-164, 526-528)
- Route conflicts (line 570)
- Route registry/repository errors with rollback (lines 237-245)
GraphQL API
Based on pkg/v2/presentation/graphql/schema/:
Queries
type Query {
app(name: String!): App
apps(filter: AppFilter, first: Int, after: String): AppConnection!
}
input AppFilter {
state: AppState
states: [AppState!]
nameContains: String
registeredAfter: DateTime
installedAfter: DateTime
}
enum AppState {
UNREGISTERED
REGISTERED
INSTALLED
UNINSTALLED
}
Mutations
type Mutation {
installApp(name: String!): AppMutationResult!
uninstallApp(name: String!): AppMutationResult!
}
type AppMutationResult {
success: Boolean!
app: App
error: Error
}
Types
type App {
id: ID!
name: String!
version: String!
state: AppState!
dependencies: [AppDependency!]!
routes: [RouteSpec!]!
permissions: PermissionConfig!
registeredAt: DateTime!
installedAt: DateTime
}
type AppDependency {
appId: String!
kind: DependencyKind!
versionConstraint: String
}
enum DependencyKind {
HARD
SOFT
}
State Transitions
Valid transitions from pkg/v2/domain/lifecycle/state_machine.go:
| From | To | Trigger |
|---|---|---|
| UNREGISTERED | REGISTERED | App connects and provides manifest |
| REGISTERED | INSTALLING | User/admin requests installation |
| INSTALLING | INSTALLED | Installation succeeds |
| INSTALLING | FAILED | Installation fails (dependencies, conflicts) |
| INSTALLED | UNINSTALLING | User/admin requests uninstall |
| UNINSTALLING | UNINSTALLED | Uninstall succeeds |
| FAILED | INSTALLING | Retry installation |
| UNINSTALLED | INSTALLING | Reinstallation |
| UNINSTALLED | REGISTERED | Re-registration updates manifest (line 383) |
Concurrency Patterns
Thread Safety
Route Registry (pkg/v2/domain/route/registry_inmem.go):
type inMemoryRegistry struct {
mu sync.RWMutex // Protects routes map
routes map[uuid.UUID]*RegisteredRoute
}
- Read operations:
RLock()for concurrent reads - Write operations:
Lock()for exclusive writes - Used in hot path (every request routing)
Marketplace Service:
- Stateless service design
- No internal mutable state
- Thread-safe through repository/registry locking
- Event bus handles concurrent event publishing
Atomic Operations
Route Registration with Rollback (lines 237-246):
// Register in in-memory registry first
if err := s.routeRegistry.Register(ctx, registeredRoute); err != nil {
return nil, fmt.Errorf("failed to register routes in registry: %w", err)
}
// Persist to database
if err := s.routeRepo.Create(ctx, registeredRoute); err != nil {
// Rollback in-memory registration on DB failure
_ = s.routeRegistry.Deregister(ctx, newApp.ID)
return nil, fmt.Errorf("failed to persist routes to database: %w", err)
}
Database Transactions:
- Repository operations wrapped in transactions
- Rollback on any step failure
- Ensures consistency across app, dependencies, assets, settings
Design Patterns
1. Dual Registration (In-Memory + Database)
Why: Fast lookups in-memory + durability across restarts
Implementation: RouteRegistry (in-mem) + RouteRepository (DB)
Consistency: Registered to registry first, rolled back if DB fails
Startup: RestoreRoutesFromDB() reloads registry from database (lines 935-980)
2. Non-Blocking Error Handling
- Permission grants, event publishes, health checks fail non-blocking
- Service continues even if these fail
- Failures logged as warnings
- Prevents cascade failures from external services
3. Re-registration Pattern
- Existing apps can be re-registered with updated manifests
- Atomic permission sync (revoke old, grant new)
- Route updates (deregister old, register new)
- Smooth transition from UNINSTALLED back to REGISTERED
- Full sync of dependencies, assets, settings
4. State Machine with Validators
- Each state transition has pre/post validators
- Validators called before state change
- Rollback on validator failure
- Self-transitions allowed (idempotent)
5. Command-Query Separation
- Commands: RegisterApp, InstallApp, UninstallApp, DeregisterApp
- Queries: GetApp, ListApps
- Clear separation of write and read operations
Security Features
- Certificate Validation - X.509 certificates required, validity checked
- Asset Signature Verification - Assets must be signed with app's certificate
- App Identity Validation - Cannot register manifest for another app
- Permission Isolation - Apps only receive their own lifecycle events
- OpenFGA Integration - Fine-grained permission checks via tuples
- Rate Limiting - Configurable per-route and API-level limits
- Health Checks - Independent health monitoring per app
- Force Uninstall Guard - Requires explicit flag to bypass dependent checks
Code Reference Table
| Component | File | Lines | Tests/Verification | Description |
|---|---|---|---|---|
| Service Interface | marketplace/service.go | 9-36 | integration/marketplace_service_test.go:61-150 | Main service contract |
| MarketplaceService Constructor | marketplace/marketplace_service.go | 60-97 | integration/marketplace_service_test.go:34-59 | Service initialization with 15+ dependencies |
| RegisterApp | marketplace/marketplace_service.go | 100-270 | integration/marketplace_service_test.go:77-92 | Full registration flow with validation |
| handleReregistration | marketplace/marketplace_service.go | 274-469 | integration/reregistration_test.go:1-200 | Updates existing app registration |
| diffPermissions | marketplace/marketplace_service.go | 473-501 | Tested in reregistration flow | Permission set difference calculator |
| InstallApp | marketplace/marketplace_service.go | 504-645 | integration/marketplace_service_test.go:95-107 | Installation with dependency checking |
| UninstallApp | marketplace/marketplace_service.go | 648-776 | integration/marketplace_service_test.go:110-123 | Uninstallation with impact analysis |
| DeregisterApp | marketplace/marketplace_service.go | 809-878 | integration/marketplace_service_test.go:126-145 | Complete app removal |
| configureRateLimits | marketplace/marketplace_service.go | 881-931 | Unit tested in service tests | Rate limit configuration |
| RestoreRoutesFromDB | marketplace/marketplace_service.go | 935-980 | integration/marketplace_service_test.go:230-260 | Startup route restoration |
| RegisterAppCommand | marketplace/commands.go | 11-19 | Used throughout tests | Registration command DTO |
| RegisterAppResponse | marketplace/responses.go | 9-14 | Verified in all register tests | Registration response DTO |
| HasDependentsError | marketplace/errors.go | 37-45 | integration/marketplace_service_test.go:155-170 | Dependent check error |
| Route Registry (in-mem) | domain/route/registry_inmem.go | 11-154 | integration/marketplace_service_test.go | Thread-safe in-memory registry |
| Dependency Resolver | domain/service/dependency_resolver.go | 1-400 | integration/dependency_resolver_test.go | Cycle detection, preconditions |
| Route Conflict Detector | domain/service/route_conflict_detector.go | 1-300 | integration/route_conflict_test.go | Pattern conflict detection |
| TupleManager (OpenFGA) | infrastructure/authz/tuple_manager.go | 1-250 | integration/marketplace_authorization_e2e_test.go:46-200 | Permission tuple management |
| Settings Integration | marketplace/marketplace_service.go | 190-208, 422-452 | integration/marketplace_settings_registration_test.go:1-150 | Settings registration/deletion |
Related Topics
- Application Lifecycle - Lifecycle states and transitions
- Dependency Management - Dependency resolution algorithms
- API Gateway & Routing - Route proxying implementation
- Authentication & Authorization - OpenFGA permission system
- Event-Driven Architecture - Event patterns and pub/sub
- Settings Management - App settings registration and validation
- Docker Orchestration - Container deployment during installation
- Node.js SDK - App registration and manifest building
- gRPC Server - gRPC presentation layer