Diseñar el vocabulario#
Un nombre no decora una solución terminada. Decide qué concepto verá el consumidor y qué podrá esperar de él.
Cuando una API usa palabras como execute, data o response, obliga a leer la implementación para descubrir su significado. Cuando nombra una intención, un resultado o una situación reconocible, permite entender el contrato sin abrir la implementación.
El vocabulario de una API debería seguir siendo verdadero aunque cambie su implementación.
Todo valor que cruza una API necesita significado, reglas de validez e igualdad, una representación de la ausencia y un propietario para su estado mutable. El consumidor solo debería observar la representación que el contrato decida conservar.
La respuesta no siempre requiere un tipo nuevo. Un tipo propio aporta cuando nombra una diferencia, protege una invariante o impide una combinación inválida. Si el contexto ya evita la confusión y las reglas son las mismas, separar dos valores solo añade ceremonia.
Ejemplo: añadir una tarea a una cola#
Esta firma describe principalmente la organización del código:
QueueService.Execute(EnqueueCommand) → QueueResponseNo sabemos qué se ejecuta, qué contiene la respuesta ni qué puede salir mal. La misma capacidad puede expresarse así:
Queue.Enqueue(Task) → Position | QueueFullQueue aporta el contexto, Enqueue expresa la intención, Task nombra la entrada, Position el resultado y QueueFull una situación ante la que el consumidor puede actuar.
La implementación puede usar memoria, archivos o un sistema remoto. Ninguna de esas decisiones obliga a cambiar el mensaje.
Ejemplo en Go#
El ejemplo mantiene Queue, Enqueue, Task, Position y QueueFull como vocabulario visible.
import "errors"
type Task struct{ ID string }
type Position int
var ErrQueueFull = errors.New("queue full")
type Queue interface {
Enqueue(Task) (Position, error)
}Nombres exportados#
Los nombres públicos son el vocabulario compartido entre un módulo y sus consumidores. Merecen más estabilidad que los identificadores internos porque aparecen en llamadas, documentación, tests y, a veces, datos serializados.
Cada lenguaje expresa esa frontera de otra forma. En Go, un identificador exportado comienza con mayúscula; los nombres con minúscula permanecen dentro del paquete. Un directorio internal también permite limitar qué parte del árbol puede importar un paquete.
Exportar no mejora un nombre ni convierte un tipo en una buena abstracción. Primero se identifica qué necesita nombrar el consumidor; después se le da la visibilidad correspondiente. Los helpers y representaciones internas pueden usar palabras más técnicas sin contaminar la conversación pública.
Buenos nombres#
El módulo#
El nombre de un módulo debe indicar la capacidad que reúne, no el patrón con el que fue construido.
Manager, Service, Handler, Facade o Helper suelen ser débiles cuando aparecen solos. Clasifican una estructura técnica, pero no explican qué ofrece. QueueManager, por ejemplo, permite imaginar casi cualquier responsabilidad; Queue establece un contexto concreto para mensajes como Enqueue, Next o Remove.
Esto no convierte los nombres técnicos en un error universal. En la composición puede ser útil distinguir MemoryQueue de RemoteQueue, porque allí el consumidor elige una implementación. La API que ambas proporcionan puede seguir llamándose Queue.
La pregunta útil es: ¿el consumidor necesita conocer este mecanismo? Si la respuesta es no, el nombre técnico pertenece al interior.
Los mensajes#
Los mensajes expresan intenciones o hechos, no pasos internos.
Un verbo como Enqueue permite anticipar el efecto. Run, Execute, Process o Handle solo son precisos cuando ejecutar, procesar o despachar constituye realmente la capacidad del módulo.
La prueba más sencilla consiste en leer el uso como una frase:
position = queue.Enqueue(task)Si para entenderla hay que traducir categorías del framework, el contrato todavía habla desde la implementación.
Valores y resultados#
Un tipo merece nombre cuando representa una diferencia que importa. Task, Position y Capacity dicen más que Data, Item o ValueObject, siempre que esas sean las palabras del contexto real.
Los sufijos que repiten la categoría técnica suelen sobrar:
TaskModel
PositionValueObject
QueueResponseDTOPueden ser necesarios dentro de un adaptador que traduce dos representaciones, pero no deberían propagarse a la API principal por accidente.
Las unidades también forman parte del significado. Delay puede resultar ambiguo si el consumidor necesita distinguir milisegundos de segundos. No siempre hace falta un nombre más largo; sí hace falta conservar la diferencia que evita un uso incorrecto.
Booleanos, errores y eventos#
Un booleano se entiende mejor como una proposición:
queue.IsFull()
queue.Contains(taskID)Check, Flag o Status no aclaran qué significa true. Si existen más de dos resultados relevantes, probablemente el contrato necesite un estado nombrado en lugar de un booleano.
Los errores públicos describen situaciones ante las que el consumidor puede reaccionar. QueueFull permite esperar o elegir otra cola. DatabaseError filtra infraestructura y quizá no ofrece ninguna decisión útil. Los detalles técnicos pueden conservarse como causa interna, log o diagnóstico sin convertirse en vocabulario estable.
Un evento nombra un hecho que ya ocurrió. TaskQueued evita confundir la notificación con la petición EnqueueTask. El pasado también ayuda a que el nombre siga siendo cierto aunque cambie el transporte.
Comunicación#
El vocabulario funciona como un sistema, no como una lista de términos aislados. Módulo, mensaje, argumentos y resultados deben poder leerse juntos como una conversación.
El contexto evita repeticiones#
La precisión no consiste en incluir toda la explicación en cada identificador:
queue.Enqueue(task)es más claro que:
queueService.ExecuteTaskEnqueueCommand(taskValueObject)En el primer caso, módulo, mensaje y argumento reparten el significado. En el segundo, las categorías técnicas añaden longitud sin explicar mejor el contrato.
Un nombre corto puede ser ambiguo y uno largo puede compensar un contexto mal elegido. La meta es usar las palabras mínimas que conserven el significado donde se leen.
Encontrar un nombre#
Para encontrar el nombre, describe primero la intención del consumidor: «quiero añadir esta tarea a la cola». Separa contexto, acción, valores y resultados; usa las palabras del problema y lee la llamada completa. Después imagina otra implementación: si el nombre deja de ser cierto, todavía describe el mecanismo.
Si cuesta nombrar un módulo, puede haber responsabilidades mezcladas o una abstracción prematura. En ese caso conviene volver a la conversación antes de buscar un sinónimo más elegante.
Esta práctica coincide con el lenguaje ubicuo de DDD: los nombres ganan precisión dentro de un contexto explícito, no en un diccionario universal (DDD Reference, Eric Evans). API-DD añade una comprobación concreta: el vocabulario debe funcionar desde el módulo consumidor y sobrevivir a implementaciones alternativas.
Renombrar es migrar#
Una vez publicada, una palabra forma parte del contrato. Cambiarla puede romper compilación, mensajes serializados, documentación, métricas o integraciones.
Una migración puede requerir introducir el nombre nuevo junto al anterior, adaptar consumidores y retirar el alias cuando ya no exista dependencia. Que el nombre nuevo sea mejor no vuelve inocuo el cambio.
Un buen nombre explica la conversación y no delata el mecanismo que la hace posible.