Skip to content

Commit

Permalink
Merge pull request morganstanley#103 from BalassaMarton/messagerouter…
Browse files Browse the repository at this point in the history
…-mvp

Message Router MVP - New hosting model
  • Loading branch information
BalassaMarton authored Nov 23, 2022
2 parents 889df4d + ae41e6c commit ddc7d15
Show file tree
Hide file tree
Showing 47 changed files with 896 additions and 289 deletions.
4 changes: 4 additions & 0 deletions Tryouts/Messaging/Client/IMessageRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace MorganStanley.ComposeUI.Tryouts.Messaging.Client;
/// </summary>
public interface IMessageRouter : IAsyncDisposable
{
// TODO: Remove this method
/// <summary>
/// Asynchronously connects to the Message Router server endpoint.
/// </summary>
Expand All @@ -28,6 +29,7 @@ public interface IMessageRouter : IAsyncDisposable
/// </remarks>
ValueTask ConnectAsync(CancellationToken cancellationToken = default);

// TODO: Declare and use IAsyncObserver or ISubscriber
/// <summary>
/// Gets an observable that represents a topic.
/// </summary>
Expand All @@ -40,6 +42,7 @@ ValueTask<IDisposable> SubscribeAsync(
IObserver<RouterMessage> observer,
CancellationToken cancellationToken = default);

// TODO: Binary payload
/// <summary>
/// Publishes a message to a topic.
/// </summary>
Expand All @@ -49,6 +52,7 @@ ValueTask<IDisposable> SubscribeAsync(
/// <returns></returns>
ValueTask PublishAsync(string topicName, string? payload = null, CancellationToken cancellationToken = default);

// TODO: Binary payload
/// <summary>
/// Invokes a named service.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions Tryouts/Messaging/Client/MessagingScope.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Morgan Stanley makes this available to you under the Apache License,
// Version 2.0 (the "License"). You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0.
//
// See the NOTICE file distributed with this work for additional information
// regarding copyright ownership. 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 MorganStanley.ComposeUI.Tryouts.Messaging.Client;

// TODO: MessagingScope
public class MessagingScope
{

}
1 change: 1 addition & 0 deletions Tryouts/Messaging/Client/RouterMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal RouterMessage(string topic, string? payload)
/// </summary>
public string Topic { get; }

// TODO: Binary payload
/// <summary>
/// The payload of the message. The format of the message is arbitrary and should
/// be defined and documented with the message definition.
Expand Down
1 change: 1 addition & 0 deletions Tryouts/Messaging/Client/ServiceInvokeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace MorganStanley.ComposeUI.Tryouts.Messaging.Client;

// TODO: Binary payload
/// <summary>
/// The delegate type that gets called when a registered service is invoked.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@
// ReSharper disable once CheckNamespace

using Microsoft.Extensions.DependencyInjection;
using MorganStanley.ComposeUI.Tryouts.Messaging.Client;

// TODO: Move to MSFT DI namespace
namespace MorganStanley.ComposeUI.Tryouts.Messaging.Client.Startup;

/// <summary>
/// Static extensions to add the Message Router client to a service collection.
/// </summary>
public static class ServiceCollectionExtensions
{
// TODO: Make the callback optional, add auto-configuration from environment variables (the module loader should inject them)
/// <summary>
/// Adds the <see cref="IMessageRouter" /> and related types to the service collection,
/// using the provided configuration callback.
Expand Down
4 changes: 4 additions & 0 deletions Tryouts/Messaging/Core/Messages/ConnectRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,9 @@ public ConnectRequest(Guid clientId)
}

public override MessageType Type => MessageType.Connect;

// TODO: Remove this property, the client should not be able to reconnect.
public Guid? ClientId { get; init; }

// TODO: Add SecurityToken property
}
11 changes: 10 additions & 1 deletion Tryouts/Messaging/Core/Messages/MessageType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,23 @@ public enum MessageType : int
/// </summary>
Subscribe,

// TODO: SubscribeResponse

/// <summary>
/// Client unsubscribes from a topic
/// </summary>
Unsubscribe,

// TODO: UnsubscribeResponse

/// <summary>
/// Client publishes a message to a topic
/// </summary>
Publish,

// TODO: PublishResponse

// TODO: Rename to Topic?
/// <summary>
/// Server notifies client of a message from a subscribed topic
/// </summary>
Expand Down Expand Up @@ -68,5 +75,7 @@ public enum MessageType : int
/// <summary>
/// Client unregisters itself from a previously registered service name.
/// </summary>
UnregisterService
UnregisterService,

// TODO: UnregisterServiceResponse
}
20 changes: 20 additions & 0 deletions Tryouts/Messaging/Core/Messages/PayloadEncoding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Morgan Stanley makes this available to you under the Apache License,
// Version 2.0 (the "License"). You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0.
//
// See the NOTICE file distributed with this work for additional information
// regarding copyright ownership. 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 MorganStanley.ComposeUI.Tryouts.Messaging.Core.Messages;

// TODO: Implement encoding
public enum PayloadEncoding
{
Utf8,
Binary
}
2 changes: 2 additions & 0 deletions Tryouts/Messaging/Core/Messages/PublishMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ public PublishMessage(string topic, string? payload)
public override MessageType Type => MessageType.Publish;
public string Topic { get; init; }
public string? Payload { get; init; }

// TODO: Binary payload
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="FluentAssertions.Json" Version="6.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.6" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
61 changes: 40 additions & 21 deletions Tryouts/Messaging/IntegrationTests/WebSocketEndToEndTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,19 @@
// or implied. See the License for the specific language governing permissions
// and limitations under the License.

using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MorganStanley.ComposeUI.Tryouts.Messaging.Client;
using MorganStanley.ComposeUI.Tryouts.Messaging.Client.Transport.WebSocket;
using MorganStanley.ComposeUI.Tryouts.Messaging.Server;

namespace MorganStanley.ComposeUI.Tryouts.Messaging.IntegrationTests;

// To run these tests, first start the server (ComposeUI.Messaging.Server) without debugging.
// The tests will not leave the server in a clean state.
// Some tests might fail if not run after a clean start.

public class WebSocketEndToEndTests
public class WebSocketEndToEndTests : IAsyncLifetime
{
public WebSocketEndToEndTests()
{
WebSocketUri = new Uri("wss://localhost:7098/ws");
}

protected readonly WebApplicationFactory<Program> App;
protected readonly Uri WebSocketUri;

[Fact]
public async Task Client_can_connect()
{
Expand All @@ -45,9 +37,9 @@ public async Task Client_can_subscribe_and_receive_messages()
await using var subscriber = CreateClient();
var observerMock = new Mock<IObserver<RouterMessage>>();

await subscriber.SubscribeAsync(topicName: "test-topic", observerMock.Object);
await subscriber.SubscribeAsync("test-topic", observerMock.Object);
await Task.Delay(100);
await publisher.PublishAsync(topicName: "test-topic", payload: "test-payload");
await publisher.PublishAsync("test-topic", "test-payload");
await Task.Delay(100);

observerMock.Verify(x => x.OnNext(It.Is<RouterMessage>(msg => msg.Payload!.SequenceEqual("test-payload"))));
Expand All @@ -57,7 +49,7 @@ public async Task Client_can_subscribe_and_receive_messages()
public async Task Client_can_register_itself_as_a_service()
{
await using var client = CreateClient();
await client.RegisterServiceAsync(serviceName: "test-service", (name, payload) => default);
await client.RegisterServiceAsync("test-service", (name, payload) => default);
await client.UnregisterServiceAsync("test-service");
}

Expand All @@ -67,28 +59,55 @@ public async Task Client_can_invoke_a_registered_service()
await using var service = CreateClient();

var handlerMock = new Mock<ServiceInvokeHandler>();

handlerMock
.Setup(_ => _.Invoke("test-service", It.IsAny<string>()))
.Returns(new ValueTask<string?>("test-response"));
await service.RegisterServiceAsync(serviceName: "test-service", handlerMock.Object);

await service.RegisterServiceAsync("test-service", handlerMock.Object);

await using var client = CreateClient();

var response = await client.InvokeAsync(serviceName: "test-service", payload: "test-request");
var response = await client.InvokeAsync("test-service", "test-request");

response.Should().BeEquivalentTo("test-response");
handlerMock.Verify(_ => _.Invoke("test-service", "test-request"));

await service.UnregisterServiceAsync("test-service");
}

public async Task InitializeAsync()
{
IHostBuilder builder = new HostBuilder();

builder.ConfigureServices(
services => services.AddMessageRouterServer(
mr => mr.UseWebSockets(
opt =>
{
opt.RootPath = _webSocketUri.AbsolutePath;
opt.Port = _webSocketUri.Port;
})));

_host = builder.Build();
await _host.StartAsync();
}

public async Task DisposeAsync()
{
await _host.StopAsync();
}

private IHost _host = null!;
private readonly Uri _webSocketUri = new("ws://localhost:7098/ws");

private IMessageRouter CreateClient()
{
return MessageRouter.Create(
mr => mr.UseWebSocket(
new MessageRouterWebSocketOptions
{
Uri = WebSocketUri
}));
new MessageRouterWebSocketOptions { Uri = _webSocketUri }));
}
}


}

55 changes: 55 additions & 0 deletions Tryouts/Messaging/Messaging.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.Tryouts.Messaging.Core", "Core\MorganStanley.ComposeUI.Tryouts.Messaging.Core.csproj", "{FC647551-B014-49A8-817E-F5FE6FEBF452}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.Tryouts.Messaging.Client", "Client\MorganStanley.ComposeUI.Tryouts.Messaging.Client.csproj", "{A3156691-0B46-4C3D-B042-E71D9BAF02EC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.Tryouts.Messaging.Server", "Server\MorganStanley.ComposeUI.Tryouts.Messaging.Server.csproj", "{4AEFD2AA-CF42-401D-A058-E2B4C8E12283}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.Tryouts.Messaging.Server.Tests", "Server.Tests\MorganStanley.ComposeUI.Tryouts.Messaging.Server.Tests.csproj", "{C95F8B4A-7E42-43A6-A57E-C3ACB6255EA3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.Tryouts.Messaging.IntegrationTests", "IntegrationTests\MorganStanley.ComposeUI.Tryouts.Messaging.IntegrationTests.csproj", "{684367EE-406C-4727-993D-EF4B6FF5C37C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfHost", "WpfHost\WpfHost.csproj", "{7BDA5B06-82A4-4BD7-BA70-82382CEABDF5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FC647551-B014-49A8-817E-F5FE6FEBF452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC647551-B014-49A8-817E-F5FE6FEBF452}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC647551-B014-49A8-817E-F5FE6FEBF452}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC647551-B014-49A8-817E-F5FE6FEBF452}.Release|Any CPU.Build.0 = Release|Any CPU
{A3156691-0B46-4C3D-B042-E71D9BAF02EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3156691-0B46-4C3D-B042-E71D9BAF02EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3156691-0B46-4C3D-B042-E71D9BAF02EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3156691-0B46-4C3D-B042-E71D9BAF02EC}.Release|Any CPU.Build.0 = Release|Any CPU
{4AEFD2AA-CF42-401D-A058-E2B4C8E12283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4AEFD2AA-CF42-401D-A058-E2B4C8E12283}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4AEFD2AA-CF42-401D-A058-E2B4C8E12283}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4AEFD2AA-CF42-401D-A058-E2B4C8E12283}.Release|Any CPU.Build.0 = Release|Any CPU
{C95F8B4A-7E42-43A6-A57E-C3ACB6255EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C95F8B4A-7E42-43A6-A57E-C3ACB6255EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C95F8B4A-7E42-43A6-A57E-C3ACB6255EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C95F8B4A-7E42-43A6-A57E-C3ACB6255EA3}.Release|Any CPU.Build.0 = Release|Any CPU
{684367EE-406C-4727-993D-EF4B6FF5C37C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{684367EE-406C-4727-993D-EF4B6FF5C37C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{684367EE-406C-4727-993D-EF4B6FF5C37C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{684367EE-406C-4727-993D-EF4B6FF5C37C}.Release|Any CPU.Build.0 = Release|Any CPU
{7BDA5B06-82A4-4BD7-BA70-82382CEABDF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7BDA5B06-82A4-4BD7-BA70-82382CEABDF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BDA5B06-82A4-4BD7-BA70-82382CEABDF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BDA5B06-82A4-4BD7-BA70-82382CEABDF5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {38CFF5DF-459A-49E1-8189-2DB429A99B3A}
EndGlobalSection
EndGlobal
Loading

0 comments on commit ddc7d15

Please sign in to comment.