Click here to Skip to main content
12,625,402 members (35,278 online)
Click here to Skip to main content
Add your own
alternative version

Stats

8.6K views
237 downloads
21 bookmarked
Posted

A Component and Message-Based Architectural Style C2

, 10 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Description of C2 architectural pattern with sample

Introduction

First of all, this is my first article and I am in a quandary. I wrote this article with the hope to give vision to beginners who wants to expand their horizon in architectural styles. This style is first proposed by a team from the Institute for Software Research at the University of California, Irvine. They have a framework for it too which is implemented with Java. I just want to remind it to old coders and instruct for beginners. I tried to implemented it with C#, they are not exactly the same, but seems enough to make some demonstration.

For more details, please see:

Background

C2 is an asynchronous, event-based architectural style, which promotes reuse, dynamism, and flexibility through limited visibility. Components and connectors have a defined top and bottom that cause them to be arranged in layers. Components are aware of elements that reside above them but not below. Hence, they may send requests, events that travel up an architecture, with an expectation that they will be fulfilled by some set of components above. Components may also send out notifications, messages that travel down an architecture, without any expectation of whether they will be handled.

As I mentioned, a component within this architecture has limited visibility or “substrate independence”; components are assembled in a layered manner, and a component is entirely unknowledgeable of components that exist “beneath” it. This independence has obvious possibilities for promoting the interchangeability and reuse of components across architectures.

Components request services from components “above“ it via message passing, and are not in possession of knowledge of components “below” it (in this architecture, messages are passed “up”, meaning the interface layer is considered to be closer to the “bottom” and the “application” layer is considered to be closer to the top. Request flow upwards and message or notifications flow downwards). The C2 style is characterized as an association of components linked by communication forwarders known as connectors.

Benefits

The benefits of this architectural style according to the Institute for Software Research include:

Separation of Concerns, the concept that architectural design should be separate from its implementation, arranging and decomposing software into more manageable and understandable pieces; Open Architectures that encourage a modular strategy where there is a clear separation of module design from the implementation mechanisms through which that design materializes.

Scalability, understanding operational constraints and the support of multiple levels of component interface granularity.

Extensibility, the substitution of components sharing the same interface, limiting component interdependence.

Flexibility, the modification of systems architecture by incorporating additional components or reconfiguring existing components prior to or during execution.

Reliability, leveraging existing components that have been carefully designed, implemented, and verified; Cost Reduction, the reduction in the development effort through component reuse and architectural guidance.

Understandability, increasing the comprehensibility of complex systems through the use of high level models.

C2 is well suited for use in a distributed system. As there is no assumption of shared memory or address space, which allows composition of these components in a highly distributed, heterogeneous environment. Components interact via connectors, the components themselves do not need to reside on the same physical machine or network. Services may be employed using a complex chain of mediators and multiple distributed servers, distributing the processes. The C2 style provides for the development of distributed, dynamic applications by focusing on the structured handling of connectors to achieve layer independence.

Using the Code

Java class framework for C2 uses a Vector of Port objects to facilitate communication between instances of Component and Connector, another option could be using an event bases, implicit invocation model. With C#, it would be possible to maintain the integrity of the architecture event model based on the use of multicast delegates . Components could then register its methods by subscribing to the events.

I decided to implement it like they did, seems easier to me, maybe I will change it later.

The Signal class is the base class for other types of messages.

Each message has a name, a type, and parameters. In C2, the type is either Request or Notification. Parameters are named and may be of any type.

The base class for messaging structure is Signal class;

public class Signal 
{
    ...
     protected string NameOfMessage;
     protected Hashtable @params;
     protected string type; 
 
     //And methods of it is like that; 

     public virtual void AddParameter(string name, object value) 
     {
        @params[name] = value;
     }
       
     public virtual object GetParameter(string name)
     {
        return @params[name];
     }

     public virtual void RemoveParameter(string name)
     {
        @params.Remove(name);
     }

     public virtual bool HasParameter(string name)
     {
       return @params.ContainsKey(name);
     } 
} 

And we have two message classes derived from signal class;

Notification; messages that travel down an architecture:

public sealed class Notification : Signal
{
    public Notification()
       : base(TypeNotification)
    {
    }
    public Notification(string name)
       : base(TypeNotification, name) 
    {
    }
}  

Request; messages that travel up an architecture:

public sealed class Request : Signal
{
    public Request()
        : base(TypeRequest)
    {
    }

    public Request(string name)
        : base(TypeRequest, name)
    {
    }

    /// <summary>
    /// Create a reply notification to this Request. The name of the notification
    /// message is the same as this Request. 
    /// </summary>
    public Notification Reply()
    {
        return Reply(MessageName());
    }
    /// <summary>
    /// Create a reply notification to this Request with the given name. </summary>
    public Notification Reply(string name)
    {
        var n = new Notification(name);
        object token = GetParameter("REPLY_TOKEN");
        if (null != token)
        {
            // if this request has a token, copy the token to its reply
            n.AddParameter("REPLY_TOKEN", token);
        }
        return n;
    }
}   

Other classes are Brick and Bus, I derived another class named BrickGUI to support applications with Graphical User Interface. Brick class is base type for components, and Bus class stands for connectors. All these classes implements IBrick interface.

public interface IBrick 
{
   void Send(Signal message);
   void Handle(Signal message);
   void AddTop(IBrick component);
   void Weld(IBrick component);
} 

Implementation of Brick class:

public abstract class Brick : IBrick
{   
    readonly List<IBrick> _bottomBusList;
    readonly List<IBrick> _topBusList;       

    protected Brick()
    {
        _bottomBusList = new List<IBrick>();
        _topBusList = new List<IBrick>();
    }

    protected Brick(string name)
        : this()
    {
        BrickName = name;
    }

    public string BrickName { get; set; }


    public void Weld(IBrick bus)
    {
        if (bus is Bus)
        {
            bus.AddTop(this);
            _bottomBusList.Add(bus);
        }
    }

    public void AddTop(IBrick bus)
    {
        if (bus is Bus)
        {
            _topBusList.Add(bus);
        }
    }

    public void Send(Signal message)
    {
        new Thread((() =>
        {
            if (message is Request)
            {
                _topBusList.ForEach(p => p.Handle(message));
            }
            else if (message is Notification)
            {
                _bottomBusList.ForEach(p => p.Handle(message));
            }
        })).Start();
    }

    public void Handle(Signal message)
    {
        DoEvents(message);
        Send(message);
    }

    public abstract void DoEvents(Signal message);

} 

and Bus class:

public class Bus : IBrick
{

    private readonly List<IBrick> _bottomLinks;
    private readonly List<IBrick> _topLinks;


    public Bus()
    {
        _bottomLinks = new List<IBrick>();
        _topLinks = new List<IBrick>();
    }
    public Bus(string name) : this()
    {
        Name = name;
    }

    public string Name { get; set; }


    public void Weld(IBrick component)
    {
        _bottomLinks.Add(component);
        component.AddTop(this);
    }

    public void AddTop(IBrick component)
    {
        _topLinks.Add(component);
    }

    public void Send(Signal message)
    {
        new Thread((() =>
        {
            if (message is Request)
            {
                _topLinks.ForEach(p => p.Handle(message));
            }
            else if (message is Notification)
            {
                _bottomLinks.ForEach(p => p.Handle(message));
            }
        })).Start();
    }
    public void Handle(Signal message)
    {
        Send(message);
    }

} 

Now let's see it in action.

Simple Application

This is a simple application of C2 architecture, in here we have one clock, two devices, one monitor and two buses. Our goal is synchronizing two devices with the help of clock and monitoring value of Device 2.

Let's first start with Clock:

public class Clock : Brick
{
    public Clock()
    {
        Timer tmr = new Timer {Interval = 1000};
        tmr.Tick += tmr_Tick;
        tmr.Start();
    }
    void tmr_Tick(object sender, EventArgs e)
    {
        Signal sig = new Notification("clock");
        this.Send(sig);
    }
    protected Clock(string name)
        : base(name)
    {
           
    }

    public override void DoEvents(Signal message)
    {
            
    } 
} 

We can leave DoEvents method blank, because Clock does not expect any message. A simple timer is created in ctor of Clock, it pushes Notification message with "clock" tag once in a second. Then Bus1 takes this message and pushes to Device1 & Device2. Let's see their implementations:

public partial class Device1 : BrickForm
{
    public Device1()
    {
        InitializeComponent();
    }
    public Device1(string name)
        : base(name)
    {
        InitializeComponent();
        Text = name;
    }
    public override void DoEvents(Signal message)
    {
        if (message is Notification && message.MessageName().Equals("clock"))
        {
            if (dateTimePicker1.InvokeRequired)
            {
                dateTimePicker1.BeginInvoke(new MethodInvoker(() => 
                {
                    dateTimePicker1.Value = dateTimePicker1.Value.AddSeconds(1);
                }));
            }
            else
            {
                dateTimePicker1.Value = dateTimePicker1.Value.AddSeconds(1);
            }
        }               
    } 
} 

Device1 simply checks incoming signal and increase its value if message is clock notification.

public partial class Device2 : BrickForm
{
    public Device2()
    {
        InitializeComponent();
    }
    public Device2(string name)
        : base(name)
    {
        InitializeComponent();
        Text = name;
    }
    public override void DoEvents(Signal message)
    {
        if (message is Notification && message.MessageName().Equals("clock"))
        {
            if (progressBar1.InvokeRequired)
            {
                progressBar1.BeginInvoke(new MethodInvoker(() =>
                {
                    progressBar1.Value++;
                }));
            }
            else
            {
                progressBar1.Value++;
            }
                
            Signal n = new Notification("device2state");
            n.AddParameter("percentage", progressBar1.Value);
            Send(n);
        }
    } 
} 

Device2 increases its progress percentage with clock signal, then pushes this value with Notification message.

And Monitor is printing grabbed values to screen:

public partial class FormMonitor : BrickForm
{ 
    public override void DoEvents(Signal message)
    {
        if (message is Notification &&
            message.MessageName().Equals("device2state") &&
            message.HasParameter("percentage"))
            {
            if (richTextBox1.InvokeRequired)
            {
                richTextBox1.BeginInvoke(new MethodInvoker(() =>
                    richTextBox1.AppendText("Device 2 state : "+ 
                    message.GetParameter("percentage").ToString() +"% \r\n")));
            }
            else
            {
                richTextBox1.AppendText(message.GetParameter("percentage").ToString());
            }
        }
    }
} 

Last part is binding them together:

private void CreateComponents()
{
    Bus bus1 = new Bus("bus1");
    Bus bus2 = new Bus("bus2");
    var clock = new Clock();
    BrickForm device1 = new Device1("device 1")
    {
        Location = new Point(200, 200)
    };

    BrickForm device2 = new Device2("device 2")
    {
        Location = new Point(300, 200)
    };

    BrickForm monitor = new FormMonitor("monitor")
    {
        Location = new Point(100, 200)
    };

    clock.Weld(bus1);
    bus1.Weld(device1);
    bus1.Weld(device2);
    device2.Weld(bus2);
    bus2.Weld(monitor);

    monitor.Show();
    device1.Show();
    device2.Show();
}  

Result will become something like this, components are aware of elements that reside above them. Closing monitor will not effect workflow of devices or clock.

Little Bit Complicated Sample

I made this sample to visualize how things work under the hood.

In this scenario, FormMain class sends a Request message with name of target and text parameter (remember, bottom is aware of top), message is distributed as far as it can go. If message is delivered, a notification message is sent by target, main prints incoming notification.

I have to create just two components; one FormMain and one Banner class. Later, I will create 5 instances from Banner.

public partial class FormMain : BrickForm
{
    ...

    public override void DoEvents(Signal message)
    {
        if (message.HasParameter("dest") && message.HasParameter("text"))
        {
            AddToLog(String.Format("{0} is set to \"{1}\"", 
            message.GetParameter("dest"), message.GetParameter("text")));
        }
    }

    public void AddToLog(string text)
    {
        lock (richTextBox1)
        {
            richTextBox1.Clear();
            richTextBox1.AppendText(text);
            richTextBox1.AppendText(Environment.NewLine);
        } 
    } 

    private void buttonSendMessage_Click(object sender, EventArgs e)
    {
        Application.OpenForms.OfType<IBrick>().OfType<Form>().ToList().ForEach
            (p => p.BackColor = SystemColors.Control);
        Signal sig = new Request(textBoxTarget.Text);
        sig.AddParameter("text",textBoxMessage.Text);
        this.Send(sig);
    } 
}  
public partial class Banner : BrickForm
{
    #region ctor
 
    public Banner()
    {
        InitializeComponent();
    }
    public Banner(string name)
       : base(name)
    {
        InitializeComponent();
        Text = name;
    } 

    public override void DoEvents(Signal message)
    {
        if (message is Request)
        {
            this.BackColor = Color.Yellow;
            if (message.MessageName() == this.BrickName)
            {
                if (message.HasParameter("text"))
                   textBox1.Text = message.GetParameter("text").ToString();
                this.BackColor = Color.Green;
                Thread.Sleep(1000);
                Signal n = (message as Request).Reply("main");
                n.AddParameter("dest", this.BrickName);
                n.AddParameter("text", message.GetParameter("text"));
                this.Send(n);
             }
             else
             {
                 Thread.Sleep(1000);
                 this.BackColor = SystemColors.Control;
             }
        }
        else
        {
            this.BackColor = Color.Yellow;
            Thread.Sleep(1000);
            this.BackColor = SystemColors.Control;
        }
    }  
} 

I added some Thread.Sleep and some coloring to be able to visualize what's happening during communication.

And last step, creating & welding components:

private void CreateForms()
{
    BrickForm t1 = new Banner("t1")
    {
        Location = new Point(200, 200)
    };
    BrickForm t2 = new Banner("t2")
    {
        Location = new Point(400, 200)
    };
    BrickForm t3 = new Banner("t3")
    {
        Location = new Point(600, 150)
    };
    BrickForm t4 = new Banner("t4")
    {
        Location = new Point(600, 250)
    };
    BrickForm t5 = new Banner("t5")
    {
        Location = new Point(800, 200)
    };
    BrickForm main = new FormMain();
    var bus1 = new Bus("Bus1");
    var bus2 = new Bus("Bus2");
    var bus3 = new Bus("Bus3");
    var bus4 = new Bus("Bus4");
            
    t5.Weld(bus4);
    bus4.Weld(t3);
    bus4.Weld(t4);
    t3.Weld(bus3);
    t4.Weld(bus3);
    bus3.Weld(t2);
    t2.Weld(bus2);
    bus2.Weld(t1);
    t1.Weld(bus1);
    bus1.Weld(main);

    main.Show();
    t1.Show();
    t2.Show();
    t3.Show();
    t4.Show();
    t5.Show();
}   

Request is propagating:

Target is found:

Points of Interest

You can check this article for further thought.

Conclusion

Separation of concerns is an important benefit of architecture. The concept that architectural design should be separate from its implementation, arranging and decomposing software into more manageable and understandable pieces. But keep in mind that each component means extra threads for program and decreases overall performance of system. C2 is well suited for use in a distributed system as there is no assumption of shared memory or address space, which allows composition of these components in a highly distributed, heterogeneous environment.

History

  • 10.05.2014 - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Emre Ataseven
Software Developer (Senior)
Turkey Turkey
Good luck, and happy philosophizing!

You may also be interested in...

Pro
Pro

Comments and Discussions

 
GeneralNice Article Pin
karthy Udhaykumar17-May-14 14:24
memberkarthy Udhaykumar17-May-14 14:24 
GeneralRe: Nice Article Pin
Emre Ataseven18-May-14 4:48
professionalEmre Ataseven18-May-14 4:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.161128.1 | Last Updated 10 May 2014
Article Copyright 2014 by Emre Ataseven
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid