Skip to content

IEventStoreFactory

IEventStoreFactory es una interfaz que define un contrato para gestionar y recuperar conexiones a instancias de EventStore. Esta interfaz permite la creación, almacenamiento en caché y gestión eficiente de conexiones a EventStore.

Se utiliza para abstraer la lógica de creación y gestión de conexiones a EventStore. Proporciona métodos para crear nuevas conexiones, recuperar conexiones existentes y eliminar conexiones cuando ya no son necesarias.

Funcionalidades


  1. Creación de Conexiones:

    • Task<ES.IEventStoreConnection> CreateAsync(string key, CancellationToken cancellationToken = default): Crea o recupera una conexión existente a EventStore basada en la clave proporcionada. Este método es asíncrono y devuelve una instancia de IEventStoreConnection inicializada.
  2. Eliminación de Conexiones:

    • bool RemoveConnection(string key): Intenta eliminar la conexión con la clave especificada. Devuelve true si la conexión se eliminó correctamente; de lo contrario, devuelve false.
  3. Obtención de Credenciales:

    • (string, string) GetCredentials(string key): Obtiene las credenciales (nombre de usuario y contraseña) para la clave de conexión especificada. Lanza una excepción si la clave es nula o vacía, o si la clave no se encuentra en la configuración.

Beneficios del Patrón Factory


El patrón Factory proporciona varios beneficios al implementar IEventStoreFactory:

  1. Desacoplamiento: Separa la lógica de creación de objetos de su uso, permitiendo que las clases dependientes no necesiten conocer los detalles de la creación de instancias.
  2. Reutilización de Conexiones: Permite la reutilización de conexiones existentes, mejorando la eficiencia y reduciendo la sobrecarga de recursos.
  3. Gestión Centralizada: Centraliza la lógica de creación y gestión de conexiones, facilitando el mantenimiento y la actualización del código.
  4. Facilidad de Pruebas: Facilita la creación de pruebas unitarias mediante el uso de mocks y stubs, ya que las dependencias pueden ser inyectadas y controladas fácilmente.

Dependencias


IEventStoreFactory depende de IEventStoreConnection para inicializar y gestionar las conexiones a EventStore. La interfaz IEventStoreConnection define el contrato para conectar e interactuar con una instancia de EventStore, mientras que IEventStoreFactory gestiona la creación y el ciclo de vida de estas conexiones.

Implementación


La interfaz IEventStoreFactory se implementa en la clase EventStoreFactory, que proporciona la lógica para gestionar y recuperar conexiones a instancias de EventStore. La clase EventStoreFactory se encuentra en el espacio de nombres CodeDesignPlus.Net.EventStore.Services.

namespace CodeDesignPlus.Net.EventStore.Services;
/// <summary>
/// Provides a factory to manage and retrieve connections to EventStore instances.
/// This class ensures that connections are created, cached, and managed efficiently.
/// </summary>
public class EventStoreFactory : IEventStoreFactory
{
private readonly ConcurrentDictionary<string, ES.IEventStoreConnection> connections = new();
private readonly IEventStoreConnection eventStoreConnection;
private readonly ILogger<EventStoreFactory> logger;
private readonly EventStoreOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="EventStoreFactory"/> class.
/// </summary>
/// <param name="eventStoreConnection">The handler to initialize EventStore connections.</param>
/// <param name="logger">The logger used for logging events and issues related to the EventStore connections.</param>
/// <param name="options">The configuration options for the EventStore connections.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="eventStoreConnection"/>, <paramref name="logger"/>, or <paramref name="options"/> is null.
/// </exception>
public EventStoreFactory(IEventStoreConnection eventStoreConnection, ILogger<EventStoreFactory> logger, IOptions<EventStoreOptions> options)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(eventStoreConnection);
ArgumentNullException.ThrowIfNull(logger);
this.eventStoreConnection = eventStoreConnection;
this.logger = logger;
this.options = options.Value;
}
/// <summary>
/// Creates or retrieves an existing connection to EventStore based on the provided key.
/// </summary>
/// <param name="key">The unique identifier representing the desired connection.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task representing the asynchronous operation. The task result contains the connection to EventStore.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="key"/> is null or empty.</exception>
/// <exception cref="EventStoreException">Thrown when the specified connection key is not found in the settings.</exception>
public async Task<ES.IEventStoreConnection> CreateAsync(string key, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
if (!options.Servers.TryGetValue(key, out Server server))
throw new EventStoreException("The connection is not registered in the settings.");
if (connections.TryGetValue(key, out ES.IEventStoreConnection connection))
return connection;
connection = await this.eventStoreConnection.InitializeAsync(server).ConfigureAwait(false);
var success = this.connections.TryAdd(key, connection);
this.logger.LogInformation("Successfully created and cached the connection for key '{Key}-{Success}'.", key, success);
return connection;
}
/// <summary>
/// Attempts to remove the connection with the specified key.
/// </summary>
/// <param name="key">The unique identifier for the connection to be removed.</param>
/// <returns><c>true</c> if the connection was successfully removed; otherwise, <c>false</c>.</returns>
public bool RemoveConnection(string key)
{
var result = this.connections.TryRemove(key, out _);
if (result)
this.logger.LogInformation("Successfully removed the connection for key '{Key}'.", key);
else
this.logger.LogWarning("Failed to remove the connection for key '{Key}'.", key);
return result;
}
/// <summary>
/// Gets the credentials for the specified connection key.
/// </summary>
/// <param name="key">The key connection.</param>
/// <returns>A tuple containing the username and password for the specified connection key.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="key"/> is null or empty.</exception>
/// <exception cref="EventStoreException">Thrown when the specified connection key is not found in the settings.</exception>
public (string, string) GetCredentials(string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
if (!options.Servers.TryGetValue(key, out Server server))
throw new EventStoreException("The connection is not registered in the settings.");
return (server.User, server.Password);
}
}

Ejemplo


Una vez que se ha implementado la interfaz IEventStoreFactory, se puede utilizar para crear y gestionar conexiones a EventStore en una aplicación .NET.

  1. Agregar las propiedades de configuración al archivo appsettings.json:

    {
    "EventStore": {
    "Servers": {
    "Core": {
    "ConnectionString": "tcp://localhost:1113?tls=false",
    "User": "admin",
    "Password": "12345678"
    }
    }
    }
    }
  2. Registrar la interfaz IEventStoreFactory en el contenedor de dependencias:

    public class Startup
    {
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddSingleton<IEventStoreConnection, EventStoreConnection>();
    services.AddSingleton<IEventStoreFactory, EventStoreFactory>();
    services.AddLogging();
    }
    }
  3. Utilizar la fábrica para crear y gestionar conexiones a EventStore en la aplicación.

    public class EventStoreService
    {
    private readonly IEventStoreFactory eventStoreFactory;
    public EventStoreService(IEventStoreFactory eventStoreFactory)
    {
    this.eventStoreFactory = eventStoreFactory;
    }
    public async Task SaveEventAsync<TEvent>(TEvent @event, string key)
    {
    var connection = await eventStoreFactory.CreateAsync(key);
    var eventData = new ES.EventData(
    Guid.NewGuid(),
    "eventType",
    true,
    Encoding.UTF8.GetBytes(JsonSerializer.Serialize(@event)),
    null);
    await connection.AppendToStreamAsync("streamName", ES.ExpectedVersion.Any, eventData);
    }
    }

Conclusiones


La interfaz IEventStoreFactory es esencial para cualquier aplicación que utilice EventStore, proporcionando una manera estructurada y eficiente de gestionar conexiones. Implementar el patrón Factory trae beneficios significativos, como el desacoplamiento, la reutilización de conexiones, la gestión centralizada y la facilidad de pruebas. Además, la relación con IEventStoreConnection asegura que las conexiones se inicialicen y gestionen de manera robusta, proporcionando una capa de abstracción que facilita el uso y la prueba de la conexión en aplicaciones que utilizan EventStore.