“Súbelo a staging primero.” “¿Eso ya está en prod?” “¿Lo probaste en TEST?” Todo el mundo hablaba así, como si fuera obvio. Y es obvio, después de que lo sabes. Antes de eso es un montón de siglas.
Lo que me trababa no era el concepto. Yo entendía, más o menos, que existía un servidor para jugar y un servidor de verdad. Lo que no lograba ver era qué significaba eso dentro del código. ¿Guardo dos copias del archivo? ¿Dos branches? ¿Cambio la dirección a mano antes de subir? (Spoiler: hice las tres, en momentos distintos.)
Entonces vamos por la versión que nadie me dio:
- DEV es tu máquina. Rómpela a gusto.
- TEST es donde se ejecuta la suite de pruebas. Base descartable, datos inventados, nace y muere en cada ejecución. Quien usa TEST es la máquina.
- HML, homologación, es la copia lo más parecida posible a producción, donde la gente valida antes de liberar. Es una sigla bien brasileña; afuera le dicen staging, o UAT. El mismo animal.
- PRD es donde están tus clientes.
Tu máquina
Solo tú ves lo que pasa aquí.
La suite de pruebas
Base descartable, datos inventados, ejecutado por una máquina.
Staging
Una copia de producción, validada por gente.
Producción
Donde están tus clientes.
El camino feliz. El cambio pasa las pruebas, el equipo lo valida en staging y sale a producción en un horario acordado. Es lo que pasa la mayoría de las veces, y nadie lo comenta.
La automatización entró en loop y disparó e-mails sin parar. Aquí eso cuesta un build en rojo y veinte minutos. Sin TEST, cuesta la bandeja de entrada de todos tus clientes.
El código está bien, la regla es la que está mal. Un campo obligatorio nuevo traba el formulario para la mitad de los clientes, y solo quien lidera soporte podría saberlo. Lo vieron un martes, no la noche del go-live.
Veinte scripts en R y una ruta de archivo
Antes de entender cualquiera de esas siglas, yo ya había pagado caro por ellas.
Mi primer proyecto serio fue un ETL escrito en R. Unos veinte archivos de script, cada uno tomando una hoja de cálculo, limpiando, cruzando y escupiendo un resultado. Funcionaba. Andaba precioso. En mi máquina.
Y entonces necesité ejecutarlo en otra máquina.
La ruta de los archivos estaba clavada en medio del código. No en un lugar, en veinte. C:/Users/leandro/Documents/dados/... esparcida por funciones que yo había escrito meses antes y de las que ya ni me acordaba bien. Me pasé el día en Ctrl+F cambiándolas una por una. Ejecutaba, se rompía, encontraba otra más. Cambiaba, ejecutaba, se rompía de nuevo. La peor estaba dentro de una función que solo se llamaba al final del proceso, así que el error aparecía cuarenta minutos después de empezar. Cuarenta minutos para descubrir que faltaba una barra.
Nada de eso era culpa de R. Era yo mezclando dos cosas que no tienen nada que ver una con la otra: lo que el programa hace, y dónde se está ejecutando.
El Programador Pragmático
Algunos años después leí El Programador Pragmático, de Hunt y Thomas, y llegué a la parte sobre configuración. El argumento es simple: todo lo que puede cambiar sin que la lógica cambie no debería vivir dentro de la lógica. Ruta de archivo, credencial, URL, puerto, número de reintentos, timeout.
Tal como yo lo leí, la vara es esta: si necesitas tocar el código para ejecutar el programa en otra máquina, hay algo guardado en el lugar equivocado.
Tenía todo el sentido. Lo gracioso es que yo ya lo había aprendido a la mala, con intereses, años antes. Leerlo solo le puso nombre al problema.
Solo que saber el porqué no me dio el cómo. Mi primer intento fue el que todo el mundo hace:
if ENV == "HML":
BASE_URL = "https://hml.api.empresa.com"
DB_HOST = "servidor-hml"
TIMEOUT = 60
elif ENV == "PRD":
BASE_URL = "https://api.empresa.com"
DB_HOST = "servidor-prod"
TIMEOUT = 10
Es mejor que la ruta clavada, sin duda. Pero envejece mal. Cada configuración nueva es un if. Y los ifs no se quedan quietos en un solo archivo, se filtran. Seis meses después hay un if ENV == "PRD" en medio de la función que dispara e-mails, y solo lo descubres porque un cliente de verdad recibió un mensaje de prueba.
El patrón de Miguel Grinberg
Lo que terminó de aclarármelo fue el libro de Miguel Grinberg sobre Flask. En el capítulo de configuración hace algo simple y bonito: cada ambiente se vuelve una clase.
class Config:
SECRET_KEY = os.environ.get("SECRET_KEY")
TIMEOUT = 30
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
BASE_URL = "http://localhost:8000"
DATABASE_URL = os.environ.get("DEV_DATABASE_URL")
class TestingConfig(Config):
TESTING = True
BASE_URL = "http://localhost:8000"
DATABASE_URL = "sqlite://"
class StagingConfig(Config):
BASE_URL = "https://hml.api.empresa.com"
DATABASE_URL = os.environ.get("HML_DATABASE_URL")
TIMEOUT = 60
class ProductionConfig(Config):
BASE_URL = "https://api.empresa.com"
DATABASE_URL = os.environ.get("DATABASE_URL")
TIMEOUT = 10
config = {
"development": DevelopmentConfig,
"testing": TestingConfig,
"staging": StagingConfig,
"production": ProductionConfig,
"default": DevelopmentConfig,
}
Y a la hora de levantar la aplicación, una línea:
app.config.from_object(config[os.environ.get("APP_ENV", "default")])
La respuesta a la pregunta “¿qué cambia entre los ambientes?” cabe en un solo archivo. Lo abres y lo ves. No hay que buscar.
La herencia hace el trabajo aburrido. Lo que es igual vive en la clase base y nadie lo repite. Lo que es distinto se sobrescribe, y solo lo que es distinto aparece en la clase hija. Si TIMEOUT no está escrito en DevelopmentConfig, es porque vale el de la base, y ese silencio también es información.
El resto del código nunca pregunta en qué ambiente está. Lee app.config["TIMEOUT"] y sigue su vida. El if se volvió una búsqueda en un diccionario y desapareció sin que nadie necesitara disciplina. Un ambiente nuevo es una clase nueva y una línea más en el diccionario.
Y el secreto nunca vive en el archivo. os.environ.get significa que el archivo describe el formato y la máquina llena el valor. Así puedes commitear el archivo sin commitear la contraseña.
Sigo ese patrón en todo proyecto desde entonces. El lenguaje cambia, la idea no. En TypeScript se vuelve un objeto por ambiente y un tipo que garantiza que todos cargan las mismas llaves. En .NET ya viene listo, con appsettings.Production.json sobrescribiendo el appsettings.json, que es la misma herencia con otro nombre.
Vale otro aviso del mismo libro, sobre el que ya escribí por aquí: no pegues tu código a los IDs del ambiente en el que se ejecuta. Sandbox y producción suelen tener los mismos campos, pero cada campo carga un ID distinto en cada lugar. Deja que el código descubra esos IDs solo.
Esto no es solo sobre código
Hoy buena parte de mi trabajo es CRM. Y ahí aparece la misma historia, solo que con precio en la etiqueta.
Aviso honesto antes: Red Lotus es partner de Freshworks, así que lee la recomendación sabiendo eso.
La mayoría de los proveedores de CRM trata el sandbox como una función premium, liberada solo en el plan Enterprise. Y esta es una de las pocas cosas sobre las que tengo una opinión firme: la empresa que se toma en serio la calidad y no quiere perjudicar al cliente debería irse con Enterprise. El sandbox solo ya paga la diferencia.
Tres motivos, y ninguno de ellos es técnico.
Velocidad. Cuando el único lugar para construir es producción, todo cambio se vuelve una negociación. El equipo empieza a evitar tocar cosas, y el CRM deja de evolucionar. Ese es un problema mucho más caro que la diferencia entre los planes, y pasan años hasta que alguien se da cuenta de que lo está pagando.
Bugs antes que el usuario. Una automatización que entra en loop y dispara e-mails sin parar es graciosa en el sandbox. En producción es una disculpa para toda tu base y un día entero de fila.
Gobernanza. Antes de que cualquier cosa salga a producción, los stakeholders hacen clic. Quien lidera el soporte ve el formulario nuevo, la gente de compliance ve dónde cae el campo, y la objeción aparece con tiempo de sobra para ajustar.
Ahora la parte que el vendedor no cuenta.
Un sandbox no es un clon. No viene con tus datos de producción, y en la mayoría de las herramientas no sincroniza solo de vuelta. Promueves la configuración a mano, o usas un “sandbox sync” que cubre algunos tipos de objeto e ignora otros calladito. Pide esa lista antes de prometerle un plazo a alguien.
Las integraciones quedan afuera. El webhook, el middleware, la API del ERP: cada uno necesita su propio ambiente de pruebas, y hay proveedores que simplemente no lo tienen. Ahí terminas probando contra la producción del sistema de otra persona, que es exactamente lo que estabas tratando de evitar.
Y el precio es real. Enterprise no es un escaloncito, y si quieres los números yo desmenucé plan por plan de Freshdesk Omni. Si ustedes son cuatro personas con una fila de e-mail, el “no” es honesto. El argumento vale cuando existe algo que romper: volumen, integración, un proceso del que la gente depende.
Aun así hay rollback
No seas ingenuo. Incluso con cuatro ambientes, el pipeline en verde, el staging aprobado y el go-live agendado, de vez en cuando un deploy termina en rollback. Algo que solo aparece con volumen de verdad y un usuario haciendo lo que ningún plan de pruebas previó.
La diferencia es que eso pasa a ser raro, y cuando pasa nadie está descubriendo el procedimiento en el momento. Existe una versión anterior, existe un plan, alguien aprieta el botón. El cliente se lleva media hora mala y la cosa termina ahí.
Mi ETL en R nunca tuvo staging de ningún tipo. Si volviera hoy, empezaría por el archivo de configuración antes de escribir el primer script, y aun así se iba a romper en algún lugar que no previ. Es la naturaleza del software.