Skip to main content

Invoking Cloud Hosting Service in Cloud Functions

Calling cloud hosting service in cloud functions is a crucial approach to implementing microservices architecture. It allows you to access HTTP services deployed on the cloud hosting platform from cloud functions, enabling efficient inter-service communication and feature reuse.

Application Scenarios

Typical Use Cases

  • Microservices Architecture: Deploying different business modules as independent cloud hosting services
  • Service Decoupling: Enabling loosely coupled communication between services via HTTP APIs
  • Resource Optimization: Leveraging the containerized deployment and automatic scaling capabilities of cloud hosting
  • Diversification of Technology Stacks: Cloud functions and cloud hosting can use different technology stacks
  • Long-Running Tasks: Delegating time-consuming operations to cloud hosting service

Architectural Advantages

  • Elastic Scaling: Cloud hosting service can automatically scale based on load
  • Technical Flexibility: Different services can utilize the most suitable technology stack
  • Independent Deployment: Each service can be independently developed, tested, and deployed
  • Resource Isolation: Resources are isolated between services, and faults do not affect each other

Overview of Invocation Methods

Using the Node.js SDK for Invocation

The Node.js SDK provides the callContainer method to invoke cloud hosted services, supporting full HTTP request configuration.

Basic Invocation

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
// Initialize the SDK
const app = tcb.init({ context });

try {
// Invoke the GET API of the Cloud Hosted Service
const result = await app.callContainer({
name: 'user-service', // Cloud Hosted Service name
method: 'GET', // HTTP method
path: '/api/users/profile', // Request path
header: { // request headers
'Content-Type': 'application/json',
'Authorization': `Bearer ${event.token}`,
'X-Request-ID': context.request_id
}
});

console.log('Invoked successfully:', {
statusCode: result.statusCode,
data: result.data,
requestId: result.requestId
});

return {
success: true,
user: result.data,
statusCode: result.statusCode
};
} catch (error) {
console.error('Cloud Hosted Service invocation failed:', error);
return {
success: false,
error: error.message,
code: error.code
};
}
};

Advanced Features

File Upload Processing

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ context });

try {
// Invoke the file processing service
const result = await app.callContainer({
name: 'file-service',
method: 'POST',
path: '/api/files/process',
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${event.token}`
},
data: {
fileUrl: event.fileUrl,
processType: event.processType,
options: {
quality: 80,
format: 'webp',
resize: { width: 800, height: 600 }
}
}
}, {
timeout: 30000 // File processing may take longer
});

return {
success: true,
processedFile: result.data,
processingTime: result.header['X-Processing-Time']
};
} catch (error) {
console.error('File processing failed:', error);
throw error;
}
};

Batch Data Processing

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ context });

// Process large amounts of data in batches
const batchSize = 100;
const results = [];

for (let i = 0; i < event.data.length; i += batchSize) {
const batch = event.data.slice(i, i + batchSize);

try {
const result = await app.callContainer({
name: 'data-processor',
method: 'POST',
path: '/api/process/batch',
header: {
'Content-Type': 'application/json',
'X-Batch-ID': `batch_${Math.floor(i / batchSize) + 1}`
},
data: {
items: batch,
batchIndex: Math.floor(i / batchSize),
totalBatches: Math.ceil(event.data.length / batchSize)
}
});

results.push({
batchIndex: Math.floor(i / batchSize),
success: true,
processedCount: result.data.processedCount,
data: result.data
});
} catch (error) {
console.error(`Batch ${Math.floor(i / batchSize)} processing failed:`, error);
results.push({
batchIndex: Math.floor(i / batchSize),
success: false,
error: error.message
});
}
}

return {
totalBatches: results.length,
successCount: results.filter(r => r.success).length,
failedCount: results.filter(r => !r.success).length,
results: results
};
};

Service Health Check

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ context });

const services = ['user-service', 'order-service', 'payment-service'];
const healthChecks = [];

// Concurrently check the health status of all services
const promises = services.map(async (serviceName) => {
try {
const startTime = Date.now();
const result = await app.callContainer({
name: serviceName,
method: 'GET',
path: '/health',
header: {
'User-Agent': 'CloudFunction-HealthCheck/1.0'
}
}, {
timeout: 5000
});

const responseTime = Date.now() - startTime;

return {
service: serviceName,
status: 'healthy',
statusCode: result.statusCode,
responseTime: responseTime,
data: result.data
};
} catch (error) {
return {
service: serviceName,
status: 'unhealthy',
error: error.message,
code: error.code
};
}
});

const results = await Promise.allSettled(promises);

return {
timestamp: new Date().toISOString(),
totalServices: services.length,
healthyServices: results.filter(r =>
r.status === 'fulfilled' && r.value.status === 'healthy'
).length,
results: results.map(r =>
r.status === 'fulfilled' ? r.value : { error: r.reason }
)
};
};

Using HTTP API Invocation

Besides the Node.js SDK, you can also use standard HTTP client libraries to invoke cloud hosted services.

Using Axios

const axios = require('axios');

exports.main = async (event, context) => {
// Construct the full URL for the cloud hosting service
const serviceUrl = `https://${event.serviceName}-${process.env.TCB_ENV}.service.tcloudbase.com`;

try {
const response = await axios({
method: 'POST',
url: `${serviceUrl}/api/data/analyze`,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${event.token}`,
'X-Request-ID': context.request_id
},
data: {
dataset: event.dataset,
analysisType: event.analysisType,
parameters: event.parameters
},
timeout: 30000
});

return {
success: true,
analysis: response.data,
statusCode: response.status,
headers: response.headers
};
} catch (error) {
console.error('Data analysis service invocation failed:', error.response?.data || error.message);

return {
success: false,
error: error.response?.data?.message || error.message,
statusCode: error.response?.status,
code: error.code
};
}
};

Using the Native HTTPS Module

const https = require('https');
const querystring = require('querystring');

exports.main = async (event, context) => {
const postData = JSON.stringify({
action: event.action,
payload: event.payload
});

const options = {
hostname: `${event.serviceName}-${process.env.TCB_ENV}.service.tcloudbase.com`,
port: 443,
path: '/api/webhook',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
'Authorization': `Bearer ${event.token}`
}
};

return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';

res.on('data', (chunk) => {
data += chunk;
});

res.on('end', () => {
try {
const result = JSON.parse(data);
resolve({
success: res.statusCode === 200,
data: result,
statusCode: res.statusCode,
headers: res.headers
});
} catch (error) {
reject(new Error(`Response parsing failed: ${error.message}`));
}
});
});

req.on('error', (error) => {
reject(error);
});

req.write(postData);
req.end();
});
};

Service Discovery and Load Balancing

Service Registration and Discovery

const tcb = require('@cloudbase/node-sdk');

// Service registry (can be stored in a database)
const serviceRegistry = {
'user-service': {
instances: [
{ name: 'user-service-v1', weight: 70 },
{ name: 'user-service-v2', weight: 30 }
]
},
'order-service': {
instances: [
{ name: 'order-service', weight: 100 }
]
}
};

exports.main = async (event, context) => {
const app = tcb.init({ context });

// Select service instances based on weight
const serviceName = selectServiceInstance(event.serviceType);

try {
const result = await app.callContainer({
name: serviceName,
method: event.method || 'GET',
path: event.path,
header: event.headers,
data: event.data
});

return result;
} catch (error) {
// If the invocation fails, try failover
console.error(`Service ${serviceName} invocation failed, attempting failover`);
return await fallbackService(event, app);
}
};

function selectServiceInstance(serviceType) {
const service = serviceRegistry[serviceType];
if (!service || !service.instances.length) {
throw new Error(`Service ${serviceType} does not exist`);
}

// Weight-based load balancing
const totalWeight = service.instances.reduce((sum, instance) => sum + instance.weight, 0);
const random = Math.random() * totalWeight;

let currentWeight = 0;
for (const instance of service.instances) {
currentWeight += instance.weight;
if (random <= currentWeight) {
return instance.name;
}
}

return service.instances[0].name;
}

async function fallbackService(event, app) {
// Implement the failover logic
const fallbackServices = ['fallback-service', 'backup-service'];

for (const serviceName of fallbackServices) {
try {
const result = await app.callContainer({
name: serviceName,
method: event.method || 'GET',
path: event.path,
header: event.headers,
data: event.data
});

console.log(`Failover to service ${serviceName} succeeded`);
return result;
} catch (error) {
console.error(`Failover service ${serviceName} also failed:`, error.message);
}
}

throw new Error('All services are unavailable');
}

Circuit Breaker Pattern

const tcb = require('@cloudbase/node-sdk');

class CircuitBreaker {
constructor(serviceName, options = {}) {
this.serviceName = serviceName;
this.failureThreshold = options.failureThreshold || 5;
this.recoveryTimeout = options.recoveryTimeout || 60000;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.failureCount = 0;
this.lastFailureTime = null;
}

async call(app, callOptions) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
this.state = 'HALF_OPEN';
console.log(`Circuit breaker ${this.serviceName} enters half-open state`);
} else {
throw new Error(`Circuit breaker ${this.serviceName} is enabled`);
}
}

try {
const result = await app.callContainer(callOptions);
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}

onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
console.log(`Circuit breaker ${this.serviceName} reset to disabled state`);
}

onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();

if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
console.log(`Circuit breaker ${this.serviceName} enabled, failure count: ${this.failureCount}`);
}
}
}

// Create a circuit breaker instance for each service
const circuitBreakers = {
'user-service': new CircuitBreaker('user-service'),
'order-service': new CircuitBreaker('order-service'),
'payment-service': new CircuitBreaker('payment-service')
};

exports.main = async (event, context) => {
const app = tcb.init({ context });
const breaker = circuitBreakers[event.serviceName];

if (!breaker) {
throw new Error(`No circuit breaker found for service ${event.serviceName}`);
}

try {
const result = await breaker.call(app, {
name: event.serviceName,
method: event.method,
path: event.path,
header: event.headers,
data: event.data
});

return {
success: true,
data: result.data,
circuitBreakerState: breaker.state
};
} catch (error) {
console.error(`Service invocation failed:`, error.message);

return {
success: false,
error: error.message,
circuitBreakerState: breaker.state,
fallback: await getFallbackResponse(event)
};
}
};

async function getFallbackResponse(event) {
// Return a degraded response
return {
'Service is temporarily unavailable, please try again later',
timestamp: new Date().toISOString(),
requestId: event.requestId
};
}

Error Handling and Retry

Intelligent Retry Mechanism

const tcb = require('@cloudbase/node-sdk');

class RetryHandler {
constructor(options = {}) {
this.maxRetries = options.maxRetries || 3;
this.baseDelay = options.baseDelay || 1000;
this.maxDelay = options.maxDelay || 10000;
this.retryableErrors = options.retryableErrors || [
'TIMEOUT', 'NETWORK_ERROR', 'SERVICE_UNAVAILABLE'
];
}

async execute(fn) {
let lastError;

for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;

if (attempt === this.maxRetries || !this.shouldRetry(error)) {
throw error;
}

const delay = this.calculateDelay(attempt);
console.log(`Retry ${attempt + 1}/${this.maxRetries}, delay ${delay}ms`);
await this.sleep(delay);
}
}

throw lastError;
}

shouldRetry(error) {
return this.retryableErrors.includes(error.code) ||
error.message.includes('timeout') ||
error.message.includes('network');
}

calculateDelay(attempt) {
// Exponential backoff + random jitter
const exponentialDelay = this.baseDelay * Math.pow(2, attempt);
const jitter = Math.random() * 0.1 * exponentialDelay;
return Math.min(exponentialDelay + jitter, this.maxDelay);
}

sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

exports.main = async (event, context) => {
const app = tcb.init({ context });
const retryHandler = new RetryHandler({
maxRetries: 3,
baseDelay: 1000,
maxDelay: 8000
});

try {
const result = await retryHandler.execute(async () => {
return await app.callContainer({
name: event.serviceName,
method: event.method,
path: event.path,
header: event.headers,
data: event.data
}, {
timeout: 10000
});
});

return {
success: true,
data: result.data,
statusCode: result.statusCode
};
} catch (error) {
console.error('Still failed after retry:', error);

return {
success: false,
error: error.message,
code: error.code,
retryAttempts: retryHandler.maxRetries
};
}
};

Timeout and Circuit Breaking Handling

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ context });

// Set timeout for different types of requests
const timeoutConfig = {
'quick': 3000, // Fast query
'normal': 10000, // Normal operation
'heavy': 30000 // Heavy computing
};

const timeout = timeoutConfig[event.operationType] || 10000;

try {
// Implement timeout control using Promise.race
const result = await Promise.race([
app.callContainer({
name: event.serviceName,
method: event.method,
path: event.path,
header: {
...event.headers,
'X-Timeout': timeout.toString(),
'X-Operation-Type': event.operationType
},
data: event.data
}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
)
]);

return {
success: true,
data: result.data,
responseTime: Date.now() - context.startTime
};
} catch (error) {
if (error.message === 'Request timeout') {
console.error(`Request timeout (${timeout}ms):`, event.serviceName);

// Log timeout events for monitoring
await recordTimeoutEvent(event.serviceName, timeout);

return {
success: false,
error: 'Service response timeout',
timeout: timeout,
'Please try again later or contact technical support'
};
}

throw error;
}
};

async function recordTimeoutEvent(serviceName, timeout) {
// Log timeout events to the monitoring system
console.log('Log timeout event:', {
service: serviceName,
timeout: timeout,
timestamp: new Date().toISOString()
});
}

Performance Optimization Recommendations

1. Connection Pool Management

const tcb = require('@cloudbase/node-sdk');

// Global SDK instance to avoid repeated initialization
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV,
timeout: 30000
});

// Connection Pool Configuration
const connectionPool = {
maxConnections: 10,
keepAlive: true,
keepAliveMsecs: 30000
};

exports.main = async (event, context) => {
// Reuse the global SDK instance
const result = await app.callContainer({
name: event.serviceName,
method: event.method,
path: event.path,
header: event.headers,
data: event.data
});

return result;
};

2. Request Merging and Batch Processing

const tcb = require('@cloudbase/node-sdk');

class RequestBatcher {
constructor(batchSize = 10, flushInterval = 100) {
this.batchSize = batchSize;
this.flushInterval = flushInterval;
this.queue = [];
this.timer = null;
}

async add(request) {
return new Promise((resolve, reject) => {
this.queue.push({ request, resolve, reject });

if (this.queue.length >= this.batchSize) {
this.flush();
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.flushInterval);
}
});
}

async flush() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}

if (this.queue.length === 0) return;

const batch = this.queue.splice(0);

try {
const results = await this.processBatch(batch.map(item => item.request));

batch.forEach((item, index) => {
item.resolve(results[index]);
});
} catch (error) {
batch.forEach(item => {
item.reject(error);
});
}
}

async processBatch(requests) {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });

// Merge multiple requests into a single batch request
const result = await app.callContainer({
name: 'batch-processor',
method: 'POST',
path: '/api/batch',
data: {
requests: requests,
batchId: Date.now()
}
});

return result.data.results;
}
}

const batcher = new RequestBatcher(5, 50);

exports.main = async (event, context) => {
try {
const result = await batcher.add({
serviceName: event.serviceName,
method: event.method,
path: event.path,
data: event.data
});

return {
success: true,
data: result
};
} catch (error) {
return {
success: false,
error: error.message
};
}
};

3. Cache Policy

const tcb = require('@cloudbase/node-sdk');

// Simple in-memory cache
class MemoryCache {
constructor(ttl = 300000) { // Default 5 minutes
this.cache = new Map();
this.ttl = ttl;
}

set(key, value) {
this.cache.set(key, {
value,
timestamp: Date.now()
});
}

get(key) {
const item = this.cache.get(key);
if (!item) return null;

if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}

return item.value;
}

clear() {
this.cache.clear();
}
}

const cache = new MemoryCache(300000); // 5-minute cache

exports.main = async (event, context) => {
const app = tcb.init({ context });

// Generate cache key
const cacheKey = `${event.serviceName}_${event.method}_${event.path}_${JSON.stringify(event.data)}`;

// Try to obtain from cache
const cachedResult = cache.get(cacheKey);
if (cachedResult) {
console.log('Cache hit:', cacheKey);
return {
success: true,
data: cachedResult,
fromCache: true
};
}

try {
const result = await app.callContainer({
name: event.serviceName,
method: event.method,
path: event.path,
header: event.headers,
data: event.data
});

// Cache only successful responses
if (result.statusCode === 200) {
cache.set(cacheKey, result.data);
}

return {
success: true,
data: result.data,
fromCache: false
};
} catch (error) {
console.error('Service invocation failed:', error);
throw error;
}
};

Monitoring and Logging

Distributed Tracing

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ context });

// Generate trace ID
const traceId = event.traceId || `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const spanId = `span_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;

console.log('Starting distributed tracing:', {
traceId,
spanId,
service: event.serviceName,
operation: `${event.method} ${event.path}`
});

const startTime = Date.now();

try {
const result = await app.callContainer({
name: event.serviceName,
method: event.method,
path: event.path,
header: {
...event.headers,
'X-Trace-ID': traceId,
'X-Span-ID': spanId,
'X-Parent-Span-ID': event.parentSpanId || '',
'X-Caller': context.function_name
},
data: event.data
});

const duration = Date.now() - startTime;

console.log('Trace completed:', {
traceId,
spanId,
duration: `${duration}ms`,
statusCode: result.statusCode,
success: true
});

return {
success: true,
data: result.data,
tracing: {
traceId,
spanId,
duration
}
};
} catch (error) {
const duration = Date.now() - startTime;

console.error('Trace failure:', {
traceId,
spanId,
duration: `${duration}ms`,
error: error.message,
success: false
});

throw error;
}
};

Performance Monitoring

const tcb = require('@cloudbase/node-sdk');

class PerformanceMonitor {
constructor() {
this.metrics = {
totalCalls: 0,
successCalls: 0,
failedCalls: 0,
totalResponseTime: 0,
slowCalls: 0
};
}

recordCall(duration, success, isSlowCall = false) {
this.metrics.totalCalls++;
this.metrics.totalResponseTime += duration;

if (success) {
this.metrics.successCalls++;
} else {
this.metrics.failedCalls++;
}

if (isSlowCall) {
this.metrics.slowCalls++;
}
}

getStats() {
return {
...this.metrics,
successRate: (this.metrics.successCalls / this.metrics.totalCalls * 100).toFixed(2),
averageResponseTime: (this.metrics.totalResponseTime / this.metrics.totalCalls).toFixed(2),
slowCallRate: (this.metrics.slowCalls / this.metrics.totalCalls * 100).toFixed(2)
};
}
}

const monitor = new PerformanceMonitor();

exports.main = async (event, context) => {
const app = tcb.init({ context });
const startTime = Date.now();

try {
const result = await app.callContainer({
name: event.serviceName,
method: event.method,
path: event.path,
header: event.headers,
data: event.data
});

const duration = Date.now() - startTime;
const isSlowCall = duration > 5000; // Considered a slow call if exceeding 5 seconds

monitor.recordCall(duration, true, isSlowCall);

// Periodically output statistics
if (monitor.metrics.totalCalls % 100 === 0) {
console.log('Performance statistics:', monitor.getStats());
}

return {
success: true,
data: result.data,
performance: {
responseTime: duration,
isSlowCall
}
};
} catch (error) {
const duration = Date.now() - startTime;
monitor.recordCall(duration, false);

console.error('Invocation failure:', {
service: event.serviceName,
duration: `${duration}ms`,
error: error.message
});

throw error;
}
};