From 3de787e07c9e0f5e4880d2e21fbaa274fdbb671f Mon Sep 17 00:00:00 2001 From: Andrija Stevanovic Date: Sat, 7 Mar 2026 16:15:21 +0100 Subject: [PATCH] implement feedback loop --- .../Abstract/MessageBroking/IMessage.cs | 5 +++- .../Message/AddArrow/AddArrowMessage.cs | 8 ++++- .../Common/Message/AddLine/AddLineMessage.cs | 8 ++++- .../AddRectangle/AddRectangleMessage.cs | 8 ++++- .../AddTextShape/AddTextShapeMessage.cs | 8 ++++- .../Message/ErrorMessage/ErrorMessage.cs | 12 ++++++++ .../ErrorMessage/ErrorMessageHandler.cs | 18 ++++++++++++ .../IErrorMessageHandleStrategy.cs | 6 ++++ .../Message/MoveShape/MoveShapeMessage.cs | 8 ++++- .../Common/Message/TestMessage/TestMessage.cs | 8 ++++- dotnet/AipsRT/Hubs/WhiteboardHub.cs | 2 +- .../Model/Whiteboard/WhiteboardManager.cs | 12 ++++++++ dotnet/AipsRT/Program.cs | 5 ++++ .../ErrorSubscriberBackgroundService.cs | 25 ++++++++++++++++ .../Services/Interfaces/IMessagingService.cs | 2 +- dotnet/AipsRT/Services/MessagingService.cs | 4 +-- .../AipsRT/Services/RtErrorHandleStrategy.cs | 29 +++++++++++++++++++ dotnet/AipsWorker/WorkerService.cs | 14 ++++++++- 18 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 dotnet/AipsCore/Application/Common/Message/ErrorMessage/ErrorMessage.cs create mode 100644 dotnet/AipsCore/Application/Common/Message/ErrorMessage/ErrorMessageHandler.cs create mode 100644 dotnet/AipsCore/Application/Common/Message/ErrorMessage/IErrorMessageHandleStrategy.cs create mode 100644 dotnet/AipsRT/Services/ErrorSubscriberBackgroundService.cs create mode 100644 dotnet/AipsRT/Services/RtErrorHandleStrategy.cs diff --git a/dotnet/AipsCore/Application/Abstract/MessageBroking/IMessage.cs b/dotnet/AipsCore/Application/Abstract/MessageBroking/IMessage.cs index ecade00..fcc7fc5 100644 --- a/dotnet/AipsCore/Application/Abstract/MessageBroking/IMessage.cs +++ b/dotnet/AipsCore/Application/Abstract/MessageBroking/IMessage.cs @@ -1,3 +1,6 @@ namespace AipsCore.Application.Abstract.MessageBroking; -public interface IMessage; \ No newline at end of file +public interface IMessage +{ + Guid? GetWhiteboardId(); +}; \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/AddArrow/AddArrowMessage.cs b/dotnet/AipsCore/Application/Common/Message/AddArrow/AddArrowMessage.cs index ee857f3..3294a15 100644 --- a/dotnet/AipsCore/Application/Common/Message/AddArrow/AddArrowMessage.cs +++ b/dotnet/AipsCore/Application/Common/Message/AddArrow/AddArrowMessage.cs @@ -3,4 +3,10 @@ using AipsCore.Application.Models.Shape.Command.CreateArrow; namespace AipsCore.Application.Common.Message.AddArrow; -public record AddArrowMessage(CreateArrowCommand Command) : IMessage; \ No newline at end of file +public record AddArrowMessage(CreateArrowCommand Command) : IMessage +{ + public Guid? GetWhiteboardId() + { + return Guid.Parse(Command.WhiteboardId); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/AddLine/AddLineMessage.cs b/dotnet/AipsCore/Application/Common/Message/AddLine/AddLineMessage.cs index e7769b5..36428ef 100644 --- a/dotnet/AipsCore/Application/Common/Message/AddLine/AddLineMessage.cs +++ b/dotnet/AipsCore/Application/Common/Message/AddLine/AddLineMessage.cs @@ -3,4 +3,10 @@ using AipsCore.Application.Models.Shape.Command.CreateLine; namespace AipsCore.Application.Common.Message.AddLine; -public record AddLineMessage(CreateLineCommand Command) : IMessage; \ No newline at end of file +public record AddLineMessage(CreateLineCommand Command) : IMessage +{ + public Guid? GetWhiteboardId() + { + return Guid.Parse(Command.WhiteboardId); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/AddRectangle/AddRectangleMessage.cs b/dotnet/AipsCore/Application/Common/Message/AddRectangle/AddRectangleMessage.cs index 0277ee1..75b1410 100644 --- a/dotnet/AipsCore/Application/Common/Message/AddRectangle/AddRectangleMessage.cs +++ b/dotnet/AipsCore/Application/Common/Message/AddRectangle/AddRectangleMessage.cs @@ -3,4 +3,10 @@ using AipsCore.Application.Models.Shape.Command.CreateRectangle; namespace AipsCore.Application.Common.Message.AddRectangle; -public record AddRectangleMessage(CreateRectangleCommand Command) : IMessage; \ No newline at end of file +public record AddRectangleMessage(CreateRectangleCommand Command) : IMessage +{ + public Guid? GetWhiteboardId() + { + return Guid.Parse(Command.WhiteboardId); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/AddTextShape/AddTextShapeMessage.cs b/dotnet/AipsCore/Application/Common/Message/AddTextShape/AddTextShapeMessage.cs index d65a896..c6a6127 100644 --- a/dotnet/AipsCore/Application/Common/Message/AddTextShape/AddTextShapeMessage.cs +++ b/dotnet/AipsCore/Application/Common/Message/AddTextShape/AddTextShapeMessage.cs @@ -3,4 +3,10 @@ using AipsCore.Application.Models.Shape.Command.CreateTextShape; namespace AipsCore.Application.Common.Message.AddTextShape; -public record AddTextShapeMessage(CreateTextShapeCommand Command) : IMessage; \ No newline at end of file +public record AddTextShapeMessage(CreateTextShapeCommand Command) : IMessage +{ + public Guid? GetWhiteboardId() + { + return Guid.Parse(Command.WhiteboardId); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/ErrorMessage/ErrorMessage.cs b/dotnet/AipsCore/Application/Common/Message/ErrorMessage/ErrorMessage.cs new file mode 100644 index 0000000..ae77633 --- /dev/null +++ b/dotnet/AipsCore/Application/Common/Message/ErrorMessage/ErrorMessage.cs @@ -0,0 +1,12 @@ +using AipsCore.Application.Abstract.MessageBroking; +using AipsCore.Domain.Common.Validation; + +namespace AipsCore.Application.Common.Message.ErrorMessage; + +public record ErrorMessage(Guid WhiteboardId, ICollection Errors) : IMessage +{ + public Guid? GetWhiteboardId() + { + return WhiteboardId; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/ErrorMessage/ErrorMessageHandler.cs b/dotnet/AipsCore/Application/Common/Message/ErrorMessage/ErrorMessageHandler.cs new file mode 100644 index 0000000..d1e2152 --- /dev/null +++ b/dotnet/AipsCore/Application/Common/Message/ErrorMessage/ErrorMessageHandler.cs @@ -0,0 +1,18 @@ +using AipsCore.Application.Abstract.MessageBroking; + +namespace AipsCore.Application.Common.Message.ErrorMessage; + +public class ErrorMessageHandler : IMessageHandler +{ + private readonly IErrorMessageHandleStrategy _handleStrategy; + + public ErrorMessageHandler(IErrorMessageHandleStrategy handleStrategy) + { + _handleStrategy = handleStrategy; + } + + public async Task Handle(ErrorMessage message, CancellationToken cancellationToken) + { + await _handleStrategy.Handle(message, cancellationToken); + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/ErrorMessage/IErrorMessageHandleStrategy.cs b/dotnet/AipsCore/Application/Common/Message/ErrorMessage/IErrorMessageHandleStrategy.cs new file mode 100644 index 0000000..027710c --- /dev/null +++ b/dotnet/AipsCore/Application/Common/Message/ErrorMessage/IErrorMessageHandleStrategy.cs @@ -0,0 +1,6 @@ +namespace AipsCore.Application.Common.Message.ErrorMessage; + +public interface IErrorMessageHandleStrategy +{ + Task Handle(ErrorMessage message, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/MoveShape/MoveShapeMessage.cs b/dotnet/AipsCore/Application/Common/Message/MoveShape/MoveShapeMessage.cs index eb97ad7..bf3a8d9 100644 --- a/dotnet/AipsCore/Application/Common/Message/MoveShape/MoveShapeMessage.cs +++ b/dotnet/AipsCore/Application/Common/Message/MoveShape/MoveShapeMessage.cs @@ -3,4 +3,10 @@ using AipsCore.Application.Models.Shape.Command.MoveShape; namespace AipsCore.Application.Common.Message.MoveShape; -public record MoveShapeMessage(MoveShapeCommand Command) : IMessage; \ No newline at end of file +public record MoveShapeMessage(Guid WhiteboardId, MoveShapeCommand Command) : IMessage +{ + public Guid? GetWhiteboardId() + { + return WhiteboardId; + } +} \ No newline at end of file diff --git a/dotnet/AipsCore/Application/Common/Message/TestMessage/TestMessage.cs b/dotnet/AipsCore/Application/Common/Message/TestMessage/TestMessage.cs index e6559e6..710c40a 100644 --- a/dotnet/AipsCore/Application/Common/Message/TestMessage/TestMessage.cs +++ b/dotnet/AipsCore/Application/Common/Message/TestMessage/TestMessage.cs @@ -2,4 +2,10 @@ using AipsCore.Application.Abstract.MessageBroking; namespace AipsCore.Application.Common.Message.TestMessage; -public record TestMessage(string Text) : IMessage; \ No newline at end of file +public record TestMessage(string Text) : IMessage +{ + public Guid? GetWhiteboardId() + { + return null; + } +} \ No newline at end of file diff --git a/dotnet/AipsRT/Hubs/WhiteboardHub.cs b/dotnet/AipsRT/Hubs/WhiteboardHub.cs index ec22db5..9ff64f8 100644 --- a/dotnet/AipsRT/Hubs/WhiteboardHub.cs +++ b/dotnet/AipsRT/Hubs/WhiteboardHub.cs @@ -127,6 +127,6 @@ public class WhiteboardHub : Hub { await MoveShape(moveShape); - await _messagingService.MoveShape(moveShape); + await _messagingService.MoveShape(CurrentWhiteboard.WhiteboardId, moveShape); } } \ No newline at end of file diff --git a/dotnet/AipsRT/Model/Whiteboard/WhiteboardManager.cs b/dotnet/AipsRT/Model/Whiteboard/WhiteboardManager.cs index 10c19dc..fcdcac6 100644 --- a/dotnet/AipsRT/Model/Whiteboard/WhiteboardManager.cs +++ b/dotnet/AipsRT/Model/Whiteboard/WhiteboardManager.cs @@ -32,6 +32,18 @@ public class WhiteboardManager _whiteboards.TryRemove(whiteboardId, out _); } + public async Task RefreshWhiteboard(Guid whiteboardId) + { + var whiteboard = GetWhiteboard(whiteboardId); + + if (whiteboard == null) + { + RemoveWhiteboard(whiteboardId); + } + + await AddWhiteboard(whiteboardId); + } + public Whiteboard? GetWhiteboard(Guid whiteboardId) { return _whiteboards.GetValueOrDefault(whiteboardId); diff --git a/dotnet/AipsRT/Program.cs b/dotnet/AipsRT/Program.cs index 0995a0a..97c3f0b 100644 --- a/dotnet/AipsRT/Program.cs +++ b/dotnet/AipsRT/Program.cs @@ -1,3 +1,4 @@ +using AipsCore.Application.Common.Message.ErrorMessage; using AipsCore.Infrastructure.DI; using AipsRT.Hubs; using AipsRT.Model.Whiteboard; @@ -15,6 +16,10 @@ builder.Configuration.AddEnvironmentVariables(); builder.Services.AddSignalR(); builder.Services.AddAips(builder.Configuration); +builder.Services.AddAipsMessageHandlers(); + +builder.Services.AddSingleton(); +builder.Services.AddHostedService(); builder.Services.AddScoped(); builder.Services.AddSingleton(); diff --git a/dotnet/AipsRT/Services/ErrorSubscriberBackgroundService.cs b/dotnet/AipsRT/Services/ErrorSubscriberBackgroundService.cs new file mode 100644 index 0000000..36146df --- /dev/null +++ b/dotnet/AipsRT/Services/ErrorSubscriberBackgroundService.cs @@ -0,0 +1,25 @@ +using AipsCore.Application.Abstract; +using AipsCore.Application.Abstract.MessageBroking; +using AipsCore.Application.Common.Message.ErrorMessage; + +namespace AipsRT.Services; + +public class ErrorSubscriberBackgroundService : BackgroundService +{ + private readonly IMessageSubscriber _subscriber; + private readonly IDispatcher _dispatcher; + + public ErrorSubscriberBackgroundService(IMessageSubscriber subscriber, IDispatcher dispatcher) + { + _subscriber = subscriber; + _dispatcher = dispatcher; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await _subscriber.SubscribeAsync(async (errorMessage, ct) => + { + await _dispatcher.Execute(errorMessage, ct); + }); + } +} \ No newline at end of file diff --git a/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs b/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs index 3bbf790..97838e0 100644 --- a/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs +++ b/dotnet/AipsRT/Services/Interfaces/IMessagingService.cs @@ -11,5 +11,5 @@ public interface IMessagingService Task CreateLine(Guid whiteboardId, Line line); Task CreateTextShape(Guid whiteboardId, TextShape textShape); - Task MoveShape(MoveShapeCommand moveShape); + Task MoveShape(Guid whiteboardId, MoveShapeCommand moveShape); } \ No newline at end of file diff --git a/dotnet/AipsRT/Services/MessagingService.cs b/dotnet/AipsRT/Services/MessagingService.cs index 3ad2aa6..3f139bd 100644 --- a/dotnet/AipsRT/Services/MessagingService.cs +++ b/dotnet/AipsRT/Services/MessagingService.cs @@ -95,9 +95,9 @@ public class MessagingService : IMessagingService await _messagePublisher.PublishAsync(message); } - public async Task MoveShape(MoveShapeCommand moveShape) + public async Task MoveShape(Guid whiteboardId, MoveShapeCommand moveShape) { - var message = new MoveShapeMessage(moveShape); + var message = new MoveShapeMessage(whiteboardId, moveShape); await _messagePublisher.PublishAsync(message); } } \ No newline at end of file diff --git a/dotnet/AipsRT/Services/RtErrorHandleStrategy.cs b/dotnet/AipsRT/Services/RtErrorHandleStrategy.cs new file mode 100644 index 0000000..7ce8646 --- /dev/null +++ b/dotnet/AipsRT/Services/RtErrorHandleStrategy.cs @@ -0,0 +1,29 @@ +using AipsCore.Application.Common.Message.ErrorMessage; +using AipsRT.Hubs; +using AipsRT.Model.Whiteboard; +using Microsoft.AspNetCore.SignalR; + +namespace AipsRT.Services; + +public class RtErrorHandleStrategy : IErrorMessageHandleStrategy +{ + private readonly IHubContext _hubContext; + private readonly WhiteboardManager _whiteboardManager; + + public RtErrorHandleStrategy(IHubContext hubContext, WhiteboardManager whiteboardManager) + { + _hubContext = hubContext; + _whiteboardManager = whiteboardManager; + } + + public async Task Handle(ErrorMessage message, CancellationToken cancellationToken) + { + await _whiteboardManager.RefreshWhiteboard(message.WhiteboardId); + + var whiteboard = _whiteboardManager.GetWhiteboard(message.WhiteboardId)!; + + await _hubContext.Clients + .Group(whiteboard.WhiteboardId.ToString()) + .SendAsync("InitWhiteboard", whiteboard, cancellationToken); + } +} \ No newline at end of file diff --git a/dotnet/AipsWorker/WorkerService.cs b/dotnet/AipsWorker/WorkerService.cs index 5f6d8a8..c1c9b4a 100644 --- a/dotnet/AipsWorker/WorkerService.cs +++ b/dotnet/AipsWorker/WorkerService.cs @@ -1,6 +1,7 @@ using System.Reflection; using AipsCore.Application.Abstract; using AipsCore.Application.Abstract.MessageBroking; +using AipsCore.Application.Common.Message.ErrorMessage; using AipsCore.Application.Common.Message.TestMessage; using AipsCore.Domain.Common.Validation; using AipsWorker.Utilities; @@ -12,12 +13,14 @@ public class WorkerService : BackgroundService { private readonly IDispatcher _dispatcher; private readonly IMessageTypesProvider _messageTypesProvider; + private readonly IMessagePublisher _publisher; private readonly SubscribeMethodUtility _subscribeMethodUtility; - public WorkerService(IMessageSubscriber subscriber, IDispatcher dispatcher, IMessageTypesProvider messageTypesProvider) + public WorkerService(IMessageSubscriber subscriber, IDispatcher dispatcher, IMessageTypesProvider messageTypesProvider, IMessagePublisher publisher) { _dispatcher = dispatcher; _messageTypesProvider = messageTypesProvider; + _publisher = publisher; _subscribeMethodUtility = new SubscribeMethodUtility(subscriber); } @@ -45,6 +48,15 @@ public class WorkerService : BackgroundService } catch (ValidationException validationException) { + var whiteboardId = message.GetWhiteboardId(); + + if (whiteboardId is not null) + { + var errorMessage = new ErrorMessage(whiteboardId.Value, validationException.ValidationErrors); + + await _publisher.PublishAsync(errorMessage, ct); + } + Console.WriteLine("===Validation Exception: "); foreach (var error in validationException.ValidationErrors) {