main.rs (6480B)
1 //! Orders Service 2 //! 3 //! A microservice for managing customer orders, payments, and shipping. 4 //! 5 //! # Features 6 //! - Order creation with guest checkout 7 //! - Order management and status tracking 8 //! - Payment processing (simulated) 9 //! - Shipping address management 10 //! - Shipment tracking 11 //! - PostgreSQL database with orders schema 12 //! - CORS enabled for web client access 13 //! 14 //! # API Endpoints 15 //! ## Orders 16 //! - `POST /orders` - Create a new order (payment must succeed or order fails) 17 //! - `GET /orders` - List orders with filters and pagination 18 //! - `GET /orders/{uuid}` - Get complete order details (includes payment & shipment) 19 //! 20 //! ## Shipments (Internal - used by scheduler) 21 //! - `POST /orders/{uuid}/shipment` - Create shipment (sets order status to 'shipped') 22 //! - `PUT /orders/{uuid}/shipment/status` - Update shipment status (sets order status to 'delivered' when complete) 23 //! 24 //! # Configuration 25 //! The service is configured via: 26 //! - Environment variables (loaded from .env if present) 27 //! - config.toml file 28 //! 29 //! # Database 30 //! Uses the `orders` schema in PostgreSQL with automatic search path configuration. 31 //! Migrations are run automatically on startup. 32 33 mod config; 34 mod db; 35 mod handlers; 36 mod logging; 37 mod metrics; 38 mod models; 39 mod telemetry; 40 41 use anyhow::Result; 42 use axum::{ 43 routing::{get, post, put}, 44 Router, 45 }; 46 use axum_otel_metrics::HttpMetricsLayerBuilder; 47 use axum_tracing_opentelemetry::middleware::{OtelAxumLayer, OtelInResponseLayer}; 48 use std::net::SocketAddr; 49 use tower_http::cors::CorsLayer; 50 use tracing::info; 51 52 use config::Config; 53 use db::Database; 54 55 /// Application state shared across handlers 56 #[derive(Clone)] 57 pub struct AppState { 58 db_pool: sqlx::PgPool, 59 http_client: reqwest_middleware::ClientWithMiddleware, 60 products_service_url: String, 61 inventory_service_url: String, 62 } 63 64 impl AppState { 65 /// Get the database pool 66 pub fn pool(&self) -> &sqlx::PgPool { 67 &self.db_pool 68 } 69 70 /// Get the HTTP client 71 pub fn http_client(&self) -> &reqwest_middleware::ClientWithMiddleware { 72 &self.http_client 73 } 74 75 /// Get the products service URL 76 pub fn products_service_url(&self) -> &str { 77 &self.products_service_url 78 } 79 80 /// Get the inventory service URL 81 pub fn inventory_service_url(&self) -> &str { 82 &self.inventory_service_url 83 } 84 } 85 86 #[tokio::main] 87 async fn main() -> Result<()> { 88 // Load environment variables from .env file if present 89 dotenvy::dotenv().ok(); 90 91 // Initialize telemetry (tracing + OpenTelemetry) 92 let _telemetry_guard = telemetry::init_telemetry("orders"); 93 94 // Load configuration from config.toml 95 let config = Config::load()?; 96 97 // Log startup information using tracing macros 98 info!( 99 port = config.server.port, 100 database_url = %config.database.url, 101 products_service = format!("{}:{}", config.services.products_host, config.services.products_port), 102 inventory_service = format!("{}:{}", config.services.inventory_host, config.services.inventory_port), 103 "Starting Orders Service" 104 ); 105 106 // Initialize database connection pool 107 let db = Database::new(&config.database.url, config.database.max_connections).await?; 108 109 // Register observable gauges for connection pool health metrics 110 let meter = opentelemetry::global::meter("inventory-service"); 111 telemetry::register_pool_metrics(&meter, db.pool().clone()); 112 113 // Build automatic HTTP RED metrics layer 114 let metrics = HttpMetricsLayerBuilder::new().build(); 115 116 // Create HTTP client with tracing middleware for automatic 117 // span creation and trace context propagation 118 let reqwest_client = reqwest::Client::builder() 119 .timeout(std::time::Duration::from_secs( 120 config.http_client.timeout_secs, 121 )) 122 .build()?; 123 let http_client = reqwest_middleware::ClientBuilder::new(reqwest_client) 124 .with(reqwest_tracing::TracingMiddleware::< 125 reqwest_tracing::SpanBackendWithUrl, 126 >::new()) 127 .build(); 128 129 // Create application state 130 let state = AppState { 131 db_pool: db.pool().clone(), 132 http_client, 133 products_service_url: config.services.products_service_url(), 134 inventory_service_url: config.services.inventory_service_url(), 135 }; 136 137 // Build the application router with all endpoints 138 let app = Router::new() 139 // Order endpoints 140 .route("/orders", post(handlers::create_order)) 141 .route("/orders", get(handlers::list_orders)) 142 .route("/orders/{uuid}", get(handlers::get_order_by_id)) 143 // Shipment endpoints (internal - used by scheduler) 144 .route( 145 "/orders/{order_uuid}/shipment", 146 post(handlers::create_shipment), 147 ) 148 .route( 149 "/orders/{order_uuid}/shipment/status", 150 put(handlers::update_shipment_status), 151 ) 152 // Automatic RED metrics (request rate, error rate, duration) 153 .layer(metrics) 154 // Include trace context as header into the response 155 .layer(OtelInResponseLayer::default()) 156 // Start OpenTelemetry trace on incoming request 157 .layer(OtelAxumLayer::default()) 158 // Enable CORS for all routes 159 .layer(CorsLayer::permissive()) 160 // Add application state 161 .with_state(state); 162 163 // Create socket address from configuration 164 let addr = SocketAddr::from(([0, 0, 0, 0], config.server.port)); 165 166 // Log API documentation 167 info!(address = %addr, "Orders Service listening"); 168 info!("Available endpoints:"); 169 info!(" Orders:"); 170 info!(" POST /orders - Create new order (payment must succeed)"); 171 info!(" GET /orders - List orders (with pagination & filters)"); 172 info!(" GET /orders/{{uuid}} - Get complete order (includes payment & shipment)"); 173 info!(" Shipments (Internal - used by scheduler):"); 174 info!( 175 " POST /orders/{{uuid}}/shipment - Create shipment (sets status='shipped')" 176 ); 177 info!(" PUT /orders/{{uuid}}/shipment/status - Update shipment (sets status='delivered' when complete)"); 178 // 179 // Start the HTTP server 180 let listener = tokio::net::TcpListener::bind(addr).await?; 181 axum::serve(listener, app).await?; 182 183 // Telemetry is flushed and shut down by the `Drop` impl on `_telemetry` 184 // when this function returns. 185 186 Ok(()) 187 }