Procedimientos recomendados de desarrollo web (Compilar aplicaciones en la nube del mundo real con Azure)

por Rick Anderson, Tom Dykstra

Descargar proyecto Fix It o Descargar libro electrónico

El libro electrónico Compilar aplicaciones en la nube del mundo real con Azure se basa en una presentación desarrollada por Scott Guthrie. Explica 13 patrones y prácticas que pueden ayudarle a tener éxito en el desarrollo de aplicaciones web para la nube. Para obtener información sobre el libro electrónico, consulte el primer capítulo.

Los tres primeros patrones trataban de establecer un proceso de desarrollo ágil; el resto son sobre arquitectura y código. Esta es una colección de procedimientos recomendados de desarrollo web:

Estos procedimientos son válidos para todo el desarrollo web, no solo para las aplicaciones en la nube, pero son especialmente importantes para las aplicaciones en la nube. Funcionan conjuntamente para ayudarle a aprovechar de forma óptima el escalado altamente flexible que ofrece el entorno en la nube. Si no sigue estos procedimientos, tendrá limitaciones al intentar escalar la aplicación.

Nivel web sin estado detrás de un equilibrador de carga inteligente

Un nivel web sin estado significa que no almacena ningún dato de la aplicación en la memoria del servidor web ni en el sistema de archivos. Mantener el nivel web sin estado le permite proporcionar una mejor experiencia de cliente y ahorrar dinero:

  • Si el nivel web no tiene estado y se encuentra detrás de un equilibrador de carga, puede responder rápidamente a los cambios en el tráfico de la aplicación agregando o quitando servidores dinámicamente. En el entorno de nube en el que solo paga por los recursos del servidor mientras los use realmente, esa capacidad de responder a los cambios en la demanda puede traducirse en grandes ahorros.
  • Un nivel web sin estado es mucho más sencillo para escalar horizontalmente la aplicación. Esto también le permite responder a las necesidades de escalado más rápidamente y gastar menos dinero en desarrollo y pruebas en el proceso.
  • Los servidores en la nube, como los servidores locales, necesitan revisarse y reiniciarse cada cierto tiempo; y si el nivel web es sin estado, redirigir el tráfico cuando un servidor se cae temporalmente no causará errores ni comportamientos inesperados.

La mayoría de las aplicaciones del mundo real sí necesitan almacenar el estado de una sesión web; lo importante aquí es no almacenarlo en el servidor web. Puede almacenar el estado de otras formas, como en el cliente en cookies o fuera del proceso del lado del servidor en el estado de sesión de ASP.NET usando un proveedor de caché. Puede almacenar archivos en Almacenamiento de blobs de Windows Azure en lugar del sistema de archivos local.

Como ejemplo de lo fácil que es escalar una aplicación en sitios web de Windows Azure si su nivel web es sin estado, consulte la pestaña Escala de un sitio web de Windows Azure en el portal de administración:

Scale tab

Si quiere agregar servidores web, solo tiene que arrastrar el control deslizante de recuento de instancias hacia la derecha. Establézcalo en 5 y haga clic en Guardar, y en cuestión de segundos tendrá 5 servidores web en Windows Azure controlando el tráfico de su sitio web.

Five instances

Con la misma facilidad puede establecer el recuento de instancias en 3 o volver a 1. Cuando vuelva a reducirlo, empezará a ahorrar dinero inmediatamente porque Windows Azure cobra por minutos, no por horas.

También puede indicar a Windows Azure que aumente o reduzca automáticamente el número de servidores web en función del uso de CPU. En el ejemplo siguiente, cuando el uso de CPU supera el 60 %, el número de servidores web disminuirá a un mínimo de 2 y, si el uso de CPU supera el 80 %, el número de servidores web aumentará hasta un máximo de 4.

Scale by CPU usage

¿O qué ocurre si sabe que su sitio solo estará ocupado durante las horas de trabajo? Puede indicar a Windows Azure que ejecute varios servidores durante el día y que disminuya a un único servidor por las tardes, noches y fines de semana. La siguiente serie de capturas de pantalla muestra cómo configurar el sitio web para que ejecute un servidor en horario no laboral y 4 servidores en horario laboral, de 8 de la mañana a 5 de la tarde.

Scale by schedule

Set schedule times

Daytime schedule

Weeknight schedule

Weekend schedule

Y, por supuesto, todo esto se puede hacer en scripts, así como en el portal.

La capacidad de la aplicación para escalar horizontalmente es casi ilimitada en Windows Azure, siempre y cuando evite impedimentos para agregar o quitar máquinas virtuales de servidor dinámicamente, manteniendo el nivel web sin estado.

Evitar el estado de sesión

No suele ser práctico evitar almacenar algún tipo de estado para una sesión de usuario en una aplicación de nube real, pero algunos enfoques afectan al rendimiento y a la escalabilidad más que otros Si tiene que almacenar el estado, la mejor solución es que la cantidad sea reducida y que se almacene en cookies. Si eso no es factible, la siguiente mejor solución es usar el estado de sesión de ASP.NET con un proveedor de caché distribuida en memoria. La peor solución desde el punto de vista del rendimiento y la escalabilidad es usar un proveedor de estado de sesión con copia de seguridad de base de datos.

Uso de una red CDN para almacenar en caché los recursos de archivos estáticos

CDN es un acrónimo de Content Delivery Network. Usted proporciona recursos de archivos estáticos, como imágenes y archivos de scripts, a un proveedor de CDN, y el proveedor almacena en caché estos archivos en centros de datos de todo el mundo para que, dondequiera que la gente acceda a su aplicación, obtenga una respuesta relativamente rápida y una baja latencia para los recursos almacenados en caché. Esto acelera el tiempo de carga total del sitio y reduce la carga en los servidores web. Las CDN son especialmente importantes si llega a un público muy repartido geográficamente.

Windows Azure tiene una CDN, y puede usar otras CDN en una aplicación que se ejecute en Windows Azure o en cualquier entorno de hospedaje web.

Uso de la compatibilidad asincrónica de .NET 4.5 para evitar el bloqueo de llamadas

.NET 4.5 ha mejorado los lenguajes de programación de C# y VB para que sea mucho más sencillo controlar las tareas de forma asincrónica. La ventaja de la programación asincrónica no es solo para situaciones de procesamiento en paralelo, como cuando desea iniciar varias llamadas de servicio web simultáneamente. También permite que el servidor web funcione de forma más eficaz y confiable en condiciones de carga elevadas. Un servidor web solo tiene un número limitado de subprocesos disponibles y, en condiciones de carga elevada, cuando todos los subprocesos se están usando, las solicitudes entrantes tienen que esperar hasta que se liberen los subprocesos. Si el código de su aplicación no controla tareas como las consultas a bases de datos y las llamadas a servicios web de forma asíncrona, muchos subprocesos se ven innecesariamente atados mientras el servidor espera una respuesta de E/S. Esto limita la cantidad de tráfico que el servidor puede controlar en condiciones de carga elevada. Con la programación asincrónica, los subprocesos que están esperando a que un servicio web o una base de datos devuelva datos se liberan para atender nuevas solicitudes hasta que se reciben los datos. En un servidor web muy ocupado, se pueden procesar con rapidez cientos o miles de solicitudes que, de otro modo, estarían esperando a que se liberaran subprocesos.

Como ha visto antes, es tan fácil disminuir el número de servidores web que controlan su sitio web como aumentarlos. Por lo tanto, si un servidor puede alcanzar un rendimiento mayor, no necesitará tantos y podrá reducir sus costos porque necesitará menos servidores para un volumen de tráfico determinado de los que necesitaría en caso contrario.

La compatibilidad con el modelo de programación asincrónica de .NET 4.5 se incluye en ASP.NET 4.5 para Web Forms, MVC y Web API; en Entity Framework 6 y en la API de Windows Azure Storage.

Compatibilidad asincrónica en ASP.NET 4.5

En ASP.NET 4.5, la compatibilidad con la programación asincrónica se ha agregado no solo al lenguaje, sino también a los marcos de MVC, Web Forms y Web API. Por ejemplo, un método de acción de controlador MVC de ASP.NET recibe datos de una solicitud web y los pasa a una vista que después crea el HTML que se enviará al navegador. Con frecuencia, el método de acción necesita obtener datos de una base de datos o de un servicio web para mostrarlos en una página web o para guardar los datos introducidos en una página web. En esos escenarios es fácil hacer que el método de acción sea asincrónico: en lugar de devolver un objeto ActionResult, se devuelve Task<ActionResult> y se marca el método con la palabra clave async. Dentro del método, cuando una línea de código inicia una operación que implica tiempo de espera, se marca con la palabra clave await.

Este es un método de acción simple que llama a un método de repositorio para una consulta de base de datos:

public ActionResult Index()
{
    string currentUser = User.Identity.Name;
    var result = fixItRepository.FindOpenTasksByOwner(currentUser);

    return View(result);
}

Y este es el mismo método que controla la llamada a la base de datos de forma asincrónica:

public async Task<ActionResult> Index()
{
    string currentUser = User.Identity.Name;
    var result = await fixItRepository.FindOpenTasksByOwnerAsync(currentUser);

    return View(result);
}

En segundo plano, el compilador genera el código asincrónico adecuado. Cuando la aplicación realiza la llamada a FindTaskByIdAsync, ASP.NET realiza la solicitud FindTask y después desconecta el subproceso de trabajo y lo deja disponible para procesar otra solicitud. Cuando finaliza la solicitud FindTask, se reinicia un subproceso para seguir procesando el código que viene después de esa llamada. Durante el intervalo entre el momento en que se inicia la solicitud FindTask y el momento en que se devuelven los datos, usted tiene un subproceso disponible para realizar un trabajo útil que, de otro modo, estaría atascado esperando la respuesta.

Existe cierta sobrecarga para el código asincrónico, pero en condiciones de baja carga, esa sobrecarga es insignificante, mientras que en condiciones de alta carga podrá procesar solicitudes que de otro modo quedarían retenidas a la espera de subprocesos disponibles.

Ha sido posible hacer este tipo de programación asincrónica desde ASP.NET 1.1, pero era difícil de escribir, propensa a errores y difícil de depurar. Ahora que hemos simplificado la codificación para ella en ASP.NET 4.5, ya no hay ninguna razón para no hacerlo.

Compatibilidad asincrónica en Entity Framework 6

Como parte de la compatibilidad asincrónica de la versión 4.5, incluimos compatibilidad asincrónica para llamadas a servicios web, sockets y E/S de sistemas de archivos, pero el patrón más común para las aplicaciones web es el de acceder a una base de datos, y nuestras bibliotecas de datos no eran compatibles con la tecnología asincrónica. Ahora Entity Framework 6 agrega compatibilidad asincrónica con el acceso a la base de datos.

En Entity Framework 6, todos los métodos que hacen que una consulta o un comando se envíen a la base de datos tienen versiones asincrónicas. En el ejemplo siguiente se muestra la versión asincrónica del método Find.

public async Task<FixItTask> FindTaskByIdAsync(int id)
{
    FixItTask fixItTask = null;
    Stopwatch timespan = Stopwatch.StartNew();

    try
    {
        fixItTask = await db.FixItTasks.FindAsync(id);
        
        timespan.Stop();
        log.TraceApi("SQL Database", "FixItTaskRepository.FindTaskByIdAsync", timespan.Elapsed, "id={0}", id);
    }
    catch(Exception e)
    {
        log.Error(e, "Error in FixItTaskRepository.FindTaskByIdAsynx(id={0})", id);
    }

    return fixItTask;
}

Y esta compatibilidad asincrónica funciona no solo para inserciones, eliminaciones, actualizaciones y búsqueda sencillas, sino que también funciona con consultas LINQ:

public async Task<List<FixItTask>> FindOpenTasksByOwnerAsync(string userName)
{
    Stopwatch timespan = Stopwatch.StartNew();

    try
    {
        var result = await db.FixItTasks
            .Where(t => t.Owner == userName)
            .Where(t=>t.IsDone == false)
            .OrderByDescending(t => t.FixItTaskId).ToListAsync();

        timespan.Stop();
        log.TraceApi("SQL Database", "FixItTaskRepository.FindTasksByOwnerAsync", timespan.Elapsed, "username={0}", userName);

        return result;
    }
    catch (Exception e)
    {
        log.Error(e, "Error in FixItTaskRepository.FindTasksByOwnerAsync(userName={0})", userName);
        return null;
    }
}

Hay una versión Async del método ToList porque en este código es el método que hace que una consulta se envíe a la base de datos. Los métodos Where y OrderByDescending solo configuran la consulta, mientras que el método ToListAsync ejecuta la consulta y almacena la respuesta en la variable result.

Resumen

Puede implementar los procedimientos recomendados de desarrollo web que se describen aquí en cualquier marco de programación web y cualquier entorno en la nube, pero tenemos herramientas en ASP.NET y Windows Azure para facilitarlo. Si sigue estos patrones, puede escalar horizontalmente fácilmente el nivel web y minimizará los gastos porque cada servidor podrá controlar más tráfico.

El siguiente capítulo examina cómo la nube habilita escenarios de inicio de sesión único.

Recursos

Para más información, vea los siguientes recursos.

Servidores web sin estado:

CDN:

Programación asincrónica:

Para conocer otros procedimientos recomendados de desarrollo web, consulte los siguientes recursos: