Part IV: Developing and Testing the Application Services

[Author’s note, July 28, 2010: Introduced Boost::shared_ptr to manage reference to message endpoint so as to enable postponed construction, as will be required in Part VI.]

Ah, we’ve made it to the application services layer.  After defining the architecture, setting up the package, and implementing the core layer which contains the domain logic of the application, we’re ready to take on developing the application services layer.  In short, the service layer provides the external API to the underlying domain layer.  Ideally, one should be able to develop the entirety of the “non-UI” portions of an application and have it exposed via the application services layer.  In other words, one should be able to swap out the front end – say from a web front end to a Flash front end – without having the application services layer effected. If you’d like to go ahead and download the source for this article, click here.

The application services layer is analogous to a task or coordination manager; i.e., it doesn’t know how to carry out the low level details of a particular action but it does know who is responsible for carrying out particular tasks.  Accordingly, the application services layer of the package (or any application for that matter) is mostly made up of a number of publicly accessible methods which, in turn, pass responsibilities on to external service dependencies (e.g., a message endpoint) and the domain layer for execution.

With that said, the services layer should still eschew direct dependencies on external services, such as communications with a messaging framework (e.g., ROS).  Accordingly, in this post, we’ll materialize the application services layer of our package and give it two responsibilities:

  • Coordinate with the domain layer to begin laser report reading and handle the raised reports, accordingly;
  • Forward laser reports on to the appropriate message endpoint(s) to have the information disseminated over the message based system.

But (there’s always a “but” isn’t there), while the application services layer should be responsible for passing messages received from the domain layer on to the messaging middleware, it should not have a direct dependency on that messaging middleware itself.  This decoupling facilitates testing of the application services layer with test doubles, keeps a clean separation of concerns between task coordination and messaging, and provides greater flexibility with being able to modify/upgrade the messaging layer without affecting the application services layer.

A moment needs to be taken to clarify the differences among application services, domain services, and “external resource” services.

  • Application services are our current concern.  The word “services” in their convoluted title is misleading and should not be inferred as being web services, RESTful services, or the like.  A better name for describing the application services responsibilities is “task coordination layer,” “workflow management layer,” or “not-real-services application services layer.”  We’ll examine a bit in this post exactly how the application services layer can be properly leveraged.
  • Domain services are really utility classes used by the core layer (the concern of our previous post) for processing data or encapsulating a specific, reusable task or behavior.  Domain services almost never have state (i.e., properties) and sometimes expose behavior as static methods.  A good example of a domain service would be a calculator class (e.g., Kalman filter calculator) or a string utility class.
  • External resource services are what we typically think of when we hear the word “service.”  An external resource service is any class which depends upon…well…an external resource.  Examples of an external resource include databases, resource files (e.g., maps), messaging middleware, third party web services, and even the laser reading class that we simulated in Part II of this series.  Ideally, external resource services should be abstracted, allowing consumers of the services to interact with them via interfaces or abstract classes.  In this way, layers remain decoupled from the implementation details of the services, reflecting the benefits of proper OOP design.  Proper abstraction of service layers usually involves separated interface and dependency injection.  We’ll see in this post how these techniques are leveraged to decouple the application services layer from the message endpoints (which are indeed external resource services).

Before we delve in, let’s briefly review what we plan to accomplish:

  • An application service class – LaserScanReportingService – will be developed to provide a public API for starting and stopping the laser reader.  In addition to exposing this behavior, the class will also listen for laser scan reads and pass the laser scans onto a message endpoint for publication to ROS.
  • A message endpoint interface will be introduced to provide an abstraction to the implementation details of the concrete message endpoint itself.  The application service class will communicate with the message endpoint via this abstraction to allow for unit testing the application services layer in isolation of the messaging middleware.
  • Unit tests will be written to verify the functionality of the application service class.  Furthermore, a message endpoint test double, a stub in this case, will be added to simulate communications with the messaging middleware.  Again, this will allow us to verify the task coordination behavior of the application services layer without concerning ourselves with messaging middleware integration details.

Enough with the chatter, let’s see some code!

Target Class Diagram

The following diagram shows what the package will look like after completing the steps in this post. While the individual elements will be discussed in more detail; the class diagram should serve as a good bird’s eye view of the current objectives. The elements with green check marks were completed in previous posts.

1. Setup the Package Skeleton and Domain Layer

If not done already, follow the steps in Part II and Part III to create the package and develop/test the domain layer.

2. Create an interface for the message endpoint service which the application service layer will leverage to communicate with ROS.

Create src/core/message_endpoint_interfaces/ILaserScanEndpoint.hpp containing the following code:

// ILaserScanEndpoint.hpp
 
#ifndef GUARD_ILaserScanEndpoint
#define GUARD_ILaserScanEndpoint
 
#include "sensor_msgs/LaserScan.h"
 
namespace ladar_reporter_core
{
  class ILaserScanEndpoint
  {
    public:
      // Virtual destructor to pass pointer ownership without exposing base class [Meyers, 2005, Item 7]
      virtual ~ILaserScanEndpoint() {}
      virtual void publish(const sensor_msgs::LaserScan& laserScan) const = 0;
  };
}
 
#endif /* GUARD_ILaserScanEndpoint */

There shouldn’t be anything too surprising in this interface.  It simply exposes a method to publish a laser scan to the underlying messaging middleware.  Why not put the interface in the application services layer, which intends to use it?  A couple of good reasons come to mind: 1) since it’s a pure interface, having elements within core aware of it (or even directly dependent upon it) does not introduce any further coupling to the underlying external resource, the messaging middleware, and 2) in very simply packages, an application services layer might be overkill, so keeping the “external resource service interface” (there’s a mouthful) in the core layer facilitates either approach without having to move anything around if the selected approach changes during development.

3. Create the application service header class.

Create src/application_services/LaserScanReportingService.hpp containing the following code:

// LaserScanReportingService.hpp
 
#ifndef GUARD_LaserScanReportingService
#define GUARD_LaserScanReportingService
 
#include <boost/shared_ptr.hpp>
#include "ILaserScanEndpoint.hpp"
 
namespace ladar_reporter_application_services
{
  class LaserScanReportingService
  {
    public:
      explicit LaserScanReportingService(boost::shared_ptr<ladar_reporter_core::ILaserScanEndpoint> laserScanEndpoint);
 
      void beginReporting() const;
      void stopReporting() const;
 
    private:
      // Forward declare the implementation class
      class LaserScanReportingServiceImpl;
      boost::shared_ptr<LaserScanReportingServiceImpl> _pImpl;
  };
}
 
#endif /* GUARD_LaserScanReportingService */

A few notes:

  • Line 7:  Includes the previously defined message endpoint interface.
  • Line 14:  Provides a constructor for the application service class, accepting an implementation of ILaserScanEndpoint, accordingly.
  • Lines 16-17:  Exposes methods for exposing behavior found within the core layer.
  • Lines 21-22:  Declaration for a pImpl (private implementation) that will allow the application service class to be updated frequently while minimizing the rebuilding of other class libraries which depend upon it.  Using the pImpl pattern will likely be overkill in smaller packages, but can be an appreciable timesaver as packages grow in size. It has been included in the sample code here as a example reference for when it might be more appropriately required.

4.  Create the application service implementation class.

Create src/application_services/LaserScanReportingService.cpp containing the following code:

// LaserScanReportingService.cpp
 
#include "sensor_msgs/LaserScan.h"
#include "ILaserScanListener.hpp"
#include "LaserScanReader.hpp"
#include "LaserScanReportingService.hpp"
 
using namespace ladar_reporter_core;
 
namespace ladar_reporter_application_services
{
  // Private implementation of LaserScanReportingService
  class LaserScanReportingService::LaserScanReportingServiceImpl : public ladar_reporter_core::ILaserScanListener
  {
    public:
      explicit LaserScanReportingServiceImpl(boost::shared_ptr<ILaserScanEndpoint> laserScanEndpoint);
 
      void onLaserScanAvailableEvent(const sensor_msgs::LaserScan& laserScan) const;
      LaserScanReader laserScanReader;
 
    private:
      boost::shared_ptr<ILaserScanEndpoint> _laserScanEndpoint;
  };
 
  LaserScanReportingService::LaserScanReportingService(boost::shared_ptr<ILaserScanEndpoint> laserScanEndpoint)
    : _pImpl(new LaserScanReportingServiceImpl(laserScanEndpoint)) {
 
    // Wire up the reader to the handler of laser scan reports
    _pImpl->laserScanReader.attach(*_pImpl);
 }
 
  LaserScanReportingService::LaserScanReportingServiceImpl::LaserScanReportingServiceImpl(boost::shared_ptr<ILaserScanEndpoint> laserScanEndpoint)
    : _laserScanEndpoint(laserScanEndpoint) { }
 
  void LaserScanReportingService::beginReporting() const {
    _pImpl->laserScanReader.beginReading();
  }
 
  void LaserScanReportingService::stopReporting() const {
    _pImpl->laserScanReader.stopReading();
  }
 
  void LaserScanReportingService::LaserScanReportingServiceImpl::onLaserScanAvailableEvent(const sensor_msgs::LaserScan& laserScan) const {
    // Send laserScan to the message end point
    _laserScanEndpoint->publish(laserScan);
  };
}

A few notes:

  • LaserScanReportingService::LaserScanReportingServiceImpl defines the pImpl implementation.  As shown, this pImpl implementation accepts a ILaserScanEndpoint via its constructor and also adds members for communicating with LaserScanReader and with the provided message end point.  As discussed earlier in this post, it’s preferable to abstract dependencies on external resource services using interfaces or abstract classes, as was done with ILaserScanEndpoint.  Quite arguably, LaserScanReader is also an external resource service.  But since the core of our package is in providing a means to read laser scans from a particular laser range finder, I made a purity concession, if you will, allowing the application services layer to have a direct dependency on the reader itself.  Alternatively, an interface could be introduced for the LaserScanReader; but since the very heart of this package is in reading and reporting laser scans, I felt this concession to be warranted…or at least justifiably defendable.  With that said, concessions such as this should be carefully thought out and discussed with the project team to determine the pros and cons of either approach and to ensure that everyone has a clear understanding of the motivations of the decision.  (If the team – or yourself – does not understand or can explain why an architectural decision was made, it’s a good chance that the architectural decision will not be adhered to or may become a source of code rot.)

    The other item to note is that the pImpl class implements ILaserScanListener; accordingly, this class will act as the observer for receiving and handling laser scan reports raised from LaserScanReader.

  • The constructor of LaserScanReportingService accepts a reference to ILaserScanEndpoint, passes that reference on to the constructor of LaserScanReportingServiceImpl and attaches the observer to LaserScanReader to listen for raised laser scans.
  • The constructor of LaserScanReportingServiceImpl accepts a reference to ILaserScanEndpoint and initializes the laserScanEndpoint member to that reference, accordingly.
  • beginReporting and stopReporting provide pass through methods to the underlying behavior exposed by laserScanReader.
  • Finally, onLaserScanAvailableEvent provides an event handler to accept a laser scan from LaserScanReader and pass it on to laserScanEndpoint’s publish function.

As mentioned previously, the above implementation could be done without a private implementation design pattern, but this serves to illustrate how such a pattern may be leveraged when warranted.

5. Configure CMake to Include the Header and Implementation

With the header and implementation classes completed, we need to make a couple of minor modifications to CMake for their inclusion in the build.

  1. Open /ladar_reporter/CMakeLists.txt. Within the include_directories statement, add an include for the following directory to include the message endpoint and application service headers:
    "src/core/message_endpoint_interfaces"
    "src/application_services"
  2. Open /ladar_reporter/src/CMakeLists.txt. Add an inclusion for the application_services directory at the end of the file:
    add_subdirectory(application_services)
  3. Now, in order to create the application services class library itself, a new CMake file is needed under /ladar_reporter/src/application_services. Accordingly, create a new CMakeLists.txt under /ladar_reporter/src/application_services, containing the following:
    # Create the library
    add_library(
      ladar_reporter_application_services
      LaserScanReportingService.cpp
    )

6. Build the application services Class Library

In a terminal window, cd to /ladar_reporter and run make. The class library should build and link successfully.

Like before, we’re not done yet…it’s now time to test our new functionality.

7. Unit Test the LaserScanReportingService Functionality

When we go to test the functionality of the laser scan reporting application service, it will likely be quickly noticed that there’s a missing dependency which will be needed to test the functionality of this class: a concrete implementation of ILaserScanEndpoint.hpp. The job of the message endpoint class will be to take laser scans and publish them to the appropriate topic on ROS. But we’re just not there yet…what we’d really like to do is to be able to test the functionality of the application service layer – LaserScanReportingService.cpp to be specific – without necessitating the presence of the message endpoint and ROS itself. While we’ll have to cross that bridge eventually (in Part V to be exact), we’re not currently interested in doing integration tests. Instead, we’re only interested in testing the behavior of the application service regardless of its integration with ROS.

Accordingly, a “stub” object will be employed to stand in for an actual message endpoint. In this case, the stub object is nothing more than a concrete implementation of ILaserScanEndpoint.hpp; but instead of publishing the laser scan to ROS, it’ll do something testable, such as keep a tally, or possibly a temporary queue, of laser scans “published” which can then be verified with testing asserts. If you’re new to unit testing, you’ll want to read about test doubles, Martin Fowler’s Mocks Aren’t Stubs, and XUnit Test Patterns for a more comprehensive treatment of the subject.

Onward with testing…

  1. Create /ladar_reporter/test/message_endpoints/test_doubles/LaserScanEndpointStub.hpp containing the following code to declare and define the message endpoint stub:
    // LaserScanEndpointStub.hpp
     
    #ifndef GUARD_LaserScanEndpointStub
    #define GUARD_LaserScanEndpointStub
     
    #include "sensor_msgs/LaserScan.h"
    #include "ILaserScanEndpoint.hpp"
     
    namespace ladar_reporter_test_message_endpoints_test_doubles
    {
      class LaserScanEndpointStub : public ladar_reporter_core::ILaserScanEndpoint
      { 
        public:
          LaserScanEndpointStub()
            : countOfLaserScansPublished(0) { }
     
          void publish(const sensor_msgs::LaserScan& laserScan) const {
            countOfLaserScansPublished++;
     
            // Output the laser scan seq number to the terminal; this isn't the
            // unit test, but merely a helpful means to show what's going on.
            std::cout << "Laser scan sent to LaserScanReceiver with angle_min of: " << laserScan.angle_min << std::endl;
          };
     
          // Extend ILaserScanEndpoint for unit testing needs.
          // May be modified by const functions but maintains logical constness [Meyers, 2005, Item 3].
          mutable int countOfLaserScansPublished;
      };
    }
     
    #endif /* GUARD_LaserScanEndpointStub */

    If you’re paying attention (of course you are!), you’ll notice that this code looks very similar to the code used within the LaserScanReaderTests.cpp class’ fake event handler from Part III. In fact, it’s the exact same code but now it’s encapsulated within a message endpoint stub. Admittedly, this is not a very good testing endpoint (since it won’t make it easier to verify that the laser scans have been “published” via testing asserts), but it does provide an easy means to visually note, while the tests are running, how the message endpoint stub is behaving. More importantly, seeing the laser scans output to the screen will show the application service layer is interacting with the message endpoint appropriately…which is exactly what we’re trying to verify.

    While the LaserScanEndpointStub class could have been coded inline within the unit test class (discussed next), encapsulating it within an external class allows the stub to be reused by other tests as well. This may not be necessary in all cases – in which coding it inline would be just fine – but I typically find myself reusing stubs and mocks to avoid duplicated code in my testing layer; besides, keeping it within an external test double keeps the code arguably cleaner.

  2. Create /ladar_reporter/test/application_services/LaserScanReportingServiceTests.cpp containing the following testing code:
    // LaserScanReportingServiceTests.cpp
     
    #include <gtest/gtest.h>
    #include "LaserScanEndpointStub.hpp"
    #include "LaserScanReportingService.hpp"
     
    using namespace ladar_reporter_application_services;
    using namespace ladar_reporter_test_message_endpoints_test_doubles;
     
    namespace ladar_reporter_test_application_services
    {
      // Define the unit test to verify ability to leverage the reporting service using the messege endpoint stub
      TEST(LaserScanReportingServiceTests, canStartAndStopLaserScanReportingService) {
        // Establish Context
        boost::shared_ptr<LaserScanEndpointStub> laserScanEndpointStub =
          boost::shared_ptr<LaserScanEndpointStub>(new LaserScanEndpointStub());
        LaserScanReportingService laserScanReportingService(laserScanEndpointStub);
     
        // Act
        laserScanReportingService.beginReporting();
        sleep(2);
        laserScanReportingService.stopReporting();
     
        // Assert
     
        // Since we just ran the reader for 2 seconds, we should expect a few readings.
        // Arguably, this test is a bit light but makes sure laser scans are being pushed 
        // to the message endpoint for publication.
        EXPECT_TRUE(laserScanEndpointStub->countOfLaserScansPublished > 0 &&
          laserScanEndpointStub->countOfLaserScansPublished <= 10);
      }
    }
  3. Open /ladar_reporter/test/CMakeLists.txt. Modify the contents to reflect the following:
    include_directories(
      "message_endpoints/test_doubles"
    )
    
    rosbuild_add_executable(
      ladar_reporter_tests
      TestRunner.cpp
      ./core/LaserScanReaderTests.cpp
      ./application_services/LaserScanReportingServiceTests.cpp
    )
    
    rosbuild_add_gtest_build_flags(ladar_reporter_tests)
    
    # Link the libraries
    target_link_libraries(
      ladar_reporter_tests
      ladar_reporter_application_services
      # Important that core comes after application_services due to direction of dependencies
      ladar_reporter_core
    )
  4. With everything in place, head back to the terminal and run:
    make
    cd bin
    ./ladar_reporter_tests

While running the tests, you should see a few messages published to the message endpoint stub. This demonstrates that all of the interactions among our core and application service layers are occurring exactly as expected. Now for the fun part…

In Part V, we’ll finally take a look at using all of these pieces together in order to publish messages to a ROS topic via a message endpoint.

Enjoy!
Billy McCafferty

Download the source for this article.