|
| 1 | + |
| 2 | +In this tutorial, we will explore the basics of publishing and consuming messages. |
| 3 | +The source code for this example is available **[here](https://github.com/OpenTransitLab/Tutorials/tree/main/Tutorials.BasicCommunication)** |
| 4 | + |
| 5 | + |
| 6 | +## Introducing the Services |
| 7 | +In this tutorial, we’ll work with three services: |
| 8 | + |
| 9 | +- **Client** – A simple console application that publishes a message to simulate an order arriving from the frontend. |
| 10 | +- **OrderService** – Handles incoming orders. After processing an order, it publishes another message to continue the workflow. |
| 11 | +- **InventoryService** – Listens for order-processing–related messages and handles the inventory side of the workflow. |
| 12 | + |
| 13 | +In this setup: |
| 14 | +- The **Client** acts as a **Producer** — it only publishes messages. |
| 15 | +- The **InventoryService** acts as a **Consumer** — it only receives messages. |
| 16 | +- The **OrderService** is both a **Producer** and a **Consumer** — it consumes one message and then publishes another. |
| 17 | + |
| 18 | + |
| 19 | +## Explanation |
| 20 | +You need to understand these 5 things to have a basic knowledge of Distributed Communication with OpenTransit. |
| 21 | + |
| 22 | +1. [Defining Messages](#1-defining-messages) |
| 23 | +2. [How Messages are published](#2-publishing-messages) |
| 24 | +3. [How Messages are Consumed](#3-consuming-a-message) |
| 25 | +4. [How the OpenTransit Setup is done](#4-masstransit-setup) |
| 26 | +5. [The Topology Created inside the Broker](#5-the-broker-topologyrabbitmq) |
| 27 | + |
| 28 | + |
| 29 | +### 1. Defining Messages |
| 30 | + |
| 31 | +Messages are the means of communication between Services. They can be defined using **classes**, **interfaces**, or **records**. |
| 32 | + |
| 33 | +In this project, we define two messages using C# classes: **SubmitOrder** and **ProcessOrder**. |
| 34 | + |
| 35 | +- The **Client** publishes a **SubmitOrder** message. |
| 36 | +- The **OrderService** consumes **SubmitOrder**, processes it, and then publishes **ProcessOrder**. |
| 37 | +- The **InventoryService** consumes **ProcessOrder**. |
| 38 | + |
| 39 | +#### Sharing Message Types Across Projects |
| 40 | + |
| 41 | +When a message is used by multiple applications—such as a producer and a consumer—they **must use the exact same namespace** for the message type. |
| 42 | + |
| 43 | +For example: |
| 44 | + |
| 45 | +- **SubmitOrder** is used by both Client and OrderService → same namespace required |
| 46 | +- **ProcessOrder** is used by OrderService and InventoryService → same namespace required |
| 47 | + |
| 48 | +To simplify this, we created a **shared class library** that contains the message definitions. This is the easiest and most maintainable approach. |
| 49 | + |
| 50 | +However, using a shared library is **not mandatory**. Each project may define its own copy of the message class, as long as the **namespace matches exactly**, ensuring that the broker treats them as the same message type. |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +### 2. Publishing Messages |
| 55 | + |
| 56 | +Messages are published by **Producers**. A Producer exposes a `Publish(T message)` method, which is used to send messages to the broker. |
| 57 | +(We’ll cover Producers in detail later in the documentation.) |
| 58 | + |
| 59 | +In this example, messages are published from two places: |
| 60 | + |
| 61 | +- From the **Client** project’s `Program.cs` |
| 62 | +[!code-csharp[](code-sample/Client.Program.cs#L25-L33)] |
| 63 | + |
| 64 | + |
| 65 | +- From inside the **SubmitOrderConsumer** |
| 66 | +[!code-csharp[](code-sample/OrderService.SubmitOrderConsumer.cs#L6-L18)] |
| 67 | + |
| 68 | +Both `IBus` and `ConsumeContext<T>` act as Producers, because you can call `Publish(T message)` on either of them. |
| 69 | + |
| 70 | +When publishing inside a [Consumer](#publishing-from-inside-a-consumer), it is **highly recommended** to use the `ConsumeContext<T>` Producer as we have done in the SubmitOrderConsumer. We’ll discuss why in the dedicated Producers section. |
| 71 | + |
| 72 | +When publishing *outside* a Consumer, you can resolve the `IBus` service and publish messages using it like we have done in the **Client** Project's `Program.cs`. |
| 73 | + |
| 74 | +In OpenTransit, the necessary services (such as `IBus`) are registered in `Program.cs`(See the [Setup](#4-masstransit-setup), allowing you to resolve and use them throughout the application. |
| 75 | + |
| 76 | +--- |
| 77 | + |
| 78 | +### 3. Consuming a Message: |
| 79 | + |
| 80 | +To consume a message of type `T`, you need to implement `IConsumer<T>`. |
| 81 | + |
| 82 | +In our example, we have two consumers: |
| 83 | + |
| 84 | +- **SubmitOrderConsumer** in the **OrderService** |
| 85 | +[!code-csharp[](code-sample/OrderService.SubmitOrderConsumer.cs#L6-L18)] |
| 86 | +- **ProcessOrderConsumer** in the **InventoryService** |
| 87 | +[!code-csharp[](code-sample/InventoryService.ProcessOrderConsumer.cs#L6-L13)] |
| 88 | + |
| 89 | +Each consumer must also be registered in the OpenTransit [Setup](#4-masstransit-setup) so the framework knows to create and connect them to the message pipeline. |
| 90 | + |
| 91 | +#### Publishing from inside a Consumer |
| 92 | + |
| 93 | +The `IConsumer<T>` defines a `Consume` method which is called when a message of type `T` is consumed. The method passes a Producer namely `ConsumeContext<T>`, it is highly recommended to use this producer(`ConsumeContext`) when we publish messages from inside the consumer. As you see we have done it in the SubmitOrderConsumer. |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | + |
| 98 | +### 4. MassTransit Setup: |
| 99 | +We configure OpenTransit in the **Service Registration** section of `Program.cs`. |
| 100 | +In this step, we perform three main tasks: |
| 101 | + |
| 102 | +1. **Register the required services** |
| 103 | + Simply call `builder.Services.AddMassTransit()` and it will register everything needed. |
| 104 | + |
| 105 | +2. **Configure the connection to the message broker** |
| 106 | + |
| 107 | +3. **Register the consumers**(if any) |
| 108 | + |
| 109 | +All the above 3 tasks is done onthe `Program.cs` of OrderService |
| 110 | +[!code-csharp[](code-sample/OrderService.Program.cs#L12-L25)] |
| 111 | + |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +### 5. The Broker Topology(RabbitMQ): |
| 116 | + |
| 117 | +This example uses **[broker-agnostic](../concepts/topology#broker-agnostic-way)** configuration, so you don’t need to understand the underlying broker [topology](../concepts/topology) for basic communication. |
| 118 | +Here, we only used the `UsingRabbitMq` method to provide the Connection Configuration and to configure the [ReceiveEndpoint](../concepts/generic-broker#generic-broker-terminologies)(a generic concept among all the brokers) on the broker. |
| 119 | + |
| 120 | +Since this Configurations aren't RabbitMQ specific, and you may use any other broker here and, the Message Communication would work fine. |
| 121 | + |
| 122 | +However, knowing the underlying broker [topology](../concepts/topology) is very helpful when debugging message-routing issues. |
| 123 | + |
| 124 | +In our example, Each Consumer is consuming a single message type. |
| 125 | +Here, for each MessageType, 2 **Exchanges** and one **Queue** are being created. |
| 126 | + |
| 127 | +For example, if you open the RabbitMq management plugin, you will see, for the SubmitOrder MessageType, a **Queue** named `SubmitOrder` is bound to the **Exchange** Named `SubmitOrder`. |
| 128 | +Another **Exchange** named `Shared:SubmitOrder` is created and the `SubmitOrder` **Exchange** is bound to it. (Here, **'Shared'** came from the **Namespace** of the message) |
| 129 | + |
| 130 | +When we publish SubmitOrder messages via a Producer, the message is published to the `Shared:SubmitOrder` exchange, then routed to the SubmitOrder exchange and, then ultimately routed to the SubmitOrder queue. |
| 131 | + |
| 132 | + |
| 133 | +#### Generic Broker's Perspective |
| 134 | +From the perspective of the [Generic Broker](../concepts/generic-broker), the `Shared:SubmitOrder` **Exchange** is the PublishEndpoint, the `SubmitOrder` **Queue** is the ReceiveEndpoint. |
| 135 | +The `SubmitOrder` **Exchange** can be seen as an Internal ReceiveEndpoint. However, we haven't added such concept/term in the [Generic Broker](../concepts/generic-broker) yet. |
| 136 | + |
| 137 | + |
| 138 | +Why the topology is defined that way, what we are gaining, etc, is out of scope of this tutorial. We will have separate sections for it. |
| 139 | + |
| 140 | +To have a better understanding, you may clone the [project](https://github.com/OpenTransitLab/Tutorials/tree/main/Tutorials.BasicCommunication) and create more message types experimentation. |
| 141 | + |
| 142 | + |
| 143 | + |
0 commit comments