Introducing AXCIOMA

Hello World

To start the introduction of AXCIOMA for application developers we will begin with a familiar "Hello World" type example. We’ll use this example mostly to show you how easy AXCIOMA tooling makes it for you to create a distributed component based application.

Prerequisites

The AXCIOMA introductions assume basic knowledge of OMG IDL and the OMG IDL to C++11 language mapping as well as basic knowledge and understanding of the OMG LwCCM specification. For more information on OMG IDL you are referred to the current IDL specification. The current version of the IDL to C++11 language mapping can be found here. Additionally Remedy IT provides an IDL to C++11 programming tutorial here. The current OMG LwCCM specification can be found here (see Part 3).

Definition

We base the example on the time honored Echo example application. This example showcases a service that echoes (as return value) a modified version of a string provided as input argument. In our case, a distributed component based example, we will create a component providing an interface offering this service and another component that will use this interface. The component model for this example is shown in Figure 1.

Directory tree
Figure 1. Example component model

The first thing to do is to design and define the datatypes and interface. In this example we will go for the most simple interaction pattern; synchronous request/reply. In IDL the type definitions look like this.

data/echo.idl
module Example
{
  interface Echo
  {
    string process (in string text);
  };
};

These IDL definitions are placed in the file echo.idl which we store in a data subdirectory of our project folder according to "AXCIOMA Best Practices".

Directory tree
Figure 2. Example project directory tree

Figure 2 shows the complete directory tree for this example project. In case of project requiring multiple connectors it is also common to create subdirectories for these too.

Based on the defined datatypes and interfaces for the interaction between our component(s) we can now go about creating the other parts of our implementation like the actual code for the datatypes and interfaces, the connectors and the components. Thanks to the BRIX11 scaffolding tool included with AXCIOMA this will cost us very little effort as well as actual coding. The BRIX11 extensions for supporting AXCIOMA include the AXCIOMA Project Control (APC) commands which allow us to generate large parts of our project implementation infrastructure based on a minimum of meta information provided in APC recipes.

We’re not going to go into the full details of BRIX11 APC at this moment but we will show you the major elements and commands available. For more information you can check out the extensive documentation included in the AXCIOMA distribution package.

The first recipe to create is the project recipe placed in a file called aprc. It’s primary function is to mark the root of the project directory tree and to specify a number of project global settings. For this example project we create the following project recipe.

aprc
# common IDL include folder(s)
prj.idl_includes %w{ data }

This specified the data subdirectory as a common include directory for IDL compilation tasks.

Next thing we do is create a connector recipe for the implementation of our chosen synchronous request/reply interaction pattern for the Echo interface. The implementation of choice is a CORBA connector so we create the following connector recipe in the file connectors/echo_corba_conn.aprc.

connectors/echo_corba_conn.aprc
connector 'echo_srr' do |conn|
  # IDL file in which to find type definitions
  conn.idl %w{ echo.idl }
  # type of connector/port to create
  conn.port_type :corba4ccm do |port|
    # interface type to create connector for (needs fully scoped name)
    port.interface 'Example::Echo'
  end
end

This specifies a connector recipe named echo_srr which is to use (include) the IDL file echo.idl (to be found by searching any defined IDL include paths like the one specified in the project recipe) to define a CORBA4CCM connector (implementing synchronous request-reply using CORBA) for the Example::Echo interface. The interface type definition must be defined in the scope of the included IDL file(s). In AXCIOMA this is all that is needed to create a completely implemented connector as you will see later on!

Going on we move to creating a component recipe for the component providing the echo service. This recipe will be placed in the file components/echo_provider/echo_provider.aprc.

components/echo_provider/echo_provider.aprc
component 'echo_provider' do |comp|
  # data/type idl
  comp.idl %w{ echo.idl }
  # component interface definition
  comp.define 'Example::EchoProvider' do |intf|
    # facet port with interface Example::Echo
    intf.port 'do_echo' do |p|
      p.provides 'Example::Echo'
    end
  end
end

This specifies a component recipe named echo_provider for a component using the IDL file echo.idl. You may notice there is no mention of an IDL file defining the interface of the component. This is because BRIX11 APC recipes allow the definition of that interface in the recipe through the comp.define …​ construct. This recipe definition directs BRIX11 APC to generate an appropriate IDL file when needed. In this case that would be an IDL file named echo_provider.idl including a definition for a component EchoProvider in the module Example having a single provider port definition do_echo with interface type Example::Echo. The generated IDL will look like this.

Generated echo_provider.idl
#include <Components.idl>
#include "echo.idl"

module Example
{
  component EchoProvider
  {
    provides Example::Echo do_echo;
  };
};

BRIX11 APC also supports component recipe definition based on user defined component IDL. More information concerning this feature can be found in the documentation provided with AXCIOMA.

And finally we create a component recipe for the component using the echo service. This recipe will be placed in the file components/echo_user/echo_user.aprc.

components/echo_user/echo_user.aprc
component 'echo_user' do |comp|
  # data/type idl
  comp.idl %w{ echo.idl }
  # component interface definition
  comp.define 'Example::EchoUser' do |intf|
    # receptacle port with interface Example::Echo
    intf.port 'use_echo' do |p|
      p.uses 'Example::Echo'
    end
  end
end

This specifies a component recipe named echo_user for a component using the IDL file echo.idl and defines an interface for the component with a single receptacle port use_echo with interface type Example::Echo. The generated IDL for this component will look like this.

Generated echo_user.idl
#include <Components.idl>
#include "echo.idl"

module Example
{
  component EchoUser
  {
    uses Example::Echo use_echo;
  };
};

After creation of the type IDL file and the BRIX11 APC recipes our project tree looks like the picture shown in Figure 3.

Directory tree
Figure 3. Example project directory tree

Notice that up to this moment we only defined our interfaces (and datatypes) in IDL and described the basic properties of our components and connectors using fairly simple recipe directives.

In AXCIOMA that is all it takes to be able to start a new project because the BRIX11 APC commands are able to fill in the required blanks and generate all other information/files required for your project after which you will have only relatively little coding left to do to complete your components as you will see in the next section.

Preparation

Now that we have defined how the interfaces in our application are to be structured and how to organize our components we can use BRIX11 APC to set up the details.

First we will have BRIX11 APC create a full set of project (build) files for our chosen development environment. In our case this means generating GNU makefiles to drive IDL and code compilation and linking but when using an AXCIOMA package for Microsoft Visual C++ on Windows BRIX11 APC will (by default) generate solution files. To execute this step we run the following command anywhere from the project directory tree (BRIX11 APC will always automatically determine the project root by looking up the project recipe file aprc).

$ brix11 apc prepare

When execution of this command finishes our project tree will contain all necessary project build files as shown in Figure 4.

Directory tree
Figure 4. Example project directory tree

As you can see the brix11 apc prepare command not only generated the project files though. In the component directories we now also find the IDL files defining the components as specified in the component recipes we created.

The generated project files take into account all the dependencies and build steps BRIX11 APC has been able to analyze from the IDL type definition files used as well as the directives of the collective recipes.

With the project files generated we are ready to start coding and building but instead of coding ourselves we will have the AXCIOMA tooling take care of a large part of that.

A large part of AXCIOMA application implementations is created through automatic code generation by the RIDL parser/generator included with AXCIOMA. The project files generated by BRIX11 APC have been set up to take full advantage of the possibilities of RIDL.

Executing the buildsteps defined by the project files will create (and build) a complete set of code files for the application including full datatype and connector implementations as well as skeleton implementations for the components. The executor code generated for the components is furthermore provided with regeneration markers. The buildsteps configured for this code will regenerate this code when needed (when IDL changes) protecting any user code between regeneration markers. The generated skeleton code for the components provides complete yet empty implementations designed to be able to validate the structural dependencies within the project by building and deploying.

Using BRIX11 APC we do not even need to worry about the actual build tools to use for the environment we are working in. Executing the following command will have BRIX11 APC take care of running the correct tools to compile IDL and code files and build the resulting binaries.

$ brix11 apc build

As with the brix11 apc prepare command this command too can be executed from anywhere within the project directory tree.

Directory tree
Figure 5. Example project directory tree

When execution of this command finishes our project tree will be filled with a large collection of code files and build artifacts and (if everything went well) all runtime binaries (shared libraries) collected under the lib subdirectory as shown in Figure 5.

Most of the generated code can be found in separate subdirectories which (by default) are called generated_code (you will find these below the data, connectors and component directories). The code in these directories is to be considered transient. The AXCIOMA build commands configured in the generated project files will regenerate these files as needed based on IDL definitions and recipe directives disregarding any changes made (additionally the standard cleanup commands of the build tools will delete these files and directories).

The exception to this rule concerns the component executor code. This is the part where the user (you) will have to implement the required business logic. The files containing this code are generated (by default) in the directory holding the component recipes (and IDL files) with names derived from the recipe id appended with _exec.{cpp,h}. Figure 6 shows how this looks like for the echo_provider component directory.

Directory tree
Figure 6. echo_provider component directory

The executor code itself is provided with regeneration markers at appropriate locations in the code for the user to add there own definitions and implementations in a way that safeguards these changes for regeneration of the executor code.
The following sample shows how this looks like for the declaration of the executor implementation class of the do_echo facet of the echo_provider component.

do_echo executor implementation class
/// Executor implementation class for do_echo facet
class do_echo_exec_i final
  : public IDL::traits< ::Example::CCM_Echo>::base_type
{
public:

  //@@{__RIDL_REGEN_MARKER__} - BEGIN : Example_EchoProvider_Impl::do_echo_exec_i[ctor]
  /// Constructor
  /// @param[in] context Component context
  do_echo_exec_i (
      IDL::traits< ::Example::CCM_EchoProvider_Context>::ref_type context);
  //@@{__RIDL_REGEN_MARKER__} - END : Example_EchoProvider_Impl::do_echo_exec_i[ctor]

  /// Destructor
  ~do_echo_exec_i () override;

  /** @name Operations from ::Example::CCM_Echo */
  //@{

  std::string
  process (
      const std::string& text) override;
  //@}

  /** @name User defined public operations. */
  //@{
  //@@{__RIDL_REGEN_MARKER__} - BEGIN : Example_EchoProvider_Impl::do_echo_exec_i[user_public_ops]
  // Your code here
  //@@{__RIDL_REGEN_MARKER__} - END : Example_EchoProvider_Impl::do_echo_exec_i[user_public_ops]
  //@}

private:
  /// Context for component instance. Used for all middleware communication.
  IDL::traits< ::Example::CCM_EchoProvider_Context>::ref_type context_;

  /** @name User defined members. */
  //@{
  //@@{__RIDL_REGEN_MARKER__} - BEGIN : Example_EchoProvider_Impl::do_echo_exec_i[user_members]
  // Your code here
  //@@{__RIDL_REGEN_MARKER__} - END : Example_EchoProvider_Impl::do_echo_exec_i[user_members]
  //@}

  /** @name User defined private operations. */
  //@{
  //@@{__RIDL_REGEN_MARKER__} - BEGIN : Example_EchoProvider_Impl::do_echo_exec_i[user_private_ops]
  // Your code here
  //@@{__RIDL_REGEN_MARKER__} - END : Example_EchoProvider_Impl::do_echo_exec_i[user_private_ops]
  //@}
};

As mentioned earlier the built binary artifacts (shared libraries) could even in this basic implementation be deployed to check structural integrity and deployment setup. We are however not going to do that here but instead go right on to implementing the business logic for the component executors as shown in the next section.

Implementation

To make sure we can see this example actually doing something we will need to add some "business logic" to the components.

First is the provider component. Here we need to add code to the single method of the facet executor for the Example::Echo interface, the process method.

For this example we’ll add some logging to let an observer know that the method was called and showing the input argument. Additionally we’ll construct an answer string from a standard prefix and the input string. The resulting code is shown below.

facet executor process method implementation (from components/echo_provider/echo_provider_exec.cpp)
/** Operations and attributes from do_echo */

std::string
do_echo_exec_i::process (
    const std::string& text)
{
  //@@{__RIDL_REGEN_MARKER__} - BEGIN : Example_EchoProvider_Impl::do_echo_exec_i::process[_text]
  CIAOX11_TEST_INFO << "[EchoProvider] Echo::process called with input: " << text << std::endl;
  return "Thank you for sending us: " + text;
  //@@{__RIDL_REGEN_MARKER__} - END : Example_EchoProvider_Impl::do_echo_exec_i::process[_text]
}

Our (simple) implementation replaced the default generated placeholder code which was originally between the regeneration markers and will now be preserved whenever the executor files are regenerated.

The CIAOX11_TEST_INFO logger stream is a special logstream provided for easy testing purposes. To use it we need to include a standard header from AXCIOMA. For this purpose there exists a regen section at the head of the echo_provider_exec.cpp where we insert the include as follows.

test logger include (from components/echo_provider/echo_provider_exec.cpp)
// -*- C++ -*-
/*
 * Your header here.
 */
//@@{__RIDL_REGEN_MARKER__} - HEADER_END : echo_provider_impl.cpp[Header]

#include "echo_provider_exec.h"

//@@{__RIDL_REGEN_MARKER__} - BEGIN : Example_EchoProvider_Impl[user_includes]
#include <ciaox11/testlib/ciaox11_testlog.h>
//@@{__RIDL_REGEN_MARKER__} - END : Example_EchoProvider_Impl[user_includes]

Again we simply replace the placeholder code between the markers to protect our include declaration against regeneration.

This is all the user supplied coding needed for this simple example to finish the provider component. All the (default) code for supporting the AXCIOMA infrastructure has been generated and does not need any changes in this case.

AXCIOMA Loggers

AXCIOMA provides an extensible logger framework providing standard C++-style streambased logging functionality with multiple backend options. Creating a component/application specific logger module based on the base logger is quite easy. AXCIOMA itself includes various logger modules for different framework components. Each module can be separately controlled through code and/or through environment variables. More information can be obtained from the documentation included in the AXCIOMA distribution.

Now that we have taken care of the echo_provider component we turn our attention to the echo_user component where we need to add some code to actually send a process request through its connected Example::Echo receptacle port.

Again, since this is a very simple example, we use the simplest solution possible and add the code to the standard component executors ccm_activate callback. This is part of the standard component lifecycle events that get triggered by the AXCIOMA component management framework for deployed components. In case of the ccm_activate event we are at a deployment stage where we are sure the components are configured and connected.

Our inserted code looks like this.

component executor ccm_activate method implementation (from components/echo_user/echo_user_exec.cpp)
void EchoUser_exec_i::ccm_activate ()
{
  //@@{__RIDL_REGEN_MARKER__} - BEGIN : Example_EchoUser_Impl::EchoUser_exec_i[ccm_activate]
  CIAOX11_TEST_INFO << "[EchoUser] ccm_activate called" << std::endl;

  IDL::traits<Example::Echo>::ref_type echo_ref = this->context_->get_connection_use_echo ();
  if (echo_ref)
  {
    std::string answer = echo_ref->process ("Hello. How are you today?");
    CIAOX11_TEST_INFO << "[EchoUser] received answer: " << answer << std::endl;
  }
  else
  {
    CIAOX11_TEST_ERROR << "[EchoUser] NO connection!" << std::endl;
  }

  //@@{__RIDL_REGEN_MARKER__} - END : Example_EchoUser_Impl::EchoUser_exec_i[ccm_activate]
}

Again we use some logging to provide feedback to the casual observer. Like with the provider component we use the CIAOX11 test logger for this and therefor have to include the same standard header which we can insert at a similar location as before.

As with the provider component this is all the user supplied coding required for finishing this component for this simple example.

Re-executing the brix11 apc build command will update the binary artifacts and than we are ready to deploy!

Deployment

A component based distributed application like this AXCIOMA example does not provide a single execution point like a monolithic application (or even two like a more "classic" client/server application) but instead requires an orchestrated deployment process involving multiple executables and a deployment plan (although AXCIOMA provides various options making the "orchestration" less stringent). We will not go into details concerning the deployment process and deployment plans at this point. More information can be obtained from the documentation included in the AXCIOMA distribution.

For this simple example application we need a script to execute the deployment process as well as deployment plan to direct said process. We can easily derive these from the multitude of examples and tests included in the AXCIOMA distribution and place them in the descriptors subdirectory of the project tree.

In addition to the standard OMG D&C XML based deployment plans AXCIOMA also supports a custom, simpler and better humanly readable declaration format. We use this format to define a deployment plan according to our original model at the start of this text. The result is shown below.

descriptors/plan.config
# This plan deploys 2 nodes; Node1 for EchoProvider and Node2 for EchoUser
# both with their respective connectors

#=====================================================
# Definitions for Node1
#-----------------------------------------------------

# EchoProvider instance
nl.remedy.it.CCM.Component EchoProviderComponent echo_provider_exec create_Example_EchoProvider_Impl
    nl.remedy.it.DnCX11.ExecParameter nl.remedy.it.DnCX11.Servant.Artifact "echo_provider_svnt"
    nl.remedy.it.DnCX11.ExecParameter nl.remedy.it.DnCX11.Servant.Factory "create_Example_EchoProvider_Servant"
    nl.remedy.it.DnCX11.Node "Node1"

# Provider Echo CORBA Connector instance
nl.remedy.it.CCM.Component EchoProvider_CORBA_connector echo_srr_corba_conn create_Example_Echo_SRR_CORBA_Connector_Impl
    nl.remedy.it.DnCX11.ExecParameter nl.remedy.it.DnCX11.Servant.Factory "create_Example_Echo_SRR_CORBA_Connector_Servant"
    nl.remedy.it.DnCX11.Node "Node1"
    nl.remedy.it.DnCX11.Connection EchoProviderComponent
      srr_receptacle < do_echo

#=====================================================
# Definitions for Node2
#-----------------------------------------------------

# EchoUser instance
nl.remedy.it.CCM.Component EchoUserComponent echo_user_exec create_Example_EchoUser_Impl
    nl.remedy.it.DnCX11.ExecParameter nl.remedy.it.DnCX11.Servant.Artifact "echo_user_svnt"
    nl.remedy.it.DnCX11.ExecParameter nl.remedy.it.DnCX11.Servant.Factory "create_Example_EchoUser_Servant"
    nl.remedy.it.DnCX11.Node "Node2"

# User Echo CORBA Connector instance
nl.remedy.it.CCM.Component EchoUser_CORBA_connector echo_srr_corba_conn create_Example_Echo_SRR_CORBA_Connector_Impl
    nl.remedy.it.DnCX11.ExecParameter nl.remedy.it.DnCX11.Servant.Factory "create_Example_Echo_SRR_CORBA_Connector_Servant"
    nl.remedy.it.DnCX11.Node "Node2"
    nl.remedy.it.DnCX11.Connection EchoUserComponent
      srr_facet > use_echo
    nl.remedy.it.DnCX11.Connection EchoProvider_CORBA_connector
      srr_receptacle < srr_facet

As you can see this deployment plan describes the deployment of the EchoProvider component (a single instance thereof) on the first node (Node1) together with an instance of the CORBA connector for the Example::Echo interface connecting the do_echo facet of the EchoProvider component to the (standard) receptacle of the CORBA connector.

Likewise the plan describes the deployment of the EchoUser component (a single instance thereof) on the second node (Node2) together with an instance of the CORBA connector for the Example::Echo interface connecting the use_echo receptacle of the EchoUser component to the (standard) facet of the CORBA connector. Additionally at this point the plan describes the connection of the (standard) facet of the CORBA connector for the EchoProvider to the (standard) receptacle of the CORBA connector for the EchoUser thereby completing the connection chain between the two component instances.

We copied (and adapted) one of the test execution scripts from the AXCIOMA distribution and placed this in the file descriptors/run_test.pl. Executing this script deploys the application according to the deployment plan described above, provides it some time (seconds) to perform its function and then shuts the deployment down (controlled). Executing the following command from within the descriptors directory will run the script.

$ brix11 run test

The output should look like this.

BRIX11 - > perl run_test.pl
Starting Naming Service with  -ORBEndpoint iiop://10.4.0.153:60003 -o ns.ior
Invoking node daemon
Run dancex11_deployment_manager with --handler dancex11_node_dm_handler -p 60001 -N -n Node1=Node1.ior --deployment-nc corbaloc:iiop:10.4.0.153:60003/NameService
Run dancex11_deployment_manager with --handler dancex11_node_dm_handler -p 60002 -N -n Node2=Node2.ior --deployment-nc corbaloc:iiop:10.4.0.153:60003/NameService
Invoking domain deployment manager (dancex11_deployment_manager --handler dancex11_domain_dm_handler) with -l plan.config
[LP_INFO]     - 10:22:47.049389 - [EchoUser] ccm_activate called
[LP_INFO]     - 10:22:47.049689 - [EchoProvider] Echo::process called with input: Hello. How are you today?
[LP_INFO]     - 10:22:47.049872 - [EchoUser] received answer: Thank you for sending us: Hello. How are you today?
Sleeping 10 seconds to allow task to complete
Invoking executor - stop the application -
by running dancex11_deployment_manager with -n ExecutionManager=em.ior -x
Executor returned.
Shutting down rest of the processes.

You have now designed, implemented and run your very first AXCIOMA. Easy does it!

All code for this example can be found at GitHub.