Diseño de módulos y APIs públicas en JavaScript | Nicolás Garzón
Un buen módulo oculta decisiones internas y expone un contrato pequeño, estable y comprensible.
Texto
Copiar consumidor
↓ API pública
módulo
↓ detalles internos
infraestructura
Un módulo debería agrupar conceptos que cambian por razones relacionadas.
Texto
Copiar orders/
create-order.js
cancel-order.js
order-errors.jsNo agrupe funciones solo porque son “helpers”. Define qué responsabilidad comparten.
JavaScript
Copiar import { database } from "../../infrastructure/db.js" ; Si cada módulo de dominio importa infraestructura concreta, el cambio se propaga.
JavaScript
Copiar export function createOrderService ( { repository } ) {
return {
create ( input ) {
return repository. save ( createOrder ( input) ) ;
} ,
} ;
} JavaScript
Copiar export {
createOrder,
cancelOrder,
OrderError,
} ; No exportes funciones internas solo para facilitar un test. Prueba comportamiento público o extrae una unidad con responsabilidad propia.
JavaScript
Copiar export function requireProduct ( ) { }
export function findProduct ( ) { } Comunican contratos distintos mejor que getData o handleItem.
Texto
Copiar UI → application → domain
↓
infrastructure adapterLas capas centrales no deberían importar frameworks o detalles externos innecesariamente.
JavaScript
Copiar
window. addEventListener ( "click" , trackClick) ; JavaScript
Copiar export function startAnalytics ( target ) {
target. addEventListener ( "click" , trackClick) ;
return ( ) => {
target. removeEventListener ( "click" , trackClick) ;
} ;
} El llamador conoce inicio y cleanup.
Evita leer globals dentro de cada módulo.
JavaScript
Copiar export function createClient ( { baseUrl, token } ) { } La configuración se valida una vez y se pasa explícitamente.
Un index.js puede ofrecer una entrada cómoda.
JavaScript
Copiar export { createOrder } from "./create-order.js" ;
export { OrderError } from "./order-error.js" ; Úsalo como frontera pública, no como ruta universal entre archivos internos.
JavaScript
Copiar function calculateTax ( ) { } No debería obligar a todos los consumidores a cambiar si el contrato externo permanece.
Cambiar nombre, forma de parámetros, retorno o errores públicos puede ser breaking change.
JavaScript
Copiar throw new OrderNotFoundError ( orderId) ; Si los consumidores dependen de esa clase o code, forma parte de la API.
JavaScript
Copiar const service = createService ( {
clock : fakeClock,
repository : fakeRepository,
} ) ; Dependencias explícitas permiten pruebas sin mutar globals ni cargar módulos especiales.
Texto
Copiar utils.js
services.js
helpers.js
common.jsCuando contienen dominios no relacionados, se convierten en puntos de acoplamiento. Divide por responsabilidad y lenguaje del producto.
JavaScript
Copiar export function createHttpClient ( {
baseUrl,
fetchImplementation,
} ) {
return {
async get ( path, { signal } = { } ) {
const response = await fetchImplementation (
new URL ( path, baseUrl) ,
{ signal } ,
) ;
if ( ! response. ok) {
throw new HttpError ( response. status) ;
}
return response. json ( ) ;
} ,
} ;
} La API es pequeña y el detalle de fetch es sustituible.
¿Cuál es la responsabilidad del módulo?
¿Qué necesita conocer el consumidor?
¿Qué debe permanecer interno?
¿Qué efectos ocurren al importar?
¿Cómo se inicializa y limpia?
¿Qué errores forman parte del contrato?
¿Puede invertirse una dependencia concreta?
¿Existe un ciclo?
¿El nombre comunica el dominio?
¿Cambiar implementación rompe consumidores?
Exportar todo por comodidad.
Crear módulos genéricos sin responsabilidad.
Leer configuración global en cualquier archivo.
Ejecutar efectos al importar.
Usar barrels internos que crean ciclos.
Acoplar dominio a frameworks.
Exponer detalles solo para tests.
Diseñar APIs con parámetros booleanos ambiguos.
Un módulo es una frontera de diseño, no solo un archivo.
La cohesión agrupa responsabilidades relacionadas.
Una API pequeña reduce acoplamiento.
Los efectos deben iniciarse y limpiarse explícitamente.
Las dependencias deberían apuntar hacia contratos estables.
Errores y formas de datos pueden ser parte de la API.
Los barrels son útiles como frontera pública.
El código interno debe poder cambiar sin afectar consumidores.
¿Por qué exportar una función interna solo para probarla puede ser mala señal?
Respuesta Porque amplía la API pública por una necesidad interna. Puede indicar que la unidad tiene otra responsabilidad que debería extraerse o que la prueba debería observar el comportamiento público.
Strings, Unicode y template literals inicia el bloque de objetos nativos y formatos.