Skip to main content

Tonica Run Modes

Tonica supports four different run modes, allowing you to deploy your application as a unified service or split into specialized components. This flexibility enables better resource utilization and scaling strategies.

Available Run Modes

ModeComponentsPortsUse Case
AIO (All-In-One)gRPC + REST + Workers + Consumers, Metrics8080, 50051, 2121Development, small deployments
ServicegRPC, Metrics50051, 2121Grpc layer scaling
WorkerTemporal workers, Metrics2121Background task scaling
ConsumerMessage consumers, Metrics2121Message processing scaling
GatewayREST, Metrics8080, 2121Api layer scaling

Mode Details

ModeAio (All-In-One)

Description: Runs all components in a single process.

What Starts:

  • HTTP/REST server (port 8080)
  • gRPC server (port 50051)
  • Temporal workers
  • Message consumers
  • Metrics endpoint (port 2121)
  • OpenAPI documentation UI

Example:

import (
"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
"github.com/tonica-go/tonica/pkg/tonica/service"
"github.com/tonica-go/tonica/pkg/tonica/worker"
"github.com/tonica-go/tonica/pkg/tonica/consumer"
)

app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(config.ModeAIO),
),
),
)

// Register services
svc := service.NewService(...)
app.GetRegistry().MustRegisterService(svc)

// Register workers
w := worker.NewWorker(...)
app.GetRegistry().MustRegisterWorker(w)

// Register consumers
c := consumer.NewConsumer(...)
app.GetRegistry().MustRegisterConsumer(c)

// Run in AIO mode
err := app.Run()

When to Use:

  • ✅ Development environment
  • ✅ Small applications with low traffic
  • ✅ Proof of concepts
  • ✅ Single-server deployments
  • ✅ When operational simplicity is more important than scaling

When NOT to Use:

  • ❌ High-traffic production (CPU/memory contention)
  • ❌ Need to scale components independently
  • ❌ CPU-intensive workers affecting API latency
  • ❌ Large-scale message processing

Resource Requirements:

# Example Kubernetes resources
resources:
requests:
cpu: "1000m" # All components combined
memory: "1Gi"
limits:
cpu: "2000m"
memory: "2Gi"

ModeService

Description: Runs only the API layer (gRPC and REST).

What Starts:

  • gRPC server (port 50051)
  • Metrics endpoint (port 2121)

What Does NOT Start:

  • Temporal workers
  • Message consumers

Example:

import (
"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
"github.com/tonica-go/tonica/pkg/tonica/service"
)

app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(config.ModeService),
),
),
)

// Register services ONLY
svc := service.NewService(...)
app.GetRegistry().MustRegisterService(svc)

// DON'T register workers or consumers in this mode

// Run in Service mode
err := app.Run()

When to Use:

  • ✅ Production API deployments
  • ✅ Need to scale API independently from workers
  • ✅ High request throughput
  • ✅ Low-latency requirements
  • ✅ Horizontal scaling of API layer

Architecture Pattern:

Scaling Example:

# Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice-api
spec:
replicas: 5 # Scale API horizontally
template:
spec:
containers:
- name: api
image: myservice:latest
env:
- name: RUN_MODE
value: "service"
resources:
requests:
cpu: "500m"
memory: "512Mi"

Resource Requirements:

# Lighter than AIO - no workers/consumers
resources:
requests:
cpu: "500m" # Just API handling
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"

ModeWorker

Description: Runs only Temporal workers for background task processing.

What Starts:

  • Temporal workers
  • Metrics endpoint (port 2121)

What Does NOT Start:

  • HTTP/REST server
  • gRPC server
  • Message consumers

Example:

import (
"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
"github.com/tonica-go/tonica/pkg/tonica/worker"
"go.temporal.io/sdk/client"
)

// Create Temporal client
temporalClient, err := client.Dial(client.Options{
HostPort: "localhost:7233",
})
if err != nil {
log.Fatal(err)
}

app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(config.ModeWorker),
),
),
)

// Register workers ONLY
w := worker.NewWorker(
worker.WithName("email-worker"),
worker.WithQueue("email-tasks"),
worker.WithClient(temporalClient),
worker.WithActivities([]interface{}{
SendEmailActivity,
GenerateReportActivity,
}),
)

app.GetRegistry().MustRegisterWorker(w)

// Run in Worker mode
err = app.Run()

When to Use:

  • ✅ CPU-intensive background tasks
  • ✅ Long-running operations (reports, exports)
  • ✅ Batch processing
  • ✅ Need to scale workers independently
  • ✅ Task-specific resource requirements

Architecture Pattern:

Specialized Workers:

// Email worker - high concurrency, low CPU
emailWorker := tonica.NewWorker(
tonica.WithWorkerName("email-worker"),
tonica.WithTaskQueue("emails"),
tonica.WithMaxConcurrentActivities(50), // Many concurrent emails
)

// Report worker - low concurrency, high CPU
reportWorker := tonica.NewWorker(
tonica.WithWorkerName("report-worker"),
tonica.WithTaskQueue("reports"),
tonica.WithMaxConcurrentActivities(2), // CPU-intensive reports
)

Resource Requirements:

# Email worker (I/O bound)
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"

---

# Report worker (CPU bound)
resources:
requests:
cpu: "2000m" # Needs more CPU
memory: "2Gi"
limits:
cpu: "4000m"
memory: "4Gi"

ModeConsumer

Description: Runs only message consumers for Kafka/PubSub processing.

What Starts:

  • Message consumers
  • Metrics endpoint (port 2121)

What Does NOT Start:

  • HTTP/REST server
  • gRPC server
  • Temporal workers

Example:

import (
"context"

"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
"github.com/tonica-go/tonica/pkg/tonica/consumer"
"github.com/tonica-go/tonica/pkg/tonica/storage/pubsub"
"github.com/tonica-go/tonica/pkg/tonica/storage/pubsub/kafka"
)

// Create Kafka client
kafkaClient := kafka.New(&kafka.Config{
Brokers: []string{"localhost:9092"},
ConsumerGroupID: "order-processors",
}, nil)

app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(config.ModeConsumer),
),
),
)

// Register consumers ONLY
c := consumer.NewConsumer(
consumer.WithName("order-consumer"),
consumer.WithTopic("orders"),
consumer.WithConsumerGroup("order-processors"),
consumer.WithClient(kafkaClient),
consumer.WithHandler(func(ctx context.Context, msg *pubsub.Message) error {
return processOrder(ctx, msg)
}),
)

app.GetRegistry().MustRegisterConsumer(c)

// Run in Consumer mode
err := app.Run()

When to Use:

  • ✅ High-volume message processing
  • ✅ Event-driven architectures
  • ✅ Stream processing
  • ✅ Need to scale consumers independently
  • ✅ Topic-specific processing logic

Architecture Pattern:

Multiple Consumers:

// Order consumer - high priority
orderConsumer := tonica.NewConsumer(
tonica.WithConsumerName("order-consumer"),
tonica.WithTopic("orders"),
tonica.WithConsumerGroup("order-processors"),
tonica.WithHandler(processOrder),
)

// Analytics consumer - low priority
analyticsConsumer := tonica.NewConsumer(
tonica.WithConsumerName("analytics-consumer"),
tonica.WithTopic("events"),
tonica.WithConsumerGroup("analytics"),
tonica.WithHandler(processAnalytics),
)

app.GetRegistry().MustRegisterConsumer(orderConsumer)
app.GetRegistry().MustRegisterConsumer(analyticsConsumer)

Resource Requirements:

# Consumer resources depend on processing complexity
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"

ModeGateway

Description: Runs only the HTTP/REST API gateway layer (without gRPC server).

What Starts:

  • HTTP/REST server (port 8080)
  • gRPC-Gateway (proxies REST requests to backend gRPC services)
  • Custom routes
  • OpenAPI documentation UI
  • Metrics endpoint (port 2121)

What Does NOT Start:

  • gRPC server (connects to external gRPC services instead)
  • Temporal workers
  • Message consumers

Example:

import (
"github.com/gin-gonic/gin"
"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
)

app := tonica.NewApp(
tonica.WithSpec("openapi/spec.json"),
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(config.ModeGateway),
),
),
)

// Don't register services directly - gateway proxies to external gRPC servers
// Just configure custom routes if needed
tonica.NewRoute(app).
GET("/health").
Handle(func(c *gin.Context) {
c.JSON(200, gin.H{"status": "healthy"})
})

// Run in Gateway mode
err := app.Run()

When to Use:

  • ✅ Separate REST API layer from gRPC services
  • ✅ Independent scaling of API gateway
  • ✅ Edge layer for multiple backend services
  • ✅ API rate limiting and caching at gateway level
  • ✅ Public-facing REST API with private gRPC backends

Architecture Pattern:

Use Case Example:

┌─────────────┐
│ Clients │ (Mobile, Web, etc.)
└──────┬──────┘
│ HTTP/REST

┌─────────────┐
│ Gateways │ ModeGateway (scaled: 10 replicas)
│ Port 8080 │ - Rate limiting
└──────┬──────┘ - Caching
│ gRPC - Auth

┌─────────────┐
│ Services │ ModeService (scaled: 5 replicas)
│ Port 50051 │ - Business logic
└─────────────┘ - Database access

Resource Requirements:

# Gateway is lightweight - just proxying
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"

Configuration:

Point gateway to backend gRPC services:

# Service addresses
export USERSERVICE_ADDR="user-service:50051"
export ORDERSERVICE_ADDR="order-service:50051"
export PAYMENTSERVICE_ADDR="payment-service:50051"

Choosing the Right Mode

Decision Tree

Deployment Patterns

Pattern 1: Development

# Single container
services:
app:
image: myservice:latest
environment:
RUN_MODE: aio
ports:
- "8080:8080" # HTTP
- "50051:50051" # gRPC
- "9090:9090" # Metrics

Pattern 2: Small Production

# Separate API and workers
services:
gateway:
image: myservice:latest
environment:
RUN_MODE: gateway
replicas: 3
ports:
- "8080:8080"

api:
image: myservice:latest
environment:
RUN_MODE: service
replicas: 3
ports:
- "8080:8080"
- "50051:50051"

worker:
image: myservice:latest
environment:
RUN_MODE: worker
replicas: 2

Pattern 3: Large Production

# Fully separated components
services:
gateway:
image: myservice:latest
environment:
RUN_MODE: gateway
replicas: 10 # Scale API independently

api:
image: myservice:latest
environment:
RUN_MODE: service
replicas: 10 # Scale API independently

email-worker:
image: myservice:latest
environment:
RUN_MODE: worker
WORKER_TASK_QUEUE: emails
replicas: 5

report-worker:
image: myservice:latest
environment:
RUN_MODE: worker
WORKER_TASK_QUEUE: reports
replicas: 2
resources:
cpu: "4000m" # More CPU for reports

order-consumer:
image: myservice:latest
environment:
RUN_MODE: consumer
CONSUMER_TOPIC: orders
replicas: 3

Mode Configuration

Via Environment Variable

# Set run mode via environment
export APP_MODE="service"
import (
"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
)

// In your code, read from environment
app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(
config.GetEnv("APP_MODE", config.ModeAIO), // Read from env with default
),
),
),
)

err := app.Run()

Via Code

import (
"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
)

// Hardcode the mode
app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(config.ModeService),
),
),
)

err := app.Run()

Via Command Line Flag

import (
"flag"
"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
)

func main() {
mode := flag.String("mode", config.ModeAIO, "Run mode: aio, service, worker, consumer, gateway")
flag.Parse()

app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(*mode),
),
),
)

err := app.Run()
if err != nil {
log.Fatal(err)
}
}

Graceful Shutdown in All Modes

All modes support graceful shutdown (handled internally by the framework):

import (
"context"
"os"
"os/signal"
"syscall"

"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
)

app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(config.ModeAIO),
),
),
)

// Graceful shutdown is handled automatically by the framework
// It listens for SIGINT/SIGTERM signals
err := app.Run()

Shutdown Behavior by Mode:

ModeShutdown Process
AIO1. Stop accepting new HTTP/gRPC requests
2. Wait for in-flight requests (30s)
3. Stop workers
4. Wait for consumers to finish current message (5s)
5. Close connections
Service1. Stop accepting new requests
2. Wait for in-flight requests (30s)
3. Close connections
Worker1. Stop accepting new tasks
2. Wait for current tasks to complete
3. Close Temporal connection
Consumer1. Stop accepting new messages
2. Wait for current message processing (5s)
3. Close message queue connection
Gateway1. Stop accepting new HTTP requests

Monitoring Each Mode

Metrics by Mode

All modes expose metrics on port 2121:

curl http://localhost:2121/metrics

Mode-Specific Metrics:

ModeService:

  • http_requests_total
  • http_request_duration_seconds
  • grpc_requests_total
  • grpc_request_duration_seconds

ModeWorker:

  • temporal_activity_execution_total
  • temporal_activity_duration_seconds
  • temporal_activity_errors_total

ModeConsumer:

  • consumer_messages_processed_total
  • consumer_messages_failed_total
  • consumer_processing_duration_seconds

Health Checks

Add mode-specific health checks:

tonica.NewRoute(app).
GET("/health").
Handle(func (c *gin.Context) {
health := gin.H{
"status": "healthy",
"mode": "service", // or worker, consumer
}

// Add mode-specific checks
if mode == "worker" {
health["workers"] = len(app.GetRegistry().GetAllWorkers())
}

c.JSON(200, health)
})

Common Pitfalls

❌ Registering Wrong Components

import (
"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
"github.com/tonica-go/tonica/pkg/tonica/worker"
)

// DON'T: Register workers when running in Service mode
app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(config.ModeService),
),
),
)
w := worker.NewWorker(...)
app.GetRegistry().MustRegisterWorker(w)
err := app.Run() // Worker won't start in Service mode!

✅ Register Appropriate Components

import (
"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
"github.com/tonica-go/tonica/pkg/tonica/service"
)

// DO: Only register services when running in Service mode
app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(config.ModeService),
),
),
)
svc := service.NewService(...)
app.GetRegistry().MustRegisterService(svc)
err := app.Run() // Correct!

❌ Port Conflicts

// DON'T: Run multiple Service mode instances on same host
// Both try to bind to port 8080 and 50051

✅ Use Different Hosts or Containers

# DO: Run in separate containers/hosts
services:
api-1:
ports: [ "8080:8080" ]
api-2:
ports: [ "8081:8080" ] # Map to different external port

Advanced: Dynamic Mode Selection

import (
"os"

"github.com/tonica-go/tonica/pkg/tonica"
"github.com/tonica-go/tonica/pkg/tonica/config"
"github.com/tonica-go/tonica/pkg/tonica/service"
"github.com/tonica-go/tonica/pkg/tonica/worker"
"github.com/tonica-go/tonica/pkg/tonica/consumer"
)

func main() {
mode := config.GetEnv("APP_MODE", config.ModeAIO)

app := tonica.NewApp(
tonica.WithConfig(
config.NewConfig(
config.WithRunMode(mode),
),
),
)

// Create components
svc := service.NewService(...)
w := worker.NewWorker(...)
c := consumer.NewConsumer(...)

// Register based on mode
switch mode {
case config.ModeService:
app.GetRegistry().MustRegisterService(svc)
case config.ModeWorker:
app.GetRegistry().MustRegisterWorker(w)
case config.ModeConsumer:
app.GetRegistry().MustRegisterConsumer(c)
case config.ModeGateway:
// No components needed for gateway
default: // config.ModeAIO
app.GetRegistry().MustRegisterService(svc)
app.GetRegistry().MustRegisterWorker(w)
app.GetRegistry().MustRegisterConsumer(c)
}

err := app.Run()
if err != nil {
log.Fatal(err)
}
}

Summary

ModeDevelopmentProductionScalingResource Efficiency
AIO✅ Perfect⚠️ Small only❌ Limited⚠️ Medium
Service✅ Good✅ Excellent✅ Horizontal✅ High
Worker✅ Good✅ Excellent✅ Horizontal + Vertical✅ High
Consumer✅ Good✅ Excellent✅ Horizontal✅ High
Gateway✅ Good✅ Excellent✅ Horizontal✅ High

Recommendation: Start with ModeAio for development, then split into ModeService + ModeWorker + * ModeConsumer* for production.

Next Steps