Thursday, January 12, 2012

Aplicaciones Multi-Tenant en Grails usando Multi-Tenant-Core plugin y Single-Tenant Mode

Para hacer que una aplicación pueda ser usada por varios clientes (o tenants) en grails existe un plugin para grails que permite hacer esta tarea de forma sencilla, simplemente haciendo un par de configuraciones dentro de la aplicación y en la base de datos.



Opciones de Configuración

  • Este plugin se puede usar de dos maneras distintas

  • Tener una sola base de datos con un campo en cada tabla que identifique a que cliente pertenece cada set de datos (multi-tenant)
  • Tener una base de datos independiente por cada cliente y resolver de alguna manera a que base de datos apuntar, según el cliente que este ingresando (Single-Tenant). Esta configuración es las que se va a cubrir en este artículo.


Características de Configuración

En este artículo se va a cubrir como usar el plugin con las siguientes características:
  • Modo Single-Tenant
  • Resolución del cliente que esta accediendo a través de DNS, y que los DNS esten almacenados en la base de datos
  • Resolución del datasource (base de datos) a la que se debe conectar cada cliente. Esta información estará almacenada en base de datos
  • La aplicación tendrá una base de datos "primaria" y varias bases de datos "secundarias" cada una para cada cliente.
La base de datos "primaria" solamente es requerida para el momento de desplegar la aplicación y para contener la información de que base de datos usa cada cliente y cual es la url del origen de datos.


Pasos de Configuración

Para usar el plugin con las caracterísitcas mencionadas, hay que seguir los siguientes pasos:

1. Instalar el plugin Multi-Tenant Plugin (Core)
2. Agregar las siguientes líneas en el archivo conf/Config.groovy
tenant {
    mode = "singleTenant" //Modo = Single tenant
 datasourceResolver.type = "db" //URL Datasources almacenadas en base de datos
 resolver.request.dns.type = "db" //DNS almacenados en base de datos
  
}
3. Correr el siguiente comando, para crear la tabla donde se van a almacenar la correspondencia entre los clientes y los datasources
grails create-data-source-map
La tabla creada (data_source_tenant_map) tiene los campos mapped_tenant_id y data_source. mapped_tenant_id es un consecutivo que identifica a cada cliente, mientras que data_source es la url JNDI que identifica un recurso para conectarse a una base de datos. Esta url es de la forma java:comp/env/jdbc/<cliente>
Dado que la url JNDI identifica un recurso la forma de crear un recurso en ambiente de desarrollo al de producción es distinto
  • En desarrollo: Para el ambiente de desarrollo se debe incluir una sección en el archivo conf/Config.groovy que contenga la configuración para conectarse a la base de datos. Por ejemplo si la url JNDI es java:comp/env/jdbc/cliente1 , la sección que se debe incluir en el Config.groovy es la siguiente:
grails.naming.entries = [
 "jdbc/cliente1": [
  type: "javax.sql.DataSource",
  auth: "Container",
  description: "<descripcion>",
  driverClassName: "<driver>",//Por ejemplo org.postgresql.Driver
  url: "<url jdbc de conexion>",  //Por ejemplo jdbc:postgresql://localhost:5432/cliente1
  username: "<usuario>",
  password: "<password>"

 ]
]
  • En producción: En producción es necesario que el contenedor de aplicaciones sea quien suministre el recurso. Por ejemplo en Tomcat es necesario incluir las siguientes lineas en el archivo context.xml
<Resource name="jdbc/cliente1" auth="Container"
          type="javax.sql.DataSource" driverClassName="<driver>"
          url="<url jdbc de conexion>"

          username="<usuario>" password="<password>" maxActive="20" maxIdle="10" maxWait="-1"/>


4. Correr el siguiente comando, para crear la tabla donde se van a almacenar la correspondencia entre los clientes y los dns
grails create-dns-map

La tabla creada (domain_tenant_map) tiene los campos domain_name, mapped_tenant_id y name. mapped_tenant_id es un consecutivo que identifica a cada cliente, y debe corresponder para el mismo cliente, con el id almacenado en la tabla data_source_tenant_map. domain_name es el DNS o nombre de dominio al que va a responder cada cliente. Por ejemplo si la aplicacion esta en el servidor ejemplo.com, la url de cada cliente puede ser cliente1.ejemplo.com, cliente2.ejemplo.com, etc. name es un campo para identificar a cada cliente (alias).

5. Configurar los DNS
Una vez que se tienen pobladas las tablas domain_tenant_map y data_source_tenant_map, simplemente basta con configurar el DNS para que apunte a la misma aplicación, así provengan de clientes diferentes.
  • En Desarrollo: Basta con incluir una línea en el archivo de hosts que apunte a la ip local bajo distintos alias, por ejemplo:
127.0.0.1    cliente1
127.0.0.1    cliente2
Si nuestra aplicación foo se encuentra corriendo en el puerto 8080, la forma de acceder a la aplicación entonces sería:
cliente1:8080/foo
cliente2:8080/foo
 

  • En producción: Simplemente se debe configurar para que los distintos subdominios, apunten a la misma dirección IP donde esta la aplicación, análogo al archivo de hosts.


Poblar Bases de Datos Secundarias

El principal problema que se puede presentar con esta configuración, es que cada vez que se despliega la aplicación, solamente se ejecuta el bootsrap de grails sobre la base de datosprimaria, por lo tanto, así se configuren en las tablas adecuadamente los nuevos clientes y se cree la base de datos correspondiente, esta nueva base de datos no va a a poblarse con las tablas y campos que demanda la aplicación.
La forma más sencilla de solucionar esto, es "correr" solamente el bootstrap de la aplicación sobre la base de datos requerida. Para ello hay que realizar los siguientes pasos
1. Permitir que la aplicación lea configuraciones externas. Esto nos va a permitir usar un archivo de configuración arbitrario que contenga la información de la base de datos que queremos poblar.
Para lograr esto basta con agregar la siguiente linea en el principio del archivo conf/Config.groovy
grails.config.locations = [ "classpath:${grails.util.Environment.current.name}-config.properties"]
En este caso, la aplicación buscara un archivo llamado <environment>-config.propierties dentro del classpath, para saber que configuración de base de datos debe utilizar de acuerdo al entorno en el que se ejecute. Por ejemplo si se ejecuta la aplicación como dev, buscara un archivo develompent-config.properties.
Este archivo debe contener las siguientes líneas:
dataSource.pooled = true
dataSource.driverClassName = <driver>

dataSource.dbCreate = update
dataSource.url = <url jdbc de conexion>
dataSource.username = <usuario>
dataSource.password = <password>

2. Crear un script en la aplicación de grails. Para correr un script (archivo .groovy en la carpeta scripts dentro de grails-app), la aplicación debe desplegar el bootstrap, para poder ser ejecutado, lo cual es justamente lo que se necesita para que la aplicación misma poble la base de datos. El script puede contener simplemente una línea como la siguiente:
println "DB Created"

3. Ejecutar el script con la configuración del entorno que corresponde a la base de datos que se necesita poblar. Para esto basta con ejecutar el siguiente comando de grails:
grails -Dgrails.env=<environment> run-script scripts/MiScript.groovy