Skip to content

Commit a6c17c6

Browse files
authored
Merge pull request #5615 from Particular/john/ingest_part1
Integrate EF Core into ingestion unit of work
2 parents 5fb38ee + d5977c6 commit a6c17c6

12 files changed

Lines changed: 82 additions & 32 deletions

File tree

src/ServiceControl.Persistence.EFCore/Abstractions/BasePersistence.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public abstract class BasePersistence
1515
protected static void RegisterDataStores(IServiceCollection services)
1616
{
1717
services.AddSingleton(TimeProvider.System);
18+
services.AddSingleton<MinimumRequiredStorageState>();
1819

1920
services.AddSingleton<IServiceControlSubscriptionStorage, SubscriptionStorage>();
2021
services.AddSingleton<ISubscriptionStorage>(p => p.GetRequiredService<IServiceControlSubscriptionStorage>());
Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,35 @@
11
namespace ServiceControl.Persistence.EFCore.Implementation.UnitOfWork;
22

3+
using Microsoft.Extensions.DependencyInjection;
4+
using ServiceControl.Persistence.EFCore.Abstractions;
5+
using ServiceControl.Persistence.EFCore.DbContexts;
6+
using ServiceControl.Persistence.EFCore.Infrastructure;
37
using ServiceControl.Persistence.UnitOfWork;
48

59
public class EFIngestionUnitOfWork : IIngestionUnitOfWork
610
{
11+
readonly ServiceControlDbContext dbContext;
12+
readonly AsyncServiceScope scope;
13+
14+
public EFIngestionUnitOfWork(AsyncServiceScope scope, ServiceControlDbContext dbContext, IBodyStoragePersistence storagePersistence, EFPersisterSettings settings)
15+
{
16+
this.scope = scope;
17+
this.dbContext = dbContext;
18+
}
19+
720
public IMonitoringIngestionUnitOfWork Monitoring =>
821
throw new NotImplementedException();
922

1023
public IRecoverabilityIngestionUnitOfWork Recoverability =>
1124
throw new NotImplementedException();
1225

13-
public Task Complete(CancellationToken cancellationToken) =>
14-
throw new NotImplementedException();
26+
public Task Complete(CancellationToken cancellationToken) => dbContext.SaveChangesAsync(cancellationToken);
1527

16-
public void Dispose()
28+
public async ValueTask DisposeAsync()
1729
{
18-
// Nothing to dispose yet
30+
await dbContext.DisposeAsync();
31+
await scope.DisposeAsync();
32+
1933
GC.SuppressFinalize(this);
2034
}
2135
}
Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
namespace ServiceControl.Persistence.EFCore.Implementation.UnitOfWork;
22

3+
using Microsoft.Extensions.DependencyInjection;
4+
using ServiceControl.Persistence.EFCore.Abstractions;
5+
using ServiceControl.Persistence.EFCore.DbContexts;
6+
using ServiceControl.Persistence.EFCore.Infrastructure;
37
using ServiceControl.Persistence.UnitOfWork;
48

5-
public class EFIngestionUnitOfWorkFactory : IIngestionUnitOfWorkFactory
9+
public class EFIngestionUnitOfWorkFactory(
10+
IServiceProvider serviceProvider,
11+
MinimumRequiredStorageState storageState,
12+
IBodyStoragePersistence storagePersistence) : IIngestionUnitOfWorkFactory
613
{
7-
public ValueTask<IIngestionUnitOfWork> StartNew() =>
8-
throw new NotImplementedException();
14+
public ValueTask<IIngestionUnitOfWork> StartNew()
15+
{
16+
var scope = serviceProvider.CreateAsyncScope();
17+
var dbContext = scope.ServiceProvider.GetRequiredService<ServiceControlDbContext>();
18+
var settings = scope.ServiceProvider.GetRequiredService<EFPersisterSettings>();
19+
var unitOfWork = new EFIngestionUnitOfWork(scope, dbContext, storagePersistence, settings);
20+
return ValueTask.FromResult<IIngestionUnitOfWork>(unitOfWork);
21+
}
922

10-
public bool CanIngestMore() =>
11-
throw new NotImplementedException();
23+
public bool CanIngestMore() => storageState.CanIngestMore;
1224
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace ServiceControl.Persistence.EFCore.Infrastructure;
2+
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
public interface IBodyStoragePersistence
8+
{
9+
Task WriteBody(string bodyId, DateTime createdOn, ReadOnlyMemory<byte> body, string contentType, CancellationToken cancellationToken = default);
10+
Task<MessageBodyFileResult?> ReadBody(string bodyId, DateTime createdOn, CancellationToken cancellationToken = default);
11+
Task DeleteBody(string bodyId, CancellationToken cancellationToken = default);
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace ServiceControl.Persistence.EFCore.Infrastructure;
2+
3+
using System.IO;
4+
5+
public class MessageBodyFileResult
6+
{
7+
public Stream Stream { get; set; } = null!;
8+
public string ContentType { get; set; } = null!;
9+
public int BodySize { get; set; }
10+
public string Etag { get; set; } = null!;
11+
}

src/ServiceControl.Persistence.Tests.RavenDB/Expiration/MessageExpiryTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public async Task SingleMessageMarkedAsArchiveShouldExpire()
3434
{
3535
var (context, attempt) = CreateMessageContext();
3636

37-
using (var uow = await IngestionUnitOfWorkFactory.StartNew())
37+
await using (var uow = await IngestionUnitOfWorkFactory.StartNew())
3838
{
3939
await uow.Recoverability.RecordFailedProcessingAttempt(context, attempt, []);
4040

@@ -63,7 +63,7 @@ public async Task AllMessagesInUnArchivedGroupShouldNotExpire()
6363
var (contextA, attemptA) = CreateMessageContext();
6464
var (contextB, attemptB) = CreateMessageContext();
6565

66-
using (var uow = await IngestionUnitOfWorkFactory.StartNew())
66+
await using (var uow = await IngestionUnitOfWorkFactory.StartNew())
6767
{
6868
await uow.Recoverability.RecordFailedProcessingAttempt(contextA, attemptA, [new FailedMessage.FailureGroup { Id = groupIdA }]);
6969
await uow.Recoverability.RecordFailedProcessingAttempt(contextB, attemptB, [new FailedMessage.FailureGroup { Id = groupIdB }]);
@@ -100,7 +100,7 @@ public async Task AllMessagesInArchivedGroupShouldExpire()
100100
var groupId = Guid.NewGuid().ToString();
101101
var (context, attempt) = CreateMessageContext();
102102

103-
using (var uow = await IngestionUnitOfWorkFactory.StartNew())
103+
await using (var uow = await IngestionUnitOfWorkFactory.StartNew())
104104
{
105105
await uow.Recoverability.RecordFailedProcessingAttempt(context, attempt, [new FailedMessage.FailureGroup { Id = groupId }]);
106106

@@ -123,7 +123,7 @@ public async Task SingleMessageMarkedAsResolvedShouldExpire()
123123
{
124124
var (context, attempt) = CreateMessageContext();
125125

126-
using (var uow = await IngestionUnitOfWorkFactory.StartNew())
126+
await using (var uow = await IngestionUnitOfWorkFactory.StartNew())
127127
{
128128
await uow.Recoverability.RecordFailedProcessingAttempt(context, attempt, []);
129129

@@ -146,7 +146,7 @@ public async Task RetryConfirmationProcessingShouldTriggerExpiration()
146146
{
147147
var (context, attempt) = CreateMessageContext();
148148

149-
using (var uow = await IngestionUnitOfWorkFactory.StartNew())
149+
await using (var uow = await IngestionUnitOfWorkFactory.StartNew())
150150
{
151151
await uow.Recoverability.RecordFailedProcessingAttempt(context, attempt, []);
152152

@@ -159,7 +159,7 @@ public async Task RetryConfirmationProcessingShouldTriggerExpiration()
159159

160160
Assert.That(errors.Results, Has.Count.EqualTo(1), "Failed message should be available to query after ingestion");
161161

162-
using (var uow = await IngestionUnitOfWorkFactory.StartNew())
162+
await using (var uow = await IngestionUnitOfWorkFactory.StartNew())
163163
{
164164
await uow.Recoverability.RecordSuccessfulRetry(errors.Results.First().Id);
165165

src/ServiceControl.Persistence.Tests/BodyStorage/AttachmentsBodyStorageTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ async Task RunTest(Func<Dictionary<string, string>, string> getIdToQuery)
4444
};
4545

4646
using (var cancellationSource = new CancellationTokenSource())
47-
using (var uow = await ingestionFactory.StartNew())
47+
await using (var uow = await ingestionFactory.StartNew())
4848
{
4949
var context = new MessageContext(messageId, headers, body, new TransportTransaction(), "receiveAddress", new NServiceBus.Extensibility.ContextBag());
5050
var processingAttempt = new FailedMessage.ProcessingAttempt

src/ServiceControl.Persistence.Tests/MonitoringDataStoreTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public async Task Unit_of_work_detects_endpoint()
131131
EndpointDetails = new EndpointDetails { Host = "Host1", HostId = Guid.NewGuid(), Name = "Endpoint" }
132132
};
133133

134-
using (var unitOfWork = await UnitOfWorkFactory.StartNew())
134+
await using (var unitOfWork = await UnitOfWorkFactory.StartNew())
135135
{
136136
await unitOfWork.Monitoring.RecordKnownEndpoint(knownEndpoint);
137137

src/ServiceControl.Persistence/UnitOfWork/FallbackIngestionUnitOfWork.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ public override Task Complete(CancellationToken cancellationToken)
2525
fallback.Complete(cancellationToken)
2626
);
2727

28-
protected override void Dispose(bool disposing)
28+
protected override async ValueTask DisposeAsyncCore()
2929
{
30-
if (disposing)
30+
if (primary != null)
3131
{
32-
primary?.Dispose();
33-
fallback?.Dispose();
32+
await primary.DisposeAsync();
33+
}
34+
35+
if (fallback != null)
36+
{
37+
await fallback.DisposeAsync();
3438
}
3539
}
3640
}

src/ServiceControl.Persistence/UnitOfWork/IIngestionUnitOfWork.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66

7-
public interface IIngestionUnitOfWork : IDisposable
7+
public interface IIngestionUnitOfWork : IAsyncDisposable
88
{
99
IMonitoringIngestionUnitOfWork Monitoring { get; }
1010
IRecoverabilityIngestionUnitOfWork Recoverability { get; }

0 commit comments

Comments
 (0)