{"id":16990403,"url":"https://github.com/marcinziabek/message-router","last_synced_at":"2025-04-12T03:31:56.168Z","repository":{"id":203089365,"uuid":"125651602","full_name":"MarcinZiabek/message-router","owner":"MarcinZiabek","description":"A lightweight message routing library supporting various messaging frameworks and serialization formats.","archived":false,"fork":false,"pushed_at":"2020-01-15T09:31:08.000Z","size":619,"stargazers_count":17,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-31T09:42:39.885Z","etag":null,"topics":["communication","message","netmq","queue","router","serialization","serializer","socket","zeromq"],"latest_commit_sha":null,"homepage":"https://marcinziabek.github.io/message-router/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MarcinZiabek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-03-17T17:07:51.000Z","updated_at":"2024-09-11T17:37:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"c64ec057-0fb2-497c-be5c-2f2f86a7a84d","html_url":"https://github.com/MarcinZiabek/message-router","commit_stats":null,"previous_names":["marcinziabek/message-router"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcinZiabek%2Fmessage-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcinZiabek%2Fmessage-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcinZiabek%2Fmessage-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcinZiabek%2Fmessage-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MarcinZiabek","download_url":"https://codeload.github.com/MarcinZiabek/message-router/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223494315,"owners_count":17154526,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["communication","message","netmq","queue","router","serialization","serializer","socket","zeromq"],"created_at":"2024-10-14T03:09:55.762Z","updated_at":"2024-11-07T10:04:26.140Z","avatar_url":"https://github.com/MarcinZiabek.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"What is it and what is it good for?\n-----------------------------------\n\nNowadays, no one considers implementing the communication layer using pure sockets. Some messaging libraries are developed such as ZeroMQ and its .NET Framework port called NetMQ. A number of problems solved by them is enormous: sending information over the network in a lightweight manner, making sure that a whole message is received or distributing messages to many subscribers. In the real world scenarios, it is not enough.\n\nLet's consider how alternatives look like. The HTTP protocol provides different endpoints - every message is appropriately deserialized and routed to methods in your code. The messaging system in ZeroMQ does not contain any header information and, therefore, does not provide any similar functionality.\n\nThe NetmqRouter acts as an additional layer of abstraction between your code and communication layer. In the internal implementation, every message consists of two message parts: a header (containing name of the communication route aka address) and a message body.\n\nAvailable packges\n-----------------\n\n| Package name         | Description                                           | Link       |\n| -------------------- | ----------------------------------------------------- | ---------- |\n| Message.Router       | A main routing package. Required.                     | [nuget][1] |\n| Message.Router.NetMQ | Support for NetMQ framework. Recommented.             | [nuget][2] |\n| Message.Router.Json  | General serializer for the JSON format. Recommented.  | [nuget][3] |\n| Message.Router.Xml   | General serializer for the Xml format.                | [nuget][4] |\n\nHere you can find all packages available on the Nuget website: [link](https://www.nuget.org/profiles/MarcinZiabek)\n\n[1]: https://www.nuget.org/packages/Message.Router/\n[2]: https://www.nuget.org/packages/Message.Router.NetMQ/\n[3]: https://www.nuget.org/packages/Message.Router.Json/\n[4]: https://www.nuget.org/packages/Message.Router.Xml/\n\nSimple yet powerful routing\n---------------------------\n\nThis library uses reflection to analyze your code and configure the system behind the scenes. Annotate you endpoint with the Route attribute and as an argument pass name of the communication route. The library will automatically analyze if your endpoint method contains an argument that can be used as a message payload.\n\n```csharp\nclass ExampleSubscriber\n{\n    [Route(\"TestRoute\")]\n    public string Test(string value)\n    {\n        // your business logic here\n    }\n}\n```\n\nResponding endpoints\n--------------------\n\nYour endpoints can simply respond to any message using the return value. All scenarios are allowed :)\n\n```csharp\nclass ExampleSubscriber\n{\n    [Route(\"TestRoute\")]\n    [ResponseRoute(\"AnotherRoute\")]\n    public string Test(string value)\n    {\n        // this endpoint will respond with text message\n    }\n\n    [Route(\"TestRoute\")]\n    [ResponseRoute(\"AnotherRoute\")]\n    public void Test(string value)\n    {\n        // this endpoint will respond with an event\n    }\n}\n```\n\nEmpty messages aka events\n-------------------------\n\nEmpty payloads are treated as a special type of messages, here called \"events\". Two scenarios are mainly interesting:\n- create an endpoint method that does not contain any argument - it will be an event subscriber,\n- create an endpoint method with the ResponseRoute attribute but returning void - it will be an event emitter.\n\nPlease note, that you can use event approach along the message pattern, e.g. an endpoint can subscribe an event but return text message. All combinations are allowed.\n\n```csharp\nclass ExampleSubscriber\n{\n    [Route(\"TestRoute\")]\n    public void Test()\n    {\n        // event subscriber\n    }\n\n    [Route(\"TestRoute\")]\n    [ResponseRoute(\"AnotherRoute\")]\n    public void Test(string text)\n    {\n        // event emitter\n    }\n\n    [Route(\"TestRoute\")]\n    [ResponseRoute(\"AnotherRoute\")]\n    public void Test()\n    {\n        // event subscriber and emitter at the same time\n    }\n}\n```\n\nBase routes\n-----------\n\nYou can annotate your class with the BaseRoute attribute to use subscribe to a specified route or family of routes.\n\n```csharp\n[BaseRoute(\"BaseRoute\")]\nclass ClassWithBaseRoute\n{\n    [Route(\"IncomingRoute\")]\n    [ResponseRoute(\"OutcomingRoute\")]\n    public void Handler()\n    {\n        // this endpoint will:\n        // - subscribe messages from \"BaseRoute/IncomingRoute\" route,\n        // - emit messages to \"OutcomingRoute\" route.\n    }\n}\n```\n\nSending messages\n----------------\n\nIf you want to do everything for yourself, send a simple message via the MessageRouter method:\n\n```csharp\n// sending event\nrouter.SendMessage(\"SomeRoute\");\n\n// sending text message\nrouter.SendMessage(\"AnotherRoute\", \"Hello world!\");\n\n// available methods\npublic void SendMessage(string routeName);\npublic void SendMessage(string routeName, byte[] data);\npublic void SendMessage(string routeName, string text);\npublic void SendMessage(string routeName, object _object);\n```\n\nRegister subscribers\n--------------------\n\nIt is possible to register your own methods as subscribers to specified routes. \n\n```csharp\nrouter.RegisterSubscriber(\"IncomingRoute\", () =\u003e { });\nrouter.RegisterSubscriber\u003cstring\u003e(\"IncomingRoute\", payload =\u003e { });\nrouter.RegisterSubscriber(\"IncomingRoute\", \"OutcomingRoute\", () =\u003e \"Hello world\");\nrouter.RegisterSubscriber\u003cstring, string\u003e(\"IncomingRoute\", \"OutcomingRoute\", payload =\u003e \"Hello \" + payload);\n```\n\nGo well with the fluent API\n---------------------------\n\nFluent APIs are sexy but always comes with additional performance cost. Here you do not need to be afraid but it is always good to take into consideration during profiling sessions. Below you can find some examples how to use MessageRouter fluent API:\n\n```csharp\n\nvar router = NetmqMessageRouter\n    .WithPubSubConnecton(publisherSocket, subscriberSocket)\n    .RegisterGeneralSerializer(new JsonObjectSerializer()) // requires nuget package!\n    .RegisterRoute(\"VectorRoute\", typeof(Vector))\n    .RegisterRoute(\"VectorLengthRoute\", typeof(double));\n\n// registering a subscriber that process the data\nrouter\n    .Subscribe(\"VectorRoute\")\n    .WithResponse(\"VectorLengthRoute\")\n    .WithHandler((Vector vector) =\u003e\n    {\n        return Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y);\n    });\n\n// registering a subscriber that receives the calculation result\nrouter\n    .Subscribe(\"VectorLengthRoute\")\n    .WithHandler((double x) =\u003e Console.WriteLine(x));\n\nrouter.StartRouting();\n\n// sending your message\nrouter\n    .SendMessage(new Vector() { X = 3, Y = 4 })\n    .To(\"VectorRoute\");\n\n```\n\nOpen to communication layers\n----------------------------\n\nThis library contains preconfigured configuration layer to most popular communication patterns in NetMQ:\n\n```csharp\n// basic example for the publisher-subscriber pattern\nvar publisherSocket = new PublisherSocket();\npublisherSocket.Bind(Address);\n\nvar subscriberSocket = new SubscriberSocket();\nsubscriberSocket.Connect(Address);\n\nvar router = MessageRouter.WithPubSubConnecton(publisherSocket, subscriberSocket)\n\n\n// available methods\npublic static MessageRouter WithPubSubConnecton(PublisherSocket publisherSocket, SubscriberSocket subscriberSocket);\npublic static MessageRouter WithPubSubConnecton(string publishAddress, string subscribeAddress);\n\npublic static MessageRouter WithPushPullConnection(PushSocket pushSocket, PullSocket pullSocket);\npublic static MessageRouter WithPushPullConnection(string pushAddress, string pullAddress);\n\npublic static MessageRouter WithPairConnection(PairSocket socket);\npublic static MessageRouter WithPairConnection(string address);\n```\n\nImplement your own communication layer\n--------------------------------------\n\nDo you have a special communication layer with custom business rules? Implement the IConnection interface and be free!\n\n```csharp\n// declare your custom connection handler\npublic class PairConnection : IConnection\n{\n    PairSocket Socket { get; }\n    private readonly object _socketLock = new object();\n\n    public PairConnection(PairSocket socket)\n    {\n        Socket = socket;\n    }\n\n    public void SendMessage(SerializedMessage message)\n    {\n        lock(_socketLock)\n            Socket.SendMessage(message);\n    }\n\n    // returns true if message was received successfully\n    public bool TryReceiveMessage(out SerializedMessage message)\n    {\n        lock(_socketLock)\n            return Socket.TryReceiveMessage(out message);\n    }\n\n    public void Connect(IEnumerable\u003cstring\u003e routeNames) { }\n\n    public void Disconnect()\n    {\n        Socket?.Close();\n        Socket?.Dispose();\n    }\n}\n\n\n// create message router\nvar socket = new PairSocket(Address);\nvar connection = new PairConnection(socket);\n\nvar router = new MessageRouter(connection);\n```\n\nSerialization layer: type and general serializers\n-------------------------------------------------\n\nYour endpoints can return any type of message as long as the library can serialize it to the binary format. Use any serialization library you want - you can use JSON or XML protocols - or implement your own solution. Batteries are included for types:\n- byte arrays,\n- text,\n- objects serialized in JSON or XML formats (please use appropriate helpers packages).\n\n```csharp\nrouter.RegisterTypeSerializerFor(new RawDataTypeSerializer());\nrouter.RegisterTypeSerializerFor(new BasicTextTypeSerializer());\nrouter.RegisterGeneralSerializerFor(new JsonObjectSerializer()); // requires nuget package!\n```\n\nOr use helpers provided by additional nuget packages:\n\n```\nrouter.RegisterJsonSerializer();\nrouter.RegisterXmlSerializer();\n```\n\nSerializer per type\n-------------------\n\nIt is possible to register a serializer designed for the specialized type. Below you can find an example implementation of text serializer:\n\n```csharp\n// create the serializer\npublic class BasicTextTypeSerializer : ITypeSerializer\u003cstring\u003e\n{\n    private readonly Encoding _encoding;\n\n    /// \u003cparam name=\"encoding\"\u003eEncoding that will be used for text serialization.\u003c/param\u003e\n    public BasicTextTypeSerializer(Encoding encoding)\n    {\n        _encoding = encoding;\n    }\n\n    public BasicTextTypeSerializer() : this(Encoding.UTF8)\n    {\n\n    }\n\n    public byte[] Serialize(string text) =\u003e _encoding.GetBytes(text);\n    public string Deserialize(byte[] data) =\u003e _encoding.GetString(data);\n}\n\n// register the serializer\nrouter.RegisterTypeSerializerFor(new BasicTextTypeSerializer());\n```\n\nGeneral serializers\n-------------------\n\nIt is possible to register a serializer designed for the group of types. Below you can find an example implementation of JSON serializer:\n\n```csharp\n// create the serializer\npublic class JsonObjectSerializer : IGeneralSerializer\u003cobject\u003e\n{\n    private readonly Encoding _encoding;\n\n    /// \u003cparam name=\"encoding\"\u003eEncoding that will be used for text serialization.\u003c/param\u003e\n    public JsonObjectSerializer(Encoding encoding)\n    {\n        _encoding = encoding;\n    }\n\n    public JsonObjectSerializer() : this(Encoding.UTF8)\n    {\n\n    }\n\n    public byte[] Serialize(object _object)\n    {\n        var json = JsonConvert.SerializeObject(_object);\n        return _encoding.GetBytes(json);\n    }\n\n    public object Deserialize(byte[] data, Type targetType)\n    {\n        var json = _encoding.GetString(data);\n        return JsonConvert.DeserializeObject(json, targetType);\n    }\n}\n\n// register the serializer\nrouter.RegisterGeneralSerializerFor(new JsonObjectSerializer());\n```\n\nHandle every exception\n----------------------\n\nThere are four main sources (and therefore four types) of exceptions in the NetmqRouter library:\n1.  **ConfigurationException** - this kind of exception is connected to wrong router configuration, e.g. lack of serializers for a used data type,\n2.  **ConnectionException** - this kind of exception may occur when any problem with connection occurs, e.g. the socket is already taken by another process,\n3.  **SerializationException** - this kind of exception is connected to the serialization process, e.g. a passed object cannot be serialized,\n4.  **SubscriberException** - this kind of exception decorate every exception that was thrown from endpoint's code.\n\nPlease note that by declaring your own business logic (e.g. connection protocols, serializers or endpoints) you are creating code that potentially may throw an exception. All of them will be available in the InnerException field.\n\nThe ConfigurationException can occur in the code where your router is configured. Other exceptions are published in the special event:\n\n```csharp\nrouter.OnException += exception =\u003e\n{\n    // handle any exception here\n    // good proposition: handle it using any logging library\n    Console.WriteLine(exception.Message);\n};\n```\n\nOutlook for the whole example\n-----------------------------\n\n```csharp\n[TestFixture]\npublic class MessagesRouterTests\n{\n    private const string Address = \"tcp://localhost:6000\";\n\n    // will be serialized as JSON\n    class CustomPayload\n    {\n        public string Text { get; set; }\n        public int Number { get; set; }\n\n        public CustomPayload(string text, int number)\n        {\n            Text = text;\n            Number = number;\n        }\n\n        public override bool Equals(object obj)\n        {\n            return obj is CustomPayload o \u0026\u0026\n                    this.Number == o.Number;\n        }\n    }\n\n    class ExampleSubscriber\n    {\n        public CustomPayload PassedValue;\n\n        [Route(\"TestRoute\")]\n        public void Test(CustomPayload value)\n        {\n            PassedValue = value;\n        }\n    }\n\n    [Test]\n    public async Task RoutingTest()\n    {\n        var publisherSocket = new PublisherSocket();\n        publisherSocket.Bind(Address);\n\n        var subscriberSocket = new SubscriberSocket();\n        subscriberSocket.Connect(Address);\n\n        var subscriber = new ExampleSubscriber();\n\n        var router = MessageRouter\n            .WithPubSubConnecton(publisherSocket, subscriberSocket)\n            .RegisterTypeSerializer(new RawDataTypeSerializer())\n            .RegisterTypeSerializer(new BasicTextTypeSerializer())\n            .RegisterGeneralSerializer(new JsonObjectSerializer()) // requires nuget package!\n            .RegisterRoute(\"TestRoute\", typeof(CustomPayload))\n            .RegisterSubscriber(subscriber)\n            .StartRouting();\n\n        router.SendMessage(\"TestRoute\", new CustomPayload(\"Hello world\", 123));\n\n        router.OnException += exception =\u003e\n        {    \n            // handle any exception\n        };\n\n        await Task.Delay(TimeSpan.FromSeconds(3));\n\n        router\n            .StopRouting()\n            .Disconnect();\n\n        var expectedValue = new CustomPayload(\"Hello world\", 123);\n        Assert.AreEqual(expectedValue, subscriber.PassedValue);\n    }\n}\n```\n\nGood practices\n--------------\n\nThere are several ways to improve your code quality. Please consider applying them to your system:\n- distribute the MessageRouter object across your system using the dependency injection mechanism. It does implement different interfaces so distribute it using interfaces that are best appropriate for the task. For example, in order to give your endpoints the possibility to send messages, use the IMessageSender interface:\n\n```csharp\npublic interface IMessageRouter : IMessageRouterManager, IMessageRouterConfiguration, IMessageSender, IExceptionSource, IDisposable\n{\n\n}\n\npublic interface IMessageSender\n{\n    void SendMessage(string routeName);\n    void SendMessage(string routeName, byte[] data);\n    void SendMessage(string routeName, string text);\n    void SendMessage(string routeName, object _object);\n}\n```\n\n- do not use magic strings as in examples, use a static class with appropriate fields:\n\n```csharp\n// your class containg route names\ninternal static class MessageRoutes\n{\n    public static readonly string IncomingRoute = \"IncomingRoute\";\n    public static readonly string OutcomingRoute = \"OutcomingRoute\";\n    public static readonly string BananaRoute = \"BananaRoute\";\n}\n\n// your subscriber\nclass ExampleSubscriber\n{\n    [Route(MessageRoutes.IncomingRoute)]\n    public string Test(string value)\n    {\n        // your business logic here\n    }\n}\n\n// somewhere in the code where you are sending a message\nrouter.SendMessage(MessageRoutes.BananaRoute, \"Hello world!\");\n```\n\nHow it works?\n-------------\n\nThis library uses a worker system in order to process messages. All workers are working in parallel in separate threads, processing data on different life stages:\n\n1.  **Receiver worker** - it is using the IConnection interface to communicate through the input socket.\n2.  **Deserialization worker** - it is getting serialized messages from the Receiver worker and deserializes it using the best matching provider.\n3.  **Handler worker** - it is getting messages and calling the subscriber's methods in order to process data.\n4.  **Serialization worker** - it is serializing all outcoming messages to the binary format.\n5.  **Sender worker** - it is queuing all outcoming messages and sending them via the IConnection interface.\n\n![Data flow chart](img/data_flow_mini.png)\n\nScale your solution\n-------------------\n\nIf the serialization process or message handling logic is expensive, a single worker instance (per job type) might be not enough. Scale the performance easily by increasing number of workers. You are able to change how many workers will be started when called the StartRoute method - just tune the number of serialization and handler workers individually.\n\nDefault values:\n- serialization process - a single worker for the serialization process and a single worker for deserialization process,\n- handling process - 4 workers.\n\n```csharp\nrouter\n\n    // your router configuration goes here\n\n    .WithWorkerPool(numberOfSerializationWorkes: 2, numberOfHandlingWorkes: 6)\n    .StartRouting();\n```\n\nFast and reliable\n-----------------\n\nThis library can be used in heavy environments because does not introduce any significant performance impact. Don't worry about your message, everything is well tested :)\n\nLet's use it!\n-------------\n\nDo you want to improve the architecture of your system and use this library? Congratulations, you made a good choice! At this moment, this framework is still under development and shouldn't be used in production but in nearest weeks it would be totally stable. ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcinziabek%2Fmessage-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcinziabek%2Fmessage-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcinziabek%2Fmessage-router/lists"}