De regreso al futuro: páginas web del lado del servidor con Kotlin (pt. 1)

Compartir esta publicación

Introducción: Páginas web del lado del servidor con Kotlin

El desarrollo web ha experimentado diversos cambios desde que Internet se popularizó en los años noventa:

  • Primero llegó lo más básico de lo básico: páginas HTML que se renderizaban de forma completamente estática, sin dinamismo alguno.
  • Más tarde llegaron tecnologías como la Common Gateway Interface, que permitía generar código HTML para una página web mediante programación.
  • Luego llegaron motores de plantillas como JavaServer Pages (ahora Jakarta Server Pages), ASP.NET y Thymeleaf, que permitieron a los desarrolladores trabajar con archivos de plantillas de aspecto predominantemente «HTML» con código de programación entremezclado.
  • Luego vinieron los frameworks de «scripting del lado del cliente» basados en Javascript, como Angular, React y Vue, que transformaron el desarrollo web en dos disciplinas separadas: el desarrollo «back-end», que contenía el servidor web tradicional y el código de lógica de negocio, junto con el desarrollo «front-end» (utilizando los frameworks antes mencionados), que se ocuparía de la visualización de un sitio web y recibiría datos del back-end.

Sin embargo, esto no quiere decir que las tendencias de desarrollo sólo avancen en una dirección y nunca hacia atrás. Por ejemplo, las bases de datos NoSQL como MongoDB ganaron popularidad rápidamente debido en gran parte a su capacidad para contener datos no estructurados en comparación con las bases de datos SQL tradicionales como PostgreSQL y MySQL, aunque estas últimas bases de datos también han evolucionado y ahora pueden contener datos no estructurados a través de los tipos de datos JSONB y JSON, respectivamente. Del mismo modo, nuevos frameworks de Javascript como Next.js están empezando a ofrecer opciones de renderizado del lado del servidor junto con sus ya tradicionales capacidades de renderizado del lado del cliente. Del mismo modo, los motores de plantillas del lado del servidor como Thymeleaf también han seguido evolucionando, y Thymeleaf lanzó una nueva versión del marco el pasado mes de diciembre.

Pero, ¿por qué seguir utilizando el renderizado del lado del servidor? Admitirlo -especialmente para un nuevo proyecto- podría provocar reacciones de desdén en el mundo del desarrollo web, donde la pregunta que se hace no es si se utilizan frameworks del lado del cliente como Angular, React o Vue, sino cuál de esos frameworks del lado del cliente se está utilizando. A pesar de ello, la generación de páginas web del lado del servidor presenta algunas ventajas en comparación con la generación de páginas web del lado del cliente:

  • Experiencia más ligera para el usuario final: en lugar de todas las librerías Javascript que se necesitan para ejecutar una aplicación del lado del cliente (junto con las partes móviles que se necesitan para ejecutar dicha aplicación), todo lo que se entrega al navegador de un usuario es el código HTML que el servidor ha generado junto con cualquier archivo Javascript y CSS auxiliar.
  • Las páginas web del lado del servidor pueden aprovechar más el procesamiento relacionado con la seguridad gracias a que el código que genera las páginas web tiene acceso directo a la funcionalidad de seguridad del servidor.
  • La optimización para motores de búsqueda (SEO) -aunque no es imposible de implementar con la renderización del lado del cliente- es más fácil de llevar a cabo con la renderización del lado del servidor.

Además, sigue habiendo opciones activas para «ir más atrás en el tiempo», es decir, utilizar motores de plantillas del lado del servidor. Por ejemplo, Thymeleaf -una de las opciones más conocidas para los motores de plantillas del lado del servidor basados en Java- también sigue evolucionando. Thymeleaf lanzó una nueva versión de su framework el pasado mes de diciembre y fue destacado en la conferencia Spring I/O 2022 de Barcelona. De nuevo la pregunta: ¿por qué elegir esto en lugar de las opciones anteriores? Además de las ventajas enumeradas anteriormente para el renderizado del lado del servidor en comparación con el renderizado del lado del cliente, los motores de plantillas ofrecen otros beneficios:

  • Habrá menos problemas potenciales de compatibilidad con respecto a si el navegador del usuario es capaz de ejecutar la funcionalidad Javascript necesaria para que la página web solicitada se renderice y realice sus funciones correctamente.
  • El desarrollo «full-stack» es más fácil porque no hay separación entre el código «back-end» y el «front-end». Además, al no haber una pila tecnológica separada para la funcionalidad de generación de páginas web, la curva de aprendizaje del proyecto es menor.

Teniendo esto en cuenta, ¿qué opciones tiene un desarrollador que quiera crear una aplicación web Spring Boot con renderizado del lado del servidor basado exclusivamente en Java? Hay bastantes opciones de motores de plantillas junto con Thymeleaf que uno podría aprovechar. Además de esta variedad de opciones, sin embargo, hay otra opción disponible en el ámbito del ecosistema Kotlin. Los desarrolladores del lenguaje de programación Kotlin han estado muy ocupados trabajando no sólo con el lenguaje en sí, sino también con una serie de bibliotecas de apoyo que aprovechan las capacidades del lenguaje para proporcionar nuevas herramientas a los desarrolladores. Una de estas bibliotecas es kotlinx.html, que, como su nombre indica, permite a los desarrolladores generar HTML de una forma «similar a Kotlin».

  CornerJob - iOS Objective-C app Un caso de exito

Acerca de kotlinx.html

En esencia, kotlinx.html crea un «Lenguaje Específico de Dominio» (DSL) para escribir código que genere o manipule código HTML. Por poner un ejemplo, he aquí un pequeño programa que lanza un servidor Node.JS y añade código HTML al cuerpo de la página web resultante:

fun main() {
   window.onload = { document.body?.sayHello() }
}


fun Node.sayHello() {
   append {
       div {
           div {
               onClickFunction = { event ->
                   window.alert("I was clicked - ${event.timeStamp}")
               }
               p {
                   +"This is a paragraph"
               }
           }
           a(href = "https://github.com/kotlin/kotlinx.html") {
               +"This is a link"
           }
       }
   }
}

El resultado es la siguiente página web:

D3 SMMij7ZDmwjK5pcX4zgfdEBgi2HxplexcXpPeB UbxlcE oAVccoGLoje OM3S1kdhQK4DIznd0UWjkkKKDzWcgb0T4TKYKIv5Z39EnInDAb9pYqnQ toOy4bfQAP7468KrSGiCrEXinaDJh4Vbk

Muy básico, pero como veremos más adelante, se puede generar código HTML más «realista» a través de esta librería. El ejemplo anterior todavía proporciona ejemplos de las partes clave de la funcionalidad de Kotlin que proporcionan la capacidad de escribir este DSL:

  • Funciones de extensión: Es posible crear una «nueva» función miembro para cualquier clase en Kotlin, incluyendo clases preexistentes como String o la clase Node en el código de ejemplo (que es una traducción básica de la clase Node en el modelo Web DOM). En esencia, se trata de azúcar sintáctico para definir una función que acepte una instancia de la clase de destino como primer parámetro de la función junto con el resto de parámetros definidos, pero la ventaja es que permite realizar llamadas a la función con un aspecto más limpio.
  • Funciones de alcance: Son funciones que aceptan una expresión lambda que toma el objeto sobre el que se ha llamado a la función como «receptor» – implícito a través de esta palabra clave o explícito – de cualquier llamada a función dentro de la lambda. Como demuestra el código, esto permite al desarrollador «incrustar» etiquetas HTML unas dentro de otras de forma similar al código HTML real.
  • Argumentos de función Named y Default: Cuando se declaran etiquetas HTML dentro del código Kotlin, es posible pasar argumentos a los atributos específicos de la etiqueta que necesitan configuración. La declaración de la etiqueta hyperlink lo demuestra especificando el atributo href y dejando que el resto de argumentos de la declaración de la etiqueta – target y classes – se rellenen por defecto de acuerdo con la definición de la función.
  • Sobrecarga de operadores: Kotlin permite la definición de funciones que utilizan operadores tradicionales como +, -, *, /, etc. Al igual que con las funciones de extensión, se trata de azúcar sintáctico para funciones que toman los argumentos implicados en la declaración dentro del código fuente. En el caso del código anterior, sirve para especificar cualquier valor de texto que deba colocarse dentro de la etiqueta HTML que lo encierra (nótese que la función text() también está disponible, y la función de operador + sirve aquí también como paso a text()).

En conjunto, estas características ponen de relieve lo potente que es la capacidad de Kotlin para crear DSLs para diversos fines. A quienes deseen ver otro ejemplo de esta funcionalidad en acción, se les anima a echar un vistazo al DSL de Kotlin para Gradle, una alternativa al lenguaje de marcado tradicional basado en Groovy para Gradle que ha estado en producción desde el lanzamiento de Gradle 5.0 en 2018.

Experimentando

Entonces, ¿cómo se compara la biblioteca kotlinx.html con un motor de plantillas tradicional como Thymeleaf? La forma más natural de demostrarlo es intentar construir el mismo sitio web potenciado por Spring Boot -un sitio web rudimentario con estilo Bootstrap para una librería hipotética que permita al usuario ver, añadir y eliminar libros y sus autores- utilizando los dos enfoques: uno que emplea Thymeleaf y otro que aprovecha kotlinx.html.

Thymeleaf

El planteamiento de Thymeleaf es relativamente sencillo:

  • En primer lugar, añade org.springframework.boot:spring-boot-starter-thymeleaf a la lista de dependencias del proyecto (en este caso, la versión 3.0.2).

A continuación, crea un archivo HTML de plantilla en el directorio resources/templates que contenga el código Thymeleaf necesario para la página web deseada:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
   <title>Bookstore - View Authors</title>
   <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
   <script th:src="@{/js/bootstrap.min.js}"></script>
   <script th:inline="javascript">
       function confirmDelete(name) {
           return window.confirm("Really delete author " + name + "?");
       }
   </script>
</head>
<body>
<div th:insert="~{fragments/general.html :: header(pageName = 'Authors')}"></div>
<div id="content">
   <h2>Our Books' Authors</h2>
   <ul>
       <li th:each="author : ${authors}">
           <form method="post" th:action="@{/authors/{authorId}/delete(authorId=${author.id})}"
                 style="margin-block-end: 1em;" th:onsubmit="return confirmDelete([[${author.name}]])">
               <a th:href="@{/authors/{authorId}(authorId=${author.id})}" th:text="${author.name}"></a>
               <button type="submit" class="btn btn-danger">Delete</button>
           </form>
       </li>
   </ul>
   <a class="btn btn-primary" th:href="@{/authors/add}">Add Author</a>
</div>
<div th:insert="~{fragments/general.html :: footer}"></div>
</body>
</html>

Este archivo sirve para ver todos los autores cuyos libros tiene disponibles la librería y se llama view_authors.html.

  • Cualquier fragmento de código Thymeleaf/HTML que se desee reutilizar -como los fragmentos de cabecera y pie de página que se utilizan en cada página web- puede colocarse en un archivo de plantilla HTML independiente, en este caso resources/templates/fragments/general.html:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head th:fragment="headerfiles">
   <meta charset="UTF-8"/>
   <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
   <script th:src="@{/js/bootstrap.min.js}"></script>
   <title></title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" th:fragment="header (pageName)">
   <div class="container-fluid">
       <a class="navbar-brand" href="/">Test Bookstore</a>
       <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarHeader">
           <span class="navbar-toggler-icon"></span>
       </button>


       <div class="collapse navbar-collapse" id="navbarHeader">
           <ul class="navbar-nav me-auto mb-2 mb-lg-0">
               <li class="nav-item">
                   <a th:if="${pageName} == 'Home'" class="nav-link active" href="/">Home</a>
                   <a th:unless="${pageName} == 'Home'" class="nav-link" href="/">Home</a>
               </li>
               <li class="nav-item">
                   <a th:if="${pageName} == 'Authors'" class="nav-link active" href="/authors">Authors</a>
                   <a th:unless="${pageName} == 'Authors'" class="nav-link" href="/authors">Authors</a>
               </li>
               <li class="nav-item">
                   <a th:if="${pageName} == 'Books'" class="nav-link active" href="/books">Books</a>
                   <a th:unless="${pageName} == 'Books'" class="nav-link" href="/books">Books</a>
               </li>
           </ul>
       </div>
   </div>
</nav>
<footer th:fragment="footer" class="footer mt-auto py-3 bg-light fixed-bottom">
       <p class="text-center">Copyright 20XX Bookstore Productions - All Rights Reserved</p>
</footer>
</body>
</html>

  • A continuación, crea un controlador web en el que los puntos finales devuelvan un valor de cadena que corresponda al nombre del archivo HTML (menos la extensión de archivo .html) que se creó en el paso anterior:

@Controller
@RequestMapping("/authors")
class AuthorController(private val authorService: AuthorService) {
   @GetMapping
   fun getAll(model: Model): String {
       model["authors"] = authorService.getAll()
       return "view_authors"
   }


   @GetMapping("/{id}")
   fun get(@PathVariable id: Int, model: Model): String {
       model["author"] = authorService.get(id)
       return "view_author"
   }


   @GetMapping("/add")
   fun add(model: Model): String {
       model["authorForm"] = AuthorForm()
       return "add_author"
   }


   @PostMapping("/save")
   fun save(@Valid authorForm: AuthorForm, bindingResult: BindingResult): String {
       return if (!bindingResult.hasErrors()) {
           authorService.save(authorForm)
           "redirect:/authors"
       } else {
           "add_author"
       }
   }


   @PostMapping("/{id}/delete")
   fun delete(@PathVariable id: Int): String {
       authorService.delete(id)
       return "redirect:/authors"
   }
}

El primer punto final -la ruta de petición /authors en el controlador- ordena al motor de plantillas de Thymeleaf que genere una página web de acuerdo con el archivo de plantilla view_authors.html; la variable authors invocada en el archivo de plantilla se suministra a través del objeto model que se pasa a la función del controlador. Tenga en cuenta que las redirecciones se realizan haciendo que el método del controlador devuelva el endpoint de destino (¡*no* el archivo de plantilla de Thymeleaf!) prefijado con redirect: como puede verse en el endpoint para eliminar un autor.

  • Con el fin de crear una página web para gestionar las respuestas de error, es necesario crear una página de plantilla llamada error.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
   <title>Bookstore - Error</title>
   <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
   <script th:src="@{/js/bootstrap.min.js}"></script>
</head>
<body>
<div th:insert="~{fragments/general.html :: header(pageName = 'Error')}"></div>
<h2>Oops!</h2>
<p th:text="'An error occurred and provided the status ' + ${status}"></p>
<div th:insert="~{fragments/general.html :: footer}"></div>
</body>
</html>

  • Al realizar peticiones a una instancia de este sitio we, se obtiene un tiempo medio de carga de ~43 ms y se utilizan 218 kilobytes de ancho de banda (principalmente para los archivos Javascript y CSS de Bootstrap):
  Stow: Terminal Alacritty

bE4AYuxV3TiMQPczu0GGH7HYHHEvOlEyIF5SuadU2D45t 4OZHcsP6kZm 6Q LhD8uNiqe2boyb7TO8weOQMuZWmkjy yt71prLzyWV52Gqd7ZDmWO6APomDEdIzxXSgtMYB11wDoGM0D0pM39X 9kc

Con estos pasos se puede crear un sitio web totalmente funcional; quienes deseen más información sobre funciones más avanzadas de Thymeleaf pueden consultar su sitio web aquí.

kotlinx.html

La creación de una aplicación web equivalente utilizando kotlinx.html requiere un flujo de trabajo diferente: como la biblioteca kotlinx.html genera código HTML directamente y no trabaja con archivos de plantilla HTML, todo el código relacionado con HTML puede colocarse directamente dentro del directorio fuente.

  • En primer lugar, añade org.jetbrains.kotlinx:kotlinx-html a la lista de dependencias del proyecto (en este caso, la versión 0.8.0).
  • A continuación, crea una clase Kotlin dentro de la estructura de clases que renderizará el código de la página web deseada:

@Service
class ViewAuthorsPageRenderer(private val authorService: AuthorService) {
   fun renderPage(): String {
       val authors = authorService.getAll()
       return writePage {
           head {
               title("Bookstore - View Authors")
               link(href = "/css/bootstrap.min.css", rel = "stylesheet")
               script(src = "/js/bootstrap.min.js") {}
               script(src = "/js/util.js") {}
           }
           body {
               header("Authors")
               div {
                   id = "content"
                   h2 { +"Our Books' Authors" }
                   ul {
                       authors.forEach { author ->
                           li {
                               form(method = FormMethod.post, action = "/authors/${author.id}/delete") {
                                   style = "margin-block-end: 1em;"
                                   onSubmit = "return confirmDelete('author', \"${author.name}\")"
                                   a(href = "/authors/${author.id}") {
                                       +author.name
                                       style = "margin-right: 0.25em;"
                                   }
                                   button(type = ButtonType.submit, classes = "btn btn-danger") { +"Delete" }
                               }
                           }
                       }
                   }
                   a(classes = "btn btn-primary", href = "/authors/add") { +"Add Author" }
               }
               footer()
           }
       }
   }
}

Observa que a parte de la diferencia obvia en cómo se genera el código HTML- las variables que requiere el código HTML (la variable authors, en este caso) se inyectan directamente en el código generado sin necesidad de un objeto model para transferir los datos desde el código back-end a un archivo HTML fuera de la estructura del código.

  • De forma similar, el código del fragmento se coloca dentro de un archivo de código común; esta es también la fuente de la función de ayuda writePage() para reducir el código boilerplate en cada función de renderizado de página.

inline fun writePage(crossinline block : HTML.() -> Unit): String {
   return createHTMLDocument().html {
       lang = "en"
       visit(block)
   }.serialize()
}


fun FlowContent.header(pageName: String) {
   nav(classes = "navbar navbar-expand-lg navbar-dark bg-dark") {
       div(classes = "container-fluid") {
           a(href = "/", classes="navbar-brand") { +"Test Bookstore" }
           button(classes = "navbar-toggler", type = ButtonType.button) {
               attributes["data-bs-toggle"] = "collapse"
               attributes["data-bs-target"] = "#navbarHeader"
               span(classes="navbar-toggler-icon")
           }
           div(classes="collapse navbar-collapse") {
               id = "navbarHeader"


               ul(classes = "navbar-nav me-auto mb-2 mb-lg-0") {
                   li(classes = "nav-item") {
                       a(classes = "nav-link${if (pageName == "Home") " active" else ""}", href = "/") { +"Home" }
                   }
                   li(classes = "nav-item") {
                       a(classes = "nav-link${if (pageName == "Authors") " active" else ""}", href = "/authors") { +"Authors" }
                   }
                   li(classes = "nav-item") {
                       a(classes = "nav-link${if (pageName == "Books") " active" else ""}", href = "/books") { +"Books" }
                   }
               }
           }
       }
   }
}


fun FlowContent.footer() {
   footer(classes = "footer mt-auto py-3 bg-light fixed-bottom") {
       p(classes = "text-center") { +"Copyright 20XX Bookstore Productions - All Rights Reserved" }
   }
}

  • A continuación, las funciones endpoint del controlador web devolverán un cuerpo de respuesta con el tipo MIME text/html. Es aquí donde se invocarán las clases que renderizan directamente el código HTML.

@Controller
@RequestMapping("/authors")
class AuthorController(
   private val viewAuthorsPageRenderer: ViewAuthorsPageRenderer,
   private val viewAuthorPageRenderer: ViewAuthorPageRenderer,
   private val addAuthorPageRenderer: AddAuthorPageRenderer,
   private val authorService: AuthorService
) {
   @GetMapping(produces = [TEXT_HTML])
   @ResponseBody
   fun getAll() = viewAuthorsPageRenderer.renderPage()


   @GetMapping(value = ["/{id}"], produces = [TEXT_HTML])
   @ResponseBody
   fun get(@PathVariable id: Int) = viewAuthorPageRenderer.renderPage(id)


   @GetMapping(value = ["/add"], produces = [TEXT_HTML])
   @ResponseBody
   fun add(): String = addAuthorPageRenderer.renderPage()


   @PostMapping(value = ["/save"], produces = [TEXT_HTML])
   @ResponseBody
   fun save(@Valid authorForm: AuthorForm, bindingResult: BindingResult, httpServletResponse: HttpServletResponse): String {
       return if (!bindingResult.hasErrors()) {
           authorService.save(authorForm)
           httpServletResponse.sendRedirect("/authors")
           ""
       } else {
           val errors = bindingResult.allErrors.toFieldErrorsMap()
           addAuthorPageRenderer.renderPage(errors)
       }
   }


   @PostMapping(value = ["/{id}/delete"])
   fun delete(@PathVariable id: Int, httpServletResponse: HttpServletResponse) {
       authorService.delete(id)
       httpServletResponse.sendRedirect("/authors")
   }
}

Ten en cuenta que la redirección de peticiones web también funciona de forma diferente en este enfoque. En lugar de devolver una cadena con un punto final prefijado con redirect:, es necesario invocar la función HttpServletResponse.sendRedirect().Esto anulará cualquier cuerpo de respuesta devuelto por la función, lo que significa que la cadena vacía devuelta en la función del controlador save() se ignora en última instancia.

  • La gestión de errores en este enfoque requiere un poco más de código. Es necesario crear un controlador independiente y marcarlo como controlador general de gestión de errores:

@Controller
class BookstoreErrorController(private val errorPageRenderer: ErrorPageRenderer) : ErrorController {
   @RequestMapping("/error", produces = [TEXT_HTML])
   @ResponseBody
   fun handleError(request: HttpServletRequest): String {
       val statusCode = request.getAttribute("jakarta.servlet.error.status_code") as Int
       return errorPageRenderer.renderPage(statusCode)
   }
}

Después de lo cual se requiere el mismo flujo de trabajo que el anterior: crear una clase que renderizará el código HTML y lo devolverá como cuerpo de respuesta para el endpoint de gestión de errores.

@Service
class ErrorPageRenderer {
   fun renderPage(status: Int): String {
       return writePage {
           head {
               title("Bookstore - Add Author")
               link(href = "/css/bootstrap.min.css", rel = "stylesheet")
               script(src = "/js/bootstrap.min.js") {}
           }
           body {
               header("Error")
               h2 { +"Oops!" }
               p { +"An error occurred and provided the status $status" }
               footer()
           }
       }
   }
}

  • Si se realizan peticiones a una instancia de este sitio web en caliente, se obtiene un tiempo medio de carga similar de ~43 ms y se utilizan 219 kilobytes de ancho de banda (de nuevo, principalmente para los archivos Javascript y CSS de Bootstrap):
  Flutter Vanilla State Management

rUMCsoN1auWPO05nGVK9M UTCpv3nwzKZzRP4KmkVUqS0DFusMCjFVZeePwx9jBuuP1oC 6LRIZdXGexT XhfrJZbECe7fqFSB6R7IFpJpQ

Observaciones

En comparación con Thymeleaf, kotlinx.html ofrece algunas ventajas interesantes para la generación de páginas web del lado del servidor:

  • El Kotlin basado en DSL para generar código HTML en kotlinx.html es mucho menos verboso que los archivos de plantilla HTML necesarios para Thymeleaf.
  • Es posible aprovechar la seguridad de tipos de Kotlin y las llamadas a parámetros/funciones para asegurarse de que se está escribiendo código HTML «correcto» con kotlinx.html en tiempo de compilación, mientras que los errores tipográficos en el código HTML con Thymeleaf sólo se descubren en tiempo de ejecución.
  • Naturalmente, toda la funcionalidad base de Kotlin, funciones inline, seguridad null, funciones de extensión, etc…están disponibles para el desarrollador también al escribir el código de generación HTML.
  • La incorporación del código de generación HTML directamente en la estructura de clases del proyecto -en contraposición a los dos pasos de hacer que los archivos de plantilla HTML residan en el directorio de recursos para Thymeleaf- significa un flujo de código más intuitivo y elimina la posibilidad de errores como olvidar transferir variables desde el controlador a los archivos de plantilla HTML que está presente en Thymeleaf.

Sin embargo, kotlinx.html también presenta algunos inconvenientes en comparación con Thymeleaf:

  • El inconveniente más obvio es que kotlinx.html es una tecnología muy nueva en comparación con Thymeleaf (en el momento de escribir este artículo aún se encuentra en fase beta, en comparación con los años que tiene Thymeleaf como biblioteca lista para producción), lo que significa no sólo mayores posibilidades de errores, sino también mucho menos apoyo de la comunidad y conocimiento general.
  • Existen algunas diferencias importantes entre la forma de declarar los atributos de las etiquetas en el código HTML y en el DSL kotlinx.html. Por ejemplo, el atributo id de las etiquetas HTML debe declararse dentro de la función scope que acompaña a la declaración de la etiqueta, en lugar de dentro de la propia declaración de la etiqueta (ve la declaración div id de «navbarHeader» en el kotlinx.html anterior).
  • Escribir scripts y bloques de estilo dentro del código HTML es un poco torpe debido a cómo kotlinx.html auto-escapes de texto normal y puede ser mejor evitar en favor de declarar el código en un archivo separado. Esto explica la referencia a util.jsen ViewAuthorsPageRenderer (y tener que descargarlo en la petición web posterior) en lugar de incrustar el código Javascript para confirmar la eliminación de un autor.
  • La otra cara de tener el código de generación HTML directamente incorporado en la estructura de clases del proyecto significa que no hay «recarga en caliente» disponible: si se descubre un error en el código HTML, todo el servidor tendrá que ser recargado también, algo que puede ralentizar el tiempo de desarrollo.

A pesar de estos problemas, los puntos fuertes de kotlinx.html justifican que sea considerado por cualquier desarrollador que esté buscando una alternativa al desarrollo web del lado del cliente para su aplicación, ya que imparte los mismos puntos fuertes que han hecho de Kotlin un ciudadano de primera clase en ecosistemas JVM como Spring Boot, Gradle, y más en el desarrollo de páginas web. Más herramientas para resolver problemas de desarrollo son siempre una ventaja en comparación con menos herramientas, y quién sabe – ¡puede que incluso hagas el desarrollo de páginas web más «divertido»!

A continuación

Como se ha mencionado anteriormente, el hecho de que el código de generación HTML de kotlinx.html se encuentre dentro de la estructura de clases del proyecto podría traducirse en tener que esperar segundos (¡o más!) a que la aplicación web Spring Boot se reinicie antes de poder ver cualquier cambio realizado en el código HTML, en comparación con la «recarga en caliente» que está disponible en motores de plantillas como Thymeleaf. Pero, ¿y si no fuera así? El próximo artículo de esta serie de dos partes sobre «Páginas web del lado del servidor con Kotlin» explorará otra tecnología de Kotlin que podría resolver este problema. Mantente atento al blog de Apiumhub porque se publicará en breve.

Author

  • Severn Everett

    Software engineer with more than 10 years of experience working with different technologies such as Java, Docker, Kubernetes, React, CDK, Kotlin.... With high ability to deal with different environments and technologies.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Suscríbete a nuestro boletín de noticias

Recibe actualizaciones de los últimos descubrimientos tecnológicos

¿Tienes un proyecto desafiante?

Podemos trabajar juntos

apiumhub software development projects barcelona
Secured By miniOrange