Skip to content

Commit bc04860

Browse files
feat: Add masstransit outbox system (#1277)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a new constructor for initializing validation options by scanning assemblies. - Introduced interfaces and a class for configuring publish-subscribe capabilities. - Enhanced infrastructure settings with new MassTransit configuration options. - Implemented notification processing context management for better resource handling. - Added idempotency management for domain events to prevent duplicate processing. - Introduced new configuration options for local development settings. - **Bug Fixes** - Improved reliability of event consumption assertions in integration tests. - **Tests** - Enhanced integration tests for cloud events related to dialog operations. - **Documentation** - Updated local development setup instructions and configuration settings in the README. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Ole Jørgen Skogstad <skogstad@softis.net>
1 parent f507060 commit bc04860

File tree

86 files changed

+5887
-816
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+5887
-816
lines changed

.azure/applications/service/main.bicep

+5-3
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ var containerAppEnvVars = [
8282
name: 'AZURE_CLIENT_ID'
8383
value: managedIdentity.properties.clientId
8484
}
85+
{
86+
name: 'Infrastructure__MassTransit__Host'
87+
value: 'sb://${serviceBusNamespaceName}.servicebus.windows.net/'
88+
}
8589
]
8690

8791
resource environmentKeyVaultResource 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
@@ -152,9 +156,7 @@ module containerApp '../../modules/containerApp/main.bicep' = {
152156
name: containerAppName
153157
params: {
154158
name: containerAppName
155-
// todo: make this dynamic based on service name. Using webapi for now.
156-
// image: '${baseImageUrl}${serviceName}:${imageTag}'
157-
image: '${baseImageUrl}webapi:${imageTag}'
159+
image: '${baseImageUrl}${serviceName}:${imageTag}'
158160
location: location
159161
envVariables: containerAppEnvVars
160162
containerAppEnvId: containerAppEnvironment.id

.azure/applications/web-api-so/main.bicep

-4
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,6 @@ var containerAppEnvVars = [
7575
name: 'ASPNETCORE_URLS'
7676
value: 'http://+:8080'
7777
}
78-
{
79-
name: 'RUN_OUTBOX_SCHEDULER'
80-
value: 'true'
81-
}
8278
]
8379

8480
resource environmentKeyVaultResource 'Microsoft.KeyVault/vaults@2023-07-01' existing = {

README.md

+10-10
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,16 @@ To generate test tokens, see https://github.com/Altinn/AltinnTestTools. There is
126126
We are able to toggle some external resources in local development. This is done through the `appsettings.Development.json` file. The following settings are available:
127127
```json
128128
"LocalDevelopment": {
129-
"UseLocalDevelopmentUser": true,
130-
"UseLocalDevelopmentResourceRegister": true,
131-
"UseLocalDevelopmentOrganizationRegister": true,
132-
"UseLocalDevelopmentNameRegister": true,
133-
"UseLocalDevelopmentAltinnAuthorization": true,
134-
"UseLocalDevelopmentCloudEventBus": true,
135-
"UseLocalDevelopmentCompactJwsGenerator": true,
136-
"DisableShortCircuitOutboxDispatcher": true,
137-
"DisableCache": false,
138-
"DisableAuth": true
129+
"UseLocalDevelopmentUser": true,
130+
"UseLocalDevelopmentResourceRegister": true,
131+
"UseLocalDevelopmentOrganizationRegister": true,
132+
"UseLocalDevelopmentNameRegister": true,
133+
"UseLocalDevelopmentAltinnAuthorization": true,
134+
"UseLocalDevelopmentCloudEventBus": true,
135+
"UseLocalDevelopmentCompactJwsGenerator": true,
136+
"DisableCache": true,
137+
"DisableAuth": true,
138+
"UseInMemoryServiceBusTransport": true
139139
}
140140
```
141141
Toggling these flags will enable/disable the external resources. The `DisableAuth` flag, for example, will disable authentication in the WebAPI project. This is useful when debugging the WebAPI project in an IDE. These settings will only be respected in the `Development` environment.

src/Digdir.Domain.Dialogporten.Application/ApplicationAssemblyMarker.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Digdir.Domain.Dialogporten.Application;
44

5-
public static class ApplicationAssemblyMarker
5+
public sealed class ApplicationAssemblyMarker
66
{
77
public static readonly Assembly Assembly = typeof(ApplicationAssemblyMarker).Assembly;
88
}

src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
using Microsoft.Extensions.Hosting;
1010
using System.Reflection;
1111
using Digdir.Domain.Dialogporten.Application.Common.Authorization;
12-
using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Content;
12+
using MediatR.NotificationPublishers;
1313

1414
namespace Digdir.Domain.Dialogporten.Application;
1515

@@ -34,7 +34,12 @@ public static IServiceCollection AddApplication(this IServiceCollection services
3434
services
3535
// Framework
3636
.AddAutoMapper(thisAssembly)
37-
.AddMediatR(x => x.RegisterServicesFromAssembly(thisAssembly))
37+
.AddMediatR(x =>
38+
{
39+
x.RegisterServicesFromAssembly(thisAssembly);
40+
x.TypeEvaluator = type => !type.IsAssignableTo(typeof(IIgnoreOnAssemblyScan));
41+
x.NotificationPublisherType = typeof(TaskWhenAllPublisher);
42+
})
3843
.AddValidatorsFromAssembly(thisAssembly, ServiceLifetime.Transient, includeInternalTypes: true,
3944
filter: type => !type.ValidatorType.IsAssignableTo(typeof(IIgnoreOnAssemblyScan)))
4045

src/Digdir.Domain.Dialogporten.Application/Common/Extensions/OptionExtensions/FluentValidationOptions.cs

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
using FluentValidation;
1+
using System.Diagnostics;
2+
using System.Reflection;
3+
using FluentValidation;
24
using Microsoft.Extensions.Options;
35

46
namespace Digdir.Domain.Dialogporten.Application.Common.Extensions.OptionExtensions;
57

68
public sealed class FluentValidationOptions<TOptions> : IValidateOptions<TOptions>
79
where TOptions : class
810
{
11+
private static readonly Type OptionType = typeof(TOptions);
912
private readonly IEnumerable<IValidator<TOptions>> _validators;
1013
public string? Name { get; }
1114

@@ -15,6 +18,26 @@ public FluentValidationOptions(string? name, IEnumerable<IValidator<TOptions>> v
1518
_validators = validators;
1619
}
1720

21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="FluentValidationOptions{TOptions}"/> class
23+
/// by scanning the specified assemblies for validators.
24+
/// </summary>
25+
/// <param name="assemblies">The assemblies to scan for validators.</param>
26+
/// <remarks>
27+
/// This constructor scans the provided assemblies for types that implement the
28+
/// <see cref="IValidator{T}"/> interface for the specified <typeparamref name="TOptions"/> type.
29+
/// It includes both public and internal validators in the search. <b>Use this constructor sparingly
30+
/// as it uses reflection to find validators.</b>
31+
/// </remarks>
32+
public FluentValidationOptions(params Assembly[] assemblies)
33+
{
34+
_validators = AssemblyScanner
35+
.FindValidatorsInAssemblies(assemblies, includeInternalTypes: true)
36+
.Where(x => x.InterfaceType.GenericTypeArguments.First() == OptionType)
37+
.Select(x => (IValidator<TOptions>)Activator.CreateInstance(x.ValidatorType, nonPublic: true)! ?? throw new UnreachableException())
38+
.ToList();
39+
}
40+
1841
public ValidateOptionsResult Validate(string? name, TOptions options)
1942
{
2043
// Null name is used to configure all named options.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Digdir.Domain.Dialogporten.Application.Common;
2+
3+
/// <summary>
4+
/// Marker interface to indicate that a class should be ignored during assembly scanning.
5+
/// </summary>
6+
public interface IIgnoreOnAssemblyScan;

src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs

-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities;
22
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions;
33
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities;
4-
using Digdir.Domain.Dialogporten.Domain.Outboxes;
54
using Digdir.Library.Entity.Abstractions.Features.Identifiable;
65
using Microsoft.EntityFrameworkCore;
76
using System.Linq.Expressions;
@@ -40,8 +39,6 @@ public interface IDialogDbContext
4039
DbSet<DialogContent> DialogContents { get; }
4140
DbSet<DialogContentType> DialogContentTypes { get; }
4241

43-
DbSet<OutboxMessage> OutboxMessages { get; }
44-
DbSet<OutboxMessageConsumer> OutboxMessageConsumers { get; }
4542
DbSet<SubjectResource> SubjectResources { get; }
4643
DbSet<DialogEndUserContext> DialogEndUserContexts { get; }
4744
DbSet<LabelAssignmentLog> LabelAssignmentLogs { get; }

src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Content/ContentValueDtoValidator.cs

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using System.Diagnostics.CodeAnalysis;
21
using Digdir.Domain.Dialogporten.Application.Common.Authorization;
2+
using System.Diagnostics.CodeAnalysis;
3+
using Digdir.Domain.Dialogporten.Application.Common;
34
using Digdir.Domain.Dialogporten.Application.Common.Extensions;
45
using Digdir.Domain.Dialogporten.Application.Common.Extensions.FluentValidation;
56
using Digdir.Domain.Dialogporten.Application.Externals.Presentation;
@@ -12,13 +13,10 @@
1213
namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Content;
1314

1415
// DialogContentValueDtoValidator has constructor parameter input, and can't be registered in DI assembly scan
15-
// This interface is used to ignore the class when scanning for validators
16+
// IIgnoreOnAssemblyScan is used to ignore the class when scanning for validators
1617
// The validator is manually created in the Create and Update validators
17-
internal interface IIgnoreOnAssemblyScan;
18-
1918
internal sealed class ContentValueDtoValidator : AbstractValidator<ContentValueDto>, IIgnoreOnAssemblyScan
2019
{
21-
2220
public ContentValueDtoValidator(DialogTransmissionContentType contentType)
2321
{
2422
RuleFor(x => x.MediaType)

src/Digdir.Domain.Dialogporten.Application/Features/V1/Outboxes/Commands/Delete/DeleteOutboxMessagesCommand.cs

-48
This file was deleted.

src/Digdir.Domain.Dialogporten.Application/LocalDevelopmentSettings.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ public sealed class LocalDevelopmentSettings
99
public bool UseLocalDevelopmentResourceRegister { get; init; } = true;
1010
public bool UseLocalDevelopmentAltinnAuthorization { get; init; } = true;
1111
public bool UseLocalDevelopmentCloudEventBus { get; init; } = true;
12-
public bool DisableShortCircuitOutboxDispatcher { get; init; } = true;
1312
public bool DisableCache { get; init; } = true;
1413
public bool DisableAuth { get; init; } = true;
1514
public bool UseLocalDevelopmentNameRegister { get; set; } = true;
1615
public bool UseLocalDevelopmentOrganizationRegister { get; set; } = true;
1716
public bool UseLocalDevelopmentCompactJwsGenerator { get; set; } = true;
17+
public bool UseInMemoryServiceBusTransport { get; set; } = true;
1818
}
1919

2020
public static class LocalDevelopmentSettingsExtensions

src/Digdir.Domain.Dialogporten.ChangeDataCapture/ChangeDataCaptureAssemblyMarker.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Digdir.Domain.Dialogporten.ChangeDataCapture;
44

5-
public static class ChangeDataCaptureAssemblyMarker
5+
public sealed class ChangeDataCaptureAssemblyMarker
66
{
77
public static readonly Assembly Assembly = typeof(ChangeDataCaptureAssemblyMarker).Assembly;
88
}

src/Digdir.Domain.Dialogporten.ChangeDataCapture/Digdir.Domain.Dialogporten.ChangeDataCapture.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<ItemGroup>
4-
<PackageReference Include="Azure.Identity" Version="1.12.1"/>
4+
<PackageReference Include="Azure.Identity" Version="1.13.0" />
55
<PackageReference Include="MassTransit" Version="8.2.5"/>
66
<PackageReference Include="Npgsql" Version="8.0.5" />
77
<PackageReference Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="8.0.0" />

src/Digdir.Domain.Dialogporten.Domain/Common/DomainEvent.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System.Text.Json.Serialization;
2-
using Digdir.Library.Entity.Abstractions.Features.EventPublisher;
2+
using Digdir.Domain.Dialogporten.Domain.Common.EventPublisher;
33

44
namespace Digdir.Domain.Dialogporten.Domain.Common;
55

src/Digdir.Library.Entity.Abstractions/Features/EventPublisher/IDomainEvent.cs src/Digdir.Domain.Dialogporten.Domain/Common/EventPublisher/IDomainEvent.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using MediatR;
22

3-
namespace Digdir.Library.Entity.Abstractions.Features.EventPublisher;
3+
namespace Digdir.Domain.Dialogporten.Domain.Common.EventPublisher;
44

55
/// <summary>
66
/// Represents a domain event.

src/Digdir.Library.Entity.Abstractions/Features/EventPublisher/IEventPublisher.cs src/Digdir.Domain.Dialogporten.Domain/Common/EventPublisher/IEventPublisher.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Digdir.Library.Entity.Abstractions.Features.EventPublisher;
1+
namespace Digdir.Domain.Dialogporten.Domain.Common.EventPublisher;
22

33
/// <summary>
44
/// Abstraction representing functionality to publish domain events.

src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Activities/DialogActivity.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using Digdir.Domain.Dialogporten.Domain.Actors;
2+
using Digdir.Domain.Dialogporten.Domain.Common.EventPublisher;
23
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions;
34
using Digdir.Domain.Dialogporten.Domain.Dialogs.Events.Activities;
45
using Digdir.Domain.Dialogporten.Domain.Localizations;
56
using Digdir.Library.Entity.Abstractions.Features.Aggregate;
6-
using Digdir.Library.Entity.Abstractions.Features.EventPublisher;
77
using Digdir.Library.Entity.Abstractions.Features.Immutable;
88

99
namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities;

src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Digdir.Domain.Dialogporten.Domain.Actors;
22
using Digdir.Domain.Dialogporten.Domain.Attachments;
3+
using Digdir.Domain.Dialogporten.Domain.Common.EventPublisher;
34
using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities;
45
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions;
56
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities;
@@ -8,7 +9,6 @@
89
using Digdir.Domain.Dialogporten.Domain.Dialogs.Events;
910
using Digdir.Library.Entity.Abstractions;
1011
using Digdir.Library.Entity.Abstractions.Features.Aggregate;
11-
using Digdir.Library.Entity.Abstractions.Features.EventPublisher;
1212
using Digdir.Library.Entity.Abstractions.Features.SoftDeletable;
1313
using Digdir.Library.Entity.Abstractions.Features.Versionable;
1414

src/Digdir.Domain.Dialogporten.Domain/DomainAssemblyMarker.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Digdir.Domain.Dialogporten.Domain;
44

5-
public static class DomainAssemblyMarker
5+
public sealed class DomainAssemblyMarker
66
{
77
public static readonly Assembly Assembly = typeof(DomainAssemblyMarker).Assembly;
88
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Digdir.Domain.Dialogporten.Domain.Common.EventPublisher;
2+
3+
namespace Digdir.Domain.Dialogporten.Domain;
4+
5+
public static class DomainExtensions
6+
{
7+
public static IEnumerable<Type> GetDomainEventTypes()
8+
=> DomainAssemblyMarker.Assembly
9+
.GetTypes()
10+
.Where(x => !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
11+
.Where(x => x.IsAssignableTo(typeof(IDomainEvent)));
12+
}

src/Digdir.Domain.Dialogporten.Domain/Outboxes/OutboxMessage.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System.Text.Json;
2-
using Digdir.Library.Entity.Abstractions.Features.EventPublisher;
2+
using Digdir.Domain.Dialogporten.Domain.Common.EventPublisher;
33

44
namespace Digdir.Domain.Dialogporten.Domain.Outboxes;
55

0 commit comments

Comments
 (0)