Service
This content is for the 0.6.3 version. Switch to the latest version for up-to-date documentation.
The Service component provides the foundation for building HTTP services in Raindrop applications. Services handle incoming HTTP requests and can communicate with other services through type-safe service-to-service calls. Every Service must implement a fetch
method, even when used exclusively for internal service-to-service communication.
Services support three visibility levels that control how they can be accessed. Public services receive automatic URL assignment for external access. Protected services require authentication for access. Private services can only be accessed through service-to-service calls from other services within the same application.
Service-to-service communication happens through the environment bindings, allowing services to call methods on other services using env.SERVICENAME.methodName()
. These calls must be awaited and support passing structured clonable objects including Blobs, ArrayBuffers, and Request objects. Each service receives its own execution context, enabling independent background task management.
Prerequisites
- Understanding of HTTP request/response patterns
- Familiarity with async/await JavaScript
- Knowledge of TypeScript generics for type-safe environment bindings
- Basic understanding of service environments
Creating
Define services in your raindrop.manifest
file with the appropriate visibility level:
application "my-app" {
service "api-gateway" { visibility = "public" }
service "business-logic" { visibility = "private" }
service "admin-api" { visibility = "protected" }}
Accessing
Import the Service class and extend it to create your service implementation:
import { Service } from '@liquidmetal-ai/raindrop-framework';import { Env } from './raindrop.gen';
export default class extends Service<Env> { async fetch(request: Request): Promise<Response> { // Handle HTTP requests const result = await this.env.BUSINESS_LOGIC.processData({ id: 123 }); return new Response(JSON.stringify(result)); }}
Core Concepts
Main Interfaces
Service<Env>
- Base class for all HTTP service implementationsServiceStub<T>
- Type-safe stub for service-to-service communicationExecutionContext
- Runtime context for managing background tasksEnv
- Generated environment type containing service bindings
Core Data Types
Service Class
abstract class Service<Env> { ctx: ExecutionContext; // Runtime context for background tasks env: Env; // Environment bindings including other services
constructor(ctx: ExecutionContext, env: Env);}
ServiceStub Type
// Converts service methods to async Promise-based callstype ServiceStub<T extends Service<Env>, Env = unknown> = Stub<T>;
ExecutionContext Type
type ExecutionContext = { waitUntil(promise: Promise<any>): void; // Register background tasks};
System Limits
- Service-to-service calls accumulate execution time as they must be awaited
- Each service receives independent execution context
- Structured clonable objects required for inter-service data passing
fetch Method
Example
Implementing the required fetch handler for HTTP requests:
import { Service } from '@liquidmetal-ai/raindrop-framework';import { Env } from './raindrop.gen';
export default class ApiGateway extends Service<Env> { async fetch(request: Request): Promise<Response> { const url = new URL(request.url);
// Route to appropriate handler if (url.pathname === '/users') { // Call private service for business logic const users = await this.env.USER_SERVICE.getAllUsers(); return new Response(JSON.stringify(users), { headers: { 'Content-Type': 'application/json' } }); }
return new Response('Not Found', { status: 404 }); }}
// HTTP Request objectrequest: Request
// HTTP Response objectPromise<Response>
Service-to-Service Communication
Example
Calling methods on other services through environment bindings:
import { Service } from '@liquidmetal-ai/raindrop-framework';import { Env } from './raindrop.gen';
export default class PublicAPI extends Service<Env> { async fetch(request: Request): Promise<Response> { const { searchParams } = new URL(request.url); const userId = searchParams.get('userId');
// Call private service method - must await const userData = await this.env.USER_SERVICE.getUserById(userId); const orders = await this.env.ORDER_SERVICE.getOrdersByUser(userId);
return new Response(JSON.stringify({ user: userData, orders })); }}
// Private service implementationexport class UserService extends Service<Env> { // Required fetch implementation even for private services async fetch(request: Request): Promise<Response> { return new Response('Not Implemented', { status: 501 }); }
async getUserById(userId: string) { // Business logic here return { id: userId, name: 'John Doe' }; }}
// Service method parametersuserId: string // Can be any structured clonable type
// Async wrapped return valuePromise<{ id: string; name: string }>
Background Tasks with ExecutionContext
Example
Using execution context for background operations:
import { Service } from '@liquidmetal-ai/raindrop-framework';import { Env } from './raindrop.gen';
export default class DataProcessor extends Service<Env> { async fetch(request: Request): Promise<Response> { const data = await request.json();
// Register background task - won't block response this.ctx.waitUntil(this.processInBackground(data));
return new Response('Processing started', { status: 202 }); }
private async processInBackground(data: any) { // Long-running operation await this.env.ANALYTICS_SERVICE.analyze(data); await this.env.NOTIFICATION_SERVICE.sendComplete(data.id); }}
// Promise to execute in backgroundpromise: Promise<any>
// No return valuevoid