How Data Flows from Client to Database in the Macro Platform
Data flows from client to database in Macro through an Axum HTTP router that authenticates requests via middleware, delegates to domain services, and executes compile-time checked SQLx queries against a PostgreSQL pool before returning serialized JSON responses.
The Macro platform (macro-inc/macro) implements a clean, service-oriented architecture that separates HTTP concerns from business logic. Understanding the data flow from client to database reveals a consistent pattern across services: Axum handles routing, middleware manages authentication, and SQLx provides type-safe database access through a PgPool. This architecture ensures that every request follows a predictable path from the initial HTTP POST to the final PostgreSQL INSERT or SELECT.
HTTP Routing with Axum
Client requests enter the system through Axum routers defined in each service. The router function in services/notification_service/src/api/mod.rs demonstrates how endpoints are registered and nested:
pub fn router<S: ::notification::domain::service::NotificationReader>() -> Router<ApiContext> {
Router::new()
.route(
"/user_notifications",
post(create_user_notification::<S>),
)
.layer(Extension::new(AuthorizationService::new()))
}
This pattern appears across services, including the unfurl service at services/unfurl_service/src/api/mod.rs, where Router::new().nest(...).merge(...) combines multiple route handlers into a unified API surface.
Authentication and Context Injection
Before reaching business logic, requests pass through the AuthorizationService middleware. This layer validates JWT or session tokens and injects a Context (or State) containing the user ID and a database pool connection. The middleware attaches this context to the request extensions, making it available to downstream handlers without coupling authentication details to domain logic.
Domain Service Layer
Handlers do not interact with the database directly. Instead, they delegate to domain services that encapsulate core business logic. In services/notification_service/src/api/user_notification.rs, the handler calls notification::domain::service::NotificationReader:
#[tracing::instrument(skip(state, payload))]
async fn create_user_notification<S>(
State(state): State<ApiContext>,
Json(payload): Json<CreateNotificationPayload>,
) -> Result<Json<Notification>, ApiError>
where
S: NotificationReader,
{
let notif = state
.notification_service
.create_notification(state.user_id, payload)
.await?;
Ok(Json(notif))
}
This separation ensures that HTTP transport concerns (JSON serialization, status codes) remain distinct from business rules and validation.
Database Access with SQLx
The domain service uses SQLx to execute compile-time checked queries against PostgreSQL. The PgPool connection pool is injected via the State or Context struct, allowing efficient connection reuse across concurrent requests.
The crates/webhook/src/outbound/pg_repository.rs file demonstrates the typical query pattern:
let inserted = sqlx::query!(
r#"
INSERT INTO notifications (user_id, title, body)
VALUES ($1, $2, $3)
RETURNING id
"#,
user_id,
payload.title,
payload.body,
)
.fetch_one(pool)
.await?;
SQLx validates these queries at build time against the database schema defined in crates/macro_db_client/migrations/, ensuring type safety before deployment.
Result Mapping and Response Serialization
Database rows are mapped to domain structs through conversion functions. The row_to_webhook function in crates/webhook/src/outbound/pg_repository.rs illustrates this pattern:
fn row_to_webhook(row: WebhookRow) -> Result<Webhook, sqlx::Error> {
Ok(Webhook {
id: row.id,
url: row.url,
status: WebhookStatus::from_str(&row.status)?,
// … other fields …
})
}
The domain struct travels back up the call stack, where Axum serializes it to JSON and sends the response to the client.
Complete Data Flow Example: Creating a Notification
The following trace illustrates the complete data flow when a client creates a notification:
- Client Request: Sends
POST /user_notificationswith JSON payload - Route Matching: Axum router (
services/notification_service/src/api/mod.rs) matches the path - Authentication:
AuthorizationServicevalidates the token and injects user context - Handler Execution:
create_user_notificationinservices/notification_service/src/api/user_notification.rsreceives the request - Business Logic:
NotificationReaderservice validates the payload and prepares the database call - Database Execution: SQLx runs the prepared INSERT statement against the
MacroDBPostgreSQL instance - Result Mapping: The returned row ID converts to a
Notificationstruct - Response: Axum serializes the struct as JSON and returns it to the client
Key Files in the Data Flow Pattern
services/notification_service/src/api/mod.rs: Defines Axum routers and attaches authentication middleware usingExtension::new(AuthorizationService::new()).services/notification_service/src/api/user_notification.rs: Implements HTTP handlers that bridge Axum's HTTP layer and the domain service layer.crates/webhook/src/outbound/pg_repository.rs: Contains SQLx query implementations showingsqlx::query!usage and therow_to_webhookmapping pattern for converting PostgreSQL rows to domain models.services/unfurl_service/src/api/mod.rs: Alternative example of router composition usingapi_routerwith nested and merged routes.crates/macro_db_client/migrations/: Houses PostgreSQL schema definitions that enable SQLx's compile-time query validation.
Summary
- Macro uses Axum for HTTP routing, with middleware-based authentication injecting user context and database pools into request handlers.
- Domain services separate business logic from HTTP concerns, with handlers delegating to traits like
NotificationReaderrather than accessing databases directly. - SQLx with PostgreSQL provides compile-time checked queries through the
query!macro, usingPgPoolfor efficient connection management. - Row mapping functions convert raw SQLx results into strongly-typed domain structs before JSON serialization.
- Consistent patterns across
notification_serviceandunfurl_servicedemonstrate a reusable architecture for client-to-database data flows.
Frequently Asked Questions
What web framework does Macro use for routing client requests?
Macro uses Axum, a Rust web framework built on Tokio. The routing logic is centralized in each service's src/api/mod.rs file, where Router::new() instances are configured with routes, handlers, and middleware layers like AuthorizationService.
How does Macro handle authentication in the data flow?
Authentication occurs in middleware before reaching business logic. The AuthorizationService validates JWT or session tokens and injects a Context struct containing the user ID and database pool into the request extensions. This context is then accessible to handlers via Axum's State extractor.
What database does Macro use and how is it accessed?
Macro uses PostgreSQL accessed through SQLx. The PgPool connection pool is created at service startup and injected into the application state. Domain services execute queries using SQLx's query! macro, which validates SQL syntax and types against the database schema at compile time.
How does SQLx ensure type safety in Macro's database queries?
SQLx connects to the database at build time using the schema defined in crates/macro_db_client/migrations/ to validate queries in the query! macro. If a query references a non-existent column or has a type mismatch, the Rust compiler returns an error before runtime, ensuring that only valid SQL reaches the production database.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →