exercises

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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 }