mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-08-23 03:38:36 +00:00
Add event sourcing
This commit is contained in:
parent
745b3930f0
commit
53e0966f51
@ -40,7 +40,7 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
|||||||
|
|
||||||
builder.Services.AddSwagger();
|
builder.Services.AddSwagger();
|
||||||
builder.Services.AddAuth(builder.Configuration);
|
builder.Services.AddAuth(builder.Configuration);
|
||||||
builder.Services.AddInfrastructure();
|
builder.Services.AddInfrastructure(builder.Configuration, "CleanArchitecture.Infrastructure");
|
||||||
builder.Services.AddQueryHandlers();
|
builder.Services.AddQueryHandlers();
|
||||||
builder.Services.AddServices();
|
builder.Services.AddServices();
|
||||||
builder.Services.AddCommandHandlers();
|
builder.Services.AddCommandHandlers();
|
||||||
@ -73,8 +73,12 @@ using (var scope = app.Services.CreateScope())
|
|||||||
{
|
{
|
||||||
var services = scope.ServiceProvider;
|
var services = scope.ServiceProvider;
|
||||||
var appDbContext = services.GetRequiredService<ApplicationDbContext>();
|
var appDbContext = services.GetRequiredService<ApplicationDbContext>();
|
||||||
|
var storeDbContext = services.GetRequiredService<EventStoreDbContext>();
|
||||||
|
var domainStoreDbContext = services.GetRequiredService<DomainNotificationStoreDbContext>();
|
||||||
|
|
||||||
appDbContext.EnsureMigrationsApplied();
|
appDbContext.EnsureMigrationsApplied();
|
||||||
|
storeDbContext.EnsureMigrationsApplied();
|
||||||
|
domainStoreDbContext.EnsureMigrationsApplied();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": false,
|
"launchBrowser": false,
|
||||||
"launchUrl": "swagger",
|
"launchUrl": "swagger",
|
||||||
"applicationUrl": "https://localhost:7260;http://localhost:5201",
|
"applicationUrl": "https://localhost:7001;http://localhost:7000",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
|||||||
using CleanArchitecture.Domain.Enums;
|
using CleanArchitecture.Domain.Enums;
|
||||||
using CleanArchitecture.Domain.Errors;
|
using CleanArchitecture.Domain.Errors;
|
||||||
using CleanArchitecture.Domain.Events.User;
|
using CleanArchitecture.Domain.Events.User;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.UpdateUser;
|
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.UpdateUser;
|
||||||
@ -54,4 +55,37 @@ public sealed class UpdateUserCommandHandlerTests
|
|||||||
ErrorCodes.ObjectNotFound,
|
ErrorCodes.ObjectNotFound,
|
||||||
$"There is no User with Id {command.UserId}");
|
$"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}");
|
||||||
|
}
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ public sealed class UpdateUserCommandTestFixture : CommandHandlerFixtureBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public UpdateUserCommandHandler CommandHandler { get; }
|
public UpdateUserCommandHandler CommandHandler { get; }
|
||||||
private Mock<IUserRepository> UserRepository { get; }
|
public Mock<IUserRepository> UserRepository { get; }
|
||||||
|
|
||||||
public Entities.User SetupUser()
|
public Entities.User SetupUser()
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
using CleanArchitecture.Domain.Enums;
|
using CleanArchitecture.Domain.Enums;
|
||||||
using CleanArchitecture.Domain.Interfaces;
|
using CleanArchitecture.Domain.Interfaces;
|
||||||
using CleanArchitecture.Domain.Notifications;
|
using CleanArchitecture.Domain.Notifications;
|
||||||
|
@ -11,6 +11,9 @@ public sealed class ApiUser : IUser
|
|||||||
{
|
{
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
|
||||||
|
private string? _name = null;
|
||||||
|
private Guid _userId = Guid.Empty;
|
||||||
|
|
||||||
public ApiUser(IHttpContextAccessor httpContextAccessor)
|
public ApiUser(IHttpContextAccessor httpContextAccessor)
|
||||||
{
|
{
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
@ -18,11 +21,17 @@ public sealed class ApiUser : IUser
|
|||||||
|
|
||||||
public Guid GetUserId()
|
public Guid GetUserId()
|
||||||
{
|
{
|
||||||
|
if (_userId != Guid.Empty)
|
||||||
|
{
|
||||||
|
return _userId;
|
||||||
|
}
|
||||||
|
|
||||||
var claim = _httpContextAccessor.HttpContext?.User.Claims
|
var claim = _httpContextAccessor.HttpContext?.User.Claims
|
||||||
.FirstOrDefault(x => string.Equals(x.Type, ClaimTypes.NameIdentifier));
|
.FirstOrDefault(x => string.Equals(x.Type, ClaimTypes.NameIdentifier));
|
||||||
|
|
||||||
if (Guid.TryParse(claim?.Value, out var userId))
|
if (Guid.TryParse(claim?.Value, out var userId))
|
||||||
{
|
{
|
||||||
|
_userId = userId;
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +51,32 @@ public sealed class ApiUser : IUser
|
|||||||
throw new ArgumentException("Could not parse user role");
|
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()
|
public string GetUserEmail()
|
||||||
{
|
{
|
||||||
@ -54,6 +88,6 @@ public sealed class ApiUser : IUser
|
|||||||
return claim.Value;
|
return claim.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentException("Could not parse user email");
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -57,6 +57,21 @@ public sealed class UpdateUserCommandHandler : CommandHandlerBase,
|
|||||||
return;
|
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)
|
if (_user.GetUserRole() == UserRole.Admin)
|
||||||
{
|
{
|
||||||
user.SetRole(request.Role);
|
user.SetRole(request.Role);
|
||||||
|
@ -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; }
|
|
||||||
}
|
|
19
CleanArchitecture.Domain/DomainEvents/DomainEvent.cs
Normal file
19
CleanArchitecture.Domain/DomainEvents/DomainEvent.cs
Normal 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; }
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CleanArchitecture.Domain.DomainEvents;
|
||||||
|
|
||||||
|
public interface IDomainEventStore
|
||||||
|
{
|
||||||
|
Task SaveAsync<T>(T domainEvent) where T : DomainEvent;
|
||||||
|
}
|
22
CleanArchitecture.Domain/DomainEvents/Message.cs
Normal file
22
CleanArchitecture.Domain/DomainEvents/Message.cs
Normal 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; }
|
||||||
|
}
|
28
CleanArchitecture.Domain/DomainEvents/StoredDomainEvent.cs
Normal file
28
CleanArchitecture.Domain/DomainEvents/StoredDomainEvent.cs
Normal 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())
|
||||||
|
{ }
|
||||||
|
}
|
@ -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)
|
||||||
|
{ }
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.Events.User;
|
namespace CleanArchitecture.Domain.Events.User;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.Events.User;
|
namespace CleanArchitecture.Domain.Events.User;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.Events.User;
|
namespace CleanArchitecture.Domain.Events.User;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.Events.User;
|
namespace CleanArchitecture.Domain.Events.User;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CleanArchitecture.Domain.Commands;
|
using CleanArchitecture.Domain.Commands;
|
||||||
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.Interfaces;
|
namespace CleanArchitecture.Domain.Interfaces;
|
||||||
|
@ -8,4 +8,5 @@ public interface IUser
|
|||||||
string Name { get; }
|
string Name { get; }
|
||||||
Guid GetUserId();
|
Guid GetUserId();
|
||||||
UserRole GetUserRole();
|
UserRole GetUserRole();
|
||||||
|
string GetUserEmail();
|
||||||
}
|
}
|
@ -1,8 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.Notifications;
|
namespace CleanArchitecture.Domain.Notifications;
|
||||||
|
|
||||||
public sealed class DomainNotification : DomainEvent
|
public class DomainNotification : DomainEvent
|
||||||
{
|
{
|
||||||
public DomainNotification(
|
public DomainNotification(
|
||||||
string key,
|
string key,
|
||||||
@ -23,4 +24,5 @@ public sealed class DomainNotification : DomainEvent
|
|||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
public string Code { get; }
|
public string Code { get; }
|
||||||
public object? Data { get; set; }
|
public object? Data { get; set; }
|
||||||
|
public int Version { get; private set; } = 1;
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||||
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
using CleanArchitecture.Domain.Events.User;
|
using CleanArchitecture.Domain.Events.User;
|
||||||
using CleanArchitecture.Domain.Notifications;
|
using CleanArchitecture.Domain.Notifications;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@ -16,8 +17,9 @@ public sealed class InMemoryBusTests
|
|||||||
public async Task InMemoryBus_Should_Publish_When_Not_DomainNotification()
|
public async Task InMemoryBus_Should_Publish_When_Not_DomainNotification()
|
||||||
{
|
{
|
||||||
var mediator = new Mock<IMediator>();
|
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 key = "Key";
|
||||||
const string value = "Value";
|
const string value = "Value";
|
||||||
@ -34,8 +36,9 @@ public sealed class InMemoryBusTests
|
|||||||
public async Task InMemoryBus_Should_Save_And_Publish_When_DomainNotification()
|
public async Task InMemoryBus_Should_Save_And_Publish_When_DomainNotification()
|
||||||
{
|
{
|
||||||
var mediator = new Mock<IMediator>();
|
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());
|
var userDeletedEvent = new UserDeletedEvent(Guid.NewGuid());
|
||||||
|
|
||||||
@ -48,8 +51,9 @@ public sealed class InMemoryBusTests
|
|||||||
public async Task InMemoryBus_Should_Send_Command_Async()
|
public async Task InMemoryBus_Should_Send_Command_Async()
|
||||||
{
|
{
|
||||||
var mediator = new Mock<IMediator>();
|
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());
|
var deleteUserCommand = new DeleteUserCommand(Guid.NewGuid());
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -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)");
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
56
CleanArchitecture.Infrastructure/EventSourcing/EventStore.cs
Normal file
56
CleanArchitecture.Infrastructure/EventSourcing/EventStore.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace CleanArchitecture.Infrastructure.EventSourcing;
|
||||||
|
|
||||||
|
public interface IEventStoreContext
|
||||||
|
{
|
||||||
|
public string GetUserEmail();
|
||||||
|
public string GetCorrelationId();
|
||||||
|
}
|
@ -1,20 +1,47 @@
|
|||||||
using CleanArchitecture.Domain.Interfaces;
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
|
using CleanArchitecture.Domain.Interfaces;
|
||||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||||
using CleanArchitecture.Domain.Notifications;
|
using CleanArchitecture.Domain.Notifications;
|
||||||
using CleanArchitecture.Infrastructure.Database;
|
using CleanArchitecture.Infrastructure.Database;
|
||||||
|
using CleanArchitecture.Infrastructure.EventSourcing;
|
||||||
using CleanArchitecture.Infrastructure.Repositories;
|
using CleanArchitecture.Infrastructure.Repositories;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace CleanArchitecture.Infrastructure.Extensions;
|
namespace CleanArchitecture.Infrastructure.Extensions;
|
||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
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
|
// Core Infra
|
||||||
services.AddScoped<IUnitOfWork, UnitOfWork<ApplicationDbContext>>();
|
services.AddScoped<IUnitOfWork, UnitOfWork<ApplicationDbContext>>();
|
||||||
|
services.AddScoped<IEventStoreContext, EventStoreContext>();
|
||||||
services.AddScoped<INotificationHandler<DomainNotification>, DomainNotificationHandler>();
|
services.AddScoped<INotificationHandler<DomainNotification>, DomainNotificationHandler>();
|
||||||
|
services.AddScoped<IDomainEventStore, DomainEventStore>();
|
||||||
services.AddScoped<IMediatorHandler, InMemoryBus>();
|
services.AddScoped<IMediatorHandler, InMemoryBus>();
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CleanArchitecture.Domain;
|
|
||||||
using CleanArchitecture.Domain.Commands;
|
using CleanArchitecture.Domain.Commands;
|
||||||
|
using CleanArchitecture.Domain.DomainEvents;
|
||||||
using CleanArchitecture.Domain.Interfaces;
|
using CleanArchitecture.Domain.Interfaces;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
@ -9,10 +9,14 @@ namespace CleanArchitecture.Infrastructure;
|
|||||||
public sealed class InMemoryBus : IMediatorHandler
|
public sealed class InMemoryBus : IMediatorHandler
|
||||||
{
|
{
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
private readonly IDomainEventStore _domainEventStore;
|
||||||
|
|
||||||
public InMemoryBus(IMediator mediator)
|
public InMemoryBus(
|
||||||
|
IMediator mediator,
|
||||||
|
IDomainEventStore domainEventStore)
|
||||||
{
|
{
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
|
_domainEventStore = domainEventStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TResponse> QueryAsync<TResponse>(IRequest<TResponse> query)
|
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
|
public async Task RaiseEventAsync<T>(T @event) where T : DomainEvent
|
||||||
{
|
{
|
||||||
// await _domainEventStore.SaveAsync(@event);
|
await _domainEventStore.SaveAsync(@event);
|
||||||
|
|
||||||
await _mediator.Publish(@event);
|
await _mediator.Publish(@event);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
CleanArchitecture.Infrastructure/Migrations/EventStoreDb/20230701135441_AddEventStore.Designer.cs
generated
Normal file
67
CleanArchitecture.Infrastructure/Migrations/EventStoreDb/20230701135441_AddEventStore.Designer.cs
generated
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user