La .NET CONF 2024 acaba de terminar. Descubre las NOVEDADES de .NET 9 junto a los CAMBIOS que trae C# 13.
Puedes ver el contenido de este vídeo junto con su curso en el modo vídeo (similar a Udemy) si pulsas aquí.

Aplicaciones con múltiples clientes

Posiblemente esta sea una de las preguntas que más he  recibido en el contenido que he creado sobre Entity Framework. Aquí vamos a ver multi tenancy en detalle.

 

 

1 - Qué es multi tenancy? 

Multi tenancy no es más que el término que utilizamos en programación cuando tenemos un sistema que funciona para múltiples clientes o tenants, lo cual suele ser una situación muy común en el día a día laboral.

 

 

2 - Qué estrategias podemos aplicar para implementar múltiples clientes

Podemos implementar la solución de varias formas distintas, la idea final es que no tengamos contaminación de datos de unos a otros. Y ese debe ser el objetivo final y que la contaminación de datos es muy mala para el negocio. 

 

 

2.1 - separación de la base de datos

Por norma general todos los clientes tienen las mismas tablas con las mismas columnas y la misma lógica de negocio, lo que cambia suele ser los datos en sí.

 

Por ese motivo uno de los casos que podemos implementar es la separación física de dichos datos, esto quiere decir que tenemos una base de datos para cada cliente.

 

Las ventajas a nivel de base de datos son obvias, simplemente accedemos a los datos como queramos ya que no tenemos ningún filtro ni comprobación que hacer y debemos presuponer que si estamos consultando dicha base de datos lo estamos haciendo desde una aplicación que debería acceder.

 

Las desventajas son obvias también, ya no tenemos una única base de datos, sino que tenemos múltiples lo que lleva a una administración mayor sobre las actualizaciones de la misma; Esto es debido a que donde antes teníamos que correr las migraciones una única vez, ahora lo tenemos que hacer una vez por base de datos. 

Nota: Esta solución también se puede ver con esquemas dentro de una misma base de datos. 

 

A nivel de aplicación tenemos dos soluciones para implementar esta solución

 

2.1.1 -  Separación de aplicaciones

La primera es que cada cliente tiene su propia aplicación, o cluster, etc, vamos que cada cliente tiene una versión del sistema y en mi opinión esto es un problema. No vamos a entrar mucho en los detalles de porque es un problema, pero si salen bugs hay que arreglarlos en versiones viejas, mantener incompatibilidades, y situaciones similares.

separacion fisica multi tenancy

La parte buena de esta solución es que especificamos de forma manual en el propio código del cliente la conexión a la base de datos, por lo que es muy difícil tener contaminación de información. La única forma es si hemos escrito mal la conexión.

 

 

2.1.2 - Administrador de conexiones

La segunda forma dentro de la separación de la base de datos, es una que se denomina de una manera diferente en cada empresa, es el administrador de conexiones, lo he visto llamado como connection pooling, connection multiplexing (yo muchas veces lo llamo database connection multiplexer)  o connection administrator y se refiere a la acción de dado un cliente calcular o recibir la conexión que va a necesitar, apuntando a diferentes bases de datos. 

administrador conexiones

 

De esta forma, mantenemos a todos los clientes en un mismo código y versión de la base de datos, pero sus bases de datos físicas son diferentes. Así que aquí hemos eliminado uno de los problemas que teníamos en el caso anterior.

 

 

2.2 - Separación por columna Id

Básicamente lo que su nombre indica, en cada tabla de nuestra base de datos tenemos una columna que se hace referencia al Id del cliente, por lo que todo el mundo utiliza el mismo código y la misma base de datos

separación columna ID

Esta suele ser una de las implementaciones más comunes ya que te asegura tener únicamente un set de aplicaciones desplegadas así como una única copia de la base de datos. 

 

Por contrapartida, debemos asegurarnos que en el código estamos comprobando siempre el campo del Id del cliente, ya que si no podríamos tener contaminación de datos. 

 

 

2.3 - Qué versión elegir

A la hora de elegir una forma en la que efectuar la separación de clientes debemos tener varias cosas en cuenta. 

 

Primero de todo temas legales, habrá empresas que requieran que su información este no solo en su región sino aislada, si habéis trabajado con clientes de Estados Unidos, el tener los datos alojados en estados unidos es una obligación. Y luego hay empresas que quieren esos datos aislados.

 

Número de clientes, si tenemos una aplicación que trabaja con bancos, al final sabemos que hay un número finito y bastante pequeño, en caso de querer podemos tener una separación física de los datos, pero si por el contrario nuestros clientes son empresas de construcción o de transporte que hay miles, lo normal será tener el campo para el Id del cliente en todas las tablas de la base de datos.

 

Numero de recursos en la empresa, el número de empleados/compañeros que tenemos en el equipo va a limitar estas acciones, ya que no es lo mismo administrar 30 copias de la base de datos con 2 personas que con 15.

Por norma general, salvo en casos concretos, lo que haremos será tener el campo del ID del cliente en la base de datos, lo cual cubre el 95% de empresas.

 

Hay otro porcentaje que tienen lo que he mencionado, un único cliente que requiere que sus datos estén separados. En ese caso podemos aislar únicamente ese cliente, obviamente necesitaremos un administrador de conexiones, pero, sabemos que además de la conexión, vamos a tener el ID del cliente, con lo que no tendremos contaminación de datos. 

En mi caso he visto esta implementación cuando trabajaba para una aerolínea y uno de los clientes (que eran otras aerolíneas) decía que tenía que tener los datos en cierto país, simplemente se duplicó la base de datos y todos sus datos iban para allá. 

 

 

3- Implementación de un administrador de conexiones en C#

La realidad es que la implementación no tiene mucho misterio. Por norma general tenemos dos opciones, o la inyectamos durante el despliegue de la aplicación o la tenemos con una combinación entre configuración y credenciales.

 

Alternativamente, podemos tener una conexión donde toda la información es la misma y lo único que cambia es el host de la base de datos,el cual contendrá el id del cliente, por ejemplo algo así como 

server=dbserver-id1 
server=dbserver-id2

Pero el resto de la conexión es exactamente el mismo, así que lo que tienes que hacer es tan simple como reemplazar la información del tenant en dicha cadena.

 

Pero lo normal será tener múltiples conexiones, para ello debemos hacer un par de cambios en el código.

Para este ejemplo vamos a utilizar el código que hemos estado utilizando durante todo el curso de Entity Framework. Y el código que tenemos originariamente para contactarnos a MySQL es este:

 services.AddDbContext<CursoEfContext>((serviceProvider, options) =>
    options
        .UseLazyLoadingProxies()
        .AddInterceptors(new ReadExampleInterceptor(),
            new SecondLevelCacheInterceptor(serviceProvider.GetRequiredService<IMemoryCache>()))
        .UseMySQL("server=127.0.0.1;port=4306;database=cursoEF;user=root;password=cursoEFpass"));

NOTA: este ejemplo está con mysql, pero funciona exactamente igual en todas las bases de datos.

 

Lo que tenemos que hacer es cambiar dicho código para permitir múltiples conexiones, lo cual es muy sencillo ya que AddDbContext lo que hace por detrás es un AddScoped, o lo que viene siendo lo mismo, el lifetime del dbcontext es la ejecución de la request. 

 

Así que únicamente debemos de crear una clase, la cual nos devuelva la conexión.

public interface IDbConnectionManager
{
    string GetConnectionString();
}

public class DbConnectionManager : IDbConnectionManager
{
    private readonly IHttpContextAccessor _contextAccessor;

    public DbConnectionManager(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public string GetConnectionString()
    {
        //_contextAccessor.GetTenant();
        //Calcular la url de la base de datos por tenant
        return "server=127.0.0.1;port=4306;database=cursoEF;user=root;password=cursoEFpass";
    }
}

La implementación no está completa, pues nuestro código actual no requiere estar autenticado, pero la idea es simple, cogemos el token, leemos el tenant y a raíz de ese tenant calculamos la conexión a la base de datos, sea de la forma que sea; como he dicho antes, puede ser con configuracion + secrets, o simplemente con el nombre del cliente en la propia conexión.

 

El siguiente paso, además de inyectar el servicio al contenedor de dependencias, es utilizarlo dentro de la definición del dbContext:

services.AddScoped<IDbConnectionManager, DbConnectionManager>(); 👈

services.AddDbContext<CursoEfContext>((serviceProvider, options) =>
    options
        .UseLazyLoadingProxies()
        .AddInterceptors(new ReadExampleInterceptor(),
            new SecondLevelCacheInterceptor(serviceProvider.GetRequiredService<IMemoryCache>()))
        .UseMySQL(serviceProvider.GetRequiredService<IDbConnectionManager>().GetConnectionString())); 👈

 

Y con eso ya estaría, ahora podemos calcular la conexión para cada tenant específico, ya que la información se calcula en cada llamada HTTP. 

 

 

Por cierto, no lo he mencionado, pero con esta implementación utilizar Database First no es tan sencillo, ya que las migraciones si las tenemos en la propia aplicación corren una única vez, cuando la aplicación empieza. En este caso lo que he visto hacer es tener un proyecto de base de datos el cual corre en todas las bases de datos.


Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café