Search Results for

    Show / Hide Table of Contents

    In this guide you will learn all basics that are necessary to install and use Agents.Net. Additionally while going through the guide you will learn about the basic concepts used in Agents.Net like agents, messages and so on.

    Contents

    • Concept
    • Installation
    • Scenario
    • Implementation
      • Preparation
      • Implementing the community
        • Creating messages
        • Creating Agents
      • Gluing it all together
      • Execute the program
    • Further Resources

    Concept

    Agents.Net Concept

    The basic idea of the framework is this. Each agent does one thing (connects to a database, reads console input, verifies some values, ...). For that it needs specific information (location of the database, the raw console input, ...). Additionally it provides all the information it knows (the active database connection, single console arguments and their values, ...). All information are handled in for of messages. The agent is not concerned where the information comes from or who needs the information provided. Based on that idea alone the system will organize all agents automatically simply based on who needs a specific information which was provided.

    Installation

    Install Agents.Net

    To use the current release simply add it via NuGet:

    dotnet add package Agents.Net
    

    To use to latest version from master use the latest NuGet package from github:

    1. Authenticating to github packages for this repository
    2. Add package via NuGet
    nuget install Agents.Net -prerelease
    

    Scenario

    Hello World Community

    The image above shows the scenario that we are about to implement in this guide. The illustration of the agent community was generated with the Agents.Net.Designer which is currently still in its early alpha state. In this guide we will not use the designer but implement the community ourselves.

    The goal of the community is to print "Hello World" to the console. But it will not do it directly. Rather at the start there are two agents (HelloAgent and WorldAgent), which provide the two words for the console. The ConsoleMessageJoiner combines both words to the sentence "Hello World" and finally the ConsoleMessageDisplayAgent will print the message to the console and terminate the program.

    Implementation

    Implemenetation

    In this chapter we will implement the above defined scenario.

    Preparation

    1. Create a new .NET Core Console application named "HelloWorldApp"
    2. In the directory of the HelloWorldApp.csproj execute the command
    dotnet add package Agents.Net
    
    1. Open the project in your favorite IDE

    Implementing the community

    Implementing HelloAgent

    Creating messages

    Hello World Messages Create the message classes HelloConsoleMessage, WorldConsoleMessage and ConsoleMessageCreated with the following content

    public class <MessageName>: Message
    {
        public <MessageName>(string message, Message predecessorMessage)
    		: base(predecessorMessage)
        {
            Message = message;
        }
    
        public <MessageName>(string message, IEnumerable<Message> predecessorMessages)
    		: base(predecessorMessages)
        {
            Message = message;
        }
    
        public string Message { get; }
    
        protected override string DataToString()
        {
            return $"{nameof(Message)}: {Message}";
        }
    }
    
    What are messages?

    Messages are the communication/data objects that are passed between agents. Agents never interact directly with each other. All messages - except the InitializeMessage - have one or more predecessor messages. These are the messages that led to the current instance. More on that later in an example. Each message can contain additional information beside their semantical one - meaning the information that is transfered by their type. In this example the messages have the Message property as an additional information. In the DataToString method the additional information are stringyfied. This is useful to have better logs - more on that later too.

    Creating Agents

    Hello World Agents

    1. Create the class HelloAgent with the following content

    [Consumes(typeof(InitializeMessage))]
    [Produces(typeof(HelloConsoleMessage))]
    public class HelloAgent : Agent
    {
        public HelloAgent(IMessageBoard messageBoard) : base(messageBoard)
        {
        }
    
        protected override void ExecuteCore(Message messageData)
        {
            OnMessage(new HelloConsoleMessage("Hello", messageData));
        }
    }
    
    What are agents?

    Agents.Net Logo

    Agents are the acting objects in the system. All logic is contained within agents and only agents should execute any logic. They need specific information in form of messages. In this example the agent needs the InitializeMessage as a trigger to execute its code. Additionally agents produce messages about the logic that was executed, containing the data that was generated. In this case the agent produces a HelloConsoleMessage with the data "Hello".

    2. Create the class WorldAgent with the following content

    [Consumes(typeof(InitializeMessage))]
    [Produces(typeof(WorldConsoleMessage))]
    public class WorldAgent : Agent
    {
        public WorldAgent(IMessageBoard messageBoard) : base(messageBoard)
        {
        }
    
        protected override void ExecuteCore(Message messageData)
        {
            OnMessage(new WorldConsoleMessage("World", messageData));
        }
    }
    
    What is implicit parallel execution?

    The agent framework allows for implicit parallel execution. In case of this scenario, the HelloAgent as well as the WorldAgent will react on the InitializeMessage that is send, when the message board starts. That means both agents can do their work parallel, although no code explicitly specifies that they can. The message board is designed in a way that these accidental potentials for parallel execution are used - meaning both agents will run parallel.

    3. Create the class ConsoleMessageJoiner with the following content

    [Consumes(typeof(WorldConsoleMessage))]
    [Consumes(typeof(HelloConsoleMessage))]
    [Produces(typeof(ConsoleMessageCreated))]
    public class ConsoleMessageJoiner : Agent
    {
        private readonly MessageCollector<HelloConsoleMessage, WorldConsoleMessage> collector;
    
        public ConsoleMessageJoiner(IMessageBoard messageBoard) : base(messageBoard)
        {
            collector = new MessageCollector<HelloConsoleMessage, WorldConsoleMessage>(OnMessagesCollected);
        }
    
        private void OnMessagesCollected(MessageCollection<HelloConsoleMessage, WorldConsoleMessage> set)
        {
            OnMessage(new ConsoleMessageCreated($"{set.Message1.Message} {set.Message2.Message}", set));
        }
    
        protected override void ExecuteCore(Message messageData)
        {
            collector.Push(messageData);
        }
    }
    
    How to safely consume more than one message?

    The class above does consume two messages HelloConsoleMessage and WorldConsoleMessage. It is impossible to tell when an agent receives a specific message. It is possible to publish 1.000 messages at once. But the order in which they are executed is completely dependent on the whims of the ThreadPool. Additionally, it is possible that any agent object executes multiple messages parallel. Because of that it is important to be extra careful, when storing something as a state of an agent. Agents.Net comes with two helper classes for this case: MessageCollector and MessageAggregator. In this scenario we will only use the MessageCollector to safely collect the two consumed messages. Only when both messages were received, the passed action will be executed.

    In this example we see the definition for the predecessor messages. In the first two agents above there was only one predecessor message - InitializeMessage. Now with the collector, the message has two predecessor message HelloConsoleMessage and WorldConsoleMessage. The predecessors are important, because the message domain is derived from the predecessors. Message domains are not scope of this guide.

    4. Create the class ConsoleMessageDisplayAgent with the following content

    [Consumes(typeof(ConsoleMessageCreated))]
    public class ConsoleMessageDisplayAgent : Agent
    {
        private readonly Action terminateAction;
    
        public ConsoleMessageDisplayAgent(IMessageBoard messageBoard, Action terminateAction) : base(messageBoard)
        {
            this.terminateAction = terminateAction;
        }
    
        protected override void ExecuteCore(Message messageData)
        {
            Console.WriteLine(messageData.Get<ConsoleMessageCreated>().Message);
            terminateAction();
        }
    }
    

    This agent prints the message that was generated by the ConsoleMessageJoiner to the actual console and afterwards terminates the program by executing the terminateAction. The terminateAction will later be passed to the agent.

    Gluing it all together

    Here we will look at all the classes that are necessary to glue together all classes we created previously. For that we will not use a DI framework, as it is easier to show what we need to setup with this. Additionally we will configure Serilog to log the execution as this is important in order to "debug" Agents.Net.

    1. Add Serilog to the project

    dotnet add package Serilog
    dotnet add package Serilog.Sinks.Async
    dotnet add package Serilog.Sinks.File
    dotnet add package Serilog.Formatting.Compact
    

    2. Configure Serilog using the following code in the program's main method

    //Setup logging
    File.Delete("log.json");
    Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Verbose()
                .WriteTo.Async(l => l.File(new CompactJsonFormatter(), "log.json"))
                .CreateLogger();
    

    This will ensure that all Agents.Net message are logged - by setting the minimum level to verbose. Additionally this ensures that the impact on the execution time is minimal.

    3. Setup the agent community using the following code in the program's main method

    //Setup community
    using ManualResetEvent finishedEvent = new(false);
    IMessageBoard messageBoard = new MessageBoard();
    messageBoard.Register(new HelloAgent(messageBoard),
                          new WorldAgent(messageBoard),
                          new ConsoleMessageJoiner(messageBoard),
                          new ConsoleMessageDisplayAgent(messageBoard,
                                                         () => finishedEvent.Set()));
    

    4. Write start code in the program's main method

    //Run
    messageBoard.Start();
    

    Execute the program

    Executing Hello World

    Look into the execution log

    Here is the execution log that was generated for the execution of the implemented HelloWorldApp:

    {"@t":"2020-12-24T17:52:51.8534304Z","@mt":"{@log}","@l":"Verbose","log":{"Agent":"WorldAgent","Type":"Executing","AgentId":"25ca3340-69c0-44b5-a538-0d97299499ec","Message":{"Name":"InitializeMessage","Id":"c163f51a-c19b-42a3-aede-585186324d32","Predecessors":[],"Domain":"3cf6acc6-a852-4c2e-a688-b247a94987eb","Data":"","Child":null,"$type":"MessageLog"},"$type":"AgentLog"}}
    {"@t":"2020-12-24T17:52:51.8534661Z","@mt":"{@log}","@l":"Verbose","log":{"Agent":"HelloAgent","Type":"Executing","AgentId":"c531c6be-d4e0-421c-9bbb-6d64a5f5d7b6","Message":{"Name":"InitializeMessage","Id":"c163f51a-c19b-42a3-aede-585186324d32","Predecessors":[],"Domain":"3cf6acc6-a852-4c2e-a688-b247a94987eb","Data":"","Child":null,"$type":"MessageLog"},"$type":"AgentLog"}}
    {"@t":"2020-12-24T17:52:51.8586857Z","@mt":"{@log}","@l":"Verbose","log":{"Agent":"WorldAgent","Type":"Publishing","AgentId":"25ca3340-69c0-44b5-a538-0d97299499ec","Message":{"Name":"WorldConsoleMessage","Id":"ca73e3bf-c380-4753-b7a8-987fca8ab56d","Predecessors":["c163f51a-c19b-42a3-aede-585186324d32"],"Domain":"3cf6acc6-a852-4c2e-a688-b247a94987eb","Data":"Message: World","Child":null,"$type":"MessageLog"},"$type":"AgentLog"}}
    {"@t":"2020-12-24T17:52:51.8587656Z","@mt":"{@log}","@l":"Verbose","log":{"Agent":"HelloAgent","Type":"Publishing","AgentId":"c531c6be-d4e0-421c-9bbb-6d64a5f5d7b6","Message":{"Name":"HelloConsoleMessage","Id":"78d499a5-c1fc-4ab2-9eec-b882617e1994","Predecessors":["c163f51a-c19b-42a3-aede-585186324d32"],"Domain":"3cf6acc6-a852-4c2e-a688-b247a94987eb","Data":"Message: Hello","Child":null,"$type":"MessageLog"},"$type":"AgentLog"}}
    {"@t":"2020-12-24T17:52:51.8589578Z","@mt":"{@log}","@l":"Verbose","log":{"Agent":"ConsoleMessageJoiner","Type":"Executing","AgentId":"961faab4-f394-4f70-83d2-79d5117e160d","Message":{"Name":"WorldConsoleMessage","Id":"ca73e3bf-c380-4753-b7a8-987fca8ab56d","Predecessors":["c163f51a-c19b-42a3-aede-585186324d32"],"Domain":"3cf6acc6-a852-4c2e-a688-b247a94987eb","Data":"Message: World","Child":null,"$type":"MessageLog"},"$type":"AgentLog"}}
    {"@t":"2020-12-24T17:52:51.8589626Z","@mt":"{@log}","@l":"Verbose","log":{"Agent":"ConsoleMessageJoiner","Type":"Executing","AgentId":"961faab4-f394-4f70-83d2-79d5117e160d","Message":{"Name":"HelloConsoleMessage","Id":"78d499a5-c1fc-4ab2-9eec-b882617e1994","Predecessors":["c163f51a-c19b-42a3-aede-585186324d32"],"Domain":"3cf6acc6-a852-4c2e-a688-b247a94987eb","Data":"Message: Hello","Child":null,"$type":"MessageLog"},"$type":"AgentLog"}}
    {"@t":"2020-12-24T17:52:51.8657758Z","@mt":"{@log}","@l":"Verbose","log":{"Agent":"ConsoleMessageJoiner","Type":"Publishing","AgentId":"961faab4-f394-4f70-83d2-79d5117e160d","Message":{"Name":"ConsoleMessageCreated","Id":"ec45ddf5-2baf-4062-8e22-78f3c498fc05","Predecessors":["78d499a5-c1fc-4ab2-9eec-b882617e1994","ca73e3bf-c380-4753-b7a8-987fca8ab56d"],"Domain":"3cf6acc6-a852-4c2e-a688-b247a94987eb","Data":"Message: Hello World","Child":null,"$type":"MessageLog"},"$type":"AgentLog"}}
    {"@t":"2020-12-24T17:52:51.8659039Z","@mt":"{@log}","@l":"Verbose","log":{"Agent":"ConsoleMessageDisplayAgent","Type":"Executing","AgentId":"67adf5b1-d286-4e3f-9069-07264fafac32","Message":{"Name":"ConsoleMessageCreated","Id":"ec45ddf5-2baf-4062-8e22-78f3c498fc05","Predecessors":["78d499a5-c1fc-4ab2-9eec-b882617e1994","ca73e3bf-c380-4753-b7a8-987fca8ab56d"],"Domain":"3cf6acc6-a852-4c2e-a688-b247a94987eb","Data":"Message: Hello World","Child":null,"$type":"MessageLog"},"$type":"AgentLog"}}

    This view is optimized for speed as even a short "Hello World" will produce quite a bit of logging. To make is easier to read the Agents.Net.LogViewer was created. The log viewer is still in its early alpha state. Still it is possible to use. Here is what the log would look like in the log viewer:

    Further Resources

    • read the API Documentation
    • take a look at the BDD styled showcase tests
    • ask a question in a new discussion
    • contact us via email
    • try and learn ;-)
    • Improve this Doc
    In This Article
    Back to top © Copyright Tobias Wilker and contributors