An Agile programming demonstration.
Introduction
By being agile, a software is expected to be able to integrate new elements (list items) and also new types of elements (polymorphism) like plug-ins. These new elements bring new functionalities for the application. Populating the lists and the mobilization of new features can be made either by code or by reading an external configuration file (no recompilation needed).
- Note
- Agile programming is not related to Agile software development methods
Example of agile software architecture
Agile machine structure
A machine can be composed of different parts called MachineElements. Those parts will be, in the example below, a Gripper and a RobotArm but it also could be a Laser, an Iron, etc. Here, the MachineElement abstraction is the key to make extensibility possible.
Machine and MachineElements
The base component ConceptComponent and its macros bring all necessary features to improve programming efficiency and agility. All the business model entities should inherit from it. See Components Declaration.
The machine element is an abstract class and will be implemented like this :
class MachineElement : public ConceptComponent
{
};
The machine class is composed by a polymorphous list of MachineElements (ConceptDynamicList).
class Machine : public ConceptComponent
{
};
The MachineElements list is declared with a getter on one line
and this object is connected to every mechanisms (dynamic invocation, serialization, visitation) through a second line
- Note
- The PROPERTY_OBJECT macro and the ConceptDynamicList enables us to implement the 1 to many composition arrow of the class diagram.
The base software structure is now ready to be enriched with various machine parts.
Gripper
class Gripper : public MachineElement
{
CONCEPT_END
public:
void Open() { System::GetConsole().WriteLine(GetName() + " - Open()"); }
void Close(){ System::GetConsole().WriteLine(GetName() + " - Close()");}
};
The methods Open and Close simulate their behaviours by writing messages in the System::GetConsole().
The property OpeningTimeout is declared with its getter and setter on one ligne
and this property is connected to every mechanisms (initialization, dynamic invocation, serialization, visitation) through a second line
RobotArm
struct Point : public DataStructure
{
};
DataStructure and its macros enable this structure to be connected to every mechanisms (dynamic invocation, serialization, visitation). The serialization of this structure will be useful in our example for the different actions composed of points.
class RobotArm : public MachineElement
{
CONCEPT_END
public:
void MoveTo(Point point)
{
System::GetConsole().WriteLine(GetName()
}
};
The methods MoveTo simulates its behaviour by writing a message in the System::GetConsole().
See complete example files here
If the machine is instantiated the list content will be empty. The machine needs to be defined by populating the list content.
Example machine description
The machine have an empty list of MachineElements. Populating the list is made by code or by reading a configuration file where the objects and their parameters have been serialized.
Let's describe the machine used for this example with an instance diagram:
Machine declaration
Machine machine;
machine.SetName("Machine");
ConceptRegistry::GetInstance().RegisterReferential(machine);
The root object machine needs to be named and to be registered in the ConceptRegistry so that the links (aggregations) can work with this node.
Machine description by code (hardcoded)
Gripper & gripperA = machine.GetMachineElements().AddNew<Gripper>("GripperA");
gripperA.SetOpeningTimeout(
Seconds(2));
RobotArm & armA = machine.GetMachineElements().AddNew<RobotArm>("ArmA");
armA.SetMaxPositionX(45.6f);
armA.SetMaxPositionY(87.2f);
armA.SetMaxPositionZ(28.1f);
Gripper & gripperB = machine.GetMachineElements().AddNew<Gripper>("GripperB");
RobotArm & armB = machine.GetMachineElements().AddNew<RobotArm>("ArmB");
armB.SetMaxPositionX(34.7f);
armB.SetMaxPositionY(88.4f);
armB.SetMaxPositionZ(62.3f);
Machine description loaded from file
ConceptFactoriesDynamic machineFactories;
machineFactories.RegisterFactory(Gripper::GetClassFactory());
machineFactories.RegisterFactory(RobotArm::GetClassFactory());
FileStream machineInfile(filePath, FileModeTextRead);
XmlDataStoreReader xmlReader(machineInfile);
ConceptDataStore reader(xmlReader);
reader.SetFactories(machineFactories);
reader.ReadValue(machine.GetName(), machine);
machine.ResolveLinks();
machineInfile.Close();
In order to deserialize polymorphous content, the ConceptDataStore will use the design pattern Factory. The factory enables us to create an instance of a class. The factories are thus used to populate the polymorphous items of the list in our example.
- Configuration file
- The configuration file to create the machine example is the following:
5 <
Factory>
Example.Gripper</
Factory>
8 <
OpeningTimeout>2
s</
OpeningTimeout>
12 <
Factory>
Example.RobotArm</
Factory>
15 <
MaxPositionX>45.6</
MaxPositionX>
16 <
MaxPositionY>87.2</
MaxPositionY>
17 <
MaxPositionZ>28.1</
MaxPositionZ>
21 <
Factory>
Example.Gripper</
Factory>
24 <
OpeningTimeout>250
ms</
OpeningTimeout>
28 <
Factory>
Example.RobotArm</
Factory>
31 <
MaxPositionX>34.7</
MaxPositionX>
32 <
MaxPositionY>88.4</
MaxPositionY>
33 <
MaxPositionZ>62.3</
MaxPositionZ>
- Note
- The xml file can be edited through a tool ConceptEditor (todo link).
- Note
- Adding a new machine part type is easy. Implement a new class and add one single line such as
machineFactories.RegisterFactory(Gripper::GetClassFactory());
Adapt the configuration file to populate items of the new object type (Laser, Iron, etc.) and load it.
Agile Process structure
The Process is composed of a list of Action. The execution of the Process will execute each Action one after another. It is possible to integrate new type of Action such as ActionDrill, etc. Here, the Action abstraction is the key to make extensibility possible.
Process and Actions
class Action : public ConceptComponent
{
CONCEPT_ABSTRACT_END
public:
virtual void Execute()
{
System::GetConsole().WriteLine("==== > Execute action : " + GetName());
}
};
Each Action can be executed. Here a simulation is made by writing the Action name into console.
class Process : public ConceptComponent
{
CONCEPT_END
public:
void Execute()
{
Int32 count = GetActions().GetCount();
for (
Int32 index = 0; index < count; index++)
{
GetActions()[index].Execute();
}
}
};
The Process is composed of a polymorphous list of Actions. The execution of the Process will execute the Actions one after another.
Process dependencies towards the MachineElements
As the ActionClose and ActionOpen need the gripper to be executed, dependencies are made between these items. The Actions cannot resolve their dependencies through a static relation or similar approach because the Machine composition is not known at the compile time. The machine composition depends on the MachineElements list (which is empty at the start). These dependencies are represented by aggregations in UML. When the Process content is saved and restore, these aggregations must be saved and restored as well.
Actions
- ActionOpen
class ActionOpen : public Action
{
CONCEPT_END
public:
void Execute() override
{
baseClass::Execute();
if (GetGripper().IsValidReference())
{
GetGripper().GetReference()->Open();
}
}
};
- ActionMove
class ActionMove : public Action
{
CONCEPT_END
public:
void Execute() override
{
baseClass::Execute();
if (GetArm().IsValidReference())
{
GetArm().GetReference()->MoveTo(GetPoint());
}
}
};
- ActionPickPlace
class ActionPickPlace : public Action
{
CONCEPT_BEGIN(ActionPickPlace, "Example.ActionPickPlace", Action)
CONCEPT_END
public:
void Execute() override
{
baseClass::Execute();
if (!GetGripper().IsValidReference() || !GetArm().IsValidReference())
{
System::GetConsole().WriteLine("Bad dependencies");
return;
}
GetGripper().GetReference()->Open();
GetArm().GetReference()->MoveTo(GetPointPick());
GetGripper().GetReference()->Close();
GetArm().GetReference()->MoveTo(GetPointPlace());
GetGripper().GetReference()->Open();
}
};
See complete example files here
Process description and configuration
The Process often changes, depending on the production. It's not possible to hardcode the Process content (Variants) since it is the end user who describes what the machine does. The Process content (objects, parameters and dependencies) should be saved and restored from a file on the hard disk.
Process declaration
Process process;
process.SetName("Process");
Process description by code (hardcoded)
Gripper & gripperUsed = *gripper;
RobotArm & armUsed = *arm;
ActionOpen & action1 = process.GetActions().AddNew<ActionOpen>("Action1");
action1.GetGripper().SetReference(gripperUsed);
ActionPickPlace & action2 = process.GetActions().AddNew<ActionPickPlace>("Action2");
action2.GetGripper().SetReference(gripperUsed);
action2.GetArm().SetReference(armUsed);
action2.GetPointPick().x = 10;
action2.GetPointPick().y = 20;
action2.GetPointPick().z = 15;
action2.GetPointPlace().x = 30;
action2.GetPointPlace().y = -20;
action2.GetPointPlace().z = 0;
ActionMove & action3 = process.GetActions().AddNew<ActionMove>("Action3");
action3.GetArm().SetReference(armUsed);
action3.GetPoint().x = 0;
action3.GetPoint().y = 0;
action3.GetPoint().z = 0;
Process description loaded from file
ConceptFactoriesDynamic processFactories;
processFactories.RegisterFactory(ActionOpen::GetClassFactory());
processFactories.RegisterFactory(ActionPickPlace::GetClassFactory());
processFactories.RegisterFactory(ActionMove::GetClassFactory());
FileStream processInfile(filePath, FileModeTextRead);
XmlDataStoreReader xmlReader(processInfile);
ConceptDataStore reader(xmlReader);
reader.SetFactories(processFactories);
reader.ReadValue(process.GetName(), process);
process.ResolveLinks();
processInfile.Close();
As well as for the machine, the process has polymorphous content. The ConceptDataStore has to be used and the factories have to be registered in order to give them the ability to create the different type of instances.
- Process description file
- The process description file could be the following but it can be adapted in order to change the global machine behaviour.
5 <
Factory>
Example.ActionOpen</
Factory>
9 <
LinkPath>\
Machine\
MachineElements\
GripperA</
LinkPath>
14 <
Factory>
Example.ActionPickPlace</
Factory>
18 <
LinkPath>\
Machine\
MachineElements\
GripperA</
LinkPath>
21 <
LinkPath>\
Machine\
MachineElements\
ArmA</
LinkPath>
36 <
Factory>
Example.ActionMove</
Factory>
40 <
LinkPath>\
Machine\
MachineElements\
ArmA</
LinkPath>
- Note
- The xml file can be edited through a tool ConceptEditor (todo link).
- Note
- Adding a new process action type is easy. Implement a new class and add one single line such as
processFactories.RegisterFactory(ActionOpen::GetClassFactory());
Execution
In the example the process is created after the machine. The following lines will execute the process.
System::GetConsole().WriteLine();
System::GetConsole().WriteLine("Process execution");
System::GetConsole().WriteLine("-----------------");
process.Execute();
System::GetConsole().WriteLine("Process execution end.");
On the first execution of the application the hardcoded description of the machine and the process are used. The execution displays the following output.
When the hardcoded descriptions are used the corresponding XML files are created.
On the next execution (as the files are found), the software will use the description file. Changing the machine or the process file enables us to adapt software behaviours without recompiling and as shown before, adding a new machine part or a new action is handled easily with this kind of agile software architecture.
ConceptRT and agile programming
- Note
- In embedded world the Machine could be a System and the MachineElements could be some Peripherals. The Process of action could be an activation with parameter set for each peripheral.
To be able to develop such a software architecture there are several needs to be met :
- Polymorphous list
- Serialization / de-serialization of polymorphous content
- Serialisation / de-serialization of aggregations
As shown in the previous example, ConceptRT has been especially designed to handle such a complexity. Moreover there are more advanced features available to manage multiple layers structure and handling functionalities brought by underneath layers (todo link).