In this article I presented a fictitious scenario of a typical automation application. The scenario describes the controls and operation of a product assembly facility. The applications that I classify as ‘automation applications’ represent applications that are normally found in manufacturing control, warehouse automation, and material handling environments. They should not be confused with ‘industrial control’ applications which usually require real-time, closed-loop control. ‘Industrial control’ applications would be found in hazardous environments such as petrochemical and power plants. I suppose another way to differentiate between these two types of applications is according to their potential for loss of life (and the safeguards against it). In other words the apps that I deal with are relatively safe. The worst that’ll happen is that you have to duck if a robot starts throwing stuff around or help pick up a pile of boxes when a conveyor doesn’t behave.
Compared to WEB and desktop office productivity applications, these automation applications have some unique requirements and constraints. First, a large part of the application (if not all) is customized for a particular client. Consequently it is very important to develop a library of reusable components or else you’ll be re-inventing the wheel on every project. Second, these installations are normally comprised of multiple vendors cooperating to supply a solution to the problem. As a result the final testing of the system is not performed until the system is integrated at the client site. It’s not that testing is not important for other applications, but for these types of applications it is critical. You do not want to find logical errors during integration testing (much less after acceptance). To mitigate the problems that may be encountered during site integration, a certain amount of integration testing can be performed using simulation. But there is a limit as to the effectiveness of simulation; it’s only as good as the modeling of the real device.
Developing the application as a collection of loosely coupled modules facilitates unit testing and substitution of simulated components. This results in a more reliable solution. Another aspect that is related to this is the fact that with these types of applications you do not enjoy the benefit of signing away ‘consequential damages’, rebooting to clean up the system is not an option. In the event that a bug is found after deployment, quickly identifying and isolating the module at fault is crucial. And the smaller and more focused the component is, the easier it will be to locate and the more isolated the effects of the change will be. Third, these applications have short development times and hard schedules. This is mainly due to the fact that there are numerous companies that have inter-related schedules to provide the final solution. If one slips the others will be affected. So being able to develop and test in parallel allows for greater control in manpower allocation. In summary, the more granular the design the more opportunities for reuse will be available, and the more loosely coupled the design, the more parallel design opportunities and independent testing will be possible.
The Composite Application Library (
CAL), released by Microsoft’s Patterns and Practices group, provides an excellent framework for building automation applications. First it provides a solid base of pre-tested, re-useable components providing consistency in the applications produced. Having a proven base to begin with will also reduce the overall development time. The library provides an infrastructure that supports loosely coupled modular designs. The definition of a ‘module’ is flexible and allows for coarse or fine grain implementations. This facilitates the independent development and automated testing of system modules. The library supports and/or implements many popular design patterns including separated presentation which enables parallel development of the UI. The library also includes a logger, and an event aggregator which are facilities normally required for automation applications. And if that was not enough the library also supports multi-targeting (WPF and Silverlight) the application.
At this point what we want to do is see how CAL can benefit us in implementing the fictitious scenario referenced above. The plan is to examine each feature provided in CAL and see what benefits it provides to our automation application. There are some facilities that are described in the fictitious scenario that are not supported by CAL so we will have to discuss those at the end. Top-down or bottom-up, which way should we start? Oh, what the heck let’s start in the middle.
A quick side note. Even though CAL is a complete library, it is designed so that the various components can be replaced with alternate versions. If there is a better container for your application you can substitute it in place of the built-in version. If you have a better logging facility, use that. In addition, you do not have to use all the facilities included in CAL, only what applies or that you need. And finally if there is a component that does not match exactly what’s required, you have access to the source which means you can modify it!
One of the facilities provided with CAL is a dependency container, the Unity Application Block (Unity for short). Unity provides functionality that is core to building modular applications. It provides module location through a locator service, module loading and instantiation, a registration service to identify application modules, and flexible facilities to define the dependencies between modules and instancing requirements. We are going to examine each of these features but first let’s take a short digression and examine dependencies in our code. Consider the following:
class ClassA
{
private WorkRobot robot = null;
public ClassA()
{
robot = new WorkRobot();
}
public void StartProduction()
{
if (robot != null)
robot.WakeUp();
else
...
}
}
In the above example ClassA depends on WorkRobot to do stuff, a typical situation. In order for the above to work you need to have intimate knowledge of WorkRobot, you have to have access to the class or library that contains WorkRobot. Now suppose WorkRobot is somehow changed. We’ll have to re-compile with the new version. I’m sure you’re experienced with updates, you’ve most likely encountered errors that indicate that WorkRobot can’t be found (oh, it was changed to WorkRobot2). Or WakeUp is not a method of WorkRobot now it’s called Init.
Here’s another related situation. Suppose that WorkRobot is the interface class provided by a vendor for the physical robot in the system. You’ll be coding to that in order to get the robot to do what is required in the solution. And the WorkRobot class knows how to interface to the real device. You’ll certainly want to test your code before it gets deployed (robots can cause quite a bit of damage when things don’t go right). But you don’t have a copy of the robot sitting in your office. So either, you develop or the vendor provides, a simulated version of WorkRobot called SimRobot. This allows you to check your logic and could provide quite a bit of functional verification depending on the ‘truenes’ of the simulator (how accurately it represents the real device). Now we might have something like the following if both SimRobot and WorkRobot implement an IRobot interface.
class ClassA
{
private IRobot robot = null;
public ClassA()
{
#if Simulation
robot = new SimRobot();
#else
robot = new WorkRobot();
}
...
}
Yeah, I know. Not a good solution, but it’s done all the time. Here’s another complication. Suppose SimRobot was provided by the vendor and it simulates the robot very well. So well in fact that it operates at the same speed as the physical device. This could mean that to simulate a day’s production it will take you a day! If you need to test a month’s worth of production…well you can quickly see that another option is needed. So maybe you develop your own robot simulator that is a simple stub interface so that you can run through test scenarios at a faster rate. Can you see the complication to the above code?
One possible solution is if the interface reference was passed in to the ClassA constructor. That would solve the local problem but whatever instantiates ClassA must then resolve the dependency anyway. A related problem is where multiple classes make use of the robot but there has to be only one instance, so that a single context can be maintained. Again the usual solution is to create it at some high level and then pass it in to the constructor (or through properties) to the classes that require access to the resource. Sometimes this can become quite convoluted because the ‘instantiator’ may not have direct access to the class(es) that actually use it. In this case you have to pass in the reference to any number of intermediary classes.
class TopLevel
{
private IRobot robot = null;
public TopLevel()
{
#if Simulation
robot = new SimRobot();
#else
robot = new WorkRobot();
TopLevelHelper helper = new TopLevelHelper(robot);
}
}
class TopLevelHelper
{
private ClassA a = null;
private ClassB b = null;
public TopLevelHelper(IRobot robot)
{
a = new ClassA(robot);
b = new ClassB(robot);
}
}
class ClassA
{
private IRobot robot = null;
public ClassA(IRobot robot)
{
...
}
}
class ClassB
{
private IRobot robot = null;
public ClassB(IRobot robot)
{
...
}
}
The problem in the above scenarios is not that we are resorting to the compiler directives; it’s the instantiation that is the culprit! If we can minimize the use of the new statement (we can’t eliminate it) then we can reduce our code dependencies. How can we do that? Easy, as with everything else in life it’s easier if someone else does it for you. So how about if we have a factory that provides us with a reference interface to whatever it is that we require. All we need to do is supply some kind of token and we get back an interface. The factory could also manage lifetimes according to some object type definitions. That would work, wouldn’t it? But we still need to create this factory. Or do we? Here again we find that it’s easier if someone else does it for us. And in this case CAL has done it for us.
In the next article we’ll continue by describing how Unity resolves our code dependencies.