Invoking Other Cloud Functions within Cloud Functions
In cloud function development, you may need to invoke one cloud function from another to achieve feature modularization, code reuse, and business logic decoupling. CloudBase supports invoking cloud functions within the current environment or other environments.
Application Scenarios
Typical Use Cases
- Feature Modularization: Splitting complex business into multiple independent cloud functions
- Code Reuse: Sharing common business logic and utility functions
- Permission Isolation: Operations with different permission levels are processed separately.
- Asynchronous Processing: Triggering background tasks and asynchronous operations
- Cross-Environment Invocation: Function invocation between development, testing, and production environments
Practical Application Example
// The order processing function invokes multiple sub-functions
exports.main = async (event, context) => {
// 1. Verify user permissions
const authResult = await app.callFunction({
name: 'user-auth',
data: { userId: event.userId, action: 'create_order' }
});
// 2. Calculate the order amount
const priceResult = await app.callFunction({
name: 'price-calculator',
data: { items: event.items, couponCode: event.couponCode }
});
// 3. Send notification
await app.callFunction({
name: 'notification-sender',
data: { type: 'order_created', orderId: orderId }
});
return { success: true, orderId: orderId };
};
Overview of Invocation Methods
📄️ Node.js SDK Invocation
Invoking other cloud functions using the Node.js SDK within a cloud function
📄️ HTTP API Invocation
Invoking cloud functions via the HTTP API
📄️ Cross-environment Invocation
Invoking cloud functions across different environments
Using the Node.js SDK for Invocation
The Node.js SDK is the recommended approach for invoking other cloud functions within a cloud function, offering a concise API and robust error handling.
Basic Invocation
- Same-environment Invocation
- Cross-environment Invocation
const tcb = require('@cloudbase/node-sdk');
exports.main = async (event, context) => {
// Initialize the SDK (automatically obtains current environment information)
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV // Use the current environment
});
try {
// Invoke a cloud function within the same environment
const result = await app.callFunction({
name: 'target-function',
data: {
message: 'Hello from caller function',
timestamp: Date.now(),
userId: event.userId
}
});
console.log('Function invoked successfully:', result);
return {
success: true,
data: result.result,
requestId: result.requestId
};
} catch (error) {
console.error('Function invocation failed:', error);
return {
success: false,
error: error.message,
code: error.code
};
}
};
const tcb = require('@cloudbase/node-sdk');
exports.main = async (event, context) => {
// Initialize the SDK to specify the target environment
const app = tcb.init({
env: 'target-env-id', // Target Environment ID
secretId: process.env.SECRET_ID, // Retrieved from environment variables
secretKey: process.env.SECRET_KEY // Retrieved from environment variables
});
try {
const result = await app.callFunction({
name: 'cross-env-function',
data: {
sourceEnv: tcb.SYMBOL_CURRENT_ENV,
message: event.message
}
});
return result.result;
} catch (error) {
console.error('Cross-environment Invocation failed:', error);
throw error;
}
};
Advanced Features
Version Control Invocation
exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});
// Invoke the specified version of the cloud function
const result = await app.callFunction({
name: 'versioned-function',
data: event.data,
qualifier: '2' // Invoke version 2
});
return result.result;
};
Timeout Control
exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});
try {
// Set a 5-second timeout
const result = await app.callFunction({
name: 'slow-function',
data: event.data
}, {
timeout: 5000 // 5 seconds timeout
});
return result.result;
} catch (error) {
if (error.code === 'TIMEOUT') {
return { error: 'Function invocation timeout' };
}
throw error;
}
};
Batch Invocation
exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});
// Concurrently invoke multiple functions
const promises = event.tasks.map(task =>
app.callFunction({
name: 'worker-function',
data: task
}).catch(error => ({ error: error.message }))
);
const results = await Promise.allSettled(promises);
return {
total: results.length,
success: results.filter(r => r.status === 'fulfilled').length,
failed: results.filter(r => r.status === 'rejected').length,
results: results.map(r =>
r.status === 'fulfilled' ? r.value.result : r.reason
)
};
};
Functional Cloud Hosting Invocation
exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});
// Invoke functional cloud hosting service
const result = await app.callFunction({
name: 'cloudrun-service',
type: 'cloudrun', // Specify as cloud hosting type
method: 'POST', // HTTP method
path: '/api/users', // Request path
header: { // request headers
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
},
data: { // Request body
name: event.userName,
email: event.userEmail
}
}, {
timeout: 10000 // 10 seconds timeout
});
return result.result;
};
Using HTTP API Invocation
Invoking cloud functions via the HTTP API is suitable for scenarios requiring finer-grained control or integration with external systems.
Basic HTTP Invocation
const https = require('https');
exports.main = async (event, context) => {
const accessToken = await getAccessToken(); // Obtain an access token
const postData = JSON.stringify({
message: 'Hello from HTTP caller',
data: event.data
});
const options = {
hostname: `${process.env.TCB_ENV}.api.tcloudbasegateway.com`,
port: 443,
path: '/v1/functions/target-function',
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
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(result);
} catch (error) {
reject(error);
}
});
});
req.on('error', (error) => {
reject(error);
});
req.write(postData);
req.end();
});
};
// Helper function to obtain an access token
async function getAccessToken() {
// Implement the logic to obtain access_token
// Can be obtained from environment variables, databases, or other secure storage
return process.env.ACCESS_TOKEN;
}
Using Axios to Simplify HTTP Calls
const axios = require('axios');
exports.main = async (event, context) => {
try {
const response = await axios.post(
`https://${process.env.TCB_ENV}.api.tcloudbasegateway.com/v1/functions/target-function`,
{
message: 'Hello from Axios caller',
data: event.data
},
{
headers: {
'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
'Content-Type': 'application/json'
},
timeout: 10000 // 10 seconds timeout
}
);
return {
success: true,
data: response.data,
status: response.status
};
} catch (error) {
console.error('HTTP invocation failed:', error.response?.data || error.message);
return {
success: false,
error: error.response?.data?.message || error.message,
status: error.response?.status
};
}
};
Cross-Environment Invocation
Cross-environment invocation allows you to call cloud functions across different CloudBase environments, commonly used for data synchronization and business collaboration between development, testing, and production environments.
Configuring Cross-Environment Invocation
const tcb = require('@cloudbase/node-sdk');
exports.main = async (event, context) => {
// Select the target environment based on different scenarios
const targetEnv = getTargetEnv(event.scenario);
const app = tcb.init({
env: targetEnv,
secretId: process.env.CROSS_ENV_SECRET_ID,
secretKey: process.env.CROSS_ENV_SECRET_KEY
});
const result = await app.callFunction({
name: 'sync-data',
data: {
sourceEnv: process.env.TCB_ENV,
syncType: event.syncType,
data: event.data
}
});
return result.result;
};
function getTargetEnv(scenario) {
const envMapping = {
'dev-to-test': 'test-env-id',
'test-to-prod': 'prod-env-id',
'prod-to-backup': 'backup-env-id'
};
return envMapping[scenario] || 'default-env-id';
}
Environment Variable Management
Set environment variables in the cloud function configuration:
# Adding Environment Variables in the Cloud Function Configuration of the TCB Console
CROSS_ENV_SECRET_ID=your-secret-id
CROSS_ENV_SECRET_KEY=your-secret-key
TARGET_ENV_ID=target-environment-id
ACCESS_TOKEN=your-access-token
Error Handling and Debugging
Comprehensive Error Handling
const tcb = require('@cloudbase/node-sdk');
exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});
try {
const result = await app.callFunction({
name: 'target-function',
data: event.data
});
return {
success: true,
data: result.result,
requestId: result.requestId
};
} catch (error) {
console.error('Function call failed:', {
code: error.code,
message: error.message,
requestId: error.requestId,
stack: error.stack
});
// Handle differently based on error types
switch (error.code) {
case 'FUNCTION_NOT_FOUND':
return {
success: false,
error: 'Target function does not exist',
code: 'FUNCTION_NOT_FOUND'
};
case 'PERMISSION_DENIED':
return {
success: false,
error: 'Insufficient permissions',
code: 'PERMISSION_DENIED'
};
case 'TIMEOUT':
return {
success: false,
error: 'Function invocation timeout',
code: 'TIMEOUT'
};
default:
return {
success: false,
error: error.message || 'Unknown error',
code: error.code || 'UNKNOWN_ERROR'
};
}
}
};
Debugging Techniques
exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});
// Add debugging information
console.log('Invocation start:', {
caller: context.function_name,
target: 'target-function',
requestId: context.request_id,
timestamp: new Date().toISOString()
});
const startTime = Date.now();
try {
const result = await app.callFunction({
name: 'target-function',
data: {
...event.data,
_debug: {
caller: context.function_name,
callTime: new Date().toISOString()
}
}
});
const duration = Date.now() - startTime;
console.log('Invoked successfully:', {
duration: `${duration}ms`,
resultSize: JSON.stringify(result.result).length,
requestId: result.requestId
});
return result.result;
} catch (error) {
const duration = Date.now() - startTime;
console.error('Invocation failure:', {
duration: `${duration}ms`,
error: error.message,
code: error.code
});
throw error;
}
};
Performance Optimization Recommendations
1. SDK Instance Reuse
// ❌ Error: Initialize on every call
exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
return await app.callFunction({ name: 'target', data: event });
};
// ✅ Correct approach: Reuse SDK instances
const tcb = require('@cloudbase/node-sdk');
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
exports.main = async (event, context) => {
return await app.callFunction({ name: 'target', data: event });
};
2. Concurrency Control
// Limit the number of concurrent calls
class ConcurrencyController {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
}
async execute(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
this.process();
});
}
async process() {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.running++;
const { fn, resolve, reject } = this.queue.shift();
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process();
}
}
}
const controller = new ConcurrencyController(3);
exports.main = async (event, context) => {
const tasks = event.tasks.map(task =>
() => app.callFunction({ name: 'worker', data: task })
);
const results = await Promise.all(
tasks.map(task => controller.execute(task))
);
return results;
};
3. Cache Policy
// Simple in-memory cache
const cache = new Map();
async function cachedCallFunction(functionName, data, ttl = 60000) {
const cacheKey = `${functionName}_${JSON.stringify(data)}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < ttl) {
console.log('Cache hit:', cacheKey);
return cached.result;
}
const result = await app.callFunction({
name: functionName,
data: data
});
cache.set(cacheKey, {
result: result.result,
timestamp: Date.now()
});
return result.result;
}
Best Practices
1. Function Design Principles
// Design reusable utility functions
exports.main = async (event, context) => {
const { action, data } = event;
switch (action) {
case 'validate_user':
return await validateUser(data);
case 'send_notification':
return await sendNotification(data);
case 'calculate_price':
return await calculatePrice(data);
default:
throw new Error(`Unsupported operation: ${action}`);
}
};
async function validateUser(userData) {
// User authentication logic
return { valid: true, userId: userData.id };
}
async function sendNotification(notificationData) {
// Notification sending logic
return { sent: true, messageId: 'msg_123' };
}
async function calculatePrice(priceData) {
// Price calculation logic
return { total: 100, discount: 10 };
}
2. Error Retry Mechanism
async function callFunctionWithRetry(functionName, data, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
const result = await app.callFunction({
name: functionName,
data: data
});
return result.result;
} catch (error) {
lastError = error;
// Retry only for specific errors
if (error.code === 'TIMEOUT' || error.code === 'NETWORK_ERROR') {
console.log(`Retry ${i + 1}/${maxRetries}:`, error.message);
await sleep(1000 * (i + 1)); // Exponential backoff
continue;
}
// Other errors are thrown directly.
throw error;
}
}
throw lastError;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
3. Monitoring and Logging
exports.main = async (event, context) => {
const startTime = Date.now();
const callId = `${context.request_id}_${Date.now()}`;
console.log('Function invocation start:', {
callId,
caller: context.function_name,
target: event.targetFunction,
timestamp: new Date().toISOString()
});
try {
const result = await app.callFunction({
name: event.targetFunction,
data: {
...event.data,
_meta: {
callId,
caller: context.function_name,
timestamp: new Date().toISOString()
}
}
});
const duration = Date.now() - startTime;
// Function invocation successful:
callId,
duration: `${duration}ms`,
resultSize: JSON.stringify(result.result).length
});
return result.result;
} catch (error) {
const duration = Date.now() - startTime;
console.error('Function call failed:', {
callId,
duration: `${duration}ms`,
error: error.message,
code: error.code
});
throw error;
}
};