Skip to content

EventStore

El EventStoreContainer es un componente de la librería CodeDesignPlus.Net.xUnit que proporciona un contenedor Docker para ejecutar EventStoreDB, un sistema de base de datos diseñado para almacenar secuencias de eventos. Este componente facilita la creación de pruebas unitarias que dependen de EventStoreDB, ofreciendo un entorno de pruebas aislado y reproducible.

¿Cómo Funciona?


El EventStoreContainer utiliza Docker Compose para definir y gestionar el contenedor EventStoreDB. Al inicializar una instancia de esta clase, se genera un archivo docker-compose.yml temporal con la configuración necesaria para ejecutar el contenedor. Luego, el contenedor se inicia y se detiene automáticamente al final de la ejecución de las pruebas.

El proceso general de funcionamiento es el siguiente:

  1. Configuración del Contenedor: El método Build() define la configuración específica del contenedor EventStore, incluyendo el puerto interno, la ruta al archivo docker-compose.yml, opciones de reinicio forzado, eliminación de contenedores huérfanos, parada al finalizar y un nombre de servicio alternativo generado dinámicamente para evitar conflictos.
  2. Creación del Contenedor: Se crea una instancia de DockerComposeCompositeService usando la configuración definida.
  3. Inicio y Detención del Contenedor: El contenedor se inicia cuando se crea una instancia de la clase EventStoreContainer y se detiene automáticamente cuando la instancia se elimina, asegurando la limpieza del entorno.

Container


El EventStoreContainer hereda de la clase DockerCompose, la cual proporciona la lógica base para interactuar con Docker Compose. La clase utiliza la clase DockerComposeCompositeService de la librería Testcontainers para iniciar y detener contenedores docker. La configuración del contenedor se define en el método Build() que crea un archivo docker-compose.yml con los parámetros necesarios para la ejecución del contenedor EventStore. El archivo docker-compose.yml se encuentra dentro del directorio Containers/EventStoreContainer.

namespace CodeDesignPlus.Net.xUnit.Containers.EventStoreContainer;
/// <summary>
/// Represents a Docker container for EventStore, managed using Docker Compose.
/// </summary>
public class EventStoreContainer : DockerCompose
{
/// <summary>
/// Builds the Docker Compose service configuration for the EventStore container.
/// </summary>
/// <returns>An <see cref="ICompositeService"/> representing the Docker Compose service.</returns>
protected override ICompositeService Build()
{
// Set the internal port for the EventStore container.
this.InternalPort = 1113;
// Define the path to the Docker Compose file.
var file = Path.Combine(Directory.GetCurrentDirectory(), "Containers", "EventStoreContainer", (TemplateString)"docker-compose.yml");
// Configure the Docker Compose settings.
var dockerCompose = new DockerComposeConfig
{
ComposeFilePath = new[] { file },
ForceRecreate = true,
RemoveOrphans = true,
StopOnDispose = true,
AlternativeServiceName = "eventstore_" + Guid.NewGuid().ToString("N"),
};
// Enable port retrieval and set the container name.
this.EnableGetPort = true;
this.ContainerName = $"{dockerCompose.AlternativeServiceName}-eventstore.db";
// Create and return the Docker Compose service.
var compose = new DockerComposeCompositeService(base.DockerHost, dockerCompose);
return compose;
}
}

La clase EventStoreCollectionFixture se utiliza como un fixture de xUnit, que proporciona una forma de compartir el contenedor entre las pruebas de una colección.

namespace CodeDesignPlus.Net.xUnit.Containers.EventStoreContainer;
/// <summary>
/// Provides a fixture for managing an EventStore container during xUnit tests.
/// </summary>
public sealed class EventStoreCollectionFixture : IDisposable
{
/// <summary>
/// The name of the collection for the EventStore tests.
/// </summary>
public const string Collection = "EventStore Collection";
/// <summary>
/// Gets the EventStore container instance.
/// </summary>
public EventStoreContainer Container { get; }
/// <summary>
/// Initializes a new instance of the <see cref="EventStoreCollectionFixture"/> class.
/// </summary>
public EventStoreCollectionFixture()
{
this.Container = new EventStoreContainer();
}
/// <summary>
/// Disposes the EventStore container instance.
/// </summary>
public void Dispose()
{
this.Container.StopInstance();
}
}

Ejemplo de Uso


Este ejemplo muestra cómo usar EventStoreContainer para realizar pruebas de integración con EventStoreDB. Se establece una conexión a una instancia de EventStoreDB, se escribe un nuevo evento a un stream, se lee este evento, y se verifica que su contenido coincida con lo escrito. La prueba demuestra la configuración de la conexión a EventStoreDB y cómo obtener la información de conexión desde EventStoreContainer.

using CodeDesignPlus.Net.xUnit.Containers.EventStoreContainer;
using EventStore.ClientAPI;
namespace CodeDesignPlus.Net.xUnit.Test;
[Collection("EventStore Collection")]
public class EventStoreContainerTest(EventStoreCollectionFixture eventStoreCollectionFixture)
{
private readonly EventStoreContainer eventStoreContainer = eventStoreCollectionFixture.Container;
[Fact]
public async Task CheckConnectionServer()
{
var connectionSettings = ConnectionSettings.Create()
.DisableTls()
.UseConsoleLogger();
var connection = EventStoreConnection.Create(connectionSettings, new Uri($"tcp://localhost:{this.eventStoreContainer.Port}"));
await connection.ConnectAsync();
// Escribir un evento
var eventDataExptected = new EventData(
eventId: Guid.NewGuid(),
type: "MyEventType",
isJson: true,
data: Encoding.UTF8.GetBytes("{\"myProperty\":\"myValue\"}"),
metadata: null
);
await connection.AppendToStreamAsync("MyStream", ExpectedVersion.Any, eventDataExptected);
// Leer eventos
var streamEvents = await connection.ReadStreamEventsForwardAsync("MyStream", start: 0, count: 10, resolveLinkTos: false);
foreach (var resolvedEvent in streamEvents.Events)
{
var eventData = resolvedEvent.Event;
var eventPayload = Encoding.UTF8.GetString(eventData.Data);
Console.WriteLine($"Event Type: {eventData.EventType}");
Console.WriteLine($"Data: {eventPayload}");
Assert.Equal(eventDataExptected.Data, eventData.Data);
}
connection.Close();
}
}

Conclusiones


  • El EventStoreContainer facilita la creación de pruebas unitarias que requieren una instancia de EventStoreDB.
  • Utiliza Docker Compose para la gestión del contenedor, proporcionando una forma sencilla de definir la configuración.
  • La clase EventStoreCollectionFixture permite compartir un contenedor entre todas las pruebas de una misma colección.
  • El uso del EventStoreContainer mejora la reproducibilidad y el aislamiento de las pruebas unitarias.
  • El contenedor EventStore se inicia automaticamente al comienzo de cada prueba y se detiene una vez termina, esto permite un ambiente de pruebas limpio.

Referencias Adicionales