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
tqpara este capítulo está en7-many-hands/04-the-proper-store/.