Skip to content

Commit

Permalink
feat: WIP: add snapshot on request
Browse files Browse the repository at this point in the history
  • Loading branch information
ArneD committed Nov 6, 2024
1 parent 172c51d commit cfb6623
Show file tree
Hide file tree
Showing 15 changed files with 454 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace BuildingRegistry.Api.BackOffice.Abstractions.Building.Requests
{
public sealed class CreateBuildingSnapshotRequest
{
public int PersistentLocalId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace BuildingRegistry.Api.BackOffice.Abstractions.Building.SqsRequests
{
using Be.Vlaanderen.Basisregisters.Sqs.Requests;
using Requests;

public sealed class CreateBuildingSnapshotSqsRequest : SqsRequest
{
public CreateBuildingSnapshotRequest Request { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace BuildingRegistry.Api.BackOffice.Handlers.Lambda.Handlers.Building
{
using Abstractions;
using Abstractions.Validation;
using Be.Vlaanderen.Basisregisters.AggregateSource;
using Be.Vlaanderen.Basisregisters.CommandHandling.Idempotency;
using Be.Vlaanderen.Basisregisters.Sqs.Lambda.Infrastructure;
using Be.Vlaanderen.Basisregisters.Sqs.Responses;
using BuildingRegistry.Building;
using BuildingRegistry.Building.Exceptions;
using Microsoft.Extensions.Configuration;
using Requests.Building;
using TicketingService.Abstractions;

public sealed class CreateBuildingSnapshotLambdaHandler : BuildingLambdaHandler<CreateBuildingSnapshotLambdaRequest>
{
public CreateBuildingSnapshotLambdaHandler(
IConfiguration configuration,
ICustomRetryPolicy retryPolicy,
ITicketing ticketing,
IIdempotentCommandHandler idempotentCommandHandler,
IBuildings buildings)
: base(
configuration,
retryPolicy,
ticketing,
idempotentCommandHandler,
buildings)
{ }

protected override async Task<object> InnerHandle(CreateBuildingSnapshotLambdaRequest request, CancellationToken cancellationToken)
{
var cmd = request.ToCommand();

try
{
await IdempotentCommandHandler.Dispatch(
cmd.CreateCommandId(),
cmd,
request.Metadata,
cancellationToken);
}
catch (IdempotencyException)
{
// Idempotent: Do Nothing return last etag
}

return "snapshot created";
}

protected override TicketError? InnerMapDomainException(DomainException exception, CreateBuildingSnapshotLambdaRequest request)
{
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace BuildingRegistry.Api.BackOffice.Handlers.Lambda
using Requests.BuildingUnit;
using System.Threading;
using System.Threading.Tasks;
using Abstractions.Building.Requests;
using Building;

public class MessageHandler : IMessageHandler
Expand Down Expand Up @@ -185,6 +186,12 @@ await mediator.Send(
cancellationToken);
break;

case CreateBuildingSnapshotSqsRequest request:
await mediator.Send(
new CreateBuildingSnapshotLambdaRequest(messageMetadata.MessageGroupId!, request),
cancellationToken);
break;

default:
throw new NotImplementedException(
$"{messageData.GetType().Name} has no corresponding SqsLambdaRequest defined.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace BuildingRegistry.Api.BackOffice.Handlers.Lambda.Requests.Building
{
using Be.Vlaanderen.Basisregisters.Sqs.Lambda.Requests;
using BuildingRegistry.AllStream.Commands;
using BuildingRegistry.Api.BackOffice.Abstractions.Building.Requests;
using BuildingRegistry.Api.BackOffice.Abstractions.Building.SqsRequests;
using BuildingRegistry.Building;
using BuildingRegistry.Building.Commands;

public sealed record CreateBuildingSnapshotLambdaRequest : BuildingLambdaRequest, Abstractions.IHasBuildingPersistentLocalId
{
public CreateBuildingSnapshotRequest Request { get; }
public int BuildingPersistentLocalId => Request.PersistentLocalId;

public CreateBuildingSnapshotLambdaRequest(
string messageGroupId,
CreateBuildingSnapshotSqsRequest sqsRequest)
: base(
messageGroupId,
sqsRequest.TicketId,
null,
sqsRequest.ProvenanceData.ToProvenance(),
sqsRequest.Metadata)
{
Request = sqsRequest.Request;
}

/// <summary>
/// Map to CreateOsloSnapshots command
/// </summary>
/// <returns>CreateOsloSnapshots</returns>
public CreateSnapshot ToCommand()
{
return new CreateSnapshot(
new BuildingPersistentLocalId(BuildingPersistentLocalId),
Provenance);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace BuildingRegistry.Api.BackOffice.Building
{
using System.Threading;
using System.Threading.Tasks;
using Abstractions.Building.Requests;
using Abstractions.Building.Validators;
using Abstractions.Building.SqsRequests;
using Abstractions.Validation;
using Be.Vlaanderen.Basisregisters.Auth.AcmIdm;
using Be.Vlaanderen.Basisregisters.Api.ETag;
using Be.Vlaanderen.Basisregisters.Api.Exceptions;
using Be.Vlaanderen.Basisregisters.GrAr.Provenance;
using BuildingRegistry.Building;
using Infrastructure;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Filters;

public partial class BuildingController
{
/// <summary>
/// Snapshot voor het gebouw aanvragen.
/// </summary>
/// <param name="buildingExistsValidator"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <response code="202">Als de snapshot voor het gebouw aangevraagd is.</response>
/// <returns></returns>
[HttpPost("{persistentLocalId}/acties/snapshot")]
[ProducesResponseType(StatusCodes.Status202Accepted)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
[SwaggerResponseHeader(StatusCodes.Status202Accepted, "location", "string", "De url van het gebouw.")]
[SwaggerResponseExample(StatusCodes.Status400BadRequest, typeof(BadRequestResponseExamples))]
[SwaggerResponseExample(StatusCodes.Status500InternalServerError, typeof(InternalServerErrorResponseExamples))]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Policy = PolicyNames.GeschetstGebouw.DecentraleBijwerker)]
public async Task<IActionResult> CreateSnapshot(
[FromServices] BuildingExistsValidator buildingExistsValidator,
[FromRoute] CreateBuildingSnapshotRequest request,
CancellationToken cancellationToken = default)
{
if (!await buildingExistsValidator.Exists(new BuildingPersistentLocalId(request.PersistentLocalId),
cancellationToken))
{
throw new ApiException(ValidationErrors.Common.BuildingNotFound.Message, StatusCodes.Status404NotFound);
}

var result = await Mediator.Send(
new CreateBuildingSnapshotSqsRequest
{
Request = request,
Metadata = GetMetadata(),
ProvenanceData = new ProvenanceData(CreateProvenance(Modification.Unknown)),
}, cancellationToken);

return Accepted(result);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace BuildingRegistry.Api.BackOffice.Handlers.Building
{
using System.Collections.Generic;
using Abstractions.Building.SqsRequests;
using AllStream;
using Be.Vlaanderen.Basisregisters.Sqs;
using Be.Vlaanderen.Basisregisters.Sqs.Handlers;
using TicketingService.Abstractions;

public sealed class CreateBuildingSnapshotSqsHandler : SqsHandler<CreateBuildingSnapshotSqsRequest>
{
public const string Action = "CreateBuildingSnapshot";

public CreateBuildingSnapshotSqsHandler(
ISqsQueue sqsQueue,
ITicketing ticketing,
ITicketingUrl ticketingUrl) : base(sqsQueue, ticketing, ticketingUrl)
{ }

protected override string? WithAggregateId(CreateBuildingSnapshotSqsRequest request)
{
return request.Request.PersistentLocalId.ToString();
}

protected override IDictionary<string, string> WithTicketMetadata(string aggregateId, CreateBuildingSnapshotSqsRequest sqsRequest)
{
return new Dictionary<string, string>
{
{ RegistryKey, nameof(BuildingRegistry) },
{ ActionKey, Action },
{ AggregateIdKey, aggregateId }
};
}
}
}
5 changes: 5 additions & 0 deletions src/BuildingRegistry/Building/Building.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,11 @@ protected override void BeforeApplyChange(object @event)

#region Snapshot

public void RequestSnapshot()
{
ApplyChange(new BuildingSnapshotWasRequested(BuildingPersistentLocalId));
}

public object TakeSnapshot()
{
return new BuildingSnapshot(
Expand Down
14 changes: 13 additions & 1 deletion src/BuildingRegistry/Building/BuildingCommandHandlerModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ public BuildingCommandHandlerModule(
IProvenanceFactory<Building> provenanceFactory,
IBuildingGeometries? buildingGeometries = null)
{
For<CreateSnapshot>()
.AddSqlStreamStore(getStreamStore, getUnitOfWork, eventMapping, eventSerializer, getSnapshotStore)
.AddEventHash<CreateSnapshot, Building>(getUnitOfWork)
.AddProvenance(getUnitOfWork, provenanceFactory)
.Handle(async (message, ct) =>
{
var streamId = new BuildingStreamId(message.Command.BuildingPersistentLocalId);
var building = await buildingRepository().GetAsync(streamId, ct);

building.RequestSnapshot();
});

For<MigrateBuilding>()
.AddSqlStreamStore(getStreamStore, getUnitOfWork, eventMapping, eventSerializer, getSnapshotStore)
.AddEventHash<MigrateBuilding, Building>(getUnitOfWork)
Expand Down Expand Up @@ -248,7 +260,7 @@ public BuildingCommandHandlerModule(
{
var streamId = new BuildingStreamId(message.Command.BuildingPersistentLocalId);
var building = await buildingRepository().GetAsync(streamId, ct);

building.ReaddressAddresses(message.Command.Readdresses);
});
}
Expand Down
41 changes: 41 additions & 0 deletions src/BuildingRegistry/Building/Commands/CreateOsloSnapshots.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace BuildingRegistry.Building.Commands
{
using System;
using System.Collections.Generic;
using Be.Vlaanderen.Basisregisters.Generators.Guid;
using Be.Vlaanderen.Basisregisters.GrAr.Provenance;
using Be.Vlaanderen.Basisregisters.Utilities;

public sealed class CreateSnapshot : IHasCommandProvenance
{
private static readonly Guid Namespace = new Guid("1ca80df2-b1e3-463a-849e-c22eeca84c3f");

public BuildingPersistentLocalId BuildingPersistentLocalId { get; }

public Provenance Provenance { get; }

public CreateSnapshot(
BuildingPersistentLocalId buildingPersistentLocalId,
Provenance provenance)
{
BuildingPersistentLocalId = buildingPersistentLocalId;
Provenance = provenance;
}

public Guid CreateCommandId()
=> Deterministic.Create(Namespace, $"CreateSnapshot-{ToString()}");

public override string? ToString()
=> ToStringBuilder.ToString(IdentityFields());

private IEnumerable<object> IdentityFields()
{
yield return BuildingPersistentLocalId;

foreach (var field in Provenance.GetIdentityFields())
{
yield return field;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace BuildingRegistry.Building.Events
{
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Be.Vlaanderen.Basisregisters.EventHandling;
using Be.Vlaanderen.Basisregisters.GrAr.Common;
using Be.Vlaanderen.Basisregisters.GrAr.Provenance;
using Newtonsoft.Json;

[EventName(EventName)]
[EventDescription("EventStore snapshot voor het gebouw werd aangevraagd.")]
public sealed class BuildingSnapshotWasRequested: IBuildingEvent, IHasBuildingPersistentLocalId
{
public const string EventName = "BuildingSnapshotWasRequested"; // BE CAREFUL CHANGING THIS!!

[EventPropertyDescription("Objectidentificator van het gebouw.")]
public int BuildingPersistentLocalId { get; }

[EventPropertyDescription("Metadata bij het event.")]
public ProvenanceData Provenance { get; private set; }

public BuildingSnapshotWasRequested(BuildingPersistentLocalId buildingPersistentLocalId)
{
BuildingPersistentLocalId = buildingPersistentLocalId;
}

[JsonConstructor]
private BuildingSnapshotWasRequested(int buildingPersistentLocalId, ProvenanceData provenance)
: this(new BuildingPersistentLocalId(buildingPersistentLocalId))
=> ((ISetProvenance)this).SetProvenance(provenance.ToProvenance());

void ISetProvenance.SetProvenance(Provenance provenance) => Provenance = new ProvenanceData(provenance);

public IEnumerable<string> GetHashFields()
{
var fields = Provenance.GetHashFields().ToList();
fields.Add(BuildingPersistentLocalId.ToString(CultureInfo.InvariantCulture));
return fields;
}

public string GetHash() => this.ToEventHash(EventName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace BuildingRegistry.Tests.AggregateTests.WhenRequestingBuildingSnapshot
{
using AutoFixture;
using Be.Vlaanderen.Basisregisters.AggregateSource;
using Be.Vlaanderen.Basisregisters.AggregateSource.Testing;
using Building;
using Building.Commands;
using Building.Events;
using Fixtures;
using Xunit;
using Xunit.Abstractions;

public class GivenBuildingExists : BuildingRegistryTest
{
public GivenBuildingExists(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
{
Fixture.Customize(new WithFixedBuildingPersistentLocalId());
}

[Fact]
public void ThenCreateSnapshotWasRequested()
{
var command = Fixture.Create<CreateSnapshot>();

Assert(new Scenario()
.Given(
new BuildingStreamId(Fixture.Create<BuildingPersistentLocalId>()),
Fixture.Create<BuildingWasPlannedV2>())
.When(command)
.Then(new Fact(
new BuildingStreamId(command.BuildingPersistentLocalId),
new BuildingSnapshotWasRequested(command.BuildingPersistentLocalId))));
}
}
}
Loading

0 comments on commit cfb6623

Please sign in to comment.