Enterprise C#: por qué el código para un banco y el código para un proyecto personal son dos universos diferentes
\n\nSi alguna vez has pasado de un pequeño estudio web a una gran corporación o fintech, conoces esa sensación de leve shock cultural. Ayer podías hacer commit de un "hotfix" sin pruebas y con un solo comentario //TODO: refactor, y hoy recibes una revisión con 20 observaciones, donde cada método público requiere documentación XML y cualquier dependencia necesita registro en el contenedor DI.
El desarrollo en C# en enterprise no se trata de "hacerlo rápido", sino de "hacerlo para que no se rompa dentro de tres años, cuando el autor del código ya haya sido despedido". Se trata de previsibilidad, escalabilidad e interminables code reviews. Analicemos en qué consiste realmente el desarrollo enterprise en C# y cómo no ser despedido en la primera semana.
\n\n1. Arquitectura: SOLID no es una palabra de moda, es la ley
\n\nEn una startup puedes permitirte un monolito con cadenas "mágicas" y clases gigantes de 2000 líneas. En enterprise, no. Aquí cada principio SOLID no es una abstracción, sino una guía para la acción.
\n\nPrincipio de Responsabilidad Única (SRP)
\nTu clase no debe saber cómo enviar correos, guardar en la base de datos y analizar XML al mismo tiempo. Si ves tres o más "responsabilidades" en una misma clase, es una señal de alerta. En enterprise, ese código será rechazado en el code review con la frase "violación de SRP, reescribir". Ejemplo de mal código:
\n\npublic class OrderService\n{\n public void ProcessOrder(Order order)\n {\n // 1. Validación\n // 2. Guardar en BD\n // 3. Enviar email\n // 4. Registro en log\n }\n}\n\nEl enfoque correcto es dividir en servicios separados y usar el patrón Mediator o Pipeline:
\n\npublic class ProcessOrderCommandHandler : IRequestHandler<ProcessOrderCommand, bool>\n{\n private readonly IOrderRepository _repo;\n private readonly IEmailService _email;\n private readonly ILogger<ProcessOrderCommandHandler> _logger;\n\n public ProcessOrderCommandHandler(IOrderRepository repo, IEmailService email, ILogger<ProcessOrderCommandHandler> logger)\n {\n _repo = repo;\n _email = email;\n _logger = logger;\n }\n\n public async Task<bool> Handle(ProcessOrderCommand request, CancellationToken cancellationToken)\n {\n var order = await _repo.GetByIdAsync(request.OrderId);\n // lógica\n await _email.SendAsync(order.CustomerEmail, "Order processed");\n _logger.LogInformation("Order {Id} processed", order.Id);\n return true;\n }\n}\n\nObserva: cada servicio hace exactamente una cosa. Probar este código es mucho más fácil.
\n\n2. Dependency Injection: tu mejor amigo
\n\nEn proyectos enterprise en C#, casi nunca verás new MyService() dentro de una clase. En su lugar, se usa un contenedor DI (normalmente Autofac o el integrado Microsoft.Extensions.DependencyInjection).
La regla es simple: si una clase crea sus propias dependencias, viola el Principio de Inversión de Dependencias. En enterprise, esto se considera un error de arquitectura. Ejemplo:
\n\n// ❌ Mal: acoplamiento rígido\npublic class ReportGenerator\n{\n private readonly PdfExporter _exporter = new PdfExporter();\n public byte[] Generate() => _exporter.Export();\n}\n\n// ✅ Bien: inversión de dependencias\npublic class ReportGenerator\n{\n private readonly IExporter _exporter;\n public ReportGenerator(IExporter exporter) => _exporter = exporter;\n public byte[] Generate() => _exporter.Export();\n}\n\nRegistro en el startup:
\nservices.AddScoped<IExporter, PdfExporter>();\nservices.AddScoped<ReportGenerator>();\n\n¿Para qué sirve esto? Primero, para las pruebas: puedes simular IExporter y no gen