← Índice

7.4 El almacén correcto

Esta traducción fue asistida por IA y revisada superficialmente. Si encuentras errores o algo no suena natural, escríbeme — lo agradezco.

Una herramienta construida para trabajar y que no se usa para su propósito no desaparece - simplemente espera. El almacén fue diseñado con intención: aceptar una tarea, encontrar una por id, mantener el contador en orden. El servidor lo recibió de primera mano - y sigue haciéndolo todo él mismo: bucle tras bucle, contador a mano. El almacén observa en silencio. Los almacenes no tienen opiniones. Esa es, quizás, su principal defecto.

Lo que necesitas

Ahora mismo los handlers trabajan con el vector directamente: add calcula el id a mano, done itera el vector por sí mismo. TaskStore de tq_core sabe hacer las dos cosas - y lo hace tras su propia puerta: .add() mantiene el contador, .get_mut() busca por id y devuelve un error si no encuentra nada. Cómo busca exactamente es asunto del almacén, no del handler.

Un paso: reemplazar Vec<Task> por TaskStore en SharedStore. Los handlers ganan métodos ya hechos - la lógica manual se mueve a donde le corresponde.

La construcción

En crates/api/src/routes.rs cambian las importaciones. Store es el trait donde se declaran .add(), .get(), .get_mut(), .all(). Sin él el compilador no puede ver los métodos en TaskStore:

use tq_core::store::{Store, TaskStore};
use std::sync::{Arc, Mutex};           // MutexGuard ya no es necesario

SharedStore recibe un nuevo tipo:

pub type SharedStore = Arc<Mutex<TaskStore>>;

El crates/api/src/routes.rs completo:

// crates/api/src/routes.rs - CAMBIADO
use axum::{
    Json, Router,
    extract::{Path, State},
    routing::{get, patch, post},
};
use serde::Deserialize;
use std::sync::{Arc, Mutex};
use tq_core::store::{Store, TaskStore};
use tq_core::task::Task;

pub type SharedStore = Arc<Mutex<TaskStore>>;

#[derive(Deserialize)]
struct AddRequest {
    title: String,
}

async fn add(State(store): State<SharedStore>, Json(req): Json<AddRequest>) -> Json<Task> {
    let mut guard = store.lock().unwrap();
    let id = guard.add(req.title.as_str()).unwrap(); // TaskStore asigna el id
    Json(guard.get(id).unwrap().clone())
}

async fn list(State(store): State<SharedStore>) -> Json<Vec<Task>> {
    let guard = store.lock().unwrap();
    Json(guard.all().to_vec()) // Json necesita Vec<Task> con posesión; to_vec() copia fuera del guardián
}

async fn get_task(State(store): State<SharedStore>, Path(id): Path<u64>) -> Json<Task> {
    let guard = store.lock().unwrap();
    Json(guard.get(id).unwrap().clone())
}

async fn done(State(store): State<SharedStore>, Path(id): Path<u64>) -> Json<Task> {
    let mut guard = store.lock().unwrap();
    let task = guard.get_mut(id).unwrap();
    task.complete();
    Json(task.clone())
}

pub fn router(store: SharedStore) -> Router {
    Router::new()
        .route("/tasks", post(add).get(list))
        .route("/tasks/{id}", get(get_task))
        .route("/tasks/{id}/done", patch(done))
        .with_state(store)
}

En crates/api/src/main.rs - TaskStore en lugar del vector vacío:

// crates/api/src/main.rs - CAMBIADO
mod routes;

use axum::{Router, routing::get};
use routes::SharedStore;
use std::sync::{Arc, Mutex};
use tq_core::store::TaskStore;

async fn health() -> String {
    "tq ok".to_string()
}

#[tokio::main]
async fn main() {
    let store: SharedStore = Arc::new(Mutex::new(TaskStore::new(vec![]))); // CAMBIADO
    let app = Router::new()
        .route("/", get(health))
        .merge(routes::router(store));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

El resultado

Las peticiones funcionan como antes:

$ curl -s -X POST localhost:3000/tasks -H "Content-Type: application/json" -d '{"title":"buy milk"}'
{"id":1,"title":"buy milk","status":"Todo","created_at":"2026-06-20T10:00:00Z"}

$ curl -s localhost:3000/tasks
[{"id":1,"title":"buy milk","status":"Todo","created_at":"2026-06-20T10:00:00Z"}]

$ curl -s -X PATCH localhost:3000/tasks/1/done
{"id":1,"title":"buy milk","status":"Done","created_at":"2026-06-20T10:00:00Z"}

Los handlers ya no se ocupan de los detalles internos del almacén - preguntan, y el almacén responde. La herramienta se usa por fin para su propósito.

El código completo de tq para este capítulo está en 7-many-hands/04-the-proper-store/.