namespaces, require, tests, project structure y domain modeling

Esta semana vamos a pasar de “escribir funciones” a organizar un proyecto real. La meta es entender cómo se reparte el código en namespaces, cómo se conectan con require, cómo se ordenan src y test, y cómo se modela un dominio con funciones puras.
La idea no es memorizar una receta. Es entender por qué esta estructura hace el proyecto más fácil de leer, probar y mantener.
1) Namespaces: la manera de ordenar el código
Un namespace (ns) es una forma de agrupar funciones relacionadas. En Clojure, normalmente cada archivo tiene un namespace, y ese namespace debe coincidir con la ruta del archivo.
;; src/clojure_saveme/week3/domain.clj
(ns clojure-saveme.week3.domain)
Piensa en esto así:
el archivo dice dónde vive el código
el namespace dice cómo lo llamas
juntos te ayudan a no mezclar responsabilidades
Regla práctica:
src/clojure_saveme/week3/domain.cljclojure-saveme.week3.domain
La carpeta usa _ y el namespace usa - porque así se traduce la ruta al nombre lógico del módulo.
2) require: conectar módulos entre sí
Cuando un namespace necesita usar funciones de otro, lo trae con :require.
;; src/clojure_saveme/week3/core.clj
(ns clojure-saveme.week3.core
(:require [clojure-saveme.week3.domain :as domain]))
(defn checkout
[raw-order min-total discount-percentage]
(let [order (domain/build-order raw-order)]
(if (domain/eligible-for-discount? order min-total)
(domain/mark-paid (domain/apply-discount order discount-percentage))
(domain/mark-paid order))))
Qué está pasando aquí:
coreno reescribe la lógica del negociocorellama adomain:as domainevita repetir el nombre completo cada vez
Eso mismo se ve en los tests: un namespace de pruebas también hace require del código que quiere verificar.
3) Estructura del proyecto: separar por responsabilidad
Cuando el proyecto crece, ya no conviene poner todo en un solo archivo. La idea es separar por intención:
src/clojure_saveme/
week3/
domain.clj
core.clj
test/clojure_saveme/
week3/
domain_test.clj
core_test.clj
Interpretación simple:
domain.clj= reglas y modeladocore.clj= orquestación del caso de uso*_test.clj= pruebas del namespace correspondiente
Así encuentras rápido qué hace cada pieza y dónde cambiarla.
4) Tests: probar comportamiento, no duplicar lógica
Un test importa el namespace que quiere probar igual que cualquier otro archivo.
(nsclojure-saveme.week3.domain-test
(:require [clojure.test :refer :all]
[clojure-saveme.week3.domain :as domain]))
(deftest order-total-test
(is (=300 (domain/order-total {:items [{:price 100 {:price 200}]})))
(is (=0 (domain/order-total {:items []}))))
La clave es esta: el test no “recalcula” el resultado por otro camino. Solo verifica que la función haga lo esperado.
Cuando pruebas core, pruebas el flujo completo:
(deftest checkout-test (testing "applies discount when order qualifies"
(let [result (core/checkout {:id 2 :customer "Luis" :items [{:price 500}]} 300 10)]
(is (= :paid (:status result)))
(is (= [{:price 450}] (:items result))))))
testing sirve para dar contexto cuando un mismo test cubre varios escenarios.
5) Funciones puras: la base para probar fácil
Una función pura:
devuelve siempre lo mismo para la misma entrada
no depende de cosas externas como archivos, base de datos o impresión en consola
(defn order-total
[order]
(reduce +0 (map :price (:items order))))
Esto es ideal para tests porque solo necesitas entrada y salida.
Contraejemplo:
(defn order-total-impuro!
[order]
(println "calculando total...")
(reduce +0 (map :price (:items order))))
Ese println mete un efecto secundario y complica la prueba.
6) Domain modeling: tres tipos de funciones
Para modelar bien un dominio, ayuda separar el código en tres grupos.
1. Constructores / normalización
Toman datos crudos y devuelven una forma consistente.
(defn build-order
[{:keys [id customer items]}]
{:id id
:customer customer
:items items
:status:pending})
2. Reglas de negocio
Preguntan algo sobre el dominio.
(defn eligible-for-discount?
[ordermin-total]
(>= (order-total order) min-total))
3. Transformaciones
Devuelven una nueva versión del dato sin mutarlo.
(defn apply-discount
[order percentage]
(update order :items
(fn [items]
(map #(update % :price * (-1 (/ percentage 100))) items))))
(defn mark-paid
[order]
(assoc order :status :paid))
Separar así hace que el código sea más claro: si cambia una regla, no tienes que tocar todo el flujo.
7) core.clj: orquestar, no inventar reglas
core.clj funciona como el coordinador del caso de uso. Su trabajo es combinar piezas, no concentrar toda la lógica.
(defn checkout
[raw-order min-total discount-percentage]
(let [order (domain/build-order raw-order)]
(if (domain/eligible-for-discount? order min-total)
(domain/mark-paid (domain/apply-discount order discount-percentage))
(domain/mark-paid order))))
Piensa en esto como un flujo:
recibo datos crudos
los normalizo
consulto una regla
aplico transformaciones
devuelvo el resultado final
8) Qué código vive esta semana
src/clojure_saveme/week3/domain.clj — dominio y funciones puras
src/clojure_saveme/week3/core.clj — orquestación y casos de uso
test/clojure_saveme/week3/domain_test.clj — pruebas del dominio
test/clojure_saveme/week3/core_test.clj — pruebas de orquestación
9) Cómo correr las pruebas
clojure -M:test -m cognitect.test-runner
10) Ejercicios para practicar
Básico
crea un namespace
clojure-saveme.week3.reportsy hazrequirededomainescribe una función pura
product-discountque reciba un producto y un porcentaje
Intermedio
agrega una regla
overdue?para pedidos y su testsepara
checkouten dos funciones: una que decide si aplica descuento y otra que aplica la transformación
Avanzado
crea
clojure-saveme.week3.reportsque dependa decoreydomainescribe una función que resuma pedidos: total general, cantidad pagada, cantidad pendiente
agrega tests con lista vacía como caso borde
11) Meta de la semana
Al terminar, deberías poder explicar:
por qué la ruta del archivo determina el namespace
cómo se usa
:requirecon:ascómo ordenar
srcytestcuando el proyecto crecepor qué las funciones puras son más fáciles de probar
cómo separar dominio y orquestación
Si entiendes eso, ya estás pensando como alguien que puede trabajar en un backend Clojure real: módulos claros, reglas bien separadas y tests que acompañan al código.
Enjoy!
Joe




