Cómo reemplazar Node.js y React por Java y AJAX

Actualmente es bastante común asociar el desarrollo de una aplicación web moderna con una arquitectura formada por React en el frontend y Node.js en el backend.

Algo parecido a esto:

Navegador
    |
  React
    |
 API REST
    |
 Node.js
    |
Base de datos

Esta arquitectura es válida y tiene muchos casos de uso. Sin embargo, no significa que sea necesaria para todas las aplicaciones web.

Un sistema administrativo, un punto de venta, un sistema de inventario o una aplicación de facturación pueden necesitar una interfaz dinámica sin que eso implique necesariamente construir una SPA completa.

Podemos hacerlo utilizando Java:

HTML + CSS
    |
JavaScript / AJAX
    |
    Java
    |
Servlets o Spring Boot
    |
Servicios
    |
JDBC
    |
Base de datos

La idea no consiste en eliminar JavaScript.

La idea es utilizar JavaScript solamente para interactuar con el navegador y realizar peticiones asíncronas, dejando la construcción de la aplicación y la lógica del servidor en Java.

AJAX no necesita React

El navegador ya dispone de mecanismos para realizar peticiones HTTP sin recargar completamente una página.

Actualmente podemos utilizar fetch():

fetch("/productos")
    .then(response => response.text())
    .then(data => {
        document.getElementById("contenido").innerHTML = data;
    });

Tenemos una página principal con una sección:

<div id="contenido">
</div>

Cuando necesitamos mostrar productos, clientes, documentos o cualquier otra información, hacemos una petición al servidor.

Usuario
   |
 click
   |
 fetch()
   |
   v
Servidor Java
   |
 respuesta
   |
   v
<div id="contenido">

Por lo tanto, podemos modificar solamente una sección de la página sin realizar una recarga completa.

Esta técnica existe desde mucho antes que React: AJAX.

Java puede generar directamente la respuesta

Una característica sencilla pero importante de los Servlets es HttpServletResponse.

No estamos obligados a construir una API REST que devuelva JSON para cada operación.

Podemos responder directamente HTML.

Por ejemplo:

@Override
protected void doGet(
        HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {

    response.setContentType("text/html;charset=UTF-8");

    PrintWriter out = response.getWriter();

    out.println("<h2>Productos</h2>");
    out.println("<table>");
    out.println("<tr>");
    out.println("<th>Producto</th>");
    out.println("<th>Precio</th>");
    out.println("</tr>");

    out.println("<tr>");
    out.println("<td>Teclado</td>");
    out.println("<td>19990</td>");
    out.println("</tr>");

    out.println("</table>");
}

Desde JavaScript:

fetch("/productos")
    .then(response => response.text())
    .then(html => {
        document.getElementById("contenido").innerHTML = html;
    });

El flujo es extremadamente sencillo:

fetch()
   |
Servlet
   |
response.getWriter()
   |
HTML
   |
innerHTML

No necesitamos React para realizar esta operación.

También podemos hacerlo con Spring Boot

El concepto no cambia demasiado si utilizamos Spring Boot.

Un controlador puede devolver directamente contenido:

@GetMapping(
    value = "/productos",
    produces = MediaType.TEXT_HTML_VALUE
)
@ResponseBody
public String productos() {

    return """
        <h2>Productos</h2>

        <table>
            <tr>
                <th>Producto</th>
                <th>Precio</th>
            </tr>

            <tr>
                <td>Teclado</td>
                <td>19990</td>
            </tr>
        </table>
        """;
}

Desde el navegador podemos consumir exactamente esa respuesta:

fetch("/productos")
    .then(response => response.text())
    .then(html => {
        document.getElementById("contenido").innerHTML = html;
    });

Incluso podemos trabajar directamente con HttpServletResponse:

@GetMapping("/productos")
public void productos(HttpServletResponse response)
        throws IOException {

    response.setContentType("text/html;charset=UTF-8");

    PrintWriter out = response.getWriter();

    out.println("<h2>Productos</h2>");
    out.println("<p>Contenido generado desde Java</p>");
}

Por lo tanto, esta idea no depende de utilizar Servlets directamente.

Podemos aplicarla tanto en una aplicación Servlet tradicional como dentro de Spring Boot.

HTML, JSON o texto

Una petición AJAX tampoco significa automáticamente JSON.

El servidor puede responder diferentes tipos de contenido dependiendo del problema que estamos resolviendo.

                   Java
                    |
        ┌───────────┼───────────┐
        |           |           |
       HTML        JSON        Texto
        |           |           |
        v           v           v
    innerHTML    JavaScript  textContent
                   + DOM

Por ejemplo, podemos devolver simplemente texto:

response.setContentType("text/plain;charset=UTF-8");
response.getWriter().print("Producto guardado correctamente");

Y mostrarlo:

fetch("/producto/guardar", {
    method: "POST"
})
.then(response => response.text())
.then(mensaje => {
    document.getElementById("mensaje").textContent = mensaje;
});

Para una operación sencilla no necesitamos necesariamente crear una estructura JSON.

Cuando JSON sí tiene sentido

Supongamos ahora que necesitamos buscar productos y manipular los resultados desde JavaScript.

Ahí JSON puede ser una mejor alternativa.

Por ejemplo:

[
    {
        "id": 1,
        "nombre": "Teclado",
        "precio": 19990
    },
    {
        "id": 2,
        "nombre": "Monitor",
        "precio": 129990
    }
]

Desde JavaScript:

fetch("/productos/buscar")
    .then(response => response.json())
    .then(productos => {

        productos.forEach(producto => {
            console.log(producto.nombre);
        });

    });

No existe ninguna razón para prohibir JSON.

La diferencia está en utilizarlo cuando realmente nos resulta conveniente.

Tampoco necesitamos generar todo con PrintWriter

response.getWriter() resulta práctico para respuestas pequeñas.

Sin embargo, construir una página completa de esta manera rápidamente se vuelve difícil de mantener.

Algo como:

out.println("<div>");
out.println("<table>");
out.println("<tr>");
out.println("<td>" + producto.getNombre() + "</td>");
out.println("</tr>");
out.println("</table>");
out.println("</div>");

puede funcionar para pequeños fragmentos, pero no necesariamente es la mejor solución para vistas grandes.

Ahí podemos utilizar un motor de plantillas como Thymeleaf.

Por ejemplo:

<tr th:each="producto : ${productos}">
    <td th:text="${producto.nombre}"></td>
    <td th:text="${producto.precio}"></td>
</tr>

Nuestra aplicación puede utilizar entonces diferentes mecanismos:

Respuesta pequeña
      |
response.getWriter()


Vista estructurada
      |
Thymeleaf


Datos para manipular
      |
JSON

No necesitamos obligarnos a utilizar una sola técnica para todo.

Cargando secciones completas mediante AJAX

También podemos construir una aplicación donde existe una estructura principal y diferentes módulos se cargan dinámicamente.

Por ejemplo:

<nav>
    <button onclick="cargarProductos()">Productos</button>
    <button onclick="cargarClientes()">Clientes</button>
    <button onclick="cargarVentas()">Ventas</button>
</nav>

<main id="contenido">
</main>

Podemos crear una función sencilla:

function cargarSeccion(url) {

    fetch(url)
        .then(response => response.text())
        .then(html => {
            document.getElementById("contenido").innerHTML = html;
        });
}

Entonces:

function cargarProductos() {
    cargarSeccion("/productos");
}

function cargarClientes() {
    cargarSeccion("/clientes");
}

function cargarVentas() {
    cargarSeccion("/ventas");
}

Tenemos una interfaz dinámica:

┌───────────────────────────────────┐
│          Aplicación               │
├───────────────────────────────────┤
│ Productos | Clientes | Ventas     │
├───────────────────────────────────┤
│                                   │
│          #contenido               │
│                                   │
│    sección cargada con AJAX       │
│                                   │
└───────────────────────────────────┘

Sin React.

¿Entonces estamos construyendo una SPA?

No necesariamente.

Estamos tomando algunas características asociadas actualmente a las SPA, principalmente la actualización parcial de la interfaz, sin adoptar toda su arquitectura.

La aplicación puede seguir teniendo páginas generadas en servidor y, dentro de ellas, utilizar AJAX donde necesitamos mayor dinamismo.

Es un enfoque híbrido.

Por ejemplo:

Página HTML
    |
    +---- formulario tradicional
    |
    +---- búsqueda AJAX
    |
    +---- tabla AJAX
    |
    +---- modal
    |
    +---- navegación tradicional

No existe ninguna obligación técnica de convertir toda la aplicación en una SPA simplemente porque una tabla necesita actualizarse sin recargar la página.

¿Dónde queda Node.js?

Si nuestro backend está desarrollado completamente en Java, Node.js simplemente deja de ser necesario como servidor.

Java puede encargarse de:

HTTP
 |
Servlet / Controller
 |
Service
 |
DAO
 |
JDBC
 |
Base de datos

Podemos ejecutar nuestra aplicación utilizando Jetty, Tomcat o Spring Boot, entre otras alternativas.

Node.js continúa siendo una excelente plataforma, pero no necesitamos introducir un segundo entorno de ejecución si Java ya resuelve las necesidades del servidor.

¿Dónde queda React?

React resuelve problemas reales relacionados con interfaces complejas, componentes, estado y aplicaciones altamente interactivas.

Pero no todas las aplicaciones tienen esos problemas.

Para un sistema donde principalmente hacemos:

crear
editar
buscar
listar
paginar
eliminar
validar
imprimir

podemos preguntarnos si realmente necesitamos construir una aplicación React completa.

Muchas de esas operaciones pueden resolverse simplemente con:

HTML
 +
JavaScript
 +
fetch()

y un backend Java.

Una arquitectura completa

Finalmente podríamos terminar con algo como:

             NAVEGADOR
                 |
        HTML + CSS + JavaScript
                 |
              fetch()
                 |
                 v
        ┌─────────────────┐
        │      JAVA       │
        │                 │
        │ Servlet         │
        │       o         │
        │ Spring Boot     │
        └────────┬────────┘
                 |
              Service
                 |
                DAO
                 |
               JDBC
                 |
          Base de datos

Las respuestas pueden ser:

Java
 |
 +---- HTML
 |
 +---- JSON
 |
 +---- texto
 |
 +---- archivos

Y el navegador decide qué hacer con ellas.

Menos frameworks no significa menos dinamismo

Este es probablemente el punto más importante.

Una aplicación dinámica no es sinónimo de React.

Una petición asíncrona tampoco es sinónimo de una API REST.

Y utilizar Java en el backend no significa que cada operación tenga que provocar una recarga completa de la página.

Podemos utilizar tecnologías que existen directamente en Java y en el navegador:

Java
Servlets / Spring Boot
HTML
CSS
JavaScript
AJAX / fetch()
JDBC

y construir aplicaciones web perfectamente dinámicas.

Conclusión

No se trata de afirmar que Node.js o React sean innecesarios.

Ambos solucionan problemas reales y existen proyectos donde utilizarlos tiene mucho sentido.

La pregunta debería ser otra:

¿Los problemas que resuelven esas herramientas existen realmente en nuestra aplicación?

Si estamos desarrollando un sistema administrativo, un punto de venta, una aplicación de inventario o un sistema de facturación, puede que no necesitemos mantener dos proyectos, dos stacks y una arquitectura frontend completamente independiente.

Podemos mantener Java en el servidor, utilizar AJAX para actualizar solamente las partes necesarias de la página y elegir el formato de respuesta según cada situación:

HTML cuando necesitamos HTML.
JSON cuando necesitamos datos.
Texto cuando necesitamos texto.

A veces una llamada a fetch(), un Servlet y un response.getWriter() solucionan el problema completo.

La simplicidad también es una decisión de arquitectura.

Comentarios

Entradas populares de este blog

Boleta Electrónica en Chile: Los 3 Ambientes del SII para Desarrolladores

Configurando Servlets y JSP en Jetty

GUIA GENERAL DE GENERACION DE DOCUMENTOS TRIBUTARIOS ELECTRONICOS