diff --git a/source/ProcessManager.Core.Tests/Integration/Infrastructure/Database/ProcessManagerContextTests.cs b/source/ProcessManager.Core.Tests/Integration/Infrastructure/Database/ProcessManagerContextTests.cs index d9be3af314..89b32768e0 100644 --- a/source/ProcessManager.Core.Tests/Integration/Infrastructure/Database/ProcessManagerContextTests.cs +++ b/source/ProcessManager.Core.Tests/Integration/Infrastructure/Database/ProcessManagerContextTests.cs @@ -97,8 +97,13 @@ private static OrchestrationDescription CreateOrchestrationDescription() private static OrchestrationInstance CreateOrchestrationInstance(OrchestrationDescription orchestrationDescription) { + var userIdentity = new UserIdentity( + new UserId(Guid.NewGuid()), + new ActorId(Guid.NewGuid())); + var orchestrationInstance = OrchestrationInstance.CreateFromDescription( - description: orchestrationDescription, + userIdentity, + orchestrationDescription, skipStepsBySequence: [3], clock: SystemClock.Instance); diff --git a/source/ProcessManager.Core.Tests/Integration/Infrastructure/Orchestration/OrchestrationInstanceRepositoryTests.cs b/source/ProcessManager.Core.Tests/Integration/Infrastructure/Orchestration/OrchestrationInstanceRepositoryTests.cs index 5e6c215460..e45db84f5f 100644 --- a/source/ProcessManager.Core.Tests/Integration/Infrastructure/Orchestration/OrchestrationInstanceRepositoryTests.cs +++ b/source/ProcessManager.Core.Tests/Integration/Infrastructure/Orchestration/OrchestrationInstanceRepositoryTests.cs @@ -266,7 +266,7 @@ public async Task GivenOrchestrationInstancesInDatabase_WhenSearchByNameAndTermi var isTerminatedAsSucceededV1 = CreateOrchestrationInstance(existingOrchestrationDescriptionV1); isTerminatedAsSucceededV1.Lifecycle.TransitionToQueued(SystemClock.Instance); isTerminatedAsSucceededV1.Lifecycle.TransitionToRunning(SystemClock.Instance); - isTerminatedAsSucceededV1.Lifecycle.TransitionToTerminated(SystemClock.Instance, OrchestrationInstanceTerminationStates.Succeeded); + isTerminatedAsSucceededV1.Lifecycle.TransitionToSucceeded(SystemClock.Instance); await _sut.AddAsync(isTerminatedAsSucceededV1); var isPendingV2 = CreateOrchestrationInstance(existingOrchestrationDescriptionV2); @@ -275,7 +275,7 @@ public async Task GivenOrchestrationInstancesInDatabase_WhenSearchByNameAndTermi var isTerminatedAsFailedV2 = CreateOrchestrationInstance(existingOrchestrationDescriptionV2); isTerminatedAsFailedV2.Lifecycle.TransitionToQueued(SystemClock.Instance); isTerminatedAsFailedV2.Lifecycle.TransitionToRunning(SystemClock.Instance); - isTerminatedAsFailedV2.Lifecycle.TransitionToTerminated(SystemClock.Instance, OrchestrationInstanceTerminationStates.Failed); + isTerminatedAsFailedV2.Lifecycle.TransitionToFailed(SystemClock.Instance); await _sut.AddAsync(isTerminatedAsFailedV2); await _sut.UnitOfWork.CommitAsync(); @@ -353,13 +353,13 @@ public async Task GivenOrchestrationInstancesInDatabase_WhenSearchByNameAndTermi var isTerminated01 = CreateOrchestrationInstance(existingOrchestrationDescriptionV1); isTerminated01.Lifecycle.TransitionToQueued(SystemClock.Instance); isTerminated01.Lifecycle.TransitionToRunning(SystemClock.Instance); - isTerminated01.Lifecycle.TransitionToTerminated(terminatedAtClockMock01.Object, OrchestrationInstanceTerminationStates.Succeeded); + isTerminated01.Lifecycle.TransitionToSucceeded(terminatedAtClockMock01.Object); await _sut.AddAsync(isTerminated01); var isTerminated02 = CreateOrchestrationInstance(existingOrchestrationDescriptionV1); isTerminated02.Lifecycle.TransitionToQueued(SystemClock.Instance); isTerminated02.Lifecycle.TransitionToRunning(SystemClock.Instance); - isTerminated02.Lifecycle.TransitionToTerminated(SystemClock.Instance, OrchestrationInstanceTerminationStates.Succeeded); + isTerminated02.Lifecycle.TransitionToFailed(SystemClock.Instance); await _sut.AddAsync(isTerminated02); await _sut.UnitOfWork.CommitAsync(); @@ -395,8 +395,13 @@ private static OrchestrationInstance CreateOrchestrationInstance( OrchestrationDescription orchestrationDescription, Instant? runAt = default) { + var userIdentity = new UserIdentity( + new UserId(Guid.NewGuid()), + new ActorId(Guid.NewGuid())); + var orchestrationInstance = OrchestrationInstance.CreateFromDescription( - description: orchestrationDescription, + userIdentity, + orchestrationDescription, skipStepsBySequence: [], clock: SystemClock.Instance, runAt: runAt); diff --git a/source/ProcessManager.Core/Application/Orchestration/ICancelScheduledOrchestrationInstanceCommand.cs b/source/ProcessManager.Core/Application/Orchestration/ICancelScheduledOrchestrationInstanceCommand.cs index 43a958e9eb..87b3892d01 100644 --- a/source/ProcessManager.Core/Application/Orchestration/ICancelScheduledOrchestrationInstanceCommand.cs +++ b/source/ProcessManager.Core/Application/Orchestration/ICancelScheduledOrchestrationInstanceCommand.cs @@ -21,5 +21,5 @@ public interface ICancelScheduledOrchestrationInstanceCommand /// /// Cancel a scheduled orchestration instance. /// - Task CancelScheduledOrchestrationInstanceAsync(OrchestrationInstanceId id); + Task CancelScheduledOrchestrationInstanceAsync(UserIdentity userIdentity, OrchestrationInstanceId id); } diff --git a/source/ProcessManager.Core/Application/Orchestration/IStartOrchestrationInstanceCommands.cs b/source/ProcessManager.Core/Application/Orchestration/IStartOrchestrationInstanceCommands.cs index e3d842ea86..60b8a692f2 100644 --- a/source/ProcessManager.Core/Application/Orchestration/IStartOrchestrationInstanceCommands.cs +++ b/source/ProcessManager.Core/Application/Orchestration/IStartOrchestrationInstanceCommands.cs @@ -23,6 +23,7 @@ public interface IStartOrchestrationInstanceCommands /// Start a new instance of an orchestration. /// Task StartNewOrchestrationInstanceAsync( + OperatingIdentity identity, string name, int version, TParameter inputParameter, @@ -33,6 +34,7 @@ Task StartNewOrchestrationInstanceAsync( /// Schedule a new instance of an orchestration. /// Task ScheduleNewOrchestrationInstanceAsync( + UserIdentity identity, string name, int version, TParameter inputParameter, diff --git a/source/ProcessManager.Core/Application/Orchestration/OrchestrationInstanceManager.cs b/source/ProcessManager.Core/Application/Orchestration/OrchestrationInstanceManager.cs index ea974f2a52..2396dbb605 100644 --- a/source/ProcessManager.Core/Application/Orchestration/OrchestrationInstanceManager.cs +++ b/source/ProcessManager.Core/Application/Orchestration/OrchestrationInstanceManager.cs @@ -39,22 +39,35 @@ internal class OrchestrationInstanceManager( /// public async Task StartNewOrchestrationInstanceAsync( + OperatingIdentity identity, string name, int version, TParameter inputParameter, IReadOnlyCollection skipStepsBySequence) where TParameter : class { - var orchestrationDescription = await GuardMatchingOrchestrationDescriptionAsync(name, version, inputParameter, skipStepsBySequence).ConfigureAwait(false); + var orchestrationDescription = await GuardMatchingOrchestrationDescriptionAsync( + name, + version, + inputParameter, + skipStepsBySequence).ConfigureAwait(false); + + var orchestrationInstance = await CreateOrchestrationInstanceAsync( + identity, + orchestrationDescription, + inputParameter, + skipStepsBySequence).ConfigureAwait(false); - var orchestrationInstance = await CreateOrchestrationInstanceAsync(inputParameter, orchestrationDescription, skipStepsBySequence).ConfigureAwait(false); - await RequestStartOfOrchestrationInstanceAsync(orchestrationDescription, orchestrationInstance).ConfigureAwait(false); + await RequestStartOfOrchestrationInstanceAsync( + orchestrationDescription, + orchestrationInstance).ConfigureAwait(false); return orchestrationInstance.Id; } /// public async Task ScheduleNewOrchestrationInstanceAsync( + UserIdentity userIdentity, string name, int version, TParameter inputParameter, @@ -62,11 +75,21 @@ public async Task ScheduleNewOrchestrationInstanceAsync IReadOnlyCollection skipStepsBySequence) where TParameter : class { - var orchestrationDescription = await GuardMatchingOrchestrationDescriptionAsync(name, version, inputParameter, skipStepsBySequence).ConfigureAwait(false); + var orchestrationDescription = await GuardMatchingOrchestrationDescriptionAsync( + name, + version, + inputParameter, + skipStepsBySequence).ConfigureAwait(false); + if (orchestrationDescription.CanBeScheduled == false) throw new InvalidOperationException("Orchestration description cannot be scheduled."); - var orchestrationInstance = await CreateOrchestrationInstanceAsync(inputParameter, orchestrationDescription, skipStepsBySequence, runAt).ConfigureAwait(false); + var orchestrationInstance = await CreateOrchestrationInstanceAsync( + userIdentity, + orchestrationDescription, + inputParameter, + skipStepsBySequence, + runAt).ConfigureAwait(false); return orchestrationInstance.Id; } @@ -86,14 +109,14 @@ public async Task StartScheduledOrchestrationInstanceAsync(OrchestrationInstance } /// - public async Task CancelScheduledOrchestrationInstanceAsync(OrchestrationInstanceId id) + public async Task CancelScheduledOrchestrationInstanceAsync(UserIdentity userIdentity, OrchestrationInstanceId id) { var orchestrationInstance = await _repository.GetAsync(id).ConfigureAwait(false); if (!orchestrationInstance.Lifecycle.IsPendingForScheduledStart()) throw new InvalidOperationException("Orchestration instance cannot be canceled."); // Transition lifecycle - orchestrationInstance.Lifecycle.TransitionToTerminated(_clock, OrchestrationInstanceTerminationStates.UserCanceled); + orchestrationInstance.Lifecycle.TransitionToUserCanceled(_clock, userIdentity); await _repository.UnitOfWork.CommitAsync().ConfigureAwait(false); } @@ -129,13 +152,15 @@ private async Task GuardMatchingOrchestrationDescripti } private async Task CreateOrchestrationInstanceAsync( - TParameter inputParameter, + OperatingIdentity identity, OrchestrationDescription orchestrationDescription, + TParameter inputParameter, IReadOnlyCollection skipStepsBySequence, Instant? runAt = default) where TParameter : class { var orchestrationInstance = OrchestrationInstance.CreateFromDescription( + identity, orchestrationDescription, skipStepsBySequence, _clock, diff --git a/source/ProcessManager.Core/Domain/OrchestrationInstance/ActorId.cs b/source/ProcessManager.Core/Domain/OrchestrationInstance/ActorId.cs new file mode 100644 index 0000000000..cbf3c16fd1 --- /dev/null +++ b/source/ProcessManager.Core/Domain/OrchestrationInstance/ActorId.cs @@ -0,0 +1,17 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; + +public record ActorId(Guid Value); diff --git a/source/ProcessManager.Core/Domain/OrchestrationInstance/ActorIdentity.cs b/source/ProcessManager.Core/Domain/OrchestrationInstance/ActorIdentity.cs new file mode 100644 index 0000000000..56faecef2a --- /dev/null +++ b/source/ProcessManager.Core/Domain/OrchestrationInstance/ActorIdentity.cs @@ -0,0 +1,29 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; + +/// +/// An actor identity performing a Process Manager operation. +/// +public class ActorIdentity : OperatingIdentity +{ + public ActorIdentity( + ActorId actorId) + { + ActorId = actorId; + } + + public ActorId ActorId { get; } +} diff --git a/source/ProcessManager.Core/Domain/OrchestrationInstance/OperatingIdentity.cs b/source/ProcessManager.Core/Domain/OrchestrationInstance/OperatingIdentity.cs new file mode 100644 index 0000000000..8566c06f30 --- /dev/null +++ b/source/ProcessManager.Core/Domain/OrchestrationInstance/OperatingIdentity.cs @@ -0,0 +1,22 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; + +/// +/// An identity performing an Process Manager operation. +/// +public abstract class OperatingIdentity +{ +} diff --git a/source/ProcessManager.Core/Domain/OrchestrationInstance/OrchestrationInstance.cs b/source/ProcessManager.Core/Domain/OrchestrationInstance/OrchestrationInstance.cs index 07bfe5384c..60a0138458 100644 --- a/source/ProcessManager.Core/Domain/OrchestrationInstance/OrchestrationInstance.cs +++ b/source/ProcessManager.Core/Domain/OrchestrationInstance/OrchestrationInstance.cs @@ -28,11 +28,12 @@ public class OrchestrationInstance private OrchestrationInstance( OrchestrationDescriptionId orchestrationDescriptionId, + OperatingIdentity identity, IClock clock, Instant? runAt = default) { Id = new OrchestrationInstanceId(Guid.NewGuid()); - Lifecycle = new OrchestrationInstanceLifecycleState(clock, runAt); + Lifecycle = new OrchestrationInstanceLifecycleState(identity, clock, runAt); ParameterValue = new(); CustomState = new OrchestrationInstanceCustomState(string.Empty); @@ -86,6 +87,7 @@ private OrchestrationInstance() /// orchestration instance. /// internal static OrchestrationInstance CreateFromDescription( + OperatingIdentity identity, OrchestrationDescription.OrchestrationDescription description, IReadOnlyCollection skipStepsBySequence, IClock clock, @@ -106,6 +108,7 @@ internal static OrchestrationInstance CreateFromDescription( var orchestrationInstance = new OrchestrationInstance( description.Id, + identity, clock, runAt); diff --git a/source/ProcessManager.Core/Domain/OrchestrationInstance/OrchestrationInstanceLifecycleState.cs b/source/ProcessManager.Core/Domain/OrchestrationInstance/OrchestrationInstanceLifecycleState.cs index e51af9aebf..efeaa4090d 100644 --- a/source/ProcessManager.Core/Domain/OrchestrationInstance/OrchestrationInstanceLifecycleState.cs +++ b/source/ProcessManager.Core/Domain/OrchestrationInstance/OrchestrationInstanceLifecycleState.cs @@ -18,8 +18,9 @@ namespace Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; public class OrchestrationInstanceLifecycleState { - internal OrchestrationInstanceLifecycleState(IClock clock, Instant? runAt) + internal OrchestrationInstanceLifecycleState(OperatingIdentity createdBy, IClock clock, Instant? runAt) { + CreatedBy = createdBy; CreatedAt = clock.GetCurrentInstant(); ScheduledToRunAt = runAt; @@ -40,6 +41,11 @@ private OrchestrationInstanceLifecycleState() public OrchestrationInstanceTerminationStates? TerminationState { get; private set; } + /// + /// The identity that caused this orchestration instance to be created. + /// + public OperatingIdentity CreatedBy { get; } + /// /// The time when the orchestration instance was created (State => Pending). /// @@ -68,6 +74,11 @@ private OrchestrationInstanceLifecycleState() /// public Instant? TerminatedAt { get; private set; } + /// + /// The identity that caused this orchestration instance to be canceled. + /// + public OperatingIdentity? CanceledBy { get; private set; } + public bool IsPendingForScheduledStart() { return @@ -93,7 +104,22 @@ public void TransitionToRunning(IClock clock) StartedAt = clock.GetCurrentInstant(); } - public void TransitionToTerminated(IClock clock, OrchestrationInstanceTerminationStates terminationState) + public void TransitionToSucceeded(IClock clock) + { + TransitionToTerminated(clock, OrchestrationInstanceTerminationStates.Succeeded); + } + + public void TransitionToFailed(IClock clock) + { + TransitionToTerminated(clock, OrchestrationInstanceTerminationStates.Failed); + } + + public void TransitionToUserCanceled(IClock clock, UserIdentity userIdentity) + { + TransitionToTerminated(clock, OrchestrationInstanceTerminationStates.UserCanceled, userIdentity); + } + + private void TransitionToTerminated(IClock clock, OrchestrationInstanceTerminationStates terminationState, UserIdentity? userIdentity = default) { switch (terminationState) { @@ -105,6 +131,8 @@ public void TransitionToTerminated(IClock clock, OrchestrationInstanceTerminatio case OrchestrationInstanceTerminationStates.UserCanceled: if (!IsPendingForScheduledStart()) throw new InvalidOperationException("User cannot cancel orchestration instance."); + CanceledBy = userIdentity + ?? throw new InvalidOperationException("User identity must be specified."); break; default: throw new InvalidOperationException($"Unsupported termination state '{terminationState}'."); diff --git a/source/ProcessManager.Core/Domain/OrchestrationInstance/UserId.cs b/source/ProcessManager.Core/Domain/OrchestrationInstance/UserId.cs new file mode 100644 index 0000000000..2e681557b0 --- /dev/null +++ b/source/ProcessManager.Core/Domain/OrchestrationInstance/UserId.cs @@ -0,0 +1,17 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; + +public record UserId(Guid Value); diff --git a/source/ProcessManager.Core/Domain/OrchestrationInstance/UserIdentity.cs b/source/ProcessManager.Core/Domain/OrchestrationInstance/UserIdentity.cs new file mode 100644 index 0000000000..27e6beac38 --- /dev/null +++ b/source/ProcessManager.Core/Domain/OrchestrationInstance/UserIdentity.cs @@ -0,0 +1,33 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; + +/// +/// A user identity performing a Process Manager operation. +/// +public class UserIdentity : OperatingIdentity +{ + public UserIdentity( + UserId userId, + ActorId actorId) + { + UserId = userId; + ActorId = actorId; + } + + public UserId UserId { get; } + + public ActorId ActorId { get; } +} diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationTerminateActivityV1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationTerminateActivityV1.cs index 43560cd96f..a0ee4d0e16 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationTerminateActivityV1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationTerminateActivityV1.cs @@ -38,7 +38,7 @@ public async Task Run( .GetAsync(new OrchestrationInstanceId(orchestrationInstanceId)) .ConfigureAwait(false); - orchestrationInstance.Lifecycle.TransitionToTerminated(Clock, OrchestrationInstanceTerminationStates.Succeeded); + orchestrationInstance.Lifecycle.TransitionToSucceeded(Clock); await ProgressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); // TODO: For demo purposes; remove when done diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/NotifyAggregatedMeasureDataHandler.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/NotifyAggregatedMeasureDataHandler.cs index a97a18161e..ba3da56830 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/NotifyAggregatedMeasureDataHandler.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/NotifyAggregatedMeasureDataHandler.cs @@ -26,6 +26,7 @@ internal class NotifyAggregatedMeasureDataHandler( private readonly IStartOrchestrationInstanceCommands _manager = manager; public async Task ScheduleNewCalculationAsync( + UserIdentity userIdentity, ScheduleOrchestrationInstanceDto dto) { // TODO: @@ -39,6 +40,7 @@ public async Task ScheduleNewCalculationAsync( var orchestrationInstanceId = await _manager .ScheduleNewOrchestrationInstanceAsync( + userIdentity, name: "BRS_023_027", version: 1, inputParameter: dto.InputParameter, diff --git a/source/ProcessManager.Tests/Unit/Api/Mappers/OrchestrationInstanceMapperExtensionsTests.cs b/source/ProcessManager.Tests/Unit/Api/Mappers/OrchestrationInstanceMapperExtensionsTests.cs index 456e27e17e..8fbf717237 100644 --- a/source/ProcessManager.Tests/Unit/Api/Mappers/OrchestrationInstanceMapperExtensionsTests.cs +++ b/source/ProcessManager.Tests/Unit/Api/Mappers/OrchestrationInstanceMapperExtensionsTests.cs @@ -57,7 +57,12 @@ private static OrchestrationInstance CreateOrchestrationInstance() orchestrationDescription.AppendStepDescription("Test step 2"); orchestrationDescription.AppendStepDescription("Test step 3"); + var userIdentity = new UserIdentity( + new UserId(Guid.NewGuid()), + new ActorId(Guid.NewGuid())); + var orchestrationInstance = OrchestrationInstance.CreateFromDescription( + userIdentity, orchestrationDescription, skipStepsBySequence: [], SystemClock.Instance);