Skip to main content

gRPC Services

Easy AppServer exposes four core gRPC services for application communication: Marketplace, Hooks, Activity, and Settings. These services use Protocol Buffers for efficient serialization and support both unary and streaming RPCs.

Overview

All application-to-platform communication happens via gRPC, providing type safety, code generation, and efficient binary serialization. Services are defined in easy.proto/v2/protos/services.proto and implement comprehensive authentication, authorization, and error handling.

Service Catalog

1. Marketplace Service

Manages application lifecycle operations.

Code Reference: easy.proto/v2/protos/services.proto:161

RPCs:

service Marketplace {
// Validate an app's signature
rpc ValidateSignature(AppSignatureRequest) returns (AppSignatureResponse);

// Bidirectional streaming RPC for app registration and lifecycle
rpc Subscribe(stream StreamingAppRequest) returns (stream StreamingAppResponse);

// Install a registered app
rpc InstallApp(AppInstallRequest) returns (AppStateResponse);

// Uninstall an installed app
rpc UninstallApp(UninstallAppRequest) returns (AppStateResponse);
}

Key Operations:

  • ValidateSignature: Validate an app's signature for authentication
  • Subscribe: Bidirectional stream for app registration and lifecycle events (onRegister, onInstall, onUninstall)
  • InstallApp: Install a registered application
  • UninstallApp: Uninstall an installed application

Code Reference: pkg/v2/presentation/grpc/marketplace_server.go:79

2. Hooks Service

Event-driven communication between applications.

Code Reference: easy.proto/v2/protos/services.proto:29

RPCs:

service Hooks {
// Bidirectional streaming RPC for hook listeners and responses
rpc On(stream StreamingHookListenerRequest) returns (stream StreamingHookListenerResponse);

// Trigger a hook for registered listeners
rpc TriggerHook(HookTriggerRequest) returns (TriggerHookResponse);
}

Key Operations:

  • On: Bidirectional stream for hook registration and event delivery
  • TriggerHook: Fire hook and wait for responses from listeners

Code Reference: pkg/v2/presentation/grpc/hooks_server.go:89

3. Activity Service

Structured request/response workflows with routing strategies.

Code Reference: easy.proto/v2/protos/services.proto:267

RPCs:

service Activity {
// Register activity handler (streaming)
rpc RegisterActivity(stream StreamingActivityRequest) returns (stream StreamingActivityResponse);

// Request activity execution
rpc RequestActivity(ActivityRequest) returns (ActivityResponse);
}

Key Operations:

  • RegisterActivity: Bidirectional stream for handler registration
  • RequestActivity: Execute activity with routing strategy (single node or broadcast)

Code Reference: pkg/v2/presentation/grpc/activity_server.go:44

4. Settings Service

Schema-driven configuration management.

Code Reference: easy.proto/v2/protos/services.proto:403

RPCs:

service Settings {
// Register settings schema
rpc RegisterSettings(RegisterSettingsRequest) returns (RegisterSettingsResponse);

// Update setting values
rpc UpdateSettings(UpdateSettingsRequest) returns (UpdateSettingsResponse);

// Get all settings for an app
rpc GetSettings(GetSettingsRequest) returns (GetSettingsResponse);

// Get a specific setting value
rpc GetSettingValue(GetSettingValueRequest) returns (GetSettingValueResponse);

// Validate that all required settings have values
rpc ValidateRequiredSettings(ValidateRequiredSettingsRequest) returns (ValidateRequiredSettingsResponse);

// Delete all settings for an app
rpc DeleteSettings(DeleteSettingsRequest) returns (DeleteSettingsResponse);
}

Key Operations:

  • RegisterSettings: Define schema from manifest
  • UpdateSettings: Modify values with validation
  • GetSettings: Retrieve all settings (masked if sensitive)
  • GetSettingValue: Retrieve a specific setting value
  • ValidateRequiredSettings: Check that all required settings have values
  • DeleteSettings: Remove all settings for an app

Code Reference: pkg/v2/presentation/grpc/settings_server.go:49

Authentication & Authorization

All gRPC calls require authentication via metadata.

App Authentication

Apps authenticate using certificate-based signatures:

Metadata Headers:

X-App-Name: de.easy-m.todos
X-App-Timestamp: 1705329000000
X-App-Signature: base64-encoded-hmac-signature

Verification Flow:

1. Extract metadata from gRPC context
2. Look up app certificate in database
3. Verify signature using certificate
4. Check timestamp is within replay window (30s)
5. Populate AuthContext
6. Proceed to authorization check

Code Reference: pkg/v2/presentation/grpc/grpcauth/authenticator.go:85

Permission Enforcement

After authentication, permissions are checked:

Example: Can app:backend trigger hook:user.created?

Check OpenFGA tuple: (app:backend, trigger, hook:user.created)

Allow/Deny

Streaming Patterns

Bidirectional Streams

Used for long-lived connections (hooks, activities, lifecycle).

Example: Hook Registration:

const stream = hooksClient.On();

// Send registration
stream.write({
type: 'REGISTER',
hookName: 'user.created'
});

// Receive events
stream.on('data', (message) => {
if (message.type === 'TRIGGER') {
handleHook(message.payload);

// Send response
stream.write({
type: 'RESPONSE',
correlationId: message.correlationId,
result: { success: true }
});
}
});

Server Streaming

Used for event subscriptions.

Example: Lifecycle Events:

const stream = marketplaceClient.Subscribe();

stream.on('data', (event) => {
switch (event.type) {
case 'INSTALL':
await onInstall();
break;
case 'UNINSTALL':
await onUninstall();
break;
}
});

Error Handling

gRPC services return standard status codes with detailed error information.

Status Codes

OK: Success
INVALID_ARGUMENT: Validation failed
UNAUTHENTICATED: Missing or invalid credentials
PERMISSION_DENIED: Unauthorized
NOT_FOUND: Resource doesn't exist
ALREADY_EXISTS: Duplicate entity
DEADLINE_EXCEEDED: Request timeout
INTERNAL: Server error

Error Details

Errors include structured details:

{
"code": "INVALID_ARGUMENT",
"message": "Validation failed",
"details": [
{
"field": "api_key",
"error": "Value does not match pattern"
}
]
}

Versioning

Services are versioned via the proto package:

easy.proto/v2/protos/services.proto

Compatibility Rules:

  • Backward compatible changes: Add optional fields, new RPCs
  • Breaking changes: Increment major version (v2 → v3)
  • Deprecation period: 6 months for removed fields

Best Practices

For App Developers

Implement Retries with Backoff:

async function callWithRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (err) {
if (err.code === 'UNAVAILABLE' && i < maxRetries - 1) {
await sleep(2 ** i * 1000); // Exponential backoff
continue;
}
throw err;
}
}
}

Handle Stream Disconnections:

function maintainStream() {
const stream = hooksClient.On();

stream.on('error', (err) => {
logger.error('Stream error', err);
setTimeout(maintainStream, 5000); // Reconnect after 5s
});

stream.on('end', () => {
logger.info('Stream ended');
setTimeout(maintainStream, 1000); // Reconnect immediately
});
}

Set Appropriate Deadlines:

const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 30);

client.RegisterApp(request, { deadline }, callback);

For Platform Operators

Monitor RPC Latencies:

RegisterApp: p95 < 500ms
TriggerHook: p95 < 100ms
UpdateSettings: p95 < 50ms

Enable Health Checks:

healthServer.addCheck('grpc', () => {
// Check if gRPC server is healthy
return { status: 'SERVING' };
});

Further Reading