diff --git a/.github/tutorial-5-01.png b/.github/tutorial-5-01.png
new file mode 100644
index 0000000..48b784f
Binary files /dev/null and b/.github/tutorial-5-01.png differ
diff --git a/.github/tutorial-6-01.png b/.github/tutorial-6-01.png
new file mode 100644
index 0000000..8592115
Binary files /dev/null and b/.github/tutorial-6-01.png differ
diff --git a/README.md b/README.md
index 17006f1..a9d435a 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,7 @@ Para obter uma cópia local atualizada e que possa ser executada corretamente, s
### Pré-requisitos
-Garanta que o **RabbitMQ** está instalado e sendo executado em `localhost` na porta padrão `5672`.
+Os tutoriais assumem que o **RabbitMQ** está instalado e sendo executado em `localhost` na porta padrão (`5672`).
- **Management:** http://localhost:15672
- **Username:** guest
@@ -114,11 +114,15 @@ dotnet restore
```
-## Visão geral
+## Introdução
-O **RabbitMQ** - e outras ferramentas de mensagens no geral, usa alguns jargões:
+**RabbitMQ** é um *message broker*: ele aceita e encaminha mensagens. Você pode pensar sobre isso como se fossem os *Correios*: quando você coloca a carta que você quer em uma caixa de postagem, você pode ter certeza de que eventualmente o carteiro irá entregar sua carta ao destinatário. Nesta analogia, o **RabbitMQ** é a caixa de postagem, a agência dos *Correios* e o carteiro.
-- Um programa que envia mensagens é um `Producer`:
+A maior diferença entre o **RabbitMQ** e uma agência dos *Correios* é que ele não lida com papel, ao invés disso aceita, armazena e encaminha blobs binários de dados ‒ *mensagens*.
+
+O **RabbitMQ** ‒ e outras ferramentas de mensagens no geral ‒ usa alguns jargões:
+
+- *Producing* significa nada mais do que *enviando*. Um programa que envia mensagens é um `Producer` (*produtor*):
![Producer](.github/producer.png)
@@ -380,6 +384,15 @@ dotnet run
No próximo tutorial iremos aprender como escutar um subconjunto de mensagens.
+## Tutorial 4 » Routing
+
+[Routing](https://www.rabbitmq.com/tutorials/tutorial-four-dotnet.html)
+
+No tutorial anterior, criamos um sistema de *log* simples. Fomos capazes de transmitir mensagens para vários receptores.
+
+Neste tutorial vamos adicionar uma funcionalidade à ele - vamos tornar possível se subscrever apenas a um subconjunto de mensagens. Por exemplo, teremos a possibilidade de direcionar apenas as mensagens de *erro crítico* para o arquivo em disco, enquanto ainda é possível exibir todas as mensagens de *log* em tela.
+
+
## Licença
Distribuído através da licença MIT. Veja `LICENSE` para mais informações.
diff --git a/src/Tutorial.RabbitMQ.Console.EmitLogTopic/EmitLogTopic.cs b/src/Tutorial.RabbitMQ.Console.EmitLogTopic/EmitLogTopic.cs
new file mode 100644
index 0000000..bd16394
--- /dev/null
+++ b/src/Tutorial.RabbitMQ.Console.EmitLogTopic/EmitLogTopic.cs
@@ -0,0 +1,41 @@
+using RabbitMQ.Client;
+using System;
+using System.Linq;
+using System.Text;
+
+namespace Tutorial.RabbitMQ.Console.EmitLogTopic
+{
+ class EmitLogTopic
+ {
+ static void Main(string[] args)
+ {
+ var factory = new ConnectionFactory() { HostName = "localhost" };
+ var exchangeName = "topic_logs";
+
+ using (var connection = factory.CreateConnection())
+ using (var channel = connection.CreateModel())
+ {
+ channel.ExchangeDeclare(exchange: exchangeName,
+ type: ExchangeType.Topic);
+
+ var routingKey = (args.Length > 0) ? args[0] : "anonymous.info";
+
+ var message = (args.Length > 1)
+ ? string.Join(" ", args.Skip(1).ToArray())
+ : "Hello World!";
+
+ var body = Encoding.UTF8.GetBytes(message);
+
+ channel.BasicPublish(exchange: "topic_logs",
+ routingKey: routingKey,
+ basicProperties: null,
+ body: body);
+
+ System.Console.WriteLine($"{DateTime.Now}: Sent '{routingKey}':'{message}'");
+ }
+
+ System.Console.WriteLine($"{DateTime.Now}: Press [enter] to exit.");
+ System.Console.ReadLine();
+ }
+ }
+}
diff --git a/src/Tutorial.RabbitMQ.Console.EmitLogTopic/Tutorial.RabbitMQ.Console.EmitLogTopic.csproj b/src/Tutorial.RabbitMQ.Console.EmitLogTopic/Tutorial.RabbitMQ.Console.EmitLogTopic.csproj
new file mode 100644
index 0000000..6c67a5e
--- /dev/null
+++ b/src/Tutorial.RabbitMQ.Console.EmitLogTopic/Tutorial.RabbitMQ.Console.EmitLogTopic.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
diff --git a/src/Tutorial.RabbitMQ.Console.RPCClient/RPCClient.cs b/src/Tutorial.RabbitMQ.Console.RPCClient/RPCClient.cs
new file mode 100644
index 0000000..46e0bc4
--- /dev/null
+++ b/src/Tutorial.RabbitMQ.Console.RPCClient/RPCClient.cs
@@ -0,0 +1,82 @@
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using System;
+using System.Collections.Concurrent;
+using System.Text;
+
+namespace Tutorial.RabbitMQ.Console.RPCClient
+{
+ class RPCClient
+ {
+ private readonly IConnection connection;
+ private readonly IModel channel;
+ private readonly string queueName = "rpc_queue";
+ private readonly string replyQueueName;
+ private readonly EventingBasicConsumer consumer;
+ private readonly BlockingCollection respQueue = new BlockingCollection();
+ private readonly IBasicProperties props;
+ private readonly string CorrelationId;
+
+ public RPCClient()
+ {
+ var factory = new ConnectionFactory() { HostName = "localhost" };
+
+ connection = factory.CreateConnection();
+ channel = connection.CreateModel();
+ replyQueueName = channel.QueueDeclare().QueueName;
+ consumer = new EventingBasicConsumer(channel);
+
+ props = channel.CreateBasicProperties();
+ CorrelationId = Guid.NewGuid().ToString();
+ props.CorrelationId = CorrelationId;
+ props.ReplyTo = replyQueueName;
+
+ consumer.Received += Consumer_Received;
+ }
+
+ private void Consumer_Received(object sender, BasicDeliverEventArgs e)
+ {
+ var body = e.Body.ToArray();
+ var response = Encoding.UTF8.GetString(body);
+ if (e.BasicProperties.CorrelationId == CorrelationId)
+ {
+ respQueue.Add(response);
+ }
+ }
+
+ public string Call(string message)
+ {
+ var messageBytes = Encoding.UTF8.GetBytes(message);
+
+ channel.BasicPublish(exchange: string.Empty,
+ routingKey: queueName,
+ basicProperties: props,
+ body: messageBytes);
+
+ channel.BasicConsume(consumer: consumer,
+ queue: replyQueueName,
+ autoAck: true);
+
+ return respQueue.Take();
+ }
+
+ public void Close()
+ {
+ connection.Close();
+ }
+ }
+
+ public class Rpc
+ {
+ static void Main(string[] args)
+ {
+ var rpcClient = new RPCClient();
+
+ System.Console.WriteLine($"{DateTime.Now}: Press Requesting fib(30).");
+ var response = rpcClient.Call("4");
+ System.Console.WriteLine($"{DateTime.Now}: Got '{response}'");
+
+ rpcClient.Close();
+ }
+ }
+}
diff --git a/src/Tutorial.RabbitMQ.Console.RPCClient/Tutorial.RabbitMQ.Console.RPCClient.csproj b/src/Tutorial.RabbitMQ.Console.RPCClient/Tutorial.RabbitMQ.Console.RPCClient.csproj
new file mode 100644
index 0000000..6c67a5e
--- /dev/null
+++ b/src/Tutorial.RabbitMQ.Console.RPCClient/Tutorial.RabbitMQ.Console.RPCClient.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
diff --git a/src/Tutorial.RabbitMQ.Console.RPCServer/RPCServer.cs b/src/Tutorial.RabbitMQ.Console.RPCServer/RPCServer.cs
new file mode 100644
index 0000000..8cd4b53
--- /dev/null
+++ b/src/Tutorial.RabbitMQ.Console.RPCServer/RPCServer.cs
@@ -0,0 +1,96 @@
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using System;
+using System.Text;
+
+namespace Tutorial.RabbitMQ.Console.RPCServer
+{
+ class RPCServer
+ {
+ static void Main(string[] args)
+ {
+ var factory = new ConnectionFactory() { HostName = "localhost" };
+ var queueName = "rpc_queue";
+
+ using (var connection = factory.CreateConnection())
+ using (var channel = connection.CreateModel())
+ {
+ channel.QueueDeclare(queue: queueName,
+ durable: false,
+ exclusive: false,
+ autoDelete: false,
+ arguments: null);
+
+ channel.BasicQos(0, 1, false);
+
+ var consumer = new EventingBasicConsumer(channel);
+
+ channel.BasicConsume(queue: queueName,
+ autoAck: false,
+ consumer: consumer);
+
+ System.Console.WriteLine($"{DateTime.Now}: Awaiting RPC requests.");
+
+ consumer.Received += Consumer_Received;
+
+ System.Console.WriteLine($"{DateTime.Now}: Press [enter] to exit.");
+ System.Console.ReadLine();
+ }
+ }
+
+ private static void Consumer_Received(object sender, BasicDeliverEventArgs e)
+ {
+ var channel = ((EventingBasicConsumer)sender).Model;
+ string response = null;
+
+ var body = e.Body.ToArray();
+ var props = e.BasicProperties;
+ var replyProps = channel.CreateBasicProperties();
+ replyProps.CorrelationId = props.CorrelationId;
+
+ try
+ {
+ var message = Encoding.UTF8.GetString(body);
+ int n = int.Parse(message);
+
+ System.Console.WriteLine($"{DateTime.Now}: fib({message})");
+ response = fib(n).ToString();
+
+ }
+ catch (Exception ex)
+ {
+ System.Console.WriteLine($"{DateTime.Now}: ERROR {ex.Message}");
+ response = string.Empty;
+ }
+ finally
+ {
+ var responseBytes = Encoding.UTF8.GetBytes(response);
+
+ channel.BasicPublish(exchange: string.Empty,
+ routingKey: props.ReplyTo,
+ basicProperties: replyProps,
+ body: responseBytes);
+
+ channel.BasicAck(deliveryTag: e.DeliveryTag,
+ multiple: false);
+ }
+ }
+
+ ///
+ /// Assumes only valid positive integer input.
+ /// Don't expect this one to work for big numbers, and it's
+ /// probably the slowest recursive implementation possible.
+ ///
+ ///
+ ///
+ private static int fib(int n)
+ {
+ if (n == 0 || n == 1)
+ {
+ return n;
+ }
+
+ return fib(n - 1) + fib(n - 2);
+ }
+ }
+}
diff --git a/src/Tutorial.RabbitMQ.Console.RPCServer/Tutorial.RabbitMQ.Console.RPCServer.csproj b/src/Tutorial.RabbitMQ.Console.RPCServer/Tutorial.RabbitMQ.Console.RPCServer.csproj
new file mode 100644
index 0000000..6c67a5e
--- /dev/null
+++ b/src/Tutorial.RabbitMQ.Console.RPCServer/Tutorial.RabbitMQ.Console.RPCServer.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
diff --git a/src/Tutorial.RabbitMQ.Console.ReceiveLogsTopic/ReceiveLogsTopic.cs b/src/Tutorial.RabbitMQ.Console.ReceiveLogsTopic/ReceiveLogsTopic.cs
new file mode 100644
index 0000000..0b12e2f
--- /dev/null
+++ b/src/Tutorial.RabbitMQ.Console.ReceiveLogsTopic/ReceiveLogsTopic.cs
@@ -0,0 +1,64 @@
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using System;
+using System.Text;
+
+namespace Tutorial.RabbitMQ.Console.ReceiveLogsTopic
+{
+ class ReceiveLogsTopic
+ {
+ static void Main(string[] args)
+ {
+ var factory = new ConnectionFactory() { HostName = "localhost" };
+ var exchangeName = "topic_logs";
+
+ using (var connection = factory.CreateConnection())
+ using (var channel = connection.CreateModel())
+ {
+ channel.ExchangeDeclare(exchange: exchangeName,
+ type: ExchangeType.Topic);
+
+ var queueName = channel.QueueDeclare().QueueName;
+
+ if (args.Length < 1)
+ {
+ System.Console.Error.WriteLine($"{DateTime.Now}: Usage: {Environment.GetCommandLineArgs()[0]} [info] [warning] [error].");
+
+ System.Console.WriteLine($"{DateTime.Now}: Press [enter] to exit.");
+ System.Console.ReadLine();
+
+ Environment.ExitCode = 1;
+ return;
+ }
+
+ foreach (var bindingKey in args)
+ {
+ channel.QueueBind(queue: queueName,
+ exchange: exchangeName,
+ routingKey: bindingKey);
+ }
+
+ System.Console.WriteLine($"{DateTime.Now}: Waiting for messages.");
+
+ var consumer = new EventingBasicConsumer(channel);
+ consumer.Received += Consumer_Received;
+
+ channel.BasicConsume(queue: queueName,
+ autoAck: true,
+ consumer: consumer);
+
+ System.Console.WriteLine($"{DateTime.Now}: Press [enter] to exit.");
+ System.Console.ReadLine();
+ }
+ }
+
+ private static void Consumer_Received(object sender, BasicDeliverEventArgs e)
+ {
+ var body = e.Body.ToArray();
+ var message = Encoding.UTF8.GetString(body);
+ var routingKey = e.RoutingKey;
+
+ System.Console.WriteLine($"{DateTime.Now}: Received '{routingKey}':'{message}'");
+ }
+ }
+}
diff --git a/src/Tutorial.RabbitMQ.Console.ReceiveLogsTopic/Tutorial.RabbitMQ.Console.ReceiveLogsTopic.csproj b/src/Tutorial.RabbitMQ.Console.ReceiveLogsTopic/Tutorial.RabbitMQ.Console.ReceiveLogsTopic.csproj
new file mode 100644
index 0000000..6c67a5e
--- /dev/null
+++ b/src/Tutorial.RabbitMQ.Console.ReceiveLogsTopic/Tutorial.RabbitMQ.Console.ReceiveLogsTopic.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
diff --git a/src/Tutorial.RabbitMQ.sln b/src/Tutorial.RabbitMQ.sln
index 2eb5c18..6912a84 100644
--- a/src/Tutorial.RabbitMQ.sln
+++ b/src/Tutorial.RabbitMQ.sln
@@ -27,6 +27,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tutorial.RabbitMQ.Console.E
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tutorial.RabbitMQ.Console.ReceiveLogsDirect", "Tutorial.RabbitMQ.Console.ReceiveLogsDirect\Tutorial.RabbitMQ.Console.ReceiveLogsDirect.csproj", "{9F3ECD8E-BEB4-459D-89BD-0705E87FED73}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5. Tutorial 5 - Topics", "5. Tutorial 5 - Topics", "{E13BE1D4-CB15-4A45-8097-397D9E552000}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tutorial.RabbitMQ.Console.EmitLogTopic", "Tutorial.RabbitMQ.Console.EmitLogTopic\Tutorial.RabbitMQ.Console.EmitLogTopic.csproj", "{09AB929F-73B6-4024-B911-AF2F03A1837B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tutorial.RabbitMQ.Console.ReceiveLogsTopic", "Tutorial.RabbitMQ.Console.ReceiveLogsTopic\Tutorial.RabbitMQ.Console.ReceiveLogsTopic.csproj", "{6C0F7FED-3833-430C-9CB1-1E635030A48B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "6. Tutorial 6 - RPC", "6. Tutorial 6 - RPC", "{97523ADE-31E1-4C9D-B7FD-BC9C6C1C2DF9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tutorial.RabbitMQ.Console.RPCServer", "Tutorial.RabbitMQ.Console.RPCServer\Tutorial.RabbitMQ.Console.RPCServer.csproj", "{B1D95063-4309-425E-9134-A2C3C4B019F8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tutorial.RabbitMQ.Console.RPCClient", "Tutorial.RabbitMQ.Console.RPCClient\Tutorial.RabbitMQ.Console.RPCClient.csproj", "{0E2ACEEA-FF23-4BD6-9410-BC3961D83EDD}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -65,6 +77,22 @@ Global
{9F3ECD8E-BEB4-459D-89BD-0705E87FED73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F3ECD8E-BEB4-459D-89BD-0705E87FED73}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F3ECD8E-BEB4-459D-89BD-0705E87FED73}.Release|Any CPU.Build.0 = Release|Any CPU
+ {09AB929F-73B6-4024-B911-AF2F03A1837B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {09AB929F-73B6-4024-B911-AF2F03A1837B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {09AB929F-73B6-4024-B911-AF2F03A1837B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {09AB929F-73B6-4024-B911-AF2F03A1837B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6C0F7FED-3833-430C-9CB1-1E635030A48B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6C0F7FED-3833-430C-9CB1-1E635030A48B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6C0F7FED-3833-430C-9CB1-1E635030A48B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6C0F7FED-3833-430C-9CB1-1E635030A48B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B1D95063-4309-425E-9134-A2C3C4B019F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B1D95063-4309-425E-9134-A2C3C4B019F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B1D95063-4309-425E-9134-A2C3C4B019F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B1D95063-4309-425E-9134-A2C3C4B019F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0E2ACEEA-FF23-4BD6-9410-BC3961D83EDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E2ACEEA-FF23-4BD6-9410-BC3961D83EDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E2ACEEA-FF23-4BD6-9410-BC3961D83EDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E2ACEEA-FF23-4BD6-9410-BC3961D83EDD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -78,6 +106,10 @@ Global
{BB96A1AD-69F5-4D7A-B305-4E5D7E87A2BF} = {ECFD14A2-DC0A-4F2E-A12B-5F9C2D57CE11}
{36029E5A-A2EF-445F-ABB0-5BCBD25E44FD} = {C487ED4B-21F0-4969-86BB-863B088BD7B0}
{9F3ECD8E-BEB4-459D-89BD-0705E87FED73} = {C487ED4B-21F0-4969-86BB-863B088BD7B0}
+ {09AB929F-73B6-4024-B911-AF2F03A1837B} = {E13BE1D4-CB15-4A45-8097-397D9E552000}
+ {6C0F7FED-3833-430C-9CB1-1E635030A48B} = {E13BE1D4-CB15-4A45-8097-397D9E552000}
+ {B1D95063-4309-425E-9134-A2C3C4B019F8} = {97523ADE-31E1-4C9D-B7FD-BC9C6C1C2DF9}
+ {0E2ACEEA-FF23-4BD6-9410-BC3961D83EDD} = {97523ADE-31E1-4C9D-B7FD-BC9C6C1C2DF9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F38BDFBB-F9A8-4372-973D-39FB03E21EA4}