Table of Contents

FlowMessage

The FlowMessage is the primary data structure for passing information between modules in a flow. It's a dynamic object that allows you to set and retrieve properties at runtime without needing to define a fixed schema.

Key Concepts

  • Dynamic Structure: FlowMessages can hold any property structure, making them flexible for various data scenarios
  • Type Safety: While dynamic, FlowMessage provides type-safe methods for getting and setting values
  • Immutable Properties: Internally uses immutable dictionaries for thread-safe operations
  • JSON Serialization: Seamlessly converts to and from JSON

A FlowMessage is what you receive in the MessageReceived method on your module, and what you pass when calling the Next method.

Creating FlowMessages

There are several ways to create and populate a FlowMessage.

Using Index Operator

You can use the index operator to set properties:

var message = new FlowMessage();
message["name"] = "Steven";
message["age"] = 42;
message["isActive"] = true;

Using Type-Safe Set Method

For better type safety and control, use the Set<T> method:

var message = new FlowMessage();
message.Set("name", "Steven");
message.Set("age", 42);
message.Set("isActive", true);

Using dynamic Syntax

You can also create a FlowMessage as a dynamic and add properties using dot notation:

dynamic message = new FlowMessage();
message.name = "Steven";
message.age = 42;
message.isActive = true;

Working with Nested Properties

FlowMessage supports hierarchical data structures using dot notation. This allows you to work with complex, nested objects efficiently.

Reading Nested Properties

When you receive a message with nested structure like this:

{
    "data": {
        "sensor": {
            "id": "abc123",
            "celsius": 34.5
        }
    }
}

You can access nested values directly using dot notation:

// Using Get<T> method (recommended)
var celsius = message.Get<double>("data.sensor.celsius");
// Using GetValue<T> method (alternative)
var sensorId = message.GetValue<string>("data.sensor.id");
// Using index operator (less type-safe)
var id = (string) message["data.sensor.id"];

Writing Nested Properties

You can set nested properties, and FlowMessage will automatically create the intermediate objects:

// Set a new property in the nested structure
message.Set("data.sensor.fahrenheit", 94.1);

After these operations, your message structure will be:

{
    "data": {
        "sensor": {
            "id": "abc123",
            "celsius": 34.5,
            "fahrenheit": 94.1        }
    }
}

Working with Arrays

FlowMessage also supports array indexing in paths:

// Set array elements
message.Set("sensors[0].temperature", 23.5);
message.Set("sensors[1].temperature", 24.2);

// Get array elements
var temp = message.Get<double>("sensors[0].temperature");

Checking for Properties

Before accessing properties, it's often useful to check if they exist. FlowMessage provides the Has method for this purpose.

Basic Property Check

Check if a property exists at any level:

// Check if a simple property exists
if (message.Has("age"))
{
    Console.WriteLine("The message has an 'age' property");
}

// Check nested properties
if (message.Has("data.sensor.temperature"))
{
    var temp = message.Get<double>("data.sensor.temperature");
}

Type-Safe Property Check

You can also verify that a property exists and has a specific type using Has<T>:

// Check if 'age' exists and is an integer
if (message.Has<int>("age"))
{
    var age = message.Get<int>("age");
    Console.WriteLine($"Age is: {age}");
}

// Check if a nested property exists with the correct type
if (message.Has<string>("data.sensor.id"))
{
    var sensorId = message.Get<string>("data.sensor.id");
}
Tip

Using Has<T> is safer than Has alone because it ensures the property can be converted to the expected type before you attempt to retrieve it.

Removing Properties

You can remove properties from a FlowMessage at any level:

// Remove a top-level property
message.Remove("age");

// Remove a nested property
message.Remove("data.sensor.humidity");

// The Remove method returns the FlowMessage for method chaining
message.Remove("prop1").Remove("prop2").Set("prop3", "value");

Cloning FlowMessages

When you need an independent copy of a FlowMessage, use the Clone method:

var original = new FlowMessage();
original.Set("name", "John");
original.Set("age", 30);

// Create a deep copy
var copy = original.Clone();

// Modifying the copy doesn't affect the original
copy.Set("age", 31);

Console.WriteLine(original.Get<int>("age")); // Still 30
Console.WriteLine(copy.Get<int>("age"));     // Now 31

Working with JSON

Parsing JSON into FlowMessage

// Parse JSON string
var json = "{\"name\":\"Alice\",\"age\":25}";
var message = FlowMessage.Parse(json);

// Safe parsing with TryParse
if (FlowMessage.TryParse(json, out IFlowMessage result))
{
    Console.WriteLine("Successfully parsed");
}

// Parse JSON arrays or complex structures
var arrayJson = "[{\"id\":1},{\"id\":2}]";
var message2 = FlowMessage.Parse("items", arrayJson);

Converting FlowMessage to JSON

var message = new FlowMessage();
message.Set("name", "Bob");
message.Set("age", 35);

// Convert to JSON string
string json = message.ToJSON();
// or simply
string json2 = message.ToString();

Converting Objects to FlowMessage

You can convert any object to a FlowMessage:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var person = new Person { Name = "Charlie", Age = 40 };

// Convert to FlowMessage with extenstion method
var message = FlowMessageExtensions.ToFlowMessage(person);
// Or just assigning the person object to a property
message.Set("person", person);
// Now you can access properties dynamically
var name = message.Get<string>("Name");
var age = message.Get<int>("Age");
var personFromMessage = message.Get<Person>("person");

Using Templates

FlowMessage supports template syntax for dynamic value substitution:

var message = new FlowMessage();
message.Set("firstName", "John");
message.Set("lastName", "Doe");

// Use template to combine values
var result = message.GetByTemplate("Hello {firstName} {lastName}!");
// Result: "Hello John Doe!"

// Templates work with nested properties too
message.Set("user.name", "Alice");
var greeting = message.GetByTemplate("Welcome {user.name}");

Error Handling

FlowMessage provides built-in properties for tracking success/error states:

// Mark a message as successful
message.SetSuccess();

// Mark a message as failed with an error message
message.SetError("Connection timeout");

// Check the status
if (message.IsError)
{
    Console.WriteLine($"Error: {message.ErrorMessage}");
}

if (message.IsSuccess)
{
    Console.WriteLine("Operation completed successfully");
}

Best Practices

  1. Use type-safe methods: Prefer Get<T>() and Set<T>() over dynamic access for better type safety
  2. Check before accessing: Always use Has() or Has<T>() before retrieving values to avoid exceptions
  3. Clone when needed: If you need to modify a message while preserving the original, use Clone()
  4. Use templates wisely: Templates are powerful but add processing overhead—use them when dynamic substitution is necessary
  5. Handle nested structures: Take advantage of dot notation for cleaner code when working with complex objects

Common Patterns

Safely Getting a Value with Default

// Get a value or return a default if it doesn't exist
var age = message.Has<int>("age") 
    ? message.Get<int>("age") 
    : 0;

Transforming Data Between Modules

protected override void MessageReceived(IFlowMessage message)
{
    // Read input
    if (message.Has<double>("celsius"))
    {
        var celsius = message.Get<double>("celsius");
        
        // Transform
        var fahrenheit = (celsius * 9 / 5) + 32;
        
        // Write output
        message.Set("fahrenheit", fahrenheit);
        
        // Pass to next module
        this.Next(message);
    }
}

Validating Required Properties

private bool ValidateMessage(IFlowMessage message)
{
    var requiredProperties = new[] { "id", "timestamp", "value" };
    
    foreach (var prop in requiredProperties)
    {
        if (!message.Has(prop))
        {
            message.SetError($"Missing required property: {prop}");
            return false;
        }
    }
    
    return true;
}

Next Steps

>> Module Lifetime