Skip to content

Conversation

@MathoMathiasCamara
Copy link
Contributor

@MathoMathiasCamara MathoMathiasCamara commented Oct 20, 2025

The following Issue is resolved in this PR.

Motivation :

Based on #656 and #657, we want to allow applications to share resources throughout multiple MORYX instances.

This PR contains the following features:

  • New feautures :
    • A shared MQTT client called IManagedMqttClient
    • ResourceSynchronizationService (Listen to resource change event and sends a message to the broker)
  • Old feature that were refactored to work with the new IManagedMqttClient
    • An MqttEndpointService for topic template support ex: "my-topic/{id:int}/invoke/{methodName}"
    • A ResourceEventService (listen to public Resource events and sends a message to the broker)
    • A ResourceRpcEndpoint (when the client receives a method invocation message, the method is invoked on the matching resource , any return value (except Task<T>) is sent back to the broker)

How the new feature works ?

Mqtt Service

Config/Moryx.Mqtt.Config.json

  {
    "Host": "localhost",
    "Port": 1883,
    "Id": "device-2",
    "Username": null,
    "Password": null,
    "Tls": false,
    "QoS": 1,
    "ReconnectDelayMs": 30000,
    "ReconnectWithClientSession": true,
    "ConfigState": "Generated",
    "RootTopic":  "moryx/device-2"
  }
// BlablaService.cs
 public class BlablaService : IMqttService
 {
    private IMyDependency _dependency;
    private IManagedMqttClient _client;
    public Blabla(IMyDependency dependency, IManagedMqttClient client)
    {
        _dependency = dependency;
        _client = client;
    }

    // </inherited>
    public override Task StartAsync(CancellationToken cancellationToken)
    {
        // your logic here
        // _client.OnConnectedAync += ....
        // _client.OnMessagereceived += ...
        return Task.CompletedTask;
    }

    // </inherited>
    public override Task StopAsync(CancellationToken cancellationToken)
    {
        //your logic here
        return Task.CompletedTask;
    }
 }
 

 // Program.cs

  builder.Services.AddMqttClient("Moryx.Mqtt.Config")
      .AddMqttService<BlablaService>()

Endpoint feature

Example 1 :

// Program.cs
  builder.Services.AddMqttClient()
     .AddMqttEndpoints(builder =>
{
    builder.MapGet("my-topic/{id:int}", ctx =>
    {
         var messageBuilder = new MqttApplicationMessageBuilder();
        messageBuilder.WithTopic("another-topic");
        messageBuilder.WithPayload(MqttMessageSerialization.GetJsonPayload("Hello World!!!!"));
        return messageBuilder.Build();
    });
});

Example 2:

// Program.cs
  builder.Services.AddMqttClient()
     .AddMqttEndpoints();

// BlablaEndpoint.cs
 public class Blabla : IMqttEndpoint 
 {
    private IMyDependency _dependency;
    public Blabla(IMyDependency dependency)
    {
        _dependency = dependency;
    }

    public void Map(IMqttRouteBuilder route)
    {
        //topic template is supported here
        route.MapGet("my-topic/{id:int}/method/{methodName}", context =>
        {
             var messageBuilder = new MqttApplicationMessageBuilder();
            messageBuilder.WithTopic("another-topic");
            messageBuilder.WithPayload(MqttMessageSerialization.GetJsonPayload("Hello World!!!!"));
            return messageBuilder.Build();
        });
    }
}

 - A shared, reusable MQTT client service configured from `Config/Moryx.Communication.Mqtt.Config.json`, to be registered in the service collection and usable across multiple features.
 - A Resource MQTT integration service that uses IResourceManagement and IResourceManagementChanges to publish resource events and handle subscriptions via the shared MQTT client.
Comment on lines +188 to +217
```csharp
/// <summary>
/// Specifies the synchronization behavior for a resource's digital twin
/// across multiple application instances.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ResourceSynchronizationAttribute : Attribute
{
/// <summary>
/// Defines the property serialization strategy for this resource.
/// </summary>
public SynchronizationMode Mode { get; set; }
}

/// <summary>
/// Defines the property serialization strategy.
/// </summary>
public enum SynchronizationMode
{
/// <summary>
/// The entire resource object, including all its properties, is serialized and synchronized.
/// </summary>
Full,

/// <summary>
/// Only properties explicitly marked with the [SynchronizableMember] attribute will be
/// serialized and synchronized.
/// </summary>
Selective
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want it is better to reference the file as a link or use a markdown table with value and description. Dont copy code in docs...

Comment on lines +231 to +238
```csharp
/// <summary>
/// Marks a property as included in the payload when its class
/// is using 'Selective' synchronization mode.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class SynchronizableMemberAttribute : Attribute { }
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.. why do you copy the code to the docs?

Comment on lines +46 to +49
You can also configure the Mqtt client using a configuration file just like any `MORYX` module.
```cs
builder.Services.AddMqttClient("Moryx.Mqtt.Config"); // specify your config file name without extension
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why must the filename be defined? The config manager normally does that automatically by the typename. Which class is used for the serialization?


The core of this feature is an attribute to mark an `IResource` class for synchronization. Its `Mode` property dictates which properties are included in the synchronization payload.

```csharp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sometimes you use csharp, sometimes cs - i vote for constistant usage of cs.

Comment on lines +189 to +217
/// <summary>
/// Specifies the synchronization behavior for a resource's digital twin
/// across multiple application instances.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ResourceSynchronizationAttribute : Attribute
{
/// <summary>
/// Defines the property serialization strategy for this resource.
/// </summary>
public SynchronizationMode Mode { get; set; }
}

/// <summary>
/// Defines the property serialization strategy.
/// </summary>
public enum SynchronizationMode
{
/// <summary>
/// The entire resource object, including all its properties, is serialized and synchronized.
/// </summary>
Full,

/// <summary>
/// Only properties explicitly marked with the [SynchronizableMember] attribute will be
/// serialized and synchronized.
/// </summary>
Selective
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again copied code to docs..

using Moryx.Serialization;
using MQTTnet;

namespace Moryx.Communication.Mqtt.Endpoints;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a special case.. this Endpoint has a dependency to mqtt stuff, and I definitely don't want a reference to that in Moryx.AbstractionLayer.Resources.Endpoints. For my POV, this endpoint should be separated because I also dont see a reference of Moryx.AbstractionLayer in Moryx.Communication.Mqtt

</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Moryx.AbstractionLayer\Moryx.AbstractionLayer.csproj" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moryx.Communication.Mqtt should not reference Moryx.AbstractionLayer nor Moryx.Runtime

<ProjectReference Include="..\Moryx.Runtime\Moryx.Runtime.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the readme placed within the package and not part of the documentation?

Comment on lines +195 to +218
#### Route constraint

The available constraints are:

- {name: int}
- {name: int?}
- {name: string}
- {name: string?}
- {name: decimal}
- {name: decimal?}
- {name: bool}
- {name: bool?}
- {name: datetime}
- {name: datetime?}
- {name: double}
- {name: double?}
- {name: float}
- {name: float?}
- {name: guid}
- {name: guid?}
- {name: long}
- {name: long?}
- {name?}
- {name/*}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do they come from? Maybe reference another doc instead of copy the stuff into these docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

4 participants