Share via


Orchestrator IP Development Best Practices: Declarative, Imperative or Both?

When I first started working with Opalis (now Orchestrator) and the SDK, I was taught that you could create activities (and Integration Packs) using the declarative approach or the imperative approach. The way it was relayed, these were two mutually-exclusive paths toward the same end goal. As I work more and more with Orchestrator and now as I am in charge of the SDK as a feature of the product, I’ve learned a lot more about it and also learned that everything is not always as advertised.

As “the OIT Guy”, I get frequent questions both internally and externally about what the best practices are for creating new activities. Should I use the declarative style, should I use the imperative style, or should I use a mixture of both? Well, the answer to that is “it depends” (I hope you saw that coming). To some people, using the declarative style with attributes and statically-defined inputs and outputs is enough. For others, they want more dynamic activities and prefer the imperative style. The imperative style also gives you additional capabilities you can’t access using the declarative style. Likewise, there are some things you can’t access using just the imperative style, so you end up with a hybrid if you want to be able to utilize everything in the SDK.

Note: to understand the basics of creating activities using the two styles, see these pages on MSDN:

Also, you can find the complete description of the SDK classes on MSDN here: Microsoft.SystemCenter.Orchestrator.Integration

In order to understand where you should use which type of model in creating custom activities, it helps to clearly delineate the capabilities of each. That kind of comparison isn’t really available anywhere, so I thought I would create one for everybody. We’ll start with the activities themselves.

Activities

Each activity encapsulates all of the functionality required to show the properties at design time as well as to execute the actions of the activity at runtime.

Functionality

[Activity] Attribute

IActivity interface

Provide a name for the activity Specified in the first string in the attribute.
For example: [Activity ("Hello World") ]

You can also use:
[Activity (Name="Hello World") ]

If no name is specified, the name of the class is used.
No equivalent
Provide a description for the activity [Activity ("Hello World", Description=”The activity description goes here”) ] No equivalent
Show the Filters Tab [Activity ("Hello World)", ShowFilters=false]

(true by default)
No equivalent
Show the Properties Tab [Activity ("Hello World)", ShowInputs=false]

(true by default)
No equivalent
Add input properties to the activity form for display to the user Use the [ActivityInput] attribute on public properties. These are statically-defined and will always be available to the user. Use the Design() method along with the IActivityDesigner type parameter passed into it to dynamically add inputs via the AddInput() method, outputs via the AddOutout() method, and Filters via the AddFilter() method.

You can also have statically-defined inputs, outputs and filters via the attributes
Add output properties to the activity form for use as published data Use the [ActivityOutput] attribute on public properties. These are statically-defined and will always be available to the user.

The getter of the property must contain the code necessary to process the inputs and derive the output unless you use [ActivityMethod] methods.
Add filters to the activity form for display to the user and filtering output data Use the [ActivityFilter] attribute on public properties. These are statically-defined and will always be available to the user.
Process data in the activity at runtime Use one or more methods with the [ActivityMethod] attribute. The order in which multiple instances run is indeterminate, so it’s best to have one ActivityMethod and call other methods from there. Use the Execute() method to run code inside the activity and run other methods. Select which data to output as published data using the IActivityResponse interface’s Publish() method.

 

I actually threw you a curve ball with this first section. The [Activity] attribute and IActivity interface are not actually similar entities. You can’t choose whether or not to use [Activity]. You have to use it. It’s the only way to define that the class you’re creating is actually an activity. The question is whether or not you also inherit your class from IActivity. If you do, you’ll now have to implement the Design() and Execute() methods, but you don’t have to change anything. You could still have statically-defined inputs, filters and outputs if you want, but you are now able to define your properties in a different way and display different things to the user that you weren’t able to by just using the [Activity] attribute alone.

So, to sum it up for the Activity as a whole:

  • You must always use the [Activity] attribute.

  • I recommend as a best practice that you always use all of the properties in the attribute so that anyone reading the code knows exactly how the activity will be displayed to the user. For example:

    [Activity (Name="Hello World”", Description=”The activity description goes here”, ShowInputs=true, ShowFilters=false) ]

  • I recommend as a best practice that you always inherit from IActivity, even if you’re not going to use the functionality. If you do it all the time, then if you ever decide to go back later and add functionality to the activity that now requires inheriting from IActivity, you won’t have to change a whole bunch of stuff because you have to switch to the interface.

Input Properties

Input properties are the items displayed to the user on the Properties tab or, in the case of optional properties, displayed when the user clicks the Optional Properties button. Properties can be displayed in only one way (in a test box), but getting the value to be displayed can be via simple typed-in value, published data subscription, or via a browser of some kind. The types of browsers and functionality available depends on how you’re defining and adding the property to the activity, whether it’s using the [ActivityInput] attribute or via the AddInput() method of the IActivityDesigner interface.

Functionality

[ActivityInput] Attribute

AddInput method

Set the input property’s name Specified in the first string in the attribute.
For example: [ActivityInput ("HWInput)") ]

You can also use:
[ActivityInput (Name="HWInput)") ]

If no name is specified, the name of the class is used.
AddInput(“HWInput”)
Set the input property’s description [ActivityInput ("HWInput)", Description=”The property description goes here”) ] No equivalent
Set the input property’s default value [ActivityInput ("HWInput”, Default=”Hello World”)] AddInput(“HWInput”).WithDefault(“Hello World”)
Define the property as optional [ActivityInput ("HWInput", Optional=true)]

(false by default)
AddInput(“HWInput”).NotRequired()
Define the property as password protected (hidden/encrypted) [ActivityInput ("HWInput", PasswordProtected=true)]

(false by default)
AddInput(“HWInput”).PasswordProtect()
Make the property a list with a browser and add a static list of values to be displayed [ActivityInput("Source Location", Default = "Production Server", Options = "Production Server, Development Server")] AddInput(“HWInput”).WithListBrowser("Production Server”, “Development Server") .WithDefault(("Source Location")
Make the property a list with a browser and add a list of values via a string array or method No equivalent. Options in an attribute must be constants and cannot be variables. AddInput(“HWInput”).WithListBrowser(myStringArray)
or
AddInput(“HWInput”).WithListBrowser(getValues())
Give the property an enum browser to look up enum values No equivalent AddInput(“HWInput”).WithEnumBrowser(enumType)
Make the property a Boolean You can simulate this by giving the options values as “True” and “False”, but it is not truly a Boolean value type property. AddInput(“HWInput”).WithBooleanBrowser()
Give the property a computer browser to look up computer names No equivalent AddInput(“HWInput”).WithComputerBrowser()
Give the property a file browser to look up file names No equivalent AddInput(“HWInput”).WithFileBrowser()
Give the property a folder browser to look up folder names No equivalent AddInput(“HWInput”).WithFolderBrowser()
Give the property a date/time browser to input a properly-formatted date/time value No equivalent AddInput(“HWInput”).WithDateTimeBrowser()

Looking at the above, the only thing you give up by using the imperative approach is the ability to specify a description for the input property. And given that I’ve yet to find out where you can actually see the description displayed, I would say not having that functionality is perfectly fine. What you gain by using the imperative approach is much, much greater. By not using the attribute, you now have the ability to dynamically populate lists with values from enums, variables or even directly from methods. You also have several new types of browsers that you don’t have with the attribute, including Boolean, File, Folder, Computer and Date/Time.

The only time you cannot use the imperative approach to define your properties is when you’re defining a structure / group of properties in a class with the ActivityData attribute. Of course, if I can figure out a way to use dynamic inputs in an ActivityData class, I’ll be sure to let you know.

My recommendations for Input Properties:

  • You should always use the imperative approach to define individual properties. You lose virtually nothing and gain a lot of functionality.
  • If you need to group properties (for correlated output data), you will need to use the ActivityData attribute, so you’ll have to use the ActivityInput attribute for those properties, but that’s usually only for the group of properties defining the configuration data for an activity.

Filter Properties

Filter properties are shown to the user on the Filters tab and represent a way to limit the input that is returned from the activity. They’re vastly similar in usage to Input Properties, with the exception of having comparators (relations) that need to be defined so the user can compare the output in some way to a value they define.

Functionality

[ActivityFilter] Attribute

AddOutput method

Set the filter’s name Specified in the first string in the attribute.
For example: [ActivityFilter(“HWFilter”) ]

You can also use:
[ActivityFilter (Name="HWFilter ") ]

If no name is specified, the name of the class is used
AddFilter(“HWFilter”)
Set the filter’s description No equivalent No equivalent
Make the filter a list with a browser and add a static list of values to be displayed [ActivityFilter(“HWFilter”, Default = "Production Server", Options = "Production Server, Development Server")] AddFilter(“HWFilter”).WithListBrowser("Production Server”, “Development Server") .WithDefault(("Source Location")
Make the filter a list with a browser and add a list of values via a string array or method No equivalent. Options in an attribute must be constants and cannot be variables. AddFilter(“HWFilter”).WithListBrowser(myStringArray)
or
AddFilter(“HWFilter”)..WithListBrowser(getValues())
Give the filter an enum browser to look up enum values No equivalent AddFilter(“HWFilter”).WithEnumBrowser(enumType)
Make the filter a Boolean lookup You can simulate this by giving the options values as “True” and “False”, but it is not truly a Boolean value type property. AddFilter(“HWFilter”).WithBooleanBrowser()
Give the filter a file browser to look up file names No equivalent AddFilter(“HWFilter”).WithFileBrowser()
Give the filter a folder browser to look up folder names No equivalent AddFilter(“HWFilter”).WithFolderBrowser()
Give the filter a date/time browser to input a properly-formatted date/time value No equivalent AddFilter(“HWFilter”).WithDateTimeBrowser()
Define the types of relations available for the filter ActivityFilter(“HWFilter”, Relations = Relation.Before | Relation.After) AddFilter(“HWFilter”).WithRelations(Relation.Before | Relation.After)

Like for Input Properties, the AddFilter() method doesn’t allow you to define a description for a filter, but guess what… neither does the attribute! So in the case of filters, you lose absolutely nothing by using the imperative approach and like inputs, you gain flexibility in the list browser as well as gaining the other types of browsers.

My recommendations for Filters:

  • You should always use the imperative approach to define individual filters. You lose nothing and gain a lot of functionality.
  • If you need to group properties (for correlated output data), you will need to use the ActivityData attribute, so you’ll have to use the ActivityFilter attribute for those properties, but that’s usually only for the group of properties defining the output data for an activity.

Output Properties

Output properties are how published data gets sent to the data bus. Output properties must be defined at design time, otherwise you won’t have access to published data from the activity in subsequent activities in the runbook.

Functionality

[ActivityOutput] Attribute

AddOutput method

Set the output property’s name Specified in the first string in the attribute.
For example: [ActivityOutput("HWOutput") ]

If no name is specified, the name of the class is used.
AddOutput(“HWOutput”)
Set the output property’s description [ActivityOutput("HWOutput", ”The property description goes here”) ] AddOutput(“HWOutput”).WithDescription(=”The property description goes here”)
Define the output property as a Date/Time value No equivalent AddOutput(“HWOutput”).AsDateTime()
Define the output property as a number value No equivalent AddOutput(“HWOutput”).AsNumber()
Define the output property as a Boolean value No equivalent No equivalent
Define the output property as a string value No equivalent AddOutput(“HWOutput”).AsString()
Define the output property as a filter value Add the [ActivityFilter] attribute to the property.
(see below)
AddOutput(“HWOutput”).WithFilter()
or
AddOutput(“HWOutput”).WithFilter(relation)

Note: using this method, you can’t add all of the features you could using AddFilter().

Unlike the other two methods, the AddOutput() method does allow you to define a description for the output property.So in the case of output properties, you lose absolutely nothing by using the imperative approach. There are no browsers associated with output properties, so you don’t get anything there, but you do get the ability to specifically define the output type, which you can’t using the attribute. For example, is “1” supposed to be a string or a number? Well you can define that using the imperative approach. So once again the imperative approach wins out in terms of flexibility and features.

My recommendations for Output properties:

  • You should always use the imperative approach to define individual properties. You lose nothing and gain extra functionality.
  • If you need to group properties (for correlated output data), you will need to use the ActivityData attribute, so you’ll have to use the ActivityOutput attribute for those properties, but that’s usually only for the group of properties defining the output data for an activity.

Attributes with No Imperative Equivalent

The following represent attributes where there is no interface equivalent available, so the attribute must be used if you want that functionality.

[ActivityMonitor]

This attribute is used to define a monitor instead of a normal activity. Like the [Activity] attribute, you have to use this attribute to define the actual activity class. You can still inherit the class from IActivity to get the benefits of dynamic inputs and outputs.

[ActivityConfiguration]

This attribute is used to define the configuration settings for activities. These are the items that show up in the Options menu and at the top of activities that specify a configuration value.

[ActivityData]

This attribute is used to define a special class of input, output, and/or filter types that are grouped together. For use within an activity. These are frequently used within configurations to define a set of properties associated with the configuration. To use this group of properties as output data, you must use the IActvity / IActivityDesigner interface and the AddCorellatedData() method. Note that there is no way to define dynamic properties within this attributed class, so if you want grouped properties and correlated data, you will need to define the outputs statically.

[ActivityMethod]

This attribute is used to define one or more methods that will be invoked during runtime of the activity. The order in which they are invoked is indeterminate. The closest equivalent would be the Execute() method within the IActivity interface, which is always invoked at runtime. Note: you can define activities that do not have any ActivityMethod methods, but they either must have the execution logic in the getters of the output properties or they need to inherit from IActivity and have the Execute() method

Interfaces/Methods with No Attribute Equivalents

IActivityDesigner.AddCorrelatedData()

As I mentioned above, you use the ActivityData attribute to specify a class containing multiple properties such as outputs, but in order to use that data as published data, it needs to be output in a format similar to a table, and that required the AddCorellatedData() method, which is only available if your activity inherits from IActivity and you’re within the Design() method, which takes a parameter of the type IActivityDesigner.

IActivityRequest

This interface is really just something defining an input parameter to the Execute() method in activities that inherit from IActivity. You will never use this interface directly to create another extended class. However, I mention it because you can use it within the Execute method to step through values of the Inputs and Filters associated with the activity at runtime.

IActivityRespose

Like IActivityRequest, you won’t be inheriting from this interface, but you will use it if your activity inherits from IActivity, because the Execute() method has a parameter of this type which allows you to publish data out from the activity as part of its runtime execution. There are a couple of methods that you’ll use all the time to publish data, and a bunch of methods that are really useful for handling errors and reporting those to the user. Of particular importance:

  • Publish- allows you to publish a single value, a single correlated data set defined using the ActivityData attribute, or a collection of name/value pairs in a Dictionary object.
  • PublishRange – allows you to publish a collection of correlated data objects that were defined using the ActivityData attribute.

If you didn’t catch the distinction above (it took me a second to realize it the first time I saw these), Publish handles single objects, and PublishRange handles a collection of objects. This interface also gives you the following methods (which I’ll have to detail in an upcoming article):

  • WithFiltering – provides a way to publish data with implicit filtering enabled.
  • LogErrorMessage - Sends an error message to the log file created by the Runbook
  • LogInfoMessage - Sends an informational message to the log file created by the Runbook
  • LogWarningMessage - Sends a warning message to the log file created by the Runbook
  • ReportErrorEvent - Creates an error event in the Orchestrator event log
  • ReportInfoEvent - Creates an informational event in the Orchestrator event log
  • ReportWarningEvent - Creates a warning event in the Orchestrator event log

Wrapping it all Up

If you take anything away from this rather long article it should be that you shouldn’t choose the simple declarative approach using attributes alone, and you can’t use just the imperative approach and use nothing but interfaces. You should be using a hybrid approach that utilizes the attributes when required, but uses the interfaces in all other cases. This approach gives you the most flexibility and functionality when designing custom activities.

Be sure to keep watching the Orchestrator team blog for lots of useful tips and examples on using all of the cool stuff in the SDK to its fullest!