Glosario de términos
Antes de empezar a desglosar todos los aspectos que se necesitan para implementar una aplicación multitenant, nos centraremos en explicar un glosario de términos muy importante para poder entender este tipo de arquitectura.
¿Qué consideraciones debemos de tener para hacer una aplicación multitenant?
El principal motivo que nos planteamos para crear una aplicación multitenant es porque nuestra aplicación va a ser consumida por varios clientes. Si por el contrario nuestra aplicación es únicamente para un cliente con su propia lógica no tiene ningún sentido plantearse este tipo de arquitectura. El motivo: No se aprovechan ninguna de las ventajas que nos ofrecen el multitenant.
Ventajas:
Desventajas
Una vez ya tenemos claro en que circunstancias se desarrollan las aplicaciones multitenant vamos a ver que debemos tener en cuenta para poder hacer una aplicación multitenant y optimizar al máximo las ventajas que nos ofrece Azure. Desde el punto de vista de arquitectura deberemos tener en cuenta aspectos como la autenticación, el almacenamiento de datos o el lugar donde se va a ejecutar nuestra aplicación. Centrémonos en cada una de esas partes.
Autenticación
Dentro de Azure, esta más que extendido el uso del servicio de Azure Active Directory en casi cualquier aplicación que esta alojada en Office 365. En el caso de que nuestra aplicación pueda ser una aplicación dentro del ecosistema Microsoft y/o Office 365 el hacer uso de este servicio puede ser un punto a favor de la adopción de nuestro desarrollo dentro de nuestros clientes. En el caso de que nuestra App necesite una personalización y no este dentro de Office 365 lo más normal es optar por un modelo de autenticación Ad-hoc siguiendo los estándares que marca OpenId.
¿Como podemos hacer usar nuestra Aplicación el servicio de Azure Active Directory Multitenant?
1. En el registro de nuestra aplicación indicar que la aplicación es multientant, en ese momento dicha aplicación se va a poder utilizar en cualquier tenant de Azure que exista (siempre que el administrador de dicho tenant consienta instalarla naturalmente).
https://login.microsoftonline.com/contoso.onmicrosoft.com
Para que nuestra aplicación sea multitenant tendremos que modificar "contoso.onmicrosoft.com" por common. Un ejemplo de esto haciendo uso de Adal.JS sería el siguiente código:
1export const adalConfig: AdalConfig = {2 tenant: 'common',3 clientId: process.env.REACT_APP_CLIENTID,4 endpoints: {5 api: process.env.REACT_APP_CLIENTID,6 graphApi: 'https://graph.microsoft.com/'7 },8 cacheLocation: 'localStorage',9};10runWithAdal(authContext, () => {11 renderApp();12}, false);13
3. Si nuestra aplicación autentica la parte servidora también haciendo uso del Azure Active Directory tendríamos que modificar los valores de este para que de esta forma se autentique independientemente del inquilino que se este conectando a nuestra aplicación. Un ejemplo de como hacerlo en .NET Core es implementar un middleware como el siguiente:
1public static AuthenticationBuilder AddAzureAdBearerMultiTenant(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions) {2 builder.Services.Configure(configureOptions);3 builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();4 builder.AddJwtBearer(j => {5 j.TokenValidationParameters = new TokenValidationParameters() {6 ValidateIssuer = false7 };8 });9 return builder;10}11
4. Por último, el Administrador del tenant que va a utilizar la App debe de instalarlo en su "tenant". Para ello hay una url de consentimiento que un usuario administrador debe de aceptar para poder hacer uso de nuestra aplicación. El flujo para dicha aprobación es como el que se muestra en el siguiente dibujo. En el caso de que un administrador, ya no quiera que la aplicación se use más en su tenant puede revocarlo desde su Portal de Azure quitando los permisos.
La Base de datos: SQL Azure/Cosmos DB
Una vez ya tenemos clara que autenticación va a tener nuestra aplicación, el siguiente eslabón que se ha de tratar es el almacenamiento de los datos. Dentro de los servicios que nos proporciona Azure podemos elegir tanto base de datos relacional como SQL Azure o bien base de datos no relacional como Cosmos DB. La elección de una u otra no depende de que nuestra aplicación sea multitenant, pero sí que debemos tener claro qué tipo de arquitectura vamos a tener para que la optimizar los accesos a datos.
¿De que depende el patrón que va a utilizar nuestra aplicación?
Posibles patrones:
Multi-tenant app with database-per-tenant.
En este patrón cada tenant tiene su propia base de datos en exclusiva para él. Su principal beneficio es el aislamiento de los datos entre cada uno de los tenant, así como los procesos que se ejecutan en cada uno de ellos. Por otro lado, su principal desventaja es que cada base de datos tiene un coste muy elevado.
Este patrón es una variante, del primer patrón, pero con la ventaja de que el utilizar una base de datos "Elastic" es un único servidor de base de datos lo cual puede proporcionar un ahorro de costos, debido a que se pueden compartir alguno de los recursos.
Utilizando este patrón deberemos de tener una o varias columnas de identificadores del tenant dentro del esquema de nuestra base de datos. Con esta opción se sacrifica el aislamiento de los datos ya que residen físicamente en la misma base de datos. También se comparte recursos de proceso y almacenamiento entre todos los tenants. Todo ello implica que es la de menos coste.
Es una variante del modelo anterior, pero los datos de un tenant pueden estar distribuidos a través de varias bases de datos o particiones. El escalado es casi ilimitado, bases de datos más pequeñas que se administran con más facilidad. Pueden usarse Elastic pools. Aquí es clave el identificador del inquilino.
¿Como podemos implementar estos patrones en Entity Framework Core?
En primer lugar, nos crearemos un TenantProvider cuya finalidad será obtener el tenant id en este caso vamos a a obtener el tenantID de la petición http que realice las peticiones. De esta forma podemos filtrar dependiendo de que tenant nos llama. Su implementación seria como la siguiente:
1public class DatabaseTenantProvider : ITenantProvider2{3 private string tenantId;4 public DatabaseTenantProvider(TenantsDbContext context, IHttpContextAccessor accessor)5 {6 var tenantIdRequest = accessor.HttpContext.User.FindFirst(c => c.Type == "http://schemas.microsoft.com/identity/claims/tenantid")?.Value;7 context.Database.EnsureCreated();8 if (!string.IsNullOrWhiteSpace(tenantIdRequest))9 {10 tenantId = tenantIdRequest;11 }12 }13 public string GetTenantId() {14 return tenantId;15 }16}17
Una vez ya tenemos el patrón que vamos a implementar vamos a indicarle a nuestro contexto de base de datos que cada vez que se realice alguna consulta a la base de datos esta se filtre por el tenant que realiza la consulta. Para ello bastaría con el siguiente ejemplo:
1public class PastryDbContext : DbContext2{3 public DbSet<Pastry> Pastry { get; set; }4 private readonly ITenantProvider tenantProvider;5 public PastryDbContext(DbContextOptions<PastryDbContext> options,6 ITenantProvider tenantProvider) : base(options)7 {8 this.tenantProvider = tenantProvider;9 }10 protected override void OnModelCreating(ModelBuilder modelBuilder)11 {12 base.OnModelCreating(modelBuilder);13 modelBuilder.Entity<Pastry>().HasQueryFilter(p => p.TenantId == tenantProvider.GetTenantId());14 }15}16
Este seria una implementación de uno de los patrones dependiendo de cada caso sería posible implementar el patrón bien utilizando EF Core.
Alojamiento de la Aplicación
Por regla general nuestra aplicación la tendremos alojada en una WebAPP, que se pueda escalar dependiendo de las necesidades. Esta opción sería para aplicaciones sencillas que no requieran de una alta escalabilidad. En Apps en la que la escalabilidad sea un elemento clave habría que optar por el uso de Kubernetes o cualquier orquestador de Microservicios, de forma que nuestra aplicación pueda escalar en los servicios que más lo requieran y de esta forma dar un servicio óptimo y adaptado a los requerimientos que demanda el negocio. En este articulo solamente es un overview para poder pensar sobre todo los artefactos o elementos que necesitamos para crear una App multitenant.
Conclusiones
El crear Aplicaciones Multitenant no es algo tan trivial como activar una opción y nuestra aplicación ya la pueden utilizar todos nuestros clientes de una forma óptima y adaptada el cloud. Hay que pensar en los aspectos que he indicado en el artículo y en muchos que no hemos abordado como por ejemplo: Ley de protección de datos, Geolocalización, Performance, Actualización, etc…
Azure no es únicamente el Cloud donde los clientes pueden empezar migrar su infraestructura, o consumir servicios, Azure también es la base de muchas de las aplicaciones empresariales/ apps/ startups que están en el mercado. Todo esto es debido a la gran cantidad de servicios por lo que desde el punto de vista tecnológico y de negocio es bastante importante el conocer al máximo la potencia que nos puede ofrecer Azure y de esta forma poder implementar aplicaciones más complejas, mantenibles y escalables.
Adrián Diaz Cervera -- Architect Software Lead at Encamina
MVP Office Development
http://blogs.encamina.com/desarrollandosobresharepoint
adiaz@encamina.com @AdrianDiaz81