Skip to main content

Assembly Examples

Real-world examples of assembly workflows for common use cases.

Example 1: API Authentication Gateway

A preflow assembly that authenticates requests, applies rate limiting, and logs access.

apiVersion: tunnelapi.in/v1
kind: Assembly
metadata:
name: API Auth Gateway
description: JWT authentication with rate limiting
spec:
type: preflow
entryNodeId: auth
nodes:
- id: auth
type: auth
name: Validate JWT
config:
type: jwt
jwt:
secret: ${env.JWT_SECRET}
algorithms: [HS256]
failAction: reject
position: { x: 100, y: 150 }

- id: rate-limit
type: rate-limit
name: Rate Limiter
config:
windowMs: 60000
maxRequests: 100
keyBy: user
position: { x: 300, y: 150 }

- id: log-access
type: log
name: Access Log
config:
level: info
message: "User ${context.userId} accessed ${request.path}"
position: { x: 500, y: 150 }

- id: invoke
type: invoke
name: Backend API
config:
url: https://api.internal.com${request.path}
method: ${request.method}
forwardBody: true
headers:
X-User-ID: ${context.userId}
position: { x: 700, y: 150 }

edges:
- source: auth
target: rate-limit
- source: rate-limit
target: log-access
- source: log-access
target: invoke

Example 2: Response Caching

A postflow assembly that caches successful responses.

apiVersion: tunnelapi.in/v1
kind: Assembly
metadata:
name: Response Cache
description: Cache GET responses for 5 minutes
spec:
type: postflow
entryNodeId: check-method
nodes:
- id: check-method
type: condition
name: Is GET Request?
config:
expression: ${request.method} == 'GET'
position: { x: 100, y: 150 }

- id: check-status
type: condition
name: Is Success?
config:
expression: ${response.status} >= 200 && ${response.status} < 300
position: { x: 300, y: 100 }

- id: cache-response
type: cache
name: Cache Response
config:
ttl: 300000
keyBy: ${request.path}:${request.query}
storage: redis
position: { x: 500, y: 100 }

- id: skip
type: log
name: Skip Cache
config:
level: debug
message: "Skipping cache for ${request.method} ${request.path}"
position: { x: 300, y: 250 }

edges:
- source: check-method
target: check-status
condition:
type: expression
value: true
- source: check-method
target: skip
condition:
type: expression
value: false
- source: check-status
target: cache-response
condition:
type: expression
value: true

Example 3: Request Validation

A preflow assembly that validates request bodies against a schema.

apiVersion: tunnelapi.in/v1
kind: Assembly
metadata:
name: User Registration Validation
description: Validate user registration requests
spec:
type: preflow
entryNodeId: validate
nodes:
- id: validate
type: validate
name: Validate Request
config:
schema:
type: object
required: [email, password, name]
properties:
email:
type: string
format: email
password:
type: string
minLength: 8
pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)"
name:
type: string
minLength: 2
maxLength: 100
failAction: reject
position: { x: 100, y: 150 }

- id: transform
type: transform
name: Sanitize Input
config:
request:
body:
email: ${request.body.email.toLowerCase()}
createdAt: ${now()}
position: { x: 300, y: 150 }

- id: invoke
type: invoke
name: Create User
config:
url: https://api.internal.com/users
method: POST
forwardBody: true
position: { x: 500, y: 150 }

edges:
- source: validate
target: transform
- source: transform
target: invoke

Example 4: Circuit Breaker Pattern

A preflow assembly with circuit breaker for resilience.

apiVersion: tunnelapi.in/v1
kind: Assembly
metadata:
name: Resilient API Call
description: Circuit breaker with retry and fallback
spec:
type: preflow
entryNodeId: circuit-breaker
nodes:
- id: circuit-breaker
type: circuit-breaker
name: Circuit Breaker
config:
failureThreshold: 5
resetTimeout: 30000
halfOpenRequests: 3
position: { x: 100, y: 150 }

- id: retry
type: retry
name: Retry Logic
config:
maxRetries: 3
retryDelay: 1000
backoffMultiplier: 2
retryOn: [500, 502, 503, 504]
position: { x: 300, y: 100 }

- id: invoke
type: invoke
name: External API
config:
url: https://external-api.com/data
method: GET
timeout: 5000
position: { x: 500, y: 100 }

- id: fallback
type: fallback
name: Fallback Response
config:
response:
status: 200
body:
message: Service temporarily unavailable
cached: true
data: []
position: { x: 300, y: 250 }

edges:
- source: circuit-breaker
target: retry
condition:
type: circuit-closed
- source: circuit-breaker
target: fallback
condition:
type: circuit-open
- source: retry
target: invoke

Example 5: Multi-Backend Routing

A preflow assembly that routes to different backends based on request path.

apiVersion: tunnelapi.in/v1
kind: Assembly
metadata:
name: API Router
description: Route requests to different backends
spec:
type: preflow
entryNodeId: router
nodes:
- id: router
type: switch
name: Route by Path
config:
expression: ${request.path.split('/')[1]}
cases:
users: users-backend
products: products-backend
orders: orders-backend
default: default-backend
position: { x: 100, y: 200 }

- id: users-backend
type: invoke
name: Users Service
config:
url: https://users.internal.com${request.path}
method: ${request.method}
forwardBody: true
position: { x: 350, y: 50 }

- id: products-backend
type: invoke
name: Products Service
config:
url: https://products.internal.com${request.path}
method: ${request.method}
forwardBody: true
position: { x: 350, y: 150 }

- id: orders-backend
type: invoke
name: Orders Service
config:
url: https://orders.internal.com${request.path}
method: ${request.method}
forwardBody: true
position: { x: 350, y: 250 }

- id: default-backend
type: mock
name: Not Found
config:
status: 404
body:
error: Not Found
message: Unknown service
position: { x: 350, y: 350 }

edges:
- source: router
target: users-backend
condition: { type: case, value: users }
- source: router
target: products-backend
condition: { type: case, value: products }
- source: router
target: orders-backend
condition: { type: case, value: orders }
- source: router
target: default-backend
condition: { type: default }

Example 6: Webhook Processing

A standalone assembly for processing incoming webhooks.

apiVersion: tunnelapi.in/v1
kind: Assembly
metadata:
name: Stripe Webhook Handler
description: Process Stripe webhook events
spec:
type: standalone
entryNodeId: validate-signature
nodes:
- id: validate-signature
type: auth
name: Verify Signature
config:
type: webhook-signature
webhook:
secret: ${env.STRIPE_WEBHOOK_SECRET}
header: stripe-signature
position: { x: 100, y: 150 }

- id: parse-event
type: set-variable
name: Parse Event
config:
variables:
eventType: ${request.body.type}
eventData: ${request.body.data.object}
position: { x: 300, y: 150 }

- id: route-event
type: switch
name: Route by Event
config:
expression: ${context.eventType}
cases:
payment_intent.succeeded: handle-payment
customer.subscription.created: handle-subscription
invoice.payment_failed: handle-failure
default: log-unknown
position: { x: 500, y: 150 }

- id: handle-payment
type: invoke
name: Process Payment
config:
url: https://api.internal.com/payments/process
method: POST
body:
paymentId: ${context.eventData.id}
amount: ${context.eventData.amount}
position: { x: 750, y: 50 }

- id: handle-subscription
type: invoke
name: Create Subscription
config:
url: https://api.internal.com/subscriptions/create
method: POST
body:
customerId: ${context.eventData.customer}
planId: ${context.eventData.plan.id}
position: { x: 750, y: 150 }

- id: handle-failure
type: invoke
name: Handle Failure
config:
url: https://api.internal.com/payments/failed
method: POST
body:
invoiceId: ${context.eventData.id}
position: { x: 750, y: 250 }

- id: log-unknown
type: log
name: Log Unknown Event
config:
level: warn
message: "Unknown Stripe event: ${context.eventType}"
position: { x: 750, y: 350 }

edges:
- source: validate-signature
target: parse-event
- source: parse-event
target: route-event
- source: route-event
target: handle-payment
condition: { type: case, value: payment_intent.succeeded }
- source: route-event
target: handle-subscription
condition: { type: case, value: customer.subscription.created }
- source: route-event
target: handle-failure
condition: { type: case, value: invoice.payment_failed }
- source: route-event
target: log-unknown
condition: { type: default }

Tips for Building Assemblies

  1. Start with the happy path - Build the main flow first, then add error handling
  2. Use meaningful node names - Makes debugging easier
  3. Test incrementally - Test after adding each node
  4. Use environment variables - Never hardcode secrets
  5. Monitor execution logs - Identify bottlenecks and errors
  6. Version your YAML files - Store in Git for history and collaboration