Después de ver un primer vistazo rápido en el post anterior, en este veremos un caso práctico y los tiempos de vida que podemos asignarles a nuestros objetos cuando los resolvemos.
Ejemplo rápido
En la cliente donde estoy, hubo una determinada situación donde el implementar Autofac nos salvaría. Aquí tenemos una aplicación MVC y siempre se tienen que filtrar los datos por determinado Id(TenantId). El usuario hace loggin, la aplicación se trae el TenantId al cual pertenece y se guarda en la cookie la identificación del usuario junto con el TenantId. Esto es el concepto de multi-tenant(más o menos) que podríais haber visto en otras ocasiones, si no aquí tenéis una explicación.
Siempre que el model herede de determinada interfaz tiene que filtrar los datos cuando hace la petición a BDD, pero cuando los guarda tiene que asegurarse de que se guarda con ese id.
Cuando lo registramos en el Ioc, le ponemos que sea perRequest para que cuando se haga una petición a un controller, se le inyecte un IUsuario con el TenantId y en la función XXXX se asigna a IUsuario los valores de la cookie. En los repositorios inyectamos el IUsuario(más bien en un base repository) y así tenemos los datos para poder filtrar el contenido.
En el repositorio como ejemplo hacemos una funcion de Add genérica tal que:
public T Add(T item, ...) { if (item is BaseTenant) { (item as BaseTenant).TenantId = this.TenantId; } return this.GetSet().Add(item); }
No es así exactamente lo que tenemos pero es parecido.
Ciclos de vida
Con ciclos de vida me refiero a cuánto tiempo puedo acceder a la misma instancia. En Web es muy claro, cuando haces una petición al backend, un ciclo de vida es desde que haces la petición hasta que recibes la respuesta (aunque sea el http Ok).
Para gestionar un ciclo de vida con DI tenemos varios tipos a nuestra disposición:
Intance per Dependency
var builder = new ContainerBuilder(); builder.Register<Tenant>().As<ITenant>().InstancePerDependency();
Aquí estamos indicando que la interfaz ITenant se resuelve con el objeto tenant y que el ciclo de vida es una instancia por dependencia. Esto significa que en cada constructor y cada vez que a mano pidamos que nos resuelva ITenant, nos data una nueva instancia del objeto, de tal manera que nunca tendremos datos modificados por otros.
Single Inscance
Este, es más conocido como Singleton, en resumen es que la primera vez que resolvamos ITenant lo instanciará y después siempre nos devolverá la misma instancia. En un post de Alejandro de forCode explica más al detalle el patrón Singleton. Patrón de diseño Singleton
Este es un ejemplo de cómo se registraría:
var builder = new ContainerBuilder(); builder.Register<Tenant>().As<ITenant>().InstancePerLifetimeScope();
Instance Per Lifetime Scope
Hay un ejemplo en la propia página de Autofact que lo explica perfectamente, lo he modificado y traducido los comentarios. Primero lo registramos así:
var builder = new ContainerBuilder(); builder.Register<Tenant>().As<ITenant>().InstancePerLifetimeScope();
Y el ejemplo comentado sería este:
using(var scope1 = container.BeginLifetimeScope()) { for(var i = 0; i < 100; i++) { // Siempre que intentamos obtener una instancia de Worker // en este scope obtenemos la misma instancia var w1 = scope1.Resolve<Worker>(); } } using(var scope2 = container.BeginLifetimeScope()) { for(var i = 0; i < 100; i++) { // Siempre que intentamos obtener una instancia de Worker // en este scope obtenemos la misma instancia, pero esta // instancia es diferente que la del anterior scope var w2 = scope2.Resolve<Worker>(); } }
Instance Per Matching Lifetime Scope
Es exactamente lo mismo que Instance Per Lifetime Scope, pero con un detalle que nos puede ir bastante bien.
Registramos el tenant tal que:
var builder = new ContainerBuilder(); builder.Register<Tenant>().As<ITenant>().InstancePerMatchingLifetimeScope("MiScope");
Cuando usamos el using(por ejemplo) podemos darle un nombre:
using(var scope1 = container.BeginLifetimeScope("MiScope"))
Si tenemos 1 o varios Scope con el nombre “MiScope”, la instancia de Tenant será la misma en estos.
Instance Per Request
Este lo vemos muy claro en un entorno web. En MVC o WebAPI, el ciclo de vida de la request es desde el momento en que el backend recibe la petición al servidor, hasta que manda una respuesta (aunque será un http Ok). Básicamente, el Instance Per Request es este, desde que entra la petición hasta que sale la respuesta.
Para registrarlo sería:
var builder = new ContainerBuilder(); builder.Register<Tenant>().As<ITenant>().InstancePerLifetimeScope();
Trabajando con Threads
Esto es más que nada es para que lo tengáis en cuenta.
El ciclo de vida de un Thread, como os imaginareis, es desde que inicias el thread hasta que se hace un dispose. Dentro de este thread si resolvemos una instancia de ITenant, el ciclo de vida sera dentro de ese Thread y nada más. Por supuesto tenemos que tener en cuenta cuando hacemos el destroy de ese Thread para que no nos perjudique.