La trampa de los microservicios prematuros
Cuando empezás a diseñar un SaaS, la tentación de microservicios es grande. Todo el mundo habla de Netflix y sus 1000 servicios. La realidad: Netflix llegó ahí después de años con un monolito. Empezar con microservicios en una startup es añadir complejidad operacional antes de tener claridad sobre los límites de negocio.
Para la mayoría de los SaaS que construyo en Deepyze, arranco con un monolito modular que se puede partir en servicios cuando el negocio lo justifica.
Estructura del monolito modular
backend/
├── modules/
│ ├── auth/ # routes, controller, service, model
│ ├── billing/
│ ├── users/
│ └── core-feature/
├── shared/
│ ├── middleware/
│ ├── utils/
│ └── config/
└── index.js
Cada módulo es independiente internamente pero comparte la misma base de datos y proceso. Cuando un módulo crece demasiado o necesita escalar diferente, tiene límites claros para extraerse.
Multitenancy: una base de datos o muchas
Hay tres patrones para multitenancy:
- Database-per-tenant: máximo aislamiento, complejidad operacional alta
- Schema-per-tenant: solo en SQL, no aplica a MongoDB
- Shared database con tenantId: más simple, suficiente para la mayoría de los SaaS
Para casi todos mis SaaS uso el tercer enfoque: cada documento en MongoDB tiene un campo tenantId (o organizationId) y todos los queries incluyen ese filtro. Lo centralizo en un middleware:
export const tenantScope = (req, res, next) => {
req.tenantId = req.user.organizationId;
next();
};
// En el service:
const users = await User.find({ organizationId: req.tenantId });
Gestión de suscripciones y pagos
Para pagos en Argentina uso MercadoPago. Para proyectos con clientes internacionales, Stripe. La lógica de suscripciones la implemento con webhooks: el proveedor de pagos llama a mi endpoint cuando cambia el estado de una suscripción, y yo actualizo el estado en mi base de datos.
// webhook de MercadoPago
app.post('/webhooks/mercadopago', async (req, res) => {
const { type, data } = req.body;
if (type === 'subscription_preapproval') {
await updateSubscriptionStatus(data.id);
}
res.status(200).send('OK');
});
Feature flags para deploys seguros
Para features experimentales o rollouts graduales, implemento feature flags simples en MongoDB:
const isFeatureEnabled = async (feature, tenantId) => {
const flag = await FeatureFlag.findOne({ name: feature });
if (!flag) return false;
if (flag.enabledForAll) return true;
return flag.enabledTenants.includes(tenantId);
};