acton-service 0.2.0

Production-ready Rust microservice framework with type-enforced API versioning
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
# acton-service

**Production-grade Rust microservice framework with type-enforced API versioning**

Build microservices that can't ship unversioned APIs. The compiler won't let you.

---

## What is this?

Most microservice frameworks make API versioning optional. You *should* version your APIs, they say. But when deadlines loom, versioning gets skipped. Six months later, you're maintaining breaking changes in production.

acton-service uses Rust's type system to make unversioned APIs **impossible**. Your service won't compile without proper versioning. It's opinionated, batteries-included, and designed for teams shipping to production.

**Current Status**: acton-service is under active development. Core features (HTTP, versioning, health checks, observability) are production-ready. Some advanced features are in progress.

## Quick Start

```rust
use acton_service::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    // Routes MUST be versioned - this is the only way to create them
    let routes = VersionedApiBuilder::new()
        .with_base_path("/api")
        .add_version(ApiVersion::V1, |router| {
            router.route("/hello", get(|| async { "Hello, V1!" }))
        })
        .add_version(ApiVersion::V2, |router| {
            router.route("/hello", get(|| async { "Hello, V2!" }))
        })
        .build_routes();

    // Zero-config service startup
    ServiceBuilder::new()
        .with_routes(routes)
        .build()
        .serve()
        .await
}
```

```bash
cargo run
curl http://localhost:8080/api/v1/hello
curl http://localhost:8080/api/v2/hello
curl http://localhost:8080/health  # automatic health checks
```

**Try to create an unversioned route? Won't compile.**

```rust
// ❌ This won't compile
let app = Router::new().route("/unversioned", get(handler));
ServiceBuilder::new().with_routes(app).build();
//                                   ^^^ expected VersionedRoutes, found Router
```

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
acton-service = { version = "0.2", features = ["http", "observability"] }
tokio = { version = "1", features = ["full"] }
```

Or use the CLI to scaffold a complete service:

```bash
cargo install acton-cli
acton service new my-api --yes
cd my-api && cargo run
```

## Why acton-service?

### The Problem

Building production microservices requires solving the same problems over and over:

- **API Versioning**: Most frameworks make it optional. Teams skip it until it's too late.
- **Health Checks**: Every orchestrator needs them. Every team implements them differently.
- **Observability**: Tracing, metrics, and logging should be standard, not afterthoughts.
- **Configuration**: Environment-based config that doesn't require boilerplate.
- **Dual Protocols**: HTTP and gRPC on the same port (modern K8s deployments need both).

### The Solution

acton-service provides:

1. **Type-enforced versioning** - The compiler prevents unversioned APIs ✅
2. **Automatic health endpoints** - Kubernetes-ready liveness and readiness probes ✅
3. **Structured logging** - JSON logging with distributed request tracing ✅
4. **Zero-config defaults** - XDG-compliant configuration with sensible defaults ✅
5. **HTTP + gRPC support** - Run both protocols (currently on separate ports) ✅

Most importantly: **it's designed for teams**. Individual contributors can't accidentally break production API contracts.

## Core Features

### Type-Safe API Versioning

Routes are versioned at compile time. The type system enforces it:

```rust
// Define your API versions
let routes = VersionedApiBuilder::new()
    .with_base_path("/api")
    .add_version_deprecated(
        ApiVersion::V1,
        |router| router.route("/users", get(list_users_v1)),
        DeprecationInfo::new(ApiVersion::V1, ApiVersion::V2)
            .with_sunset_date("2026-12-31T23:59:59Z")
            .with_message("V1 is deprecated. Migrate to V2.")
    )
    .add_version(ApiVersion::V2, |router| {
        router.route("/users", get(list_users_v2))
    })
    .build_routes();
```

Deprecated versions automatically include `Deprecation`, `Sunset`, and `Link` headers per [RFC 8594](https://datatracker.ietf.org/doc/html/rfc8594).

### Automatic Health Checks

Health and readiness endpoints are included automatically and follow Kubernetes best practices:

```rust
// Health checks are automatic - no code needed
ServiceBuilder::new()
    .with_routes(routes)
    .build()
    .serve()
    .await?;

// Endpoints available immediately:
// GET /health    - Liveness probe (process alive?)
// GET /ready     - Readiness probe (dependencies healthy?)
```

The readiness endpoint automatically checks configured dependencies:

```toml
# config.toml
[database]
url = "postgres://localhost/mydb"
optional = false  # Readiness fails if DB is down

[redis]
url = "redis://localhost"
optional = true   # Readiness succeeds even if Redis is down
```

### Batteries-Included Middleware

Production-ready middleware stack included:

```rust
ServiceBuilder::new()
    .with_routes(routes)
    .with_middleware(|router| {
        router
            .layer(JwtAuth::new("your-secret"))
            .layer(RequestTrackingConfig::default().layer())
            .layer(RateLimit::new(100, Duration::from_secs(60)))
    })
    .build()
    .serve()
    .await?;
```

Available middleware:

- **JWT Authentication** - Token validation with configurable algorithms
- **Rate Limiting** - Token bucket and sliding window strategies (governor)
- **Request Tracking** - Request ID generation and propagation
- **Compression** - gzip, br, deflate, zstd
- **CORS** - Configurable cross-origin policies
- **Timeouts** - Configurable request timeouts
- **Body Size Limits** - Prevent oversized payloads
- **Panic Recovery** - Graceful handling of panics

### HTTP + gRPC Support

Run HTTP and gRPC services together:

```rust
// HTTP handlers
let http_routes = VersionedApiBuilder::new()
    .add_version(ApiVersion::V1, |router| {
        router.route("/users", get(list_users))
    })
    .build_routes();

// gRPC service
#[derive(Default)]
struct MyGrpcService;

#[tonic::async_trait]
impl my_service::MyService for MyGrpcService {
    async fn my_method(&self, req: Request<MyRequest>)
        -> Result<Response<MyResponse>, Status> {
        // ...
    }
}

// Serve both protocols
// Currently on separate ports (HTTP: 8080, gRPC: 9090)
// Single-port multiplexing coming soon
ServiceBuilder::new()
    .with_routes(http_routes)
    .with_grpc_service(my_service::MyServiceServer::new(MyGrpcService))
    .build()
    .serve()
    .await?;
```

Configure gRPC port in `config.toml`:

```toml
[grpc]
enabled = true
port = 9090              # Separate port for gRPC
use_separate_port = true # Currently required
```

### Zero-Configuration Defaults

Configuration follows the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html):

```
~/.config/acton-service/
├── my-service/
│   └── config.toml
├── auth-service/
│   └── config.toml
└── user-service/
    └── config.toml
```

Services load configuration automatically with environment variable overrides:

```bash
# No config file needed for development
cargo run

# Override specific values
ACTON_SERVICE_PORT=9090 cargo run

# Production config location
~/.config/acton-service/my-service/config.toml
```

## Feature Flags

Enable only what you need:

```toml
[dependencies]
acton-service = { version = "0.2", features = [
    "http",          # Axum HTTP framework (default)
    "grpc",          # Tonic gRPC support
    "database",      # PostgreSQL via SQLx
    "cache",         # Redis connection pooling
    "events",        # NATS JetStream
    "observability", # Structured logging (default)
    "governor",      # Advanced rate limiting
    "openapi",       # Swagger/OpenAPI documentation
] }
```

**Note**: Some feature flags (like `resilience`, `otel-metrics`) are defined but not fully implemented yet. See the roadmap below.

Or use `full` to enable everything:

```toml
[dependencies]
acton-service = { version = "0.2", features = ["full"] }
```

## Examples

### Minimal HTTP Service

```rust
use acton_service::prelude::*;

async fn hello() -> &'static str {
    "Hello, world!"
}

#[tokio::main]
async fn main() -> Result<()> {
    let routes = VersionedApiBuilder::new()
        .with_base_path("/api")
        .add_version(ApiVersion::V1, |router| {
            router.route("/hello", get(hello))
        })
        .build_routes();

    ServiceBuilder::new()
        .with_routes(routes)
        .build()
        .serve()
        .await
}
```

### Production Service with Database

```rust
use acton_service::prelude::*;

#[derive(Serialize)]
struct User {
    id: i64,
    name: String,
}

async fn list_users(State(state): State<AppState>) -> Result<Json<Vec<User>>> {
    let db = state.database()?;
    let users = sqlx::query_as!(User, "SELECT id, name FROM users")
        .fetch_all(db)
        .await?;
    Ok(Json(users))
}

#[tokio::main]
async fn main() -> Result<()> {
    let routes = VersionedApiBuilder::new()
        .with_base_path("/api")
        .add_version(ApiVersion::V1, |router| {
            router.route("/users", get(list_users))
        })
        .build_routes();

    ServiceBuilder::new()
        .with_routes(routes)
        .build()
        .serve()
        .await
}
```

Configuration in `~/.config/acton-service/my-service/config.toml`:

```toml
[service]
name = "my-service"
port = 8080

[database]
url = "postgres://localhost/mydb"
max_connections = 50
```

### Event-Driven Service

```rust
use acton_service::prelude::*;

async fn process_event(msg: async_nats::Message) -> Result<()> {
    let payload: serde_json::Value = serde_json::from_slice(&msg.payload)?;
    info!("Processing event: {:?}", payload);
    Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
    let config = Config::load()?;
    init_tracing(&config)?;

    let state = AppState::builder()
        .config(config.clone())
        .build()
        .await?;

    let nats = state.nats()?;
    let mut subscriber = nats.subscribe("events.>").await?;

    while let Some(msg) = subscriber.next().await {
        if let Err(e) = process_event(msg).await {
            error!("Event processing failed: {}", e);
        }
    }

    Ok(())
}
```

See the [`examples/`](./acton-service/examples) directory for complete examples including:

- Simple versioned API - [`simple-api.rs`]./acton-service/examples/simple-api.rs
- User management API with deprecation - [`users-api.rs`]./acton-service/examples/users-api.rs
- Dual-protocol HTTP + gRPC - [`ping-pong.rs`]./acton-service/examples/ping-pong.rs
- Event-driven architecture - [`event-driven.rs`]./acton-service/examples/event-driven.rs

Run examples:

```bash
cargo run --example simple-api
cargo run --example users-api
cargo run --example ping-pong --features grpc
cargo run --example event-driven --features grpc
```

## CLI Tool

The `acton` CLI scaffolds production-ready services:

```bash
# Install the CLI
cargo install acton-cli

# Create a new service
acton service new my-api --yes

# Full-featured service
acton service new user-service \
    --http \
    --database postgres \
    --cache redis \
    --events nats \
    --observability

# Add endpoints to existing service
cd user-service
acton service add endpoint POST /users --handler create_user
acton service add worker email-worker --source nats --stream emails

# Generate Kubernetes manifests
acton service generate deployment --hpa --monitoring
```

See the [CLI documentation](./acton-cli/README.md) for details.

## Architecture

acton-service is built on production-proven Rust libraries:

- **HTTP**: [axum]https://github.com/tokio-rs/axum - Ergonomic web framework
- **gRPC**: [tonic]https://github.com/hyperium/tonic - Native Rust gRPC
- **Database**: [SQLx]https://github.com/launchbadge/sqlx - Compile-time checked queries
- **Cache**: [redis-rs]https://github.com/redis-rs/redis-rs - Redis client
- **Events**: [async-nats]https://github.com/nats-io/nats.rs - NATS client
- **Observability**: [OpenTelemetry]https://github.com/open-telemetry/opentelemetry-rust - Distributed tracing

Design principles:

1. **Type safety over runtime checks** - Use the compiler to prevent mistakes
2. **Opinionated defaults** - Best practices should be the default path
3. **Explicit over implicit** - No magic, clear code flow
4. **Production-ready by default** - Health checks, config, observability included
5. **Modular features** - Only compile what you need

## Documentation

- [Configuration Guide]./acton-service/CONFIG.md - Environment and file-based configuration
- [API Versioning]./acton-service/docs/API_VERSIONING.md - Type-safe versioning patterns
- [Health Endpoints]./HEALTH_ENDPOINTS_GUIDE.md - Kubernetes liveness and readiness
- [Examples]./acton-service/examples/ - Complete working examples

API documentation: `cargo doc --open`

## Performance

acton-service is built on tokio and axum, which are known for excellent performance characteristics. The framework adds minimal abstraction overhead beyond the underlying libraries.

**Performance benchmarks will be published as the project matures.** Performance is primarily determined by your application logic and the underlying libraries (axum for HTTP, tonic for gRPC, sqlx for database operations).

## Deployment

### Docker

```dockerfile
FROM rust:1.84-slim as builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/my-service /usr/local/bin/
EXPOSE 8080
CMD ["my-service"]
```

### Kubernetes

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-service
  template:
    metadata:
      labels:
        app: my-service
    spec:
      containers:
      - name: my-service
        image: my-service:latest
        ports:
        - containerPort: 8080
        env:
        - name: ACTON_SERVICE_PORT
          value: "8080"
        - name: ACTON_DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: url
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
```

Generate complete Kubernetes manifests with the CLI:

```bash
acton service generate deployment --hpa --monitoring --ingress
```

## Migration Guide

### From Axum

acton-service is a thin layer over axum. Your existing handlers work unchanged:

```rust
// Your existing axum handler
async fn handler(State(state): State<MyState>, Json(body): Json<Request>)
    -> Result<Json<Response>, StatusCode> {
    // ...
}

// Works directly in acton-service
let routes = VersionedApiBuilder::new()
    .add_version(ApiVersion::V1, |router| {
        router.route("/endpoint", post(handler))
    })
    .build_routes();
```

Main changes:

1. Routes must be versioned (wrap in `VersionedApiBuilder`)
2. Use `ServiceBuilder` instead of `axum::serve()`
3. Configuration loaded automatically (optional)

### From Actix-Web

Similar handler patterns, different framework:

```rust
// Actix-web
#[post("/users")]
async fn create_user(user: web::Json<User>) -> impl Responder {
    HttpResponse::Created().json(user)
}

// acton-service
async fn create_user(Json(user): Json<User>) -> impl IntoResponse {
    (StatusCode::CREATED, Json(user))
}
```

See the [examples directory](./acton-service/examples/) for complete migration examples.

## Roadmap

**Implemented** ✅
- Type-enforced API versioning with deprecation support
- Automatic health/readiness checks with dependency monitoring
- Structured JSON logging with distributed request tracing
- XDG-compliant configuration
- HTTP + gRPC on separate ports
- Core middleware (JWT, rate limiting, compression, CORS, timeouts)
- CLI scaffolding tool with service generation
- Database (PostgreSQL), Cache (Redis), Events (NATS) support

**In Progress** 🚧
- Single-port HTTP + gRPC multiplexing
- OpenTelemetry integration (OTLP exporter)
- Circuit breaker, retry, and bulkhead middleware
- HTTP metrics collection
- Enhanced CLI commands (add endpoint, worker, etc.)

**Planned** 📋
- GraphQL support
- WebSocket support
- Service mesh integration
- Observability dashboards
- Additional database backends

## FAQ

**Q: Why enforce versioning so strictly?**

A: API versioning is critical in production but easy to skip. Making it impossible to bypass ensures consistent team practices. The type system is the enforcement mechanism.

**Q: Can I use this without versioning?**

A: No. If you need unversioned routes, use axum directly. acton-service is opinionated about API evolution.

**Q: Does this work with existing axum middleware?**

A: Yes. Tower middleware works unchanged. Use `.layer()` with any tower middleware.

**Q: What about REST vs gRPC?**

A: Both are first-class. Run them simultaneously (currently on separate ports; single-port multiplexing coming soon), or choose one.

**Q: How does this compare to other frameworks?**

A: acton-service is opinionated where others are flexible. We trade flexibility for safety and consistency. If you need maximum control, use axum or tonic directly.

**Q: Is this production-ready?**

A: Partially. Core features (versioning, health checks, HTTP/gRPC, database support) are production-ready and battle-tested via underlying libraries (axum, tonic, sqlx). Some advanced features (OpenTelemetry integration, resilience patterns) are in progress. Review the roadmap and test thoroughly for your use case.

## Contributing

Contributions are welcome! Areas of focus:

- Additional middleware patterns
- More comprehensive examples
- Documentation improvements
- Performance optimizations
- CLI enhancements

See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for guidelines (coming soon).

## Changelog

See [`CHANGELOG.md`](./CHANGELOG.md) for version history (coming soon).

## License

Licensed under the MIT License. See [`LICENSE`](./LICENSE) for details.

## Credits

Built with excellent open source libraries:

- [tokio]https://tokio.rs - Async runtime
- [axum]https://github.com/tokio-rs/axum - Web framework
- [tonic]https://github.com/hyperium/tonic - gRPC implementation
- [tower]https://github.com/tower-rs/tower - Middleware foundation
- [SQLx]https://github.com/launchbadge/sqlx - Database client

Inspired by production challenges at scale. Built by developers who've maintained microservice architectures in production.

---

**Start building production microservices with enforced best practices:**

```bash
cargo install acton-cli
acton service new my-api --yes
cd my-api && cargo run
```