Welcome to Real Life

Dans ce premier article consacré à la découverte de RabbitMQ, nous avons vu comment envoyer un message dans une file d’attente nommée et le consommer.
Base

Démonstration

Passons à quelque chose d’un peu plus complexe.
Partons du principe que, pour des raisons professionelles, vous ayez recourt à RabbitMQ pour générer des apk à partir de dossiers présent sur votre pc.

Nous allons reprendre notre architecture et la modifier pour qu’elle prenne en paramètre le dossier, l’envoi dans la file d’attente, traite le fichier puis retourne une réponse contenant l’apk généré.
Concrètement, elle fonctionnera de cette manière:

RPC

1. Envoi

Comme précédemment, nous générons 2 projets dans un dossier APK-Generator:

```
    dotnet new console --name Sender
    dotnet new console --name Worker
```

Nous renommons les fichiers Program.cs en conséquence de leur fonction pour obtenir un Sender.cs et un Worker.cs Ensuite, sur la base de notre précédent Send.cs, notre Sender.cs ressemblera à ceci:

```CSharp
using System.IO.Compression;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

class Program
{
    static void Main(string[] args)
    {
        // Chemin vers le dossier 
        var path = args[0];
        // Le script yarn à utiliser
        var script = args[1];

        // ...
    }
}
```

Comme dit dans le premier article sur le sujet, RabbitMQ n’accepte que les tableaux d’octets.
Hors, contrairement à un dossier, un fichier est un tableau d’octet.
Donc, nous allons transformer notre dossier en le compressant dans un fichier .zip, puis l’envoyer dans notre Queue.

    ```CSharp
        // Chemin et nom de fichier .zip à créer
        string zipPath = $"{path}.zip";

        // Compression du dossier en .zip
        ZipFile.CreateFromDirectory(path, zipPath);

        // Connexion au serveur RabbitMQ
        var factory = new ConnectionFactory() { HostName = "localhost" };
        using (var connection = factory.CreateConnection())
        using (var channel = connection.CreateModel())
        { 
            /* Compression et envoi */
            /* Attente d'une réponse du worker */
        }
    ```

Définissons les propriétés du message ainsi que son contenu.
Pour commencer, occupons nous d’abord de compresser notre message puis de l’envoyer dans notre Queue:

```CSharp
// Nom de la file d'attente RabbitMQ
string queueName = "apk-to-generate";

// Lit le contenu du fichier .zip dans un tableau d'octets
byte[] fileBytes = File.ReadAllBytes(zipPath);

// Définit les propriétés du message RPC
var props = channel.CreateBasicProperties();
props.CorrelationId = Guid.NewGuid().ToString();
props.ReplyTo = "apk-generated";

// Envoi du message RPC contenant le fichier .zip dans la file d'attente
channel.QueueDeclare(queue: queueName,
                    durable: false,
                    exclusive: false,
                    autoDelete: false,
                    arguments: null);

channel.BasicPublish(exchange: "",
                    routingKey: queueName,
                    basicProperties: props,
                    body: fileBytes);

Console.WriteLine("File sent");
```

Maintenant, faisons en sorte de maintenir notre Sender éveillé, le temps de recevoir une réponse du worker:

```CSharp
// Attente d'une réponse à la demande RPC
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue: "apk-generated",
                    autoAck: true,
                    consumer: consumer);

consumer.Received += (model, ea) =>
{
    byte[] responseBytes = ea.Body.ToArray();
    string responseString = System.Text.Encoding.UTF8.GetString(responseBytes);
    Console.WriteLine("[x] Received {0}", responseString);
};
Console.WriteLine("[x] Awaiting Worker response");

Console.ReadLine();
```

2. Traitement et retour

Concernant le Worker.cs, nous allons partir sur les mêmes bases que tout à l’heure en déclarant une Queue:

```csharp
using System.IO.Compression;
using System.Diagnostics;
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

var factory = new ConnectionFactory() { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

channel.QueueDeclare(queue: "apk-to-generate",
                     durable: true,
                     exclusive: false,
                     autoDelete: false,
                     arguments: null);
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);

// ...
```

Nous ajoutons le consumer, mais cette fois il sera un peu différent.
Tout d’abord, nous allons consommer et découper le message reçu en propriétés et arguments:

```csharp
var consumer = new EventingBasicConsumer(channel);
channel.BasicConsume(queue: "apk-to-generate",
                    autoAck: false,
                    consumer: consumer);
Console.WriteLine(" [x] Awaiting RPC requests");

consumer.Received += (model, ea) =>
{
    string response = string.Empty;
    var body = ea.Body.ToArray();
    var message = Encoding.UTF8.GetString(body);
    

    var props = ea.BasicProperties;
    var replyProps = channel.CreateBasicProperties();
    replyProps.CorrelationId = props.CorrelationId;

    var args = message.Split(' '); // Sépare le message en arguments
    var path = args[0];
    var script = args[1];
    var extractPath = @$"/C/{path}";

    /* Décompression du fichier zip */

    /* Traitement */

    /* Gestion d'erreur et renvoie de l'apk */
};
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
```

Décompressons maintenant le fichier zip reçu:

```CSharp
    // Décompression du fichier zip
    byte[] fileBytes = ea.Body.ToArray();
    File.WriteAllBytes(path, fileBytes);
    ZipFile.ExtractToDirectory(path, extractPath);
```

Il ne nous reste plus qu’à traiter la demande:

```CSharp
    try{
        Console.WriteLine(" [x] Received {0}", message);
        // Exécute la commande yarn install pour installer les dépendances puis "yarn run" pour générer l'APK avec le chemin et le script spécifiés en paramètres
        var apkprocess = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "cmd.exe",
                Arguments = $"/C cd {extractPath} && yarn install && yarn run {script}",
                UseShellExecute = false,
                RedirectStandardOutput = true,
                CreateNoWindow = true
            }
        };
        apkprocess.Start();
        string output = apkprocess.StandardOutput.ReadToEnd();
        apkprocess.WaitForExit();
        Console.WriteLine(output);
    }
```

Ajoutons une gestion d’erreur minimaliste, puis renvoyons l’apk généré à l’envoyeur:

```CSharp
    catch (Exception e)
    {
        Console.WriteLine($" [.] {e.Message}");
        response = string.Empty;
    }
    finally
    {
        // Renvoie du fichier APK généré en réponse
        var apkFile = $"{path}\\app-release.apk";
        if (File.Exists(apkFile))
        {
            var fileB = File.ReadAllBytes(apkFile);
            channel.BasicPublish(exchange: "", routingKey: props.ReplyTo, basicProperties: replyProps, body: fileB);
            channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
            Console.WriteLine(" [x] Sent apk file");
        }
        else
        {
            Console.WriteLine(" [x] Failed to generate apk file");
        }
    }
```

Pour conclure ce sender, son code va consommer la Queue apk-to-generate sur laquelle nous recevons un message contenant 2 paramètres: un chemin d’accès et un script indiquant à yarn lequel utiliser pour entamer le processus de build.
Une fois ce processus terminé, le programme va retourner un fichier apk contenant le résultat de la génération, ou une erreur si le fichier généré n’existe pas ou que l’envoi a échoué.

Conclusion

En conclusion, RabbitMQ est un logiciel de messagerie open source fiable et hautement évolutif qui permet aux applications de communiquer de manière asynchrone et efficace.
En utilisant le protocole AMQP, RabbitMQ fournit une plate-forme robuste pour la distribution de messages entre les applications et les services, avec des fonctionnalités telles que la durabilité des messages, la haute disponibilité, la gestion des files d’attente, la répartition de charge, la gestion des erreurs, etc.
Avec une grande variété de langages de programmation et de bibliothèques client disponibles, RabbitMQ est facile à intégrer dans un large éventail de solutions et d’environnements technologiques.
En outre, il dispose d’une communauté active qui fournit un support et des ressources utiles pour les développeurs.
Dans l’ensemble, RabbitMQ est un choix judicieux pour les applications nécessitant une communication asynchrone et une distribution de messages fiable, notamment dans les architectures distribuées et les systèmes orientés services.

Dans cette introduction nous avons vu comment utiliser RabbitMQ pour de la messagerie textuelle, et du traitement de donnée.
Nous avons abordés les sujets de compression, de traitement de données, de persistance des messages, et de retour serveur par message.
Plusieurs concepts ont été volontairement survolés ou oubliés, afin de ne pas rendre cette découverte trop longue ni trop complexe.
Vous pourrez retrouver d’autres concepts sur le site de RabbitMQ

Pour finir voici une liste de liens externes pour aller plus loin :

  • L’intégralité du code associé à cet article sur notre Github ;
  • La formation de base à RabbitMQ dans la Documentation Officielle;
  • Le point d’entrée vers la documentation complète de RabbitMQ

Si cet article est un peu trop complexe pour vous, n’hésitez pas à reprendre celui-ci, consacré à la prise en main du concept de RabbitMQ.