Skip to content

Commit

Permalink
chore: Only allow for opting out of aggregate side effects in unit of…
Browse files Browse the repository at this point in the history
… work. (#1389)
  • Loading branch information
MagnusSandgren authored Nov 5, 2024
1 parent ed3e64e commit c887542
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Digdir.Domain.Dialogporten.Application.Externals;

public interface IUnitOfWork
{
IUnitOfWork WithoutAuditableSideEffects();
IUnitOfWork WithoutAggregateSideEffects();
Task<SaveChangesResult> SaveChangesAsync(CancellationToken cancellationToken = default);

IUnitOfWork EnableConcurrencyCheck<TEntity>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public async Task<GetDialogResult> Handle(GetDialogQuery request, CancellationTo
currentUserInformation.Name);

var saveResult = await _unitOfWork
.WithoutAuditableSideEffects()
.WithoutAggregateSideEffects()
.SaveChangesAsync(cancellationToken);

saveResult.Switch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public async Task<GetDialogResult> Handle(GetDialogQuery request, CancellationTo
currentUserInformation.Name);

var saveResult = await _unitOfWork
.WithoutAuditableSideEffects()
.WithoutAggregateSideEffects()
.SaveChangesAsync(cancellationToken);

saveResult.Switch(
Expand Down
12 changes: 7 additions & 5 deletions src/Digdir.Domain.Dialogporten.Infrastructure/UnitOfWork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal sealed class UnitOfWork : IUnitOfWork, IAsyncDisposable, IDisposable

private IDbContextTransaction? _transaction;

private bool _auditableSideEffects = true;
private bool _aggregateSideEffects = true;
private bool _enableConcurrencyCheck;

public UnitOfWork(DialogDbContext dialogDbContext, ITransactionTime transactionTime, IDomainContext domainContext)
Expand All @@ -51,9 +51,9 @@ public IUnitOfWork EnableConcurrencyCheck<TEntity>(
return this;
}

public IUnitOfWork WithoutAuditableSideEffects()
public IUnitOfWork WithoutAggregateSideEffects()
{
_auditableSideEffects = false;
_aggregateSideEffects = false;
return this;
}

Expand Down Expand Up @@ -94,9 +94,11 @@ private async Task<SaveChangesResult> SaveChangesAsync_Internal(CancellationToke
return new Success();
}

if (_auditableSideEffects)
_dialogDbContext.ChangeTracker.HandleAuditableEntities(_transactionTime.Value);

if (_aggregateSideEffects)
{
await _dialogDbContext.ChangeTracker.HandleAuditableEntities(_transactionTime.Value, cancellationToken);
await _dialogDbContext.ChangeTracker.HandleAggregateEntities(_transactionTime.Value, cancellationToken);
}

if (!_enableConcurrencyCheck)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Digdir.Library.Entity.EntityFrameworkCore.Features.Creatable;
using Digdir.Library.Entity.Abstractions.Features.Aggregate;
using Digdir.Library.Entity.EntityFrameworkCore.Features.Creatable;
using Digdir.Library.Entity.EntityFrameworkCore.Features.Identifiable;
using Digdir.Library.Entity.EntityFrameworkCore.Features.Lookup;
using Digdir.Library.Entity.EntityFrameworkCore.Features.SoftDeletable;
Expand All @@ -8,6 +9,7 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Digdir.Library.Entity.Abstractions.Features.Creatable;
using Digdir.Library.Entity.Abstractions.Features.Identifiable;
using Digdir.Library.Entity.Abstractions.Features.Immutable;
using Digdir.Library.Entity.Abstractions.Features.Lookup;
using Digdir.Library.Entity.Abstractions.Features.SoftDeletable;
using Digdir.Library.Entity.Abstractions.Features.Updatable;
Expand All @@ -23,36 +25,54 @@ namespace Digdir.Library.Entity.EntityFrameworkCore;
/// </summary>
public static class EntityLibraryEfCoreExtensions
{
/// <summary>
/// Updates the properties and sets the correct <see cref="EntityState"/> on the <see cref="ChangeTracker"/> for the entities implementing the following abstractions in context of aggregates.
/// <list type="bullet">
/// <item><see cref="IAggregateCreatedHandler"/></item>
/// <item><see cref="IAggregateUpdatedHandler"/></item>
/// <item><see cref="IAggregateDeletedHandler"/></item>
/// <item><see cref="IAggregateRestoredHandler"/></item>
/// <item><see cref="IUpdateableEntity"/></item>
/// <item><see cref="IVersionableEntity"/></item>
/// </list>
/// </summary>
/// <remarks>
/// Should be called right before saving the entities.
/// </remarks>
/// <param name="changeTracker">The change tracker.</param>
/// <param name="utcNow">The time in UTC in which the changes tok place.</param>
/// <param name="cancellationToken">A token for requesting cancellation of the operation.</param>
/// <returns>The same <see cref="ChangeTracker"/> instance so that multiple calls can be chained.</returns>
public static Task<ChangeTracker> HandleAggregateEntities(
this ChangeTracker changeTracker,
DateTimeOffset utcNow,
CancellationToken cancellationToken = default)
=> AggregateExtensions.HandleAggregateEntities(changeTracker, utcNow, cancellationToken);

/// <summary>
/// Updates the properties and sets the correct <see cref="EntityState"/> on the <see cref="ChangeTracker"/> for the entities implementing the following abstractions.
/// <list type="bullet">
/// <item><see cref="ILookupEntity"/></item>
/// <item><see cref="IIdentifiableEntity"/></item>
/// <item><see cref="ICreatableEntity"/></item>
/// <item><see cref="IUpdateableEntity"/></item>
/// <item><see cref="ISoftDeletableEntity"/></item>
/// <item><see cref="IIdentifiableEntity"/></item>
/// <item><see cref="IVersionableEntity"/></item>
/// <item><see cref="IImmutableEntity"/></item>
/// <item><see cref="ILookupEntity"/></item>
/// </list>
/// </summary>
/// <remarks>
/// Should be called right before saving the entities.
/// </remarks>
/// <param name="changeTracker">The change tracker.</param>
/// <param name="utcNow">The time in UTC in which the changes tok place.</param>
/// <param name="cancellationToken">A token for requesting cancellation of the operation.</param>
/// <returns>The same <see cref="ChangeTracker"/> instance so that multiple calls can be chained.</returns>
public static async Task<ChangeTracker> HandleAuditableEntities(this ChangeTracker changeTracker, DateTimeOffset utcNow, CancellationToken cancellationToken = default)
{
changeTracker.HandleLookupEntities()
public static ChangeTracker HandleAuditableEntities(this ChangeTracker changeTracker, DateTimeOffset utcNow)
=> changeTracker.HandleLookupEntities()
.HandleIdentifiableEntities()
.HandleImmutableEntities()
//.HandleVersionableEntities()
.HandleCreatableEntities(utcNow);
//.HandleUpdatableEntities(utcNow);
await changeTracker.HandleAggregateEntities(utcNow, cancellationToken);
return changeTracker.HandleSoftDeletableEntities(utcNow);
}
.HandleCreatableEntities(utcNow)
.HandleUpdatableEntities(utcNow)
.HandleSoftDeletableEntities(utcNow);

/// <summary>
/// Configures the shape of, and how the entities implementing the following abstractions are mapped to the database.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal static class AggregateExtensions
{
private static readonly EntityEntryComparer _entityEntryComparer = new();

internal static async Task HandleAggregateEntities(this ChangeTracker changeTracker,
internal static async Task<ChangeTracker> HandleAggregateEntities(this ChangeTracker changeTracker,
DateTimeOffset utcNow, CancellationToken cancellationToken)
{
var aggregateNodeByEntry = await changeTracker
Expand Down Expand Up @@ -57,6 +57,8 @@ internal static async Task HandleAggregateEntities(this ChangeTracker changeTrac
versionable.NewVersion();
}
}

return changeTracker;
}

internal static ModelBuilder AddAggregateEntities(this ModelBuilder modelBuilder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public static ChangeTracker HandleUpdatableEntities(this ChangeTracker changeTra
{
var updatableEntities = changeTracker
.Entries<IUpdateableEntity>()
.Where(x => x.State is EntityState.Added or EntityState.Modified && (x.Entity is not ISoftDeletableEntity deletable || !deletable.Deleted));
.Where(IsNotSoftDeleted)
.Where(x => AddedWithoutExplicitUpdatedAt(x) || Modified(x));

foreach (var entity in updatableEntities)
{
Expand All @@ -26,4 +27,8 @@ public static ChangeTracker HandleUpdatableEntities(this ChangeTracker changeTra

return changeTracker;
}

private static bool IsNotSoftDeleted(EntityEntry<IUpdateableEntity> x) => x.Entity is not ISoftDeletableEntity { Deleted: true };
private static bool AddedWithoutExplicitUpdatedAt(EntityEntry<IUpdateableEntity> x) => x.State is EntityState.Added && x.Entity.UpdatedAt == default;
private static bool Modified(EntityEntry<IUpdateableEntity> x) => x.State is EntityState.Modified;
}

0 comments on commit c887542

Please sign in to comment.