2. ¿Que es una aplicación Multitenant? Aplicación diseñada para soportar múltiples clientes Compartir recursos de instalación Reducir costos Ej: GXportal, GXserver, Wiki, Blog, etc
3. ¿Cómo se diseña una aplicación Multitenant? El principal problema está en los datos No hay UNA respuesta Hay varias técnicas que hacen que la aplicación sea multitenant
4. ¿Cómo se diseña una aplicación Multitenant? Separated DB SeparatedSchema SharedSchema
5. Separated DB Enfoque más simple Se comparte la instalación y los programas instalados Facilita backups individuales Aumentan costos de mantenimiento Apropiada para un número reducido de tenants
6. SeparatedSchema Enfoque relativamente simple Cada tenant tiene sus tablas en su propio esquema en la misma DB CREATE SCHEMATenantNSchemaAUTHORIZATION TenantN ALTER USERTenantNWITH DEFAULT_SCHEMA = TenantNSchema Aumenta complejidad de administración de datos Apropiada para un número reducido de tablas (<100)
7. SharedSchema Los tenants comparten las tablas de la base de datos Cada tenant tiene su ID, y las consultas filtran los registros por ese ID SELECT * FROM Customer WHERETenantId = @TenantId Aumenta el costo de desarrollo ¿Qué sucede si me olvido de filtrar por TenantId? Mayor escalabilidad
8. ¿Cuál es la mejor? Cada una tiene sus ventajas y desventajas Cada una es la más apropiada en determinado contexto
9. ¿Quién lo dijo? “… La implementación sharedschema en 3 pasos es una roca!” @FedeAzzato Encontramos una solución para agregar el TenantId en todas las tablas, con cero impacto en tiempo de desarrollo Los programas generados no cambian en nada
10. Usando SQL Views y Triggers Los programas generados no se enteran que existe un campo TenantId Los SELECT, INSERT, UPDATEy DELETE se hacen contra las vistas Lo único que cambia es el CreateDB, y el string de conexión, para tener un usuario de DB para cada Tenant
11. Prueba de Concepto Definimos las siguientes transacciones: Customer, Product, Invoice El precio del producto varía en función del tiempo Creamos una DB singletenant, y otra con varios tenants Cargamos en cada tenant: 2000 clientes, 500 productos, 6000 facturas
13. Prueba de concepto SELECT T1.[CustomerId], T1.[CustomerName], COALESCE( T2.[CustomerTotalBought], 0) AS CustomerTotalBought, COALESCE( COALESCE( T3.[CustomerTotalBought], 0), 0) AS CustomerTotalBought FROM (([Customer] T1 WITH (NOLOCK) LEFT JOIN(SELECT SUM(COALESCE( T5.[InvoiceTotal], 0)) AS CustomerTotalBought, T4.[CustomerId] FROM ([Invoice] T4 WITH (NOLOCK) LEFT JOIN(SELECT SUM(COALESCE( T7.[InvoiceLineTotal], 0)) AS InvoiceTotal, T6.[InvoiceId] FROM ([InvoiceLines] T6 WITH (NOLOCK) INNER JOIN(SELECT T8.[InvoiceLineQuantity] * CAST(COALESCE( T9.[ProductPriceValue], 0) AS decimal( 14, 5)) AS InvoiceLineTotal, T8.[InvoiceId], T8.[ProductId] FROM ([InvoiceLines] T8 WITH (NOLOCK) LEFT JOIN(SELECT T10.[ProductPriceValue], T10.[ProductId], T12.[InvoiceId], T10.[ProductPriceDate], T11.[GXC3] AS GXC3, T13.[InvoiceDate] FROM ((([ProductPrices] T10 WITH (NOLOCK) INNER JOIN [InvoiceLines] T12 WITH (NOLOCK) ON T12.[ProductId] = T10.[ProductId]) LEFT JOIN(SELECT MAX(T14.[ProductPriceDate]) AS GXC3, T14.[ProductId], T15.[InvoiceId] FROM (([ProductPrices] T14 WITH (NOLOCK) INNER JOIN [InvoiceLines] T15 WITH (NOLOCK) ON T15.[ProductId] = T14.[ProductId]) INNER JOIN [Invoice] T16 WITH (NOLOCK) ON T16.[InvoiceId] = T15.[InvoiceId]) WHERE T14.[ProductPriceDate] <= T16.[InvoiceDate] GROUP BY T14.[ProductId], T15.[InvoiceId] ) T11 ON T11.[ProductId] = T10.[ProductId] AND T11.[InvoiceId] = T12.[InvoiceId]) INNER JOIN [Invoice] T13 WITH (NOLOCK) ON T13.[InvoiceId] = T12.[InvoiceId]) WHERE (T10.[ProductPriceDate] = T11.[GXC3]) AND(T10.[ProductPriceDate] <= T13.[InvoiceDate]) ) T9 ON T9.[ProductId] = T8.[ProductId] AND T9.[InvoiceId] = T8.[InvoiceId]) ) T7 ON T7.[InvoiceId] = T6.[InvoiceId] AND T7.[ProductId] = T6.[ProductId]) GROUP BY T6.[InvoiceId] ) T5 ON T5.[InvoiceId] = T4.[InvoiceId]) GROUP BY T4.[CustomerId]) T2 ON T2.[CustomerId] = T1.[CustomerId]) LEFT JOIN(SELECT SUM(COALESCE( T5.[InvoiceTotal], 0)) AS CustomerTotalBought, T4.[CustomerId] FROM ([Invoice] T4 WITH (NOLOCK) LEFT JOIN(SELECT SUM(COALESCE( T7.[InvoiceLineTotal], 0)) AS InvoiceTotal, T6.[InvoiceId] FROM ([InvoiceLines] T6 WITH (NOLOCK) INNER JOIN(SELECT T8.[InvoiceLineQuantity] * CAST(COALESCE( T9.[ProductPriceValue], 0) AS decimal( 14, 5)) AS InvoiceLineTotal, T8.[InvoiceId], T8.[ProductId] FROM ([InvoiceLines] T8 WITH (NOLOCK) LEFT JOIN(SELECT T10.[ProductPriceValue], T10.[ProductId], T12.[InvoiceId], T10.[ProductPriceDate], T11.[GXC3] AS GXC3, T13.[InvoiceDate] FROM ((([ProductPrices] T10 WITH (NOLOCK) INNER JOIN [InvoiceLines] T12 WITH (NOLOCK) ON T12.[ProductId] = T10.[ProductId]) LEFT JOIN(SELECT MAX(T14.[ProductPriceDate]) AS GXC3, T14.[ProductId], T15.[InvoiceId] FROM (([ProductPrices] T14 WITH (NOLOCK) INNER JOIN [InvoiceLines] T15 WITH (NOLOCK) ON T15.[ProductId] = T14.[ProductId]) INNER JOIN [Invoice] T16 WITH (NOLOCK) ON T16.[InvoiceId] = T15.[InvoiceId]) WHERE T14.[ProductPriceDate] <= T16.[InvoiceDate] GROUP BY T14.[ProductId], T15.[InvoiceId] ) T11 ON T11.[ProductId] = T10.[ProductId] AND T11.[InvoiceId] = T12.[InvoiceId]) INNER JOIN [Invoice] T13 WITH (NOLOCK) ON T13.[InvoiceId] = T12.[InvoiceId]) WHERE (T10.[ProductPriceDate] = T11.[GXC3]) AND(T10.[ProductPriceDate] <= T13.[InvoiceDate]) ) T9 ON T9.[ProductId] = T8.[ProductId] AND T9.[InvoiceId] = T8.[InvoiceId]) ) T7 ON T7.[InvoiceId] = T6.[InvoiceId] AND T7.[ProductId] = T6.[ProductId]) GROUP BY T6.[InvoiceId] ) T5 ON T5.[InvoiceId] = T4.[InvoiceId]) WHERE YEAR(T4.[InvoiceDate]) = @YearGROUP BY T4.[CustomerId] ) T3 ON T3.[CustomerId] = T1.[CustomerId]) WHERE COALESCE( COALESCE( T3.[CustomerTotalBought], 0), 0) > 0 ORDER BY T1.[CustomerName]
Una aplicación multitenant es una aplicación diseñada para soportar múltiples clientes, con el objetivo principal de compartir los recursos de hardware y software de la instalación, con el objetivo final de reducir los costos de agregar nuevos clientes.
El principal problema está en la separación de datos. No queremos que un tenant vea accidentalmente los datos de otro.No existe una única forma de diseñar una aplicación multitenant. Existen 2 enfoques opuestos bien definidos, pero hay muchas variantes que también se consideran aplicaciones multitenant.Por un lado, en un extremo tenemos una solución que define una separación absoluta de los datos, almacenando los datos de cada tenant en una base de datos distinta. En este caso, como hablamos de compartir los recursos de instalación, las distintas bases de datos están hosteadas en un mismo DBMS.En el extrmo opuesto, tenemos una única base de datos, donde los programas tienen parametrizadas las consultas, para filtrar los datos según el tenant que corresponda.
Entreestas variantes, hay 3 enfoques que son los más interesantes.Separated DB: Esta es la alternativa que les mencionaba recién, en la que cada tenant tiene su propia base de datos.SeparatedSchema: En este enfoque se tiene una sola base de datos para todos los tenants, pero se hace una separación de los datos de los tenants utilizando esquemas.SharedSchema: En este enfoque se tienen los datos de todos los tenants en un mismo esquema, y cada tabla tiene un campo adicional al que llamaremos TenantId, el cual se utiliza para identificar a que tenant le pertenece el registro.
Tener una base de datos para cada tenant, es el enfoque más simple para implementar este tipo de soluciones. Esta solución implica compartir la instalación de los programas y recursos de hardware.El tener los datos separados en distintas bases de datos por tenant, permite la oferta de servicios premium, como por ejemplo hacer backups programados, o una cantidad variable de backups por tenant.El problema de esta solución es que escala menos que las otras. O sea, las bases de datos están separadas, pero el DBMS se comparte. Entonces el límite la cantidad de tenants soportados va a estar dado por la cantidad máxima de bases de datos abiertas que soporte el DBMS.Este tipo de soluciones puede ser requerida por clientes en ámbitos financieros, o de salud, donde la política de administración de datos no les permite compartir la base de datos con otros.
Este enfoque es relativamente simple de implementar. En esta solución, cada tenant tiene sus propias tablas, separadas de las tablas de otros tenants, compartiendo la misma base de datos. La separación se realiza teniendo un esquema y un usuario en la base de datos para cada tenant.De esta forma cuando un tenant hace un “SELECT * FROM Customer”, lo que está haciendo es consultar la tabla Customer del esquema que tiene asignado su usuario de base de datos.La complejidad de administración de la base de datos aumenta, ya que ahora los backups se realizan para todos los tenants en conjunto, y es muy complejo recuperar los datos de un único tenant.Este enfoque permite acomodar una cantidad mayor de tenants por base de datos, pero es necesario que los tenants tengan un número reducido de tablas.
Este enfoque utiliza una única base de datos, y un único esquema dentro de la base de datos. Cada tabla tiene una columna TenantId, en la que se indica a qué tenant le pertenece el registro.Esta solución es la que tiene un mayor impacto en el tiempo y costo de desarrollo, pero su principal beneficio es que es la solución que puede acomodar el mayor número de tenants.
Cada enfoque tiene sus claras ventajas y desventajas. En determinado contexto una solución va a ser mejor que las otras, pero en otro contexto distinto no.En la gráfica vemos una comparación del costo en función del tiempo para los enfoques de datos aislados (ya sea por bases separadas, o por esquemas separados), contra el enfoque de esquema compartido.
Este es un fragmento de una frase recientemente twitteada por un integrante del equipo de desarrollo.
La solución que estamos investigando es la de utilizar vistas para acceder a la base de datos, filtrando en la vista los registros visibles por el tenant que ejecuta la consulta.La estructura y nombre de la vista sería igual a la tabla definida por GX.La tabla real, tendría la misma estructura definida por GX, y además el campo TenantId como parte de la PK.
Tener una base de datos para cada tenant, es el enfoque más simple para implementar este tipo de soluciones. Esta solución generalmente implica compartir la instalación de los programas y recursos de hardware.El tener los datos separados en distintas bases de datos por tenant, permite la oferta de servicios premium, como por ejemplo hacer backups programados, o una cantidad variable de backups por tenant.El problema de esta solución es que escala menos que las otras. O sea, las bases de datos están separadas, pero el DBMS se comparte. Entonces el límite la cantidad de tenants soportados va a estar dado por la cantidad máxima de bases de datos abiertas que soporte el DBMS.Este tipo de soluciones puede ser requerida por clientes en ámbitos financieros, o de salud, donde la política de separación de datos no les permite compartir la base de datos con otros.