Get it on Google Play
Tu partner Freelance para tus proyectos IT

Liferay en una arquitectura de microservicios | Edge Service, API y Headless

16-07-2020

Hace unos meses escribí un post llamado “Liferay en una arquitectura de microservicios” donde describía como montar una arquitectura de Liferay mas orientada a microservicios. El post tuvo mucho éxito y varias personas me pidieron un ejemplo(Así que aquí lo tenéis).

Las ventajas de esta arquitectura son varias: Nos permite crear aplicaciones sobre Liferay en cualquier lenguaje de programación(Algo que Liferay no lleva nada bien), podremos hacer upgrades de Liferay sin recompilar y redesplegar los plugins(Algo que lleva mucho trabajo) o podremos escalar/desescalar nuestra aplicación independientemente de Liferay. Nuestra aplicación, además, será mucho mas portable, ya que los actuales portlets de Liferay tienen una gran dependencia del portal(Nunca se sabe cuando querrás que tu aplicación corra sobre Drupal o Wordpres, por ejemplo).

Esta es la pinta de lo que queremos implementar:

Arquitectura Liferay | Microservicios, Edge Service y NodeJS

Arquitectura Liferay | Microservicios, Edge Service y NodeJS

Aquí la idea es desarrollar un plugin como un microservicio que insertaremos en la página de Liferay como si de un widget de Twitter se tratara(Con un pequeño script HTML). Al recibir las peticiones, este microservicio/plugin se conectará a Liferay a través de su API para recoger información. En este caso, vamos a desarrollar un plugin que muestre datos del usuario logueado(Portrait, nombre, email, roles, etc.).

Este ejemplo es fácilmente adaptable a gran cantidad de casos. También es fácilmente adaptable para que funcione sobre Drupal, WordPress o cualquier otra plataforma.

Instalación de Liferay

Vamos ha arrancar una instancia de Liferay simplemente descargando y descomprimiendo el portal. La única configuración especial es la que vamos a poner en el fichero ‘portal-ext.properties’ para informar a Liferay que va a funcionar por el puerto 4040(Aunque de momento funcione por el 8080 ya que 4040 será el puerto de nuestro Edge Service):

web.server.http.port=4040

Y ya podremos arrancar Liferay.

Instalación del Edge Service con Nginx y Docker

Nginx lo arrancaremos como contenedor Docker para ir mas rápido, crearemos el siguiente fichero de configuración ‘default.conf’:

server {
    listen 4040;
    server_name midominio.com;
 
    location / {
        proxy_pass  http://192.168.1.46:8080;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_redirect off;
        proxy_buffering off;
        proxy_set_header        Host            $host;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        }

    location /liferayplugin {
        proxy_pass  http://192.168.1.46:3000;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_redirect off;
        proxy_buffering off;
        proxy_set_header        Host            $host;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

Como veis es tan solo un proxy que escucha por el puerto 4040 y que envía todas las peticiones al Liferay(http://192.168.1.46:8080), excepto las que van destinadas a ‘/liferayplugin’ que las envía a nuestro microservicio en NodeJS(http://192.168.1.46:3000).

Para arrancar el contenedor con esta configuración:

sudo docker run \
    -p 4040:4040 \
    --name nginx2 \
    -v /path/a/mi/fichero/default.conf:/etc/nginx/conf.d/default.conf:ro \
    -d nginx:alpine

Desarrollo microservicio Liferay | Inicialización del proyecto

Como en todas las aplicaciones NodeJS empezaremos inicializando el proyecto e instalando las dependencias(Que son unas cuantas):

npm init
npm install express btoa body-parser node-fetch

Crearemos los siguientes ficheros y carpetas:

  • Fichero /index.js : Contendrá el código de nuestro microservicio, lo veremos mas adelante.
  • Fichero /public/liferayplugin/css/main.css : Contendrá el CSS de nuestro plugin, para que quede bonito. Lo veremos mas adelante.
  • Fichero /public/liferayplugin/js/main.js : Contendrá el JS cliente de nuestro plugin, lo veremos mas adelante.

Desarrollo microservicio Liferay | CSS del proyecto

Esta es, sin duda, la parte mas liviana, pero la dejo aquí para que la tengáis(fichero /public/liferayplugin/css/main.css):

.nodejs-plugin {
	margin: 30px;
	border: 1px solid #ccc;
	border-radius: 4px;
	background-color: #eee;
	text-align: center;
	padding: 30px;
	}

.nodejs-plugin .pbox {
	display: inline-block;
	text-align: left;
	width: 300px;
	vertical-align: top;
	}

.nodejs-plugin h4 {
	margin-top: 15px;
	margin-bottom: 15px;
	}

Desarrollo microservicio Liferay | JS público del proyecto

Esto ya se pone interesante, Os dejo el código comentado:

/* Función que hace petición POST y escribe la respuesta en un elemento HTML con id 'liferayplugin1'  */
function execute( url, reg ) {
	var post=JSON.stringify(reg);

	fetch( url, {
		  method: 'POST', 
		  body: post,
		  headers: {
			'Accept': 'application/json',
			'Content-Type': 'application/json'
			},		  
		} ).then(function(response) {
			response.text().then(function(p) {
				document.getElementById("liferayplugin1").innerHTML=p;
	        	});
	    });		
	}

/* Función que se ejecuta al inicio llamando a nuestro microservicio con dos parámetros 'p_auth' que no vamos a usar y el Id del usuario actual 'user_id' */
function initPlugin() {
	execute( "/liferayplugin", {
		"p_auth": Liferay.authToken,
		"user_id": Liferay.ThemeDisplay.getUserId()
		} );
	}

initPlugin();

El código es muy simple y fácil de seguir, la única parte que no es portable a cualquier otro CMS es ‘Liferay.ThemeDisplay.getUserId()’ que obtiene el Id del usuario. Pero como ya hemos comentado es fácil de adaptar.

Desarrollo microservicio Liferay | JS backend del proyecto

Aquí es donde hay bastante ‘chicha’ (fichero /index.js):

/* Dependencias */
const btoa = require('btoa');
const express = require('express');
const bodyParser = require('body-parser')
const fetch = require('node-fetch');

/* Inicializamos Express */
const app = express();
app.use(express.static('public'));
app.use(bodyParser.json());

/* Datos para conectarnos a la API de Liferay */
const API_HOST="http://127.0.0.1:4040/api/jsonws";
const API_USERID="[email protected]";
const API_PASS="test";

/* Cuando recibimos una petición POST a '/liferayplugin' leemos los datos de la API de liferay y maquetamos la respuesta, SI! mejor con templates... */
app.post('/liferayplugin', function (req, res) {
	var s="<div class='nodejs-plugin'><h3>NodeJS Plugin</h3>";
	var e="</div>";

	s+="<link href='/liferayplugin/css/main.css' rel='stylesheet' type='text/css' />";

	if ( req.body.user_id == 20103 ) {
		res.end( s+"<p>Usuario No logueado</p>"+e);

		return;
		}

	getUserData( req.body.user_id, function(userRoles,user) {

				s+="<div class='pbox' style='text-align: center; padding-top: 30px;'>";
				s+=" <img src='/image/user_portrait?img_id="+user.portraitId+"'/>";
				s+="</div>";

				s+="<div class='pbox'>";
				s+=" <h4>User</h4>";
				s+=" <ul>";
				s+="  <li>Name: "+user.firstName+" "+user.lastName+"</li>";
				s+="  <li>eMail: "+user.emailAddress+"</li>";
				s+="  <li>Language: "+user.languageId+"</li>";
				s+="  <li>Last login IP: "+user.loginIP+"</li>";
				s+=" </ul>";
				s+="</div>";

				s+="<div class='pbox'>";
				s+=" <h4>User Roles</h4>";
				s+=" <ul>";
				for ( var i=0; i<userRoles.length; i++ )
					s+="  <li>"+userRoles[i].name+"</li>";
				s+=" </ul>";
				s+="</div>";

				res.end( s+e );
				} );
	});

/* Arrancamos servidor web por el puerto 3000 */
app.listen(3000, function () {
    console.log('Sistema armado en el puerto 3000!');
    });

/* Función que lee los datos del API de Liferay, es llamada cada vez que recibimos una petición */
async function getUserData( userId, f ) {

	var response = await fetch( API_HOST+"/role/get-user-roles", {
		  method: 'POST', 
		  headers: { 
		  	'Authorization': 'Basic '+btoa( API_USERID+':'+API_PASS ), 
		  	'Content-Type': 'application/x-www-form-urlencoded'
		  	},
		  body: "userId="+userId		  
		} );	

	var userRoles = await response.json();	

	response = await fetch( API_HOST+"/user/get-user-by-id", {
		  method: 'POST', 
		  headers: { 
		  	'Authorization': 'Basic '+btoa( API_USERID+':'+API_PASS ), 
		  	'Content-Type': 'application/x-www-form-urlencoded'
		  	},
		  body: "userId="+userId		  
		} );

	var user = await response.json();

	console.log( userRoles );
	console.log( user );

	f( userRoles, user );
	}

Para consultar la oferta del API Rest de Liferay podéis hacerlo en la URL ‘/api/jsonws’, en este caso no estamos usando ni el API GraphQL ni mucho menos la XML/Soap.

Arrancamos el microservicio NodeJS con la instrucción ‘node index.js’

Insertar nuestro plugin/microservicio en una página

La inserción del plugin en una página la haremos como contenido web simplemente insertando este pedacito de código:

<div id="liferayplugin1">Cargando</div>
<script data-senna-off="true" src='/liferayplugin/js/main.js'></script>

Como veis es una arquitectura super simple y que abre muchas puertas ya que además de las ventajas que hemos mencionado arriba también hace que nuestros super proyectos sean mucho mas simples de gestionar.

Espero que os haya gustado y espero vuestros comentarios.

Si te ha servido, por favor comparte

Interesado en formación Liferay?

 

Leave a Reply