Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
September 13, 2021 09:50 pm GMT

ASP.NET Core 6: Multi-tenant Single Database (Parte 2)

Introduccin

Seguimos con la continuacin de esta serie de posts sobre aplicaciones multi-tenant con asp.net core.

En este post veremos como implementar una aplicacin multi-tenant con una base de datos que contenga todos los tenants.

Veremos las ventajas y desventajas y la razn de porque podramos utilizar este approach.

Te vuelvo a recordar que esta serie de posts viene con muchos code snippets, es mejor que lo leas siguiendo el repositorio en Github con el ejemplo final.

Esta serie de posts se dividen en 3 partes:

Tipos de multi-tenancy single database

Como vimos en el post pasado, podemos tener distintas arquitecturas al crear aplicaciones SaaS. En este caso vamos a hablar sobre como tener mltiples tenants en una misma base de datos.

Cabe mencionar que es importante analizar si nos conviene esta modalidad ya que tiene sus desventajas pero en ciertos escenarios, sus ventajas.

Siempre debemos de analizar que necesitamos, porque al guardar informacin de nuestros usuarios, debemos considerar siempre la seguridad, mantenibilidad y escalabilidad.

Una base de datos lenta hace un sistema lento. Una base de datos insegura, filtra informacin. Una base de datos inmantenible, no dar soluciones a futuro.

Schema-based.

Una sola base de datos para todos los tenants y cada tenant tiene su propio esquema en la base de datos.

Untitled

Seguridad

  • La informacin entre tenants se mantiene aislada, ya que cada uno tendr su propio esquema.
  • Filtrar informacin entre tenants tiene un riesgo bajo, ya que no necesitas de un WHERE para limitar la informacin entre Tenants.
  • Aun as, puedes hacer queries al esquema equivocado y consultar un tenant totalmente diferente.

Mantenibilidad

  • Manejar una base de datos sigue siendo una ventaja al darle mantenimiento.
  • Tambin, al tener esquemas diferentes podemos tener un scope de cada tenant.
  • Actualizar los esquemas ser doloroso, ms si se tienen muchos tenants
  • No se puede restaurar un solo tenant, ya que sigue siendo una sola base de datos
  • Agregar nuevos tenants involucra agregar un esquema totalmente nuevo, una BD con muchas tablas ser problema

Escalabilidad

  • La informacin est particionada en tablas pequeas
  • Optimizar tablas se podra hacer por tenant
  • Al ser una sola base de datos y un servidor, se limita la escalabilidad a un solo hardware
  • Riesgo de "noisy neighbors" Un tenant puede impactar el rendimiento de otros tenants

Table-based.

De igual forma, una base de datos para todos los tenants y todos comparten el mismo esquema. La forma de aislar los tenants se realiza por medio de un Identificador en todas las tablas.

Personalmente prefiero esta modalidad si usamos Single database, y en este post haremos un ejemplo usando esta modalidad.

Untitled 1

Seguridad

  • Existe un riesgo alto de exponer informacin entre tenants pero existen varias mitigaciones para eliminar este riesgo.
    • El ejemplo que haremos en este post, ser configurar Entity Framework para que haga Queries de forma automtica segn el tenant actual, reduciendo a 0 el riesgo de filtracin a nivel aplicacin.
  • No existe aislamiento entre tenants

Mantenibilidad

  • Fcil de mantener por ser una sola base de datos con un solo esquema
  • Fcil de recuperar en caso de un desastre o cadas prolongadas
  • Agregar tenants nuevos es fcil, no hay que hacer ninguna modificacin al esquema.
  • Queries pueden ser riesgosos de modificar Tenants no deseados
    • Mitigacin: Row-Level Security puede ser usado para controlar el acceso de los rows en las tablas
  • Si usamos RLS, se debe de actualizar en cada tabla nueva que agregamos
  • Sera difcil restaurar informacin de un solo tenant.

Escalabilidad

  • Solo se podra hacer scale-up (agregar ms hardware) y no distribuirlo de forma horizontal (scale-out)
  • Mismo problema de "Noisy neighbors"
  • Si la base de datos crece (cada tenant tiene mucha informacin) las actividades de mantenimiento tardarn ms y potencialmente afectar a otros tenants

Por qu la modalidad table-based?

Realmente por experiencias que he tenido en microservicios, me gusta esta modalidad.

S debemos de tener en cuenta los factores mencionados arriba, una base de datos grande y con mucha informacin por tenant, definitivamente ninguna de estas ser la solucin.

Para estos casos, el siguiente post con multi-database ser una solucin muy buena. Pero para bases de datos relativamente pequeas, s es una buena opcin.

Un microservicio usualmente tiene esquemas pequeos, eso lo hace fcil de manejar. Podra ser que las tablas s crezcan mucho, pero al ser pocas tablas de manejar, podra ser viable alguna de estas dos opciones.

MultiTenant DbContext (una DB para todos )

Para continuar con este ejemplo en ASP.NET Core, tenemos que seguir utilizando la solucin del ejemplo anterior (la dejamos preparada para lo mismo).

Para comenzar, crearemos un nuevo DbContext en la solucin con una tabla de ejemplo Product.

public class Product : AuditableEntity{    public int ProductId { get; set; }    public string Description { get; set; }}

La clase padre AuditableEntity nos servir para obligar que todos los Entities tengan ciertas propiedades y eso nos ayudar a crear un aislamiento y manejo de nuestros Entities.

public class AuditableEntity{    public int TenantId { get; set; }    public DateTime CreatedAt { get; set; }    public DateTime? ModifiedAt { get; set; }}

Aqu simplemente estamos agregando el campo TenantId (es obligatorio para todos los entities) y adems unas propiedades adicionales para control de ellas.

Aqu podemos agregar Ids de usuarios que crearon/modificaron los registros para que realmente sea un Entity Auditable.

using Microsoft.EntityFrameworkCore;using Z.EntityFramework.Plus;public class MultiTenantDbContext : DbContext{    private readonly int _tenantId;    public MultiTenantDbContext(        DbContextOptions<MultiTenantDbContext> options,        IHttpContextAccessor contextAccessor) : base(options)    {        var currentTenant = contextAccessor.HttpContext?.GetTenant();        _tenantId = currentTenant?.Id ?? 0;        this.Filter<AuditableEntity>(f => f.Where(q => q.TenantId == _tenantId));    }    public DbSet<Product> Products { get; set; }    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())    {        foreach (var entry in ChangeTracker.Entries<AuditableEntity>())        {            switch (entry.State)            {                case EntityState.Added:                    entry.Entity.TenantId = _tenantId;                    entry.Entity.CreatedAt = DateTime.UtcNow;                    break;                case EntityState.Modified:                    entry.Entity.ModifiedAt = DateTime.UtcNow;                    break;            }        }        return base.SaveChangesAsync(cancellationToken);    }}

Aqu se complicaron un poco las cosas. Pero antes de explicarlo, necesitamos una librera que nos ayude con este truco de EF Core.

<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="6.0.0-preview.7.21378.4-4" />

Lo que est sucediendo aqu realmente es sencillo:

Primero que nada, utilizando IHttpContextAccessor estamos accediendo al HttpContext de la solicitud actual y a su vez nos da el Tenant que se quiere acceder.

Si no existe ningn HttpContext (puede ser alguna inicializacin o similar) de entrada no se podrn hacer Queries ya que es requisito saber que Tenant se est accediendo para poder mostrar informacin.

Tal vez aqu no es lo ms optimo, talvez podemos crear un ITenantAccessor y llenarlo de formas personalizadas (y no siempre de un HttpContext) pero por ahora, el ejemplo ser as.

En el mismo constructor de MultiTenantDbContext se est configurando para que siempre agregue una clusula WHERE a cualquier query que se haga en el contexto, esto utilizando la librera Z.EntityFramework.Plus.EFCore.

Esta es la parte donde nos aseguramos que exista un aislamiento (al menos uno lgico) y se puedan realizar Queries de forma segura.

Nota: Cabe mencionar, que esta medida de seguridad solo funciona si se usa el DbContextdirectamente, cualquier raw SQL est potencialmente propenso a fugas de informacin.

El mtodo SaveChangesAsync simplemente es para actualizar cualquier Entity que se haya creado o modificado. Esto no tiene mucho que ver con la BD multi-tenant, pero es buena idea que las operaciones sean auditables.

Integracin con ASP.NET

Para poder crear la base de datos y empezar a hacer pruebas, tenemos que registrar este nuevo DbContext.

builder.Services.AddDbContext<MultiTenantDbContext>(options =>    options.UseSqlServer(builder.Configuration.GetConnectionString("MultiTenant")));

Teniendo ya las siguientes connection strings.

"ConnectionStrings": {    "TenantAdmin": "Server=(localdb)\\mssqllocaldb;Database=MultiTenant_Admin;Trusted_Connection=True;MultipleActiveResultSets=true",    "MultiTenant": "Server=(localdb)\\mssqllocaldb;Database=MultiTenantSingleDb;Trusted_Connection=True;MultipleActiveResultSets=true"  }

Como en el post anterior, ejecutamos los siguientes comandos para crear la primera migracin y crear la base de datos.

dotnet ef database add FirstMigration --context MultiTenantDbContext -o Persistence/Migrations/MultiTenantdotnet ef database update --context MultiTenantDbContext

Como ya contamos con dos DbContext en el proyecto, cada comando ef que hagamos, debemos especificar a que contexto nos estamos refiriendo.

Y nos crear lo siguiente.

Untitled 2

Untitled 3

Es por eso que desde el post pasado, especificamos el output de la migracin.

Por qu dos bases de datos?

Es recomendable tener una Base de datos "compartida" o "principal" para que en esta podamos guardar meta-data. Es decir, informacin que describan nuestros tenants, catlogos compartidos y entre otras cosas.

En multi-database esta base de datos compartida ser de vital importancia.

Adems, para agregar tenants nuevos, solo tendramos que agregar un registro a la tabla Tenants dentro del contexto TenantAdminDbContext.

Finalizando

Para hacer pruebas, agregaremos datos de prueba.

Untitled 4

Desde el post anterior estamos utilizando Razor Pages, por lo que haremos una consulta directamente en nuestro PageModel (en este caso, uno llamado Products.cshtml) y as comprobar que cuando estamos en localhost, solo nos debe de regresar Product 1 y Product 2.

using Microsoft.AspNetCore.Mvc.RazorPages;using Microsoft.EntityFrameworkCore;public class ProductsModel : PageModel{    private readonly MultiTenantDbContext _context;    public ProductsModel(MultiTenantDbContext context)    {        _context = context;    }    public List<Product> Products { get; set; }    public async Task OnGet()    {        Products = await _context.Products.ToListAsync();    }}

Como pueden ver aqu, simplemente estamos enlistando todos los productos de la tabla, aqu se encuentran mezclados entre tenants, pero el Filter base que pusimos har que estn aislados.

@page@model MultiTenantSingleDatabase.Pages.ProductsModel@{}<h1>Productos</h1><table class="table">    <thead>        <tr>            <th>Product Id</th>            <th>Description</th>        </tr>    </thead>    <tbody>        @foreach (var product in Model.Products)        {            <tr>                <td>@product.ProductId</td>                <td>@product.Description</td>            </tr>        }    </tbody></table>

Como resultado.

Untitled 5

Si visitamos otro tenant.

Untitled 6

Conclusin

Podramos pensar que esta modalidad de hacer aplicaciones multi-tenants tiene ms desventajas que ventajas. Pero si analizamos la situacin correctamente, podra ser nuestro proyecto candidato para hacer un multi-tenancy con single database y funcionar perfectamente.

Cuntame Cmo ves est opcin? yo soy totalmente C# Developer y manejar todo desde C# siempre es lo que hago, crear Raw SQL queries es algo que no hago muy seguido.

Y recuerda,

Code4Fun .

Referencias

Multi-tenant Application Database Design

Multi-Tenancy with SQL Server, Part 2: Database Design Approaches

Multi-tenant SaaS patterns - Azure SQL Database


Original Link: https://dev.to/isaacojeda/asp-net-core-6-multi-tenant-single-database-parte-2-4cbh

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To