Backend Framework
Building a modern backend framework with Axum and SeaORM
StateSet API
StateSet API is a comprehensive, scalable, and robust backend system designed for enterprise-grade web services. It excels in order management, inventory control, returns processing, warranty management, shipment tracking, and work order handling. Built with Rust, StateSet API leverages cutting-edge web technologies and best practices to deliver a high-performance, reliable infrastructure solution tailored for e-commerce and manufacturing businesses.
Features
-
Order Management:
- Create, retrieve, update, and delete orders
- Support for complex order workflows and statuses
-
Inventory Control:
- Real-time inventory tracking across multiple locations
- Automated reorder point notifications
-
Returns Processing:
- Streamlined return authorization and processing
- Integration with refund and exchange systems
-
Warranty Management:
- Track and manage product warranties
- Automated claim processing and resolution
-
Shipment Tracking:
- Real-time tracking integration with major carriers
- Custom shipment status notifications
-
Manufacturing & Production:
- Supplier management and communication
- Bill of Materials (BOM) tracking and version control
-
Work Order Handling:
- Create and manage work orders for repairs or modifications
- Track work order progress and resource allocation
Tech Stack
Our carefully selected tech stack ensures high performance, scalability, and maintainability.
Core Technologies
- Language: Rust (for performance and safety)
- Web Framework: Axum (lightweight and fast asynchronous web framework)
- Database: PostgreSQL with SQLx (for robust, async operations)
ORM and Query Building
- SeaORM (async ORM for Rust, providing powerful database operations)
API Protocols and Services
- REST: Handled natively by Axum
- GraphQL: Async-GraphQL (high-performance GraphQL server library for Rust)
- gRPC: Tonic (for efficient, type-safe gRPC support)
Caching and Messaging
- Caching: Redis (for high-speed data caching)
- Message Queue: RabbitMQ (for reliable async processing)
Observability
- Metrics: Prometheus (for detailed system monitoring)
- Tracing: OpenTelemetry with Jaeger (for distributed tracing)
- Logging: slog (for structured, efficient logging)
Technological Advantages:
- Rust’s Performance & Safety: Ensures memory safety without sacrificing performance, making the API highly reliable and efficient.
- Asynchronous Operations: Leveraging Rust’s async capabilities for non-blocking, high-throughput processing. Comprehensive Observability: Detailed monitoring and tracing facilitate proactive maintenance and rapid issue resolution. Flexible API Protocols: Support for multiple API protocols allows seamless integration with diverse client applications and services.
Use Cases:
- E-commerce Platforms: Managing orders, inventory, shipments, and customer interactions with high efficiency and reliability.
- Manufacturing Systems: Handling production workflows, inventory control, supplier management, and quality assurance processes.
- Enterprise Solutions: Providing a scalable and maintainable backend infrastructure for various business operations and services.
Architecture
StateSet API follows a modular, asynchronous, event-driven architecture designed for scalability and maintainability.
Key Components
- Services: Implement core business logic
- Handlers: Process HTTP requests
- Commands: Handle write operations
- Queries: Manage read operations
- Events: Enable asynchronous processing
- Models: Represent domain entities
- Middleware: Provide cross-cutting concerns (auth, rate limiting, etc.)
Performance
StateSet API is designed for high performance and scalability:
- Handles 10,000+ requests per second on a single node
- Scales horizontally for increased load
- 99.99% uptime SLA
Acknowledgments
We’re grateful to the open-source community and especially:
- Axum for the web framework
- SeaORM for ORM functionality
- Tonic for gRPC support
- Async-Graphql for GraphQL
Axum Web Service
Axum is a web framework for Rust that is designed to be simple, efficient, and powerful. It is built on top of the Tokio runtime and is designed to be asynchronous and non-blocking.
// StateSet API Web Services
#[tokio::main]
async fn main() -> Result<(), AppError> {
dotenv().ok();
let config = Arc::new(config::load()?);
let log = setup_logger(&config);
info!(log, "Starting StateSet API";
"environment" => &config.environment,
"version" => env!("CARGO_PKG_VERSION")
);
let app_state = build_app_state(&config, &log).await?;
let schema = Arc::new(graphql::create_schema(
app_state.services.order_service.clone(),
app_state.services.inventory_service.clone(),
app_state.services.return_service.clone(),
app_state.services.warranty_service.clone(),
app_state.services.shipment_service.clone(),
app_state.services.work_order_service.clone(),
app_state.services.billofmaterials_service.clone(),
app_state.services.manufacturing_service.clone(),
app_state.services.suppliers_service.clone(),
app_state.services.customers_service.clone()
));
setup_telemetry(&config)?;
// Spawn event processing
tokio::spawn(events::process_events(
app_state.event_sender.subscribe(),
app_state.services.clone(),
log.clone(),
));
// Start gRPC server
#[cfg(feature = "grpc")]
let grpc_server = grpc_server::start(config.clone(), app_state.services.clone()).await?;
// Build our application with a route
let app = Router::new()
.route("/health", get(health::health_check))
.nest("/orders", handlers::orders::routes())
.nest("/inventory", handlers::inventory::routes())
.nest("/return", handlers::returns::routes())
.nest("/warranties", handlers::warranties::routes())
.nest("/shipments", handlers::shipments::routes())
.nest("/work_orders", handlers::work_orders::routes())
.nest("/billofmaterials", handlers::billofmaterials::routes())
.nest("/manufacturing", handlers::manufacturing::routes())
.nest("/suppliers", handlers::suppliers::routes())
.nest("/customers", handlers::customers::routes())
.route("/proto_endpoint", post(handle_proto_request))
.layer(Extension(app_state))
.layer(Extension(schema))
.layer(TraceLayer::new_for_http())
.layer(CompressionLayer::new())
.layer(axum::middleware::from_fn(auth::auth_middleware))
.layer(axum::middleware::from_fn(rate_limiter::rate_limit_middleware));
// Run our app with hyper
let addr = format!("{}:{}", config.host, config.port);
info!(log, "StateSet API server running"; "address" => &addr);
axum::Server::bind(&addr.parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
info!(log, "Shutting down");
Ok(())
}
Handlers
This routes our API requests to the correct modules and handlers.
Create Order Handler
async fn create_order(
State(order_service): State<Arc<OrderService>>,
Json(command): Json<CreateOrderCommand>,
) -> Result<impl IntoResponse, ServiceError> {
let result = command.execute(order_service).await?;
Ok(Json(result))
}
Close Return Handler
async fn close_return(
State(return_service): State<Arc<ReturnService>>,
Path(return_id): Path<i32>,
) -> Result<impl IntoResponse, ServiceError> {
let command = CloseReturnCommand { return_id };
let closed_return = command.execute(return_service).await?;
Ok(Json(closed_return))
}
Create Order Command
use std::sync::Arc;
use sea_orm::*;
use crate::{
db::DbPool,
errors::ServiceError,
events::{Event, EventSender},
models::{
order_entity::{self, Entity as Order},
order_item_entity::{self, Entity as OrderItem},
OrderStatus,
},
};
use serde::{Deserialize, Serialize};
use tracing::{error, info, instrument};
use uuid::Uuid;
use validator::Validate;
use prometheus::IntCounter;
use lazy_static::lazy_static;
use chrono::{DateTime, Utc};
lazy_static! {
static ref ORDER_CREATIONS: IntCounter =
IntCounter::new("order_creations_total", "Total number of orders created")
.expect("metric can be created");
static ref ORDER_CREATION_FAILURES: IntCounter =
IntCounter::new("order_creation_failures_total", "Total number of failed order creations")
.expect("metric can be created");
}
#[derive(Debug, Serialize, Deserialize, Validate)]
pub struct CreateOrderCommand {
pub customer_id: Uuid,
#[validate(length(min = 1, message = "At least one item is required"))]
pub items: Vec<OrderItem>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OrderItem {
pub product_id: Uuid,
#[validate(range(min = 1))]
pub quantity: i32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateOrderResult {
pub id: Uuid,
pub customer_id: Uuid,
pub status: String,
pub created_at: DateTime<Utc>,
pub items: Vec<OrderItem>,
}
#[async_trait::async_trait]
impl Command for CreateOrderCommand {
type Result = CreateOrderResult;
#[instrument(skip(self, db_pool, event_sender))]
async fn execute(
&self,
db_pool: Arc<DbPool>,
event_sender: Arc<EventSender>,
) -> Result<Self::Result, ServiceError> {
self.validate().map_err(|e| {
ORDER_CREATION_FAILURES.inc();
let msg = format!("Invalid input: {}", e);
error!("{}", msg);
ServiceError::ValidationError(msg)
})?;
let db = db_pool.as_ref();
let saved_order = self.create_order(db).await?;
self.log_and_trigger_event(&event_sender, &saved_order).await?;
ORDER_CREATIONS.inc();
Ok(CreateOrderResult {
id: saved_order.id,
customer_id: saved_order.customer_id,
status: saved_order.status,
created_at: saved_order.created_at.and_utc(),
items: self.items.clone(),
})
}
}
impl CreateOrderCommand {
async fn create_order(
&self,
db: &DatabaseConnection,
) -> Result<order_entity::Model, ServiceError> {
db.transaction::<_, order_entity::Model, ServiceError>(|txn| {
Box::pin(async move {
let new_order = order_entity::ActiveModel {
customer_id: Set(self.customer_id),
status: Set(OrderStatus::Pending.to_string()),
created_at: Set(Utc::now().naive_utc()),
..Default::default()
};
let saved_order = new_order.insert(txn).await.map_err(|e| {
let msg = format!("Failed to save order: {}", e);
error!("{}", msg);
ServiceError::DatabaseError(msg)
})?;
for item in &self.items {
let new_item = order_item_entity::ActiveModel {
order_id: Set(saved_order.id),
product_id: Set(item.product_id),
quantity: Set(item.quantity),
..Default::default()
};
new_item.insert(txn).await.map_err(|e| {
let msg = format!("Failed to save order item: {}", e);
error!("{}", msg);
ServiceError::DatabaseError(msg)
})?;
}
Ok(saved_order)
})
}).await
}
async fn log_and_trigger_event(
&self,
event_sender: &EventSender,
saved_order: &order_entity::Model,
) -> Result<(), ServiceError> {
info!(
order_id = %saved_order.id,
customer_id = %self.customer_id,
items_count = %self.items.len(),
"Order created successfully"
);
event_sender
.send(Event::OrderCreated(saved_order.id))
.await
.map_err(|e| {
ORDER_CREATION_FAILURES.inc();
let msg = format!("Failed to send event for created order: {}", e);
error!("{}", msg);
ServiceError::EventError(msg)
})
}
}
Return Commands
The handlers route to the commands and queries that are used to handle the requests.
Close Return Command
use std::sync::Arc;
use sea_orm::*;
use crate::{
db::DbPool,
errors::ServiceError,
events::{Event, EventSender},
models::{
return_entity::{self, Entity as Return},
return_entity::ReturnStatus,
},
};
use serde::{Deserialize, Serialize};
use tracing::{error, info, instrument};
use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize)]
pub struct CloseReturnCommand {
pub return_id: Uuid,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CloseReturnResult {
pub id: Uuid,
pub object: String,
pub completed: bool,
}
#[async_trait::async_trait]
impl Command for CloseReturnCommand {
type Result = CloseReturnResult;
#[instrument(skip(self, db_pool, event_sender))]
async fn execute(
&self,
db_pool: Arc<DbPool>,
event_sender: Arc<EventSender>,
) -> Result<Self::Result, ServiceError> {
let db = db_pool.as_ref();
let completed_return = self.close_return(db).await?;
self.log_and_trigger_event(&event_sender, &completed_return)
.await?;
Ok(CloseReturnResult {
id: completed_return.id,
object: "return".to_string(),
completed: true,
})
}
}
impl CloseReturnCommand {
async fn close_return(
&self,
db: &DatabaseConnection,
) -> Result<return_entity::Model, ServiceError> {
let return_request = Return::find_by_id(self.return_id)
.one(db)
.await
.map_err(|e| {
let msg = format!("Failed to find return request: {}", e);
error!("{}", msg);
ServiceError::DatabaseError(msg)
})?
.ok_or_else(|| {
let msg = format!("Return request with ID {} not found", self.return_id);
error!("{}", msg);
ServiceError::NotFound(msg)
})?;
let mut return_request: return_entity::ActiveModel = return_request.into();
return_request.status = Set(ReturnStatus::Closed.to_string());
let updated_return = return_request.update(db).await.map_err(|e| {
let msg = format!("Failed to close return request: {}", e);
error!("{}", msg);
ServiceError::DatabaseError(msg)
})?;
Ok(updated_return)
}
async fn log_and_trigger_event(
&self,
event_sender: &EventSender,
completed_return: &return_entity::Model,
) -> Result<(), ServiceError> {
info!("Return request closed for return ID: {}", self.return_id);
event_sender
.send(Event::ReturnClosed(self.return_id))
.await
.map_err(|e| {
let msg = format!("Failed to send event for closed return: {}", e);
error!("{}", msg);
ServiceError::EventError(msg)
})
}
}
Queries
Get Returns By Order Query
#[derive(Debug, Serialize, Deserialize)]
pub struct GetReturnsByOrderQuery {
pub order_id: i32,
}
#[async_trait]
impl Query for GetReturnsByOrderQuery {
type Result = Vec<Return::Model>;
async fn execute(&self, db_pool: Arc<DbPool>) -> Result<Self::Result, ServiceError> {
let db = db_pool.get().map_err(|_| ServiceError::DatabaseError)?;
Return::find()
.filter(Return::Column::OrderId.eq(self.order_id))
.all(&db)
.await
.map_err(|_| ServiceError::DatabaseError)
}
}
Get Returns By Date Range Query
#[derive(Debug, Serialize, Deserialize)]
pub struct GetReturnsByDateRangeQuery {
pub start_date: DateTime<Utc>,
pub end_date: DateTime<Utc>,
pub limit: u64,
pub offset: u64,
}
#[async_trait]
impl Query for GetReturnsByDateRangeQuery {
type Result = Vec<Return::Model>;
async fn execute(&self, db_pool: Arc<DbPool>) -> Result<Self::Result, ServiceError> {
let db = db_pool.get().map_err(|_| ServiceError::DatabaseError)?;
Return::find()
.filter(Return::Column::CreatedAt.between(self.start_date, self.end_date))
.order_by_desc(Return::Column::CreatedAt)
.limit(self.limit)
.offset(self.offset)
.all(&db)
.await
.map_err(|_| ServiceError::DatabaseError)
}
}
SeaORM
To learn more about SeaORM, check out this article.