0
0
mirror of https://github.com/alex289/CleanArchitecture.git synced 2025-06-30 02:31:08 +00:00

Add event sourcing

This commit is contained in:
Alexander Konietzko 2023-07-01 16:46:08 +02:00
parent 745b3930f0
commit 53e0966f51
No known key found for this signature in database
GPG Key ID: BA6905F37AEC2B5B
37 changed files with 840 additions and 28 deletions

View File

@ -40,7 +40,7 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
builder.Services.AddSwagger();
builder.Services.AddAuth(builder.Configuration);
builder.Services.AddInfrastructure();
builder.Services.AddInfrastructure(builder.Configuration, "CleanArchitecture.Infrastructure");
builder.Services.AddQueryHandlers();
builder.Services.AddServices();
builder.Services.AddCommandHandlers();
@ -73,8 +73,12 @@ using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var appDbContext = services.GetRequiredService<ApplicationDbContext>();
var storeDbContext = services.GetRequiredService<EventStoreDbContext>();
var domainStoreDbContext = services.GetRequiredService<DomainNotificationStoreDbContext>();
appDbContext.EnsureMigrationsApplied();
storeDbContext.EnsureMigrationsApplied();
domainStoreDbContext.EnsureMigrationsApplied();
}
app.Run();

View File

@ -14,7 +14,7 @@
"dotnetRunMessages": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7260;http://localhost:5201",
"applicationUrl": "https://localhost:7001;http://localhost:7000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -4,6 +4,7 @@ using CleanArchitecture.Domain.Commands.Users.UpdateUser;
using CleanArchitecture.Domain.Enums;
using CleanArchitecture.Domain.Errors;
using CleanArchitecture.Domain.Events.User;
using Moq;
using Xunit;
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.UpdateUser;
@ -54,4 +55,37 @@ public sealed class UpdateUserCommandHandlerTests
ErrorCodes.ObjectNotFound,
$"There is no User with Id {command.UserId}");
}
[Fact]
public async Task Should_Not_Update_With_Existing_User_Email()
{
var user = _fixture.SetupUser();
var command = new UpdateUserCommand(
user.Id,
"test@email.com",
"Test",
"Email",
UserRole.User);
_fixture.UserRepository
.Setup(x => x.GetByEmailAsync(command.Email))
.ReturnsAsync(new Entities.User(
Guid.NewGuid(),
command.Email,
"Some",
"User",
"234fs@#*@#",
UserRole.User));
await _fixture.CommandHandler.Handle(command, default);
_fixture
.VerifyNoCommit()
.VerifyNoRaisedEvent<UserUpdatedEvent>()
.VerifyAnyDomainNotification()
.VerifyExistingNotification(
DomainErrorCodes.UserAlreadyExists,
$"There is already a User with Email {command.Email}");
}
}

View File

@ -21,7 +21,7 @@ public sealed class UpdateUserCommandTestFixture : CommandHandlerFixtureBase
}
public UpdateUserCommandHandler CommandHandler { get; }
private Mock<IUserRepository> UserRepository { get; }
public Mock<IUserRepository> UserRepository { get; }
public Entities.User SetupUser()
{

View File

@ -1,5 +1,6 @@
using System;
using System.Linq.Expressions;
using CleanArchitecture.Domain.DomainEvents;
using CleanArchitecture.Domain.Enums;
using CleanArchitecture.Domain.Interfaces;
using CleanArchitecture.Domain.Notifications;

View File

@ -11,6 +11,9 @@ public sealed class ApiUser : IUser
{
private readonly IHttpContextAccessor _httpContextAccessor;
private string? _name = null;
private Guid _userId = Guid.Empty;
public ApiUser(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
@ -18,11 +21,17 @@ public sealed class ApiUser : IUser
public Guid GetUserId()
{
if (_userId != Guid.Empty)
{
return _userId;
}
var claim = _httpContextAccessor.HttpContext?.User.Claims
.FirstOrDefault(x => string.Equals(x.Type, ClaimTypes.NameIdentifier));
if (Guid.TryParse(claim?.Value, out var userId))
{
_userId = userId;
return userId;
}
@ -42,7 +51,32 @@ public sealed class ApiUser : IUser
throw new ArgumentException("Could not parse user role");
}
public string Name => _httpContextAccessor.HttpContext?.User.Identity?.Name ?? string.Empty;
public string Name
{
get
{
if (_name != null)
{
return _name;
}
var identity = _httpContextAccessor.HttpContext?.User.Identity;
if (identity == null)
{
_name = string.Empty;
return string.Empty;
}
if (!string.IsNullOrWhiteSpace(identity.Name))
{
_name = identity.Name;
return identity.Name;
}
var claim = _httpContextAccessor.HttpContext!.User.Claims
.FirstOrDefault(c => string.Equals(c.Type, "name", StringComparison.OrdinalIgnoreCase))?
.Value;
_name = claim ?? string.Empty;
return _name;
}
}
public string GetUserEmail()
{
@ -54,6 +88,6 @@ public sealed class ApiUser : IUser
return claim.Value;
}
throw new ArgumentException("Could not parse user email");
return string.Empty;
}
}

View File

@ -57,6 +57,21 @@ public sealed class UpdateUserCommandHandler : CommandHandlerBase,
return;
}
if (request.Email != user.Email)
{
var existingUser = await _userRepository.GetByEmailAsync(request.Email);
if (existingUser != null)
{
await Bus.RaiseEventAsync(
new DomainNotification(
request.MessageType,
$"There is already a User with Email {request.Email}",
DomainErrorCodes.UserAlreadyExists));
return;
}
}
if (_user.GetUserRole() == UserRole.Admin)
{
user.SetRole(request.Role);

View File

@ -1,14 +0,0 @@
using System;
using MediatR;
namespace CleanArchitecture.Domain;
public abstract class DomainEvent : INotification
{
protected DomainEvent(Guid aggregateId)
{
Timestamp = DateTime.Now;
}
private DateTime Timestamp { get; }
}

View File

@ -0,0 +1,19 @@
using System;
using MediatR;
namespace CleanArchitecture.Domain.DomainEvents;
public abstract class DomainEvent : Message, INotification
{
protected DomainEvent(Guid aggregateId) : base(aggregateId)
{
Timestamp = DateTime.Now;
}
protected DomainEvent(Guid aggregateId, string? messageType) : base(aggregateId, messageType)
{
Timestamp = DateTime.Now;
}
public DateTime Timestamp { get; private set; }
}

View File

@ -0,0 +1,8 @@
using System.Threading.Tasks;
namespace CleanArchitecture.Domain.DomainEvents;
public interface IDomainEventStore
{
Task SaveAsync<T>(T domainEvent) where T : DomainEvent;
}

View File

@ -0,0 +1,22 @@
using System;
using MediatR;
namespace CleanArchitecture.Domain.DomainEvents;
public abstract class Message : IRequest
{
protected Message(Guid aggregateId)
{
AggregateId = aggregateId;
MessageType = GetType().Name;
}
protected Message(Guid aggregateId, string? messageType)
{
AggregateId = aggregateId;
MessageType = messageType ?? string.Empty;
}
public Guid AggregateId { get; private set; }
public string MessageType { get; protected set; }
}

View File

@ -0,0 +1,28 @@
using System;
namespace CleanArchitecture.Domain.DomainEvents;
public class StoredDomainEvent : DomainEvent
{
public Guid Id { get; private set; }
public string Data { get; private set; } = string.Empty;
public string User { get; private set; } = string.Empty;
public string CorrelationId { get; private set; } = string.Empty;
public StoredDomainEvent(
DomainEvent domainEvent,
string data,
string user,
string correlationId)
: base(domainEvent.AggregateId, domainEvent.MessageType)
{
Id = Guid.NewGuid();
Data = data;
User = user;
CorrelationId = correlationId;
}
// EF Constructor
protected StoredDomainEvent() : base(Guid.NewGuid())
{ }
}

View File

@ -0,0 +1,35 @@
using System;
using CleanArchitecture.Domain.Notifications;
namespace CleanArchitecture.Domain.DomainNotifications;
public class StoredDomainNotification : DomainNotification
{
public Guid Id { get; private set; }
public string SerializedData { get; private set; } = string.Empty;
public string User { get; private set; } = string.Empty;
public string CorrelationId { get; private set; } = string.Empty;
public StoredDomainNotification(
DomainNotification domainNotification,
string data,
string user,
string correlationId) : base(
domainNotification.Key,
domainNotification.Value,
domainNotification.Code,
null,
domainNotification.AggregateId)
{
Id = Guid.NewGuid();
User = user;
SerializedData = data;
CorrelationId = correlationId;
MessageType = domainNotification.MessageType;
}
// EF Constructor
protected StoredDomainNotification() : base(string.Empty, string.Empty, string.Empty)
{ }
}

View File

@ -1,4 +1,5 @@
using System;
using CleanArchitecture.Domain.DomainEvents;
namespace CleanArchitecture.Domain.Events.User;

View File

@ -1,4 +1,5 @@
using System;
using CleanArchitecture.Domain.DomainEvents;
namespace CleanArchitecture.Domain.Events.User;

View File

@ -1,4 +1,5 @@
using System;
using CleanArchitecture.Domain.DomainEvents;
namespace CleanArchitecture.Domain.Events.User;

View File

@ -1,4 +1,5 @@
using System;
using CleanArchitecture.Domain.DomainEvents;
namespace CleanArchitecture.Domain.Events.User;

View File

@ -1,5 +1,6 @@
using System.Threading.Tasks;
using CleanArchitecture.Domain.Commands;
using CleanArchitecture.Domain.DomainEvents;
using MediatR;
namespace CleanArchitecture.Domain.Interfaces;

View File

@ -8,4 +8,5 @@ public interface IUser
string Name { get; }
Guid GetUserId();
UserRole GetUserRole();
string GetUserEmail();
}

View File

@ -1,8 +1,9 @@
using System;
using CleanArchitecture.Domain.DomainEvents;
namespace CleanArchitecture.Domain.Notifications;
public sealed class DomainNotification : DomainEvent
public class DomainNotification : DomainEvent
{
public DomainNotification(
string key,
@ -23,4 +24,5 @@ public sealed class DomainNotification : DomainEvent
public string Value { get; }
public string Code { get; }
public object? Data { get; set; }
public int Version { get; private set; } = 1;
}

View File

@ -2,6 +2,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
using CleanArchitecture.Domain.DomainEvents;
using CleanArchitecture.Domain.Events.User;
using CleanArchitecture.Domain.Notifications;
using MediatR;
@ -16,8 +17,9 @@ public sealed class InMemoryBusTests
public async Task InMemoryBus_Should_Publish_When_Not_DomainNotification()
{
var mediator = new Mock<IMediator>();
var domainEventStore = new Mock<IDomainEventStore>();
var inMemoryBus = new InMemoryBus(mediator.Object);
var inMemoryBus = new InMemoryBus(mediator.Object, domainEventStore.Object);
const string key = "Key";
const string value = "Value";
@ -34,8 +36,9 @@ public sealed class InMemoryBusTests
public async Task InMemoryBus_Should_Save_And_Publish_When_DomainNotification()
{
var mediator = new Mock<IMediator>();
var domainEventStore = new Mock<IDomainEventStore>();
var inMemoryBus = new InMemoryBus(mediator.Object);
var inMemoryBus = new InMemoryBus(mediator.Object, domainEventStore.Object);
var userDeletedEvent = new UserDeletedEvent(Guid.NewGuid());
@ -48,8 +51,9 @@ public sealed class InMemoryBusTests
public async Task InMemoryBus_Should_Send_Command_Async()
{
var mediator = new Mock<IMediator>();
var domainEventStore = new Mock<IDomainEventStore>();
var inMemoryBus = new InMemoryBus(mediator.Object);
var inMemoryBus = new InMemoryBus(mediator.Object, domainEventStore.Object);
var deleteUserCommand = new DeleteUserCommand(Guid.NewGuid());

View File

@ -18,6 +18,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@
using CleanArchitecture.Domain.DomainEvents;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
namespace CleanArchitecture.Infrastructure.Configurations.EventSourcing;
public sealed class StoredDomainEventConfiguration : IEntityTypeConfiguration<StoredDomainEvent>
{
public void Configure(EntityTypeBuilder<StoredDomainEvent> builder)
{
builder.Property(c => c.Timestamp)
.HasColumnName("CreationDate");
builder.Property(c => c.MessageType)
.HasColumnName("Action")
.HasColumnType("varchar(100)");
builder.Property(c => c.CorrelationId)
.HasMaxLength(100);
builder.Property(c => c.User)
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
using CleanArchitecture.Domain.DomainNotifications;
namespace CleanArchitecture.Infrastructure.Configurations.EventSourcing;
public sealed class StoredDomainNotificationConfiguration : IEntityTypeConfiguration<StoredDomainNotification>
{
public void Configure(EntityTypeBuilder<StoredDomainNotification> builder)
{
builder.Property(c => c.MessageType)
.IsRequired()
.HasMaxLength(100);
builder.Property(c => c.Key)
.IsRequired()
.HasMaxLength(100);
builder.Property(c => c.Value)
.HasMaxLength(1024);
builder.Property(c => c.Code)
.IsRequired()
.HasMaxLength(100);
builder.Property(c => c.SerializedData)
.IsRequired();
builder.Property(c => c.User)
.IsRequired()
.HasMaxLength(100);
builder.Property(c => c.CorrelationId)
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
builder.Ignore(c => c.Data);
builder.Property(c => c.SerializedData)
.HasColumnName("Data");
}
}

View File

@ -0,0 +1,21 @@
using CleanArchitecture.Domain.DomainNotifications;
using CleanArchitecture.Infrastructure.Configurations.EventSourcing;
using Microsoft.EntityFrameworkCore;
namespace CleanArchitecture.Infrastructure.Database;
public class DomainNotificationStoreDbContext : DbContext
{
public DomainNotificationStoreDbContext(DbContextOptions<DomainNotificationStoreDbContext> options) : base(options)
{
}
public virtual DbSet<StoredDomainNotification> StoredDomainNotifications { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new StoredDomainNotificationConfiguration());
}
}

View File

@ -0,0 +1,21 @@
using CleanArchitecture.Domain.DomainEvents;
using CleanArchitecture.Infrastructure.Configurations.EventSourcing;
using Microsoft.EntityFrameworkCore;
namespace CleanArchitecture.Infrastructure.Database;
public class EventStoreDbContext : DbContext
{
public EventStoreDbContext(DbContextOptions<EventStoreDbContext> options) : base(options)
{
}
public virtual DbSet<StoredDomainEvent> StoredDomainEvents { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new StoredDomainEventConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,56 @@
using CleanArchitecture.Domain.DomainEvents;
using CleanArchitecture.Domain.DomainNotifications;
using System.Threading.Tasks;
using CleanArchitecture.Domain.Notifications;
using CleanArchitecture.Infrastructure.Database;
using Newtonsoft.Json;
namespace CleanArchitecture.Infrastructure.EventSourcing;
public class DomainEventStore : IDomainEventStore
{
private readonly EventStoreDbContext _eventStoreDbContext;
private readonly DomainNotificationStoreDbContext _domainNotificationStoreDbContext;
private readonly IEventStoreContext _context;
public DomainEventStore(
EventStoreDbContext eventStoreDbContext,
DomainNotificationStoreDbContext domainNotificationStoreDbContext,
IEventStoreContext context)
{
_eventStoreDbContext = eventStoreDbContext;
_domainNotificationStoreDbContext = domainNotificationStoreDbContext;
_context = context;
}
public async Task SaveAsync<T>(T domainEvent) where T : DomainEvent
{
var serializedData = JsonConvert.SerializeObject(domainEvent);
switch (domainEvent)
{
case DomainNotification d:
var storedDomainNotification = new StoredDomainNotification(
d,
serializedData,
_context.GetUserEmail(),
_context.GetCorrelationId());
_domainNotificationStoreDbContext.StoredDomainNotifications.Add(storedDomainNotification);
await _domainNotificationStoreDbContext.SaveChangesAsync();
break;
default:
var storedDomainEvent = new StoredDomainEvent(
domainEvent,
serializedData,
_context.GetUserEmail(),
_context.GetCorrelationId());
_eventStoreDbContext.StoredDomainEvents.Add(storedDomainEvent);
await _eventStoreDbContext.SaveChangesAsync();
break;
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using CleanArchitecture.Domain.Interfaces;
using Microsoft.AspNetCore.Http;
namespace CleanArchitecture.Infrastructure.EventSourcing;
public sealed class EventStoreContext : IEventStoreContext
{
private readonly string _correlationId;
private readonly IUser? _user;
public EventStoreContext(IUser? user, IHttpContextAccessor? httpContextAccessor)
{
_user = user;
if (httpContextAccessor?.HttpContext == null ||
!httpContextAccessor.HttpContext.Request.Headers.TryGetValue("X-CLEAN-ARCHITECTURE-CORRELATION-ID", out var id))
{
_correlationId = $"internal - {Guid.NewGuid()}";
}
else
{
_correlationId = id.ToString();
}
}
public string GetCorrelationId() => _correlationId;
public string GetUserEmail() => _user?.GetUserEmail() ?? string.Empty;
}

View File

@ -0,0 +1,7 @@
namespace CleanArchitecture.Infrastructure.EventSourcing;
public interface IEventStoreContext
{
public string GetUserEmail();
public string GetCorrelationId();
}

View File

@ -1,20 +1,47 @@
using CleanArchitecture.Domain.Interfaces;
using CleanArchitecture.Domain.DomainEvents;
using CleanArchitecture.Domain.Interfaces;
using CleanArchitecture.Domain.Interfaces.Repositories;
using CleanArchitecture.Domain.Notifications;
using CleanArchitecture.Infrastructure.Database;
using CleanArchitecture.Infrastructure.EventSourcing;
using CleanArchitecture.Infrastructure.Repositories;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.Infrastructure.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration,
string migrationsAssemblyName,
string connectionStringName = "DefaultConnection")
{
// Add event store db context
services.AddDbContext<EventStoreDbContext>(
options =>
{
options.UseSqlServer(
configuration.GetConnectionString(connectionStringName),
b => b.MigrationsAssembly(migrationsAssemblyName));
});
services.AddDbContext<DomainNotificationStoreDbContext>(
options =>
{
options.UseSqlServer(
configuration.GetConnectionString(connectionStringName),
b => b.MigrationsAssembly(migrationsAssemblyName));
});
// Core Infra
services.AddScoped<IUnitOfWork, UnitOfWork<ApplicationDbContext>>();
services.AddScoped<IEventStoreContext, EventStoreContext>();
services.AddScoped<INotificationHandler<DomainNotification>, DomainNotificationHandler>();
services.AddScoped<IDomainEventStore, DomainEventStore>();
services.AddScoped<IMediatorHandler, InMemoryBus>();
// Repositories

View File

@ -1,6 +1,6 @@
using System.Threading.Tasks;
using CleanArchitecture.Domain;
using CleanArchitecture.Domain.Commands;
using CleanArchitecture.Domain.DomainEvents;
using CleanArchitecture.Domain.Interfaces;
using MediatR;
@ -9,10 +9,14 @@ namespace CleanArchitecture.Infrastructure;
public sealed class InMemoryBus : IMediatorHandler
{
private readonly IMediator _mediator;
private readonly IDomainEventStore _domainEventStore;
public InMemoryBus(IMediator mediator)
public InMemoryBus(
IMediator mediator,
IDomainEventStore domainEventStore)
{
_mediator = mediator;
_domainEventStore = domainEventStore;
}
public Task<TResponse> QueryAsync<TResponse>(IRequest<TResponse> query)
@ -22,7 +26,7 @@ public sealed class InMemoryBus : IMediatorHandler
public async Task RaiseEventAsync<T>(T @event) where T : DomainEvent
{
// await _domainEventStore.SaveAsync(@event);
await _domainEventStore.SaveAsync(@event);
await _mediator.Publish(@event);
}

View File

@ -0,0 +1,85 @@
// <auto-generated />
using System;
using CleanArchitecture.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CleanArchitecture.Infrastructure.Migrations.DomainNotificationStoreDb
{
[DbContext(typeof(DomainNotificationStoreDbContext))]
[Migration("20230701135523_AddDomainNotificationStore")]
partial class AddDomainNotificationStore
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("CleanArchitecture.Domain.DomainNotifications.StoredDomainNotification", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AggregateId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("CorrelationId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("MessageType")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("SerializedData")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("Data");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2");
b.Property<string>("User")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.Property<int>("Version")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("StoredDomainNotifications");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CleanArchitecture.Infrastructure.Migrations.DomainNotificationStoreDb
{
/// <inheritdoc />
public partial class AddDomainNotificationStore : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "StoredDomainNotifications",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Data = table.Column<string>(type: "nvarchar(max)", nullable: false),
User = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
CorrelationId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
AggregateId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MessageType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Timestamp = table.Column<DateTime>(type: "datetime2", nullable: false),
Key = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Value = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: false),
Code = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Version = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_StoredDomainNotifications", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "StoredDomainNotifications");
}
}
}

View File

@ -0,0 +1,82 @@
// <auto-generated />
using System;
using CleanArchitecture.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CleanArchitecture.Infrastructure.Migrations.DomainNotificationStoreDb
{
[DbContext(typeof(DomainNotificationStoreDbContext))]
partial class DomainNotificationStoreDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("CleanArchitecture.Domain.DomainNotifications.StoredDomainNotification", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AggregateId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("CorrelationId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("MessageType")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("SerializedData")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("Data");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2");
b.Property<string>("User")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.Property<int>("Version")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("StoredDomainNotifications");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,67 @@
// <auto-generated />
using System;
using CleanArchitecture.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CleanArchitecture.Infrastructure.Migrations.EventStoreDb
{
[DbContext(typeof(EventStoreDbContext))]
[Migration("20230701135441_AddEventStore")]
partial class AddEventStore
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("CleanArchitecture.Domain.DomainEvents.StoredDomainEvent", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AggregateId")
.HasColumnType("uniqueidentifier");
b.Property<string>("CorrelationId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("MessageType")
.IsRequired()
.HasColumnType("varchar(100)")
.HasColumnName("Action");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2")
.HasColumnName("CreationDate");
b.Property<string>("User")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.ToTable("StoredDomainEvents");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CleanArchitecture.Infrastructure.Migrations.EventStoreDb
{
/// <inheritdoc />
public partial class AddEventStore : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "StoredDomainEvents",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Data = table.Column<string>(type: "nvarchar(max)", nullable: false),
User = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
CorrelationId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
AggregateId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Action = table.Column<string>(type: "varchar(100)", nullable: false),
CreationDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_StoredDomainEvents", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "StoredDomainEvents");
}
}
}

View File

@ -0,0 +1,64 @@
// <auto-generated />
using System;
using CleanArchitecture.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CleanArchitecture.Infrastructure.Migrations.EventStoreDb
{
[DbContext(typeof(EventStoreDbContext))]
partial class EventStoreDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("CleanArchitecture.Domain.DomainEvents.StoredDomainEvent", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("AggregateId")
.HasColumnType("uniqueidentifier");
b.Property<string>("CorrelationId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("MessageType")
.IsRequired()
.HasColumnType("varchar(100)")
.HasColumnName("Action");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2")
.HasColumnName("CreationDate");
b.Property<string>("User")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.ToTable("StoredDomainEvents");
});
#pragma warning restore 612, 618
}
}
}