Hi, today I want to present Multi Service Bus Core 2.0. The new version has a lot of small bugs fixed. I recently bought myself an excellent tool to draw diagrams. Below I will want first to start by showing the concept diagram. Multi-Service Bus Core 2.0 offers Server-Client communication. It is an important point, so I described it first with this but let me start from the basics principles of the library solution. The whole library exposes only 3 kinds of generic C# components named Requester, Receiver, and Responder. The requester is a server in a client-server pattern. It is responsible for creating messages in the queue ready to be taken by the Receiver or Responder. The main difference between the last two is that Receive does not have to create a response, and Responder has to. That is the main difference between them. Now let me quickly describe the components inside the complete solution. For exchanging messages, you have to serialize them, and for that, I am using binary serialization based on Protbuf for .NET, written by Marc Gravell. I use a simple contract created in Apache Thrift and written by some brilliant guys for communication. And all mentioned libraries, when I downloaded them, were shared on Apache License 2.0 as an open-source. And that is why I am also sharing Multi Service Bus Core 2.0 on the same Apache License 2.0. Below you can see the overall solution from the construction of the component used to build its perspective.
Now that all details about the internal design have been described, I want to explain possible communication layers inside your own implementations. First, that is very important. You can create inside one process communication. That may sound silly. Why should I make the same process communication inside one and use MSBC2.0 for that, you may ask? The reason is that behind the scenes, it uses the Publisher-Consumer pattern based on queues. Thanks to that, all components are Multi-Core and Multi-Thread programming ready. In the solution, you have performance tests that use “Parallel.For” from System.Threading.Task namespace as a part of TPL, Task Parallel Library, and that “simulates” multi-threading access to components invocations the same what you have in ASP.NET-based solutions for both WebForms and MVC many threads will “attack” your components. As I mentioned, the best practice is the have those components static. So that components need to be thread-safe ready and that all are. So in components methods, all is safe. You can have variables in your class that you can modify without any Interlocked or Barrier techniques. The only thing important is that one variable is for one method, but it is usually obvious. Only one method, for example, increments the counter of sessions, and all others only read it, right?.
Now, let me describe the next layer. The in-host communication layer. Behind the scenes, it uses the long-polling pattern, which means that communications are always working and need open ports only on the server site. That principle will also be true for the last layer inside the network, but let me first describe all possibilities inside one host communication. The first thing is that you can use a loopback TCP/IP port and address. For one IP, you can use 64k port numbers; for loopback, you can use at least 254 x 254 x 254 addresses from 127.0.0.1 to 127.254.254.254… so quite a lot :-)… exactly 16387064 addresses of loopback communication. You may think it is a lot for one machine, but if you imagine HPC solutions that are not a part of this article where you have one massive cluster “host,” that may be useful to have several ports of TCP and addresses of IP on the communication model. So, you can use inside host communication if you need to be sure no other computer will see your “traffic.” You can simply use a localhost address with some TCP port or extend to localhost2 with IP 127.0.0.2 and use it as well… I think you get the idea.
Now let me describe the last possible communication layer inside network communication that is very important for two reasons. First, it has to be secure, fast, and easy. Many solutions on the internet have good usage capability, so it is easy; a few of the solutions are fast, but only this one is secured 🙂 it uses server site SSL and client site SSL, both password protected and session keys that you must know before you connect to the real solution. So by default, you should provide a server site SSL certificate and put it inside the server solution folder and also create a client site SSL and put or distribute your client code. For example, you can create a DEB package for Raspberry Pi 3 and include your client site SSL certificate, which should be used to encrypt transport communication. But anyone can have it, right? So is it safe? It is because the client site SSL certificate cannot be used to create a server with the same CN or with any CN is it only for clients. The second thing is the session or API key, if you like to name it. that will be unique and, for example, be 1 or even 4 GUID space-separated… impossible to guess. And that allows you to create your own server with FQDN, a fully qualified domain name, set up the server on it, create SSL for both server and client, and create a distribution of packages with client SSL certificate included. Now, when you get an API key request, you create one, put that on the server, and share it with your client. If you do everything correctly only with the correct API / session key, the client will be able to communicate with you. Of course, you do not have to use SSL or Session Keys protection for some academic research. With Layer5 and Layer7 protection, you can allow everyone to connect, which is also fine. But I wanted to describe to you the most advanced security scenario first.
All three described layers are on shown diagram.
The Last thing I want to encourage you all before I share the download link is the modification you can and should do on your own. Both Requesters components have SessionKeys collection. It is a HashSet<string>, exactly. If you want to protect your communication, you should provide a list of supported session keys when you construct the component-only session key for it is necessary, for example, “S0” but if your solution works all the time on the cloud, you need to add or remove session keys or in other words subscriptions dynamically, but changing only this collection is not enough, so you have to define AddSessionKey(string sessionKey) and RemoveSessionKey(string sessionKey) methods. Inside those methods, you have to add to the SessionKeys collection and on Add, begin the session and on Remove, end the session. I want to give you this task to finish the work that took me about 7 years of experiments and improvements. That would be my fee to you :-). Once you make it all combination and possible usages will be possible to you.
About the usages, let me share some code included in the solution. This is TestExamples project content, which should give you an idea of all possible usages. Except for security, but after a few minutes, you can figure it out or ask me on comment for more guidance. So here is the code…
namespace TestsExamples { using System; using iSowa.io.msbc.Communication.Contracts; using iSowa.io.msbc.Communication; using ProtoBuf; // This is the definition of your message protocol // and you need to put it in shared assembly for // all components that need to communicate. // Only requirement is to derive from IMessage, // that interface is empty. [ProtoContract] public class MyMessageReq : IMessage { // this is an example of entity content. [ProtoMember(0)] public string Uri { get; set; } [ProtoMember(1)] public DateTime TimeStampUtc { get; set; } [ProtoMember(2)] public string Command { get; set; } } // This is an example response message with data. [ProtoContract] public class MyMessageRes : IMessage { // this is an example of entity content. [ProtoMember(0)] public string Data { get; set; } } class MultiServiceBusDemoClassInProc { // receiver in-proc private static IReceiver<MyMessageReq> receiver1 = new Receiver<MyMessageReq> { RecieveAction = ReceiveMessage }; // receiver in-proc receive method private static void ReceiveMessage(MyMessageReq message) { // do what you want with received messages here } // requester in-proc private static IRequester<MyMessageReq> requester1 = new Requester<MyMessageReq>(); // in-proc send/request method private static void SendMessageAsync1() { // sending example message to any Receiver<MyMessageReq> requester1.RequestAsync(new MyMessageReq { Uri = "https://iblog.isowa.io", TimeStampUtc = DateTime.UtcNow, Command = "GET" } /* sessionKey (optional) 1 from requester1.SessionKeys */ ); } // receiver in-proc that can respond but does not have to private static IReceiver<MyMessageReq, MyMessageRes> receiver2 = new Receiver<MyMessageReq, MyMessageRes> { RecieveAction = ReceiveMessage2 }; // method that receives messages private static void ReceiveMessage2(TransactId id, MyMessageReq mesage) { // you can respond back, but you do not have to receiver2.ResponseAsync( id, new MyMessageRes { Data = "CodingByToDesign.NET" } ); } // requester in-proc that can get responses private static IRequester<MyMessageReq, MyMessageRes> requester2 = new Requester<MyMessageReq, MyMessageRes> { ResponseAction = ReceiveMessageBack2 }; private static void ReceiveMessageBack2(MyMessageRes message) { // do what you want with received response messages here } // in-proc send/request method 2 private static void SendMessageAsync2() { // sending example message requester2.RequestAsync(new MyMessageReq { Uri = "https://iblog.isowa.io", TimeStampUtc = DateTime.UtcNow, Command = "GET" }, // optional parameter if it is null ReceiveMessageBack2 will be used new Action<MyMessageRes>( msgRes => { /* use your response message here */ }) /* sessionKey (optional) 1 from requester2.SessionKeys */ ); } // responder in-proc that has to respond always to messages private static IResponder<MyMessageReq, MyMessageRes> responder3 = new Responder<MyMessageReq, MyMessageRes> { ResponseFunc = RespondMessage }; // method that receives messages and needs to create a response message private static MyMessageRes RespondMessage(MyMessageReq message) { return new MyMessageRes { Data = "CodingByToDesign.NET" }; } // requester in-proc that can get responses private static IRequester<MyMessageReq, MyMessageRes> requester3 = new Requester<MyMessageReq, MyMessageRes> { ResponseAction = ReceiveMessageBack3 }; private static void ReceiveMessageBack3(MyMessageRes message) { // do what you want with received response messages here } // in-proc send/request method 3 private static void SendMessageAsync3() { // sending example message requester3.RequestSync(new MyMessageReq { Uri = "https://iblog.isowa.io", TimeStampUtc = DateTime.UtcNow, Command = "GET" }, // optional parameter if it is null ReceiveMessageBack3 will be used new Action<MyMessageRes>( msgRes => { /* use your response message here */ }) /* sessionKey (optional) 1 from requester3.SessionKeys */ ); } } class MultiServiceBusDemoClassInHostOrInNet { // receiver in-host or in-net private static IReceiver<MyMessageReq> receiver1 = new Receiver<MyMessageReq>( netTcpRequesterAddress: "net.tcp://localhost:27271/Demo" ) { RecieveAction = ReceiveMessage }; // receiver in-host or in-net receive method private static void ReceiveMessage(MyMessageReq message) { // do what you want with received messages here } // requester in-host or in-net private static IRequester<MyMessageReq> requester1 = new Requester<MyMessageReq>( netTcpRequesterAddress: "net.tcp://localhost:27271/Demo" ); // receive in-host or in-net send/request method private static void SendMessageAsync1() { // sending example message to any Receiver<MyMessageReq> requester1.RequestAsync(new MyMessageReq { Uri = "https://iblog.isowa.io", TimeStampUtc = DateTime.UtcNow, Command = "GET" } /* sessionKey (optional) 1 from requester1.SessionKeys */ ); } // receiver in-host or in-net that can respond but does not have to private static IReceiver<MyMessageReq, MyMessageRes> receiver2 = new Receiver<MyMessageReq, MyMessageRes>( netTcpRequesterAddress: "net.tcp://localhost:27272/Demo" ) { RecieveAction = ReceiveMessage2 }; // method that receives messages private static void ReceiveMessage2(TransactId id, MyMessageReq mesage) { // you can respond back, but you do not have to receiver2.ResponseAsync( id, new MyMessageRes { Data = "CodingByToDesign.NET" } ); } // requester in-host or in-net that can get responses private static IRequester<MyMessageReq, MyMessageRes> requester2 = new Requester<MyMessageReq, MyMessageRes>( netTcpRequesterAddress: "net.tcp://localhost:27272/Demo" ) { ResponseAction = ReceiveMessageBack2 }; private static void ReceiveMessageBack2(MyMessageRes message) { // do what you want with received response messages here } // in-host or in-net send/request method 2 private static void SendMessageAsync2() { // sending example message requester2.RequestAsync(new MyMessageReq { Uri = "https://iblog.isowa.io", TimeStampUtc = DateTime.UtcNow, Command = "GET" }, // optional parameter if it is null ReceiveMessageBack2 will be used new Action<MyMessageRes>( msgRes => { /* use your response message here */ }) /* sessionKey (optional) 1 from requester2.SessionKeys */ ); } //Responder in-host or in-net that has to respond always to messages private static IResponder<MyMessageReq, MyMessageRes> responder3 = new Responder<MyMessageReq, MyMessageRes>( netTcpRequesterAddress: "net.tcp://localhost:27273/Demo" ) { ResponseFunc = RespondMessage }; // method that receives messages and needs to create a response message private static MyMessageRes RespondMessage(MyMessageReq message) { return new MyMessageRes { Data = "CodingByToDesign.NET" }; } // requester in-host or in-net that can get responses private static IRequester<MyMessageReq, MyMessageRes> requester3 = new Requester<MyMessageReq, MyMessageRes>( netTcpRequesterAddress: "net.tcp://localhost:27273/Demo" ) { ResponseAction = ReceiveMessageBack3 }; private static void ReceiveMessageBack3(MyMessageRes message) { // do what you want with received response messages here } // in-host or in-net send/request method 3 private static void SendMessageAsync3() { // sending example message requester3.RequestSync(new MyMessageReq { Uri = "https://iblog.isowa.io", TimeStampUtc = DateTime.UtcNow, Command = "GET" }, // optional parameter if it is null ReceiveMessageBack3 will be used new Action<MyMessageRes>( msgRes => { /* use your response message here */ }) /* sessionKey (optional) 1 from requester3.SessionKeys */ ); } } class Program { static void Main() { // it is just instantiated new objects, // there is no logic inside the classes yet. var test1 = new MultiServiceBusDemoClassInProc(); var test2 = new MultiServiceBusDemoClassInHostOrInNet(); } } }
And at the end, I am sharing the source code of two solutions ready to build as .NET 4.6.1 and/or as .NET Core 2.0 libraries, and you will be able to use them on any possible imagine usages. You may create a communication-based IoT platform, use it for some Windows, GNU/Linux, or macOS services for telecommunication, and even create a solid foundation of next-generation API for cloud-based solutions. And all that in the secure, fast, and easiest to extend. Here it is Multi Service Bus Core 2.0 Source Code (512 downloads).
Thanks for reading,
p ;).
There is one more thing… ;-). If you like to contribute to Multi Service Bus Core 2.0 project please let me know by using Contact page or by leaving the comment down below… ;-).
Pingback: Multi Service Bus Core – coding by to design