Voy a hacer un comentario hoy sobre diseño de APIs y una cosa que no me gusta sobre las recomendaciones del DDD y similares. La gente que diseña estas librerías lo hace para jugar el rato o montar sobre-enginieria, pocas veces lo hace gente para trabajar (go). Y esto se nota mucho en que siguen ideas como MODELAR EL DOMINIO en lugar de diseñar cosas UTILES.
Una manera muy fácil de ver si una librerías la ha escrito alguien que sabe lo que hace es ver si la ha escrito para los usuarios. Y aqui es importante hacer hincapié en un detalle sutil que se convierte en critico con los años. La documentación NO debe ser necesaria. La documentación es una AYUDA para visualizar la API/Libreria, los métodos las interfaces el sistema de tipado. Pero no debo necesitar estar constantemente re-buscando en los ejemplos porque no entiendo que cosas van con que. Y ahi esta el problema de Rust. Es una torre de naipes de sistemas de traits construido para la masturbacion de sus autores en su gran mayoria.
Asi pues, como se detecta si algo esta escrito para los usuarios? Las cosas son obvias donde van. Solo hay 1 manera de hacer las cosas. Los modulos indican intención y porque, no DOMINIO. Es decir, todo lo contrario que te enseñan en la universidad y todo lo que se ha hecho en unix/gpl y demas escuelas de open source toda la vida. Por ejemplo:
En Axum tenemos el concepto de extractors. Son las cosas que nos "extraen" información de las Request como el body,headers,paths params,query params... y ademas sirve para pasar el estado. La idea conceptual es request => extractors => handler => response.
Pues bien, fijémonos en la MIERDA que es AXUM. AXUM y la gran mayoria de software que tocas: https://docs.rs/axum/latest/axum/extract/index.html#common-extractors
use axum::{
extract::{Request, Json, Path, Extension, Query},
routing::post,
http::header::HeaderMap,
body::{Bytes, Body},
Router,
};
use serde_json::Value;
use std::collections::HashMap;
// `Path` gives you the path parameters and deserializes them. See its docs for
// more details
async fn path(Path(user_id): Path<u32>) {}
// `Query` gives you the query parameters and deserializes them.
async fn query(Query(params): Query<HashMap<String, String>>) {}
// `HeaderMap` gives you all the headers
async fn headers(headers: HeaderMap) {}
// `String` consumes the request body and ensures it is valid utf-8
async fn string(body: String) {}
// `Bytes` gives you the raw request body
async fn bytes(body: Bytes) {}
// We've already seen `Json` for parsing the request body as json
async fn json(Json(payload): Json<Value>) {}
// `Request` gives you the whole request for maximum control
async fn request(request: Request) {}
// `Extension` extracts data from "request extensions"
// This is commonly used to share state with handlers
async fn extension(Extension(state): Extension<State>) {}
No os voy a decir el problema, os voy a dar la solución para terminar el post, pensad en ello vosotros mismos, veréis que es muy obvio. De nada.
use axum::{
extract::{Request, Json, String, Path, Extension, Query, header::HeaderMap, body::Bytes, body::Body},
routing::post,
Router,
};
use serde_json::Value;
use std::collections::HashMap;
// `Path` gives you the path parameters and deserializes them. See its docs for
// more details
async fn path(Path(user_id): Path<u32>) {}
// `Query` gives you the query parameters and deserializes them.
async fn query(Query(params): Query<HashMap<String, String>>) {}
// `HeaderMap` gives you all the headers
async fn headers(headers: HeaderMap) {}
// `String` consumes the request body and ensures it is valid utf-8
async fn string(body: String) {}
// `Bytes` gives you the raw request body
async fn bytes(body: Bytes) {}
// We've already seen `Json` for parsing the request body as json
async fn json(Json(payload): Json<Value>) {}
// `Request` gives you the whole request for maximum control
async fn request(request: Request) {}
// `Extension` extracts data from "request extensions"
// This is commonly used to share state with handlers
async fn extension(Extension(state): Extension<State>) {}
Venga, para fperos:
use axum::{
routing::post,
Router,
};
use serde_json::Value;
use std::collections::HashMap;
// `Path` gives you the path parameters and deserializes them. See its docs for
// more details
async fn path(extract::Path(user_id): extract::Path<u32>) {}
// `Query` gives you the query parameters and deserializes them.
async fn query(extract::Query(params): extract::Query<HashMap<String, String>>) {}
// `HeaderMap` gives you all the headers
async fn headers(headers: extract::HeaderMap) {}
// `String` consumes the request body and ensures it is valid utf-8
async fn string(body: extract::String) {}
// `Bytes` gives you the raw request body
async fn bytes(body: extract::Bytes) {}
// We've already seen `Json` for parsing the request body as json
async fn json(extract::Json(payload): Json<extract::Value>) {}
// `Request` gives you the whole request for maximum control
async fn request(request: extract::Request) {}
// `Extension` extracts data from "request extensions"
// This is commonly used to share state with handlers
async fn extension(extract::Extension(state): extract::Extension<State>) {}
Imaginate que quiero sacar un header concreto, ahora mismo NO puedo, he tenido que pasar todos los headers, entre comillas ya me entendéis. Pero podria tener un custom T que implementase extract::ExtractFromRequest y fuera. Asi de fácil.
Marie Kondo: Rule 4: Tidy by Category, Not by Location