Saltar a contenido

Andreani.ARQ.CQRS

Overview

Se desarrolló la librería Andreani.ARQ.CQRS para realizar la implementación de EntityFramework y Dapper. El objetivo de la librería es abstraer de las configuraciones a lxs desarrolladorxs y disponibilizar de interfaces comunes para una rápida adopción y uso de las tecnologías.

Configuración

Para realizar la configuración debemos, en principio instalar como paquete nuget la librería Andreani.ARQ.CQRS.

En los archivos de configuración appsettings debemos disponer de una sección con la configuración de CQRS

  {
    "DataAccessRegistry":{
        "ReadOnlyConnection": "Data Source=DBFileName.db",
        "TransactionalConnection": "Data Source=DBFileName.db",
        "ProviderName": "Microsoft.Data.Sqlite",
    }
  }
    DataAccessRegistry:
        ReadOnlyConnection: "Data Source=DBFileName.db"
        TransactionalConnection: "Data Source=DBFileName.db"
        ProviderName: Microsoft.Data.Sqlite

Al utilizar la arquitectura CQRS podemos disponer de dos bases de datos, una para realizar transacciones (Operaciones de escritura) y otra para solo realizar consultas.

En la mayoría de casos quizás la base sea la misma, pero la librería esta preparada para optar por utilizar dos bases de datos.

El parámetro TransactionalConnection será la connection que tome EF core mientras que ReadOnlyConnection es utilizado para la configuración de la connection de Dapper

Para incluir la configuración a nuestro proyecto debemos agregar la siguiente línea al bootstrap de la capa de Infrastructure

public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddCQRS<ApplicationDbContext>(configuration);

            return services;
        }

El método AddCQRS realiza la configuración y la inyección de dependencias.

Al método AddCQRS se puede configurar con:

public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddCQRS<ApplicationDbContext>(configuration, configSectionName: "DataAccessRegistry", serviceLifetime: ServiceLifetime.Transient);

            return services;
        }

  1. configSectionName: es el nombre de la sección de configuración. Por defecto: DataAccessRegistry
  2. serviceLifetime: ciclo de vida del contexto y el IDbConnection, por defecto es Transient.

Providers

A continuación se detalla las bases de datos soportadas y el provider name que se deberá utilizar en la configuración.

Base de Datos ProviderName
Sql Server System.Data.SqlClient
Oracle Oracle.ManagedDataAccess.Client
MySql MySql.Data.MySqlClient
Sqlite Microsoft.Data.Sqlite
PostgreSql Npgsql

Nota

MySql: en la versión de Net 6 es necesario agregar el parámetro Version a la configuración.

    DataAccessRegistry:
        ReadOnlyConnection: "Data Source=..."
        TransactionalConnection: "Data Source=..."
        ProviderName: MySql.Data.MySqlClient
        Version: 5.5.X

Healthcheck

La librería ya tiene la configuración de healthcheck, se complenta con la configuración de Andreani.ARQ.WebHost, para acceder a los healthcheck debe ivocar la ruta: http://*:*/health o http://*:*/healthcheck para obtener más info.

Interfaces Disponibles

La librería CQRS implementa dos interfaces con funcionalidades comunes que puden utilizar para interactuar con la base de datos.

ITransactionalRepository

Interface que implementa Ef core, posee las principales operaciones de escritura.

    public interface ITransactionalRepository
    {
        void Insert<TEntity>(TEntity entity) where TEntity : class;

        void Delete<TEntity>(TEntity entity) where TEntity : class;

        void Update<TEntity>(TEntity entity) where TEntity : class;

        void InsertRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;

        void UpdateRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;

        void DeleteRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;

        void UseContext(Action<DbContext> action);

        TResult UseContext<TResult>(Func<DbContext, TResult> action) where TResult : class;

        TEntity FindById<TEntity>(dynamic id) where TEntity : class;

        Task SaveChangeAsync();

        void SaveChange();
    }
Esta interface permite persistir cualquier entidad mapeada en el contexto.

Ejemplo de uso

    public class MyHandler
    {
        private readonly ITransactionalRepository _repository
        public MyHandler(ITransactionalRepository repository)
        {
            _repository = repository
        }

        public async Task MyMethod(){
            var entity = new ObjectEntity
            {
                AnyValue = "ejemplo"
            }
            _repository.Insert(entity);
            await _repository.SaveChangeAsync();
        }
    }

UseContext es una función que inyecta el contexto, esta pensado para utilizarse en caso de necesitar actualizar un modelo complejo o utilizar las funciones del dbContext que no estan disponibles desde la librería.

        var person = _command.UseContext(con =>
       {
           return con.Set<Person>().FirstOrDefault(p => p.PersonId == id);
       });
        _command.UseContext(con => {
            con.Attach(entity).State = EntityState.Modified;
            con.Update(entity);
        });

IReadOnlyQuery

Interface que implementa Dapper para realizar consultas de lectura, posee algunos métodos comunes para hacer consultas simples y generales.

    public interface IReadOnlyQuery
    {
        Task<IEnumerable<T>> GetAllAsync<T>() where T : class;

        Task<IEnumerable<T>> GetAllAsync<T>(string table) where T : class;

        Task<T> GetByIdAsync<T>(object id) where T : class;

        Task<T> GetByIdAsync<T>(string columnId, object id) where T : class;

        Task<IEnumerable<T>> GetAsync<T>(string table, string column, string parameter) where T : class;

        Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, object param = null) where T : class;

        Task<T> FirstOrDefaultQueryAsync<T>(string sql, object param = null) where T : class;
    }
En caso de necesitar realizar consultas complejas recomendamos crear sus propios objetos Query que interactúen con dapper. No se recomienda realizar múltiples consultas a base de datos con los métodos genéricos de la interface, Sugerimos utilizar ExecuteQueryAsync o FirstOrDefaultQueryAsync o crear sus propias consultas con dapper.

UseConnection

Con el método UseConnection, inyectamos la conexión de base de datos en la consulta que vamos a realizar. Este método construye e inyecta la conexión respetando las buenas prácticas. Esto permite utilizar Dapper nativamente lo que significa que se puede aprovechar el 100% de las funcionalidades de base de datos.

Llamamos al método al principio de la consulta, como en este ejemplo:

var result = _query.UseConnection(connection => {
    var sql = "SELECT * FROM Board";
    var resultSet = connection.Query<Board>(sql);
    return resultSet;
});

UseConnectionAsync permite realizar la consulta de forma asíncrona:

var results = await _query.UseConnectionAsync(async connection => {
    var sql = "SELECT * FROM Board";
    var resultSet = await connection.QueryAsync<Board>(sql);
    return resultSet;
});