IP 开发最佳做法:使用声明性方法、命令性方法还是同时使用?
当我开始使用 Opalis(现在称 Orchestrator)和 SDK 之初,就知道了可以使用声明性方法或命令性方法创建活动(和集成包)。据称,这是通向同一最终目标的两个相互排斥的途径。随着我越来越多地使用 Orchestrator,并且我现在负责作为此产品功能之一的 SDK,我对它有了更深入的了解,而且还了解到并非一切都总是像宣传的那样。
作为“OIT 人”,内部和外部人员经常问我这样的问题:创建新活动的最佳做法是什么?是使用声明性方法、还是命令性方法,或者混合使用这两种方法?好,这个问题的答案是“要看具体情况”(我希望您知道我会这样说)。对于某些人而言,使用声明性方法带一些特性和静态定义的输入和输出就足够了。对于其他人而言,他们需要更动态的活动,更喜欢命令性方法。命令性方法还为您提供使用声明性方法无法访问的其他功能。同样,还有一些仅使用命令性方法无法访问的情况,因此,如果希望能够使用 SDK 中的所有功能,最终还需要使用混合方法。
注意: 要了解使用这两种方法创建活动的基础知识,请参阅 MSDN 上的下列页面:
- Orchestrator SDK 入门(英文)
- 采用命令性方法(英文)
另外,您还可以在 MSDN 上的以下位置找到 SDK 类的完整描述:Microsoft.SystemCenter.Orchestrator.Integration
应理解在创建自定义活动中应在何处使用哪种类型的模型,它可以帮助您清楚地划定每种模型的功能。此类比较实际上并不是随处可见,因此我想为大家创建一个。我将从活动本身开始。
活动
每个活动都封装在设计时显示属性和在运行时执行活动的操作所需的所有功能。
功能 |
[Activity] 特性 |
IActivity 接口 |
提供活动的名称 | 在特性的第一个字符串中指定。例如:[Activity ("Hello World") ] 还可以使用:[Activity (Name="Hello World") ] 如果未指定任何名称,将使用该类的名称。 | 无等效项 |
提供活动的描述 | [Activity ("Hello World", Description=”The activity description goes here”) ] | 无等效项 |
显示 [Filters](筛选器)选项卡 | [Activity ("Hello World)", ShowFilters=false] (默认值为 True) | 无等效项 |
显示 [Properties](属性)选项卡 | [Activity ("Hello World)", ShowInputs=false] (默认值为 True) | 无等效项 |
将输入属性添加到活动表以显示给用户 | 使用公共属性上的 [ActivityInput] 特性。这些是静态定义的属性,将始终向用户提供。 | 结合使用 Design() 方法与传递到该方法的 IActivityDesigner 类型参数动态添加以下各项:通过 AddInput() 方法添加输入,通过 AddOutout() 方法添加输出,并通过 AddFilter() 方法添加筛选器。 还可以通过特性添加静态定义的输入、输出和筛选器 |
将输出属性添加到活动表以用作发布的数据 | 使用公共属性上的 [ActivityOutput] 特性。这些是静态定义的属性,将始终向用户提供。除非使用 [ActivityMethod] 方法,否则该属性的 getter 必须包含处理输入和派生输出所需的代码。 | |
将筛选器添加到活动表以显示给用户并筛选输出数据 | 使用公共属性上的 [ActivityFilter] 特性。这些是静态定义的属性,将始终向用户提供。 | |
在运行时处理活动中的数据 | 将一种或多种方法与 [ActivityMethod] 特性结合使用。多个实例的运行顺序是不确定的,因此最好有一个 ActivityMethod 并从这里调用其他方法。 | 使用 Execute() 方法在活动内运行代码并运行其他方法。使用 IActivityResponse 接口的 Publish() 方法选择哪些数据作为发布的数据输出。 |
我实际上在上述第一部分给您抛了一个难题。[Activity] 特性和 IActivity 接口实际上并不是类似的实体。您无法选择是否使用 [Activity]。您必须使用它。 唯一的方法是定义您创建的类实际上是一个活动。问题是您是否也从 IActivity 继承您的类。如果是,那么现在必须实现 Design() 和 Execute() 方法,而不必更改任何内容。如果愿意,仍可以拥有静态定义的输入、筛选器和输出,但现在您可以使用不同的方法定义自己的属性,并将不同的内容显示给用户,这是仅使用 [Activity] 特性所无法做到的。
因此,对该活动的总结如下:
必须始终使用 [Activity] 特性。
作为最佳做法,我建议您始终在特性中使用所有属性,以便阅读该代码的所有人明确知道该活动将显示给用户。例如:
[Activity (Name="Hello World”", Description=”The activity description goes here”, ShowInputs=true, ShowFilters=false) ]
作为最佳做法,我建议您始终从 IActivity 继承,即使不准备使用此功能。如果始终这样做,则在以后决定回过头来向现在需要从 IActivity 继承的活动添加一些功能时,就不必更改一大堆东西,这是因为您需要切换到该接口。
输入属性
输入属性是在 [Properties](属性)选项卡上显示给用户的项,或者作为可选属性在用户单击 [Optional Properties](可选属性)按钮时显示。属性只能通过一种方式显示(在测试框中),但可以通过简单的键入值、发布的数据订阅或通过某种浏览器显示该值。浏览器类型和可用功能取决于您定义和向该活动添加属性的方式,要么使用 [ActivityInput] 特性,要么通过 IActivityDesigner 接口的 AddInput() 方法。
功能 | [ActivityInput] 特性 |
AddInput 方法 |
设置输入属性的名称 | 在特性的第一个字符串中指定。例如:[ActivityInput ("HWInput)") ] 还可以使用:[ActivityInput (Name="HWInput)") ] 如果未指定任何名称,将使用该类的名称。 | AddInput(“HWInput”) |
设置输入属性的描述 | [ActivityInput ("HWInput)", Description=”The property description goes here”) ] | 无等效项 |
设置输入属性的默认值 | [ActivityInput ("HWInput”, Default=”Hello World”)] | AddInput(“HWInput”).WithDefault(“Hello World”) |
将该属性定义为可选属性 | [ActivityInput ("HWInput", Optional=true)] (默认值为 False) | AddInput(“HWInput”).NotRequired() |
将该属性定义为受密码保护(隐藏/加密) | [ActivityInput ("HWInput", PasswordProtected=true)] (默认值为 False) | AddInput(“HWInput”).PasswordProtect() |
为该属性提供一个带浏览器的列表,并添加一个要显示的值列表 | [ActivityInput("Source Location", Default = "Production Server", Options = "Production Server, Development Server")] | AddInput(“HWInput”).WithListBrowser("Production Server”, “Development Server") .WithDefault(("Source Location") |
为该属性提供一个带浏览器的列表,并通过字符串数组或方法添加一个值列表 | 无等效项。特性中的选项必须是常量,不能是变量。 | AddInput(“HWInput”).WithListBrowser(myStringArray) 或 AddInput(“HWInput”).WithListBrowser(getValues()) |
为该属性提供一个枚举浏览器来查找枚举值 | 无等效项 | AddInput(“HWInput”).WithEnumBrowser(enumType) |
为该属性提供一个布尔参数 | 您可以通过提供选项值“True”和“False”模拟此布尔值,但它不是真正的布尔值类型属性。 | AddInput(“HWInput”).WithBooleanBrowser() |
为该属性提供一个计算机浏览器来查找计算机名称 | 无等效项 | AddInput(“HWInput”).WithComputerBrowser() |
为该属性提供一个文件浏览器来查找文件名称 | 无等效项 | AddInput(“HWInput”).WithFileBrowser() |
为该属性提供一个文件夹浏览器来查找文件夹名称 | 无等效项 | AddInput(“HWInput”).WithFolderBrowser() |
为该属性提供一个日期/时间浏览器来输入一个适当格式化的日期/时间值 | 无等效项 | AddInput(“HWInput”).WithDateTimeBrowser() |
从上文看出,使用命令性方法唯一放弃的就是为输入属性指定描述的能力。假设我尚未发现您实际上可以在何处看到显示的描述,我要说没有该功能非常好。通过使用命令性方法获得的功能要强大得多。通过不使用该特性,您现在能够使用从枚举、变量甚至直接从方法获得的值动态填充列表。还有多个该特性没有的新浏览器类型,包括布尔、文件、文件夹、计算机和日期/时间。
无法使用命令性方法定义属性的唯一情况是使用 ActivityData 特性定义类中属性的结构/组。当然,如果我可以想出一个办法在 ActivityData 类中使用动态输入,肯定会让您知道
对输入属性,我的建议是:
- 应始终使用命令性方法定义各个属性。您几乎什么也没有失去,并可以获得许多功能。
- 如果需要对属性分组(对于相关的输出数据),则需要使用 ActivityData 特性,因此必须对这些属性使用 ActivityInput 特性,但这通常仅用于定义活动的配置数据的属性组。
筛选器属性
筛选器属性在 [Filters](筛选器)选项卡上显示给用户,并代表一种限制从该活动返回的输入的方法。它们的用法与输入属性非常类似,不同的是需要定义比较器(关系),以便用户可以通过某种方法比较输出与他们定义的值。
功能 | [ActivityFilter] 特性 |
AddOutput 方法 |
设置筛选器的名称 | 在特性的第一个字符串中指定。例如:[ActivityFilter(“HWFilter”) ] 还可以使用:[ActivityFilter (Name="HWFilter ") ] 如果未指定任何名称,将使用该类的名称 | AddFilter(“HWFilter”) |
设置筛选器的描述 | 无等效项 | 无等效项 |
为该筛选器提供一个带浏览器的列表,并添加一个要显示的值列表 | [ActivityFilter(“HWFilter”, Default = "Production Server", Options = "Production Server, Development Server")] | AddFilter(“HWFilter”).WithListBrowser("Production Server”, “Development Server") .WithDefault(("Source Location") |
为该筛选器提供一个带浏览器的列表,并通过字符串数组或方法添加一个值列表 | 无等效项。特性中的选项必须是常量,不能是变量。 | AddFilter(“HWFilter”).WithListBrowser(myStringArray) 或 AddFilter(“HWFilter”)..WithListBrowser(getValues()) |
为该筛选器提供一个枚举浏览器来查找枚举值 | 无等效项 | AddFilter(“HWFilter”).WithEnumBrowser(enumType) |
为该筛选器提供一个布尔参数查找 | 您可以通过提供选项值“True”和“False”模拟此布尔值,但它不是真正的布尔值类型属性。 | AddFilter(“HWFilter”).WithBooleanBrowser() |
为该筛选器提供一个文件浏览器来查找文件名称 | 无等效项 | AddFilter(“HWFilter”).WithFileBrowser() |
为该筛选器提供一个文件夹浏览器来查找文件夹名称 | 无等效项 | AddFilter(“HWFilter”).WithFolderBrowser() |
为该筛选器提供一个日期/时间浏览器来输入一个设置适当格式的日期/时间值 | 无等效项 | AddFilter(“HWFilter”).WithDateTimeBrowser() |
定义可用于筛选器的关系类型 | ActivityFilter(“HWFilter”, Relations = Relation.Before | Relation.After) | AddFilter(“HWFilter”).WithRelations(Relation.Before | Relation.After) |
与输入属性类似,AddFilter() 方法不允许您定义筛选器的描述,但你猜发生了什么,…特性也不允许!因此,对于筛选器,通过使用命令性方法您绝对没有失去任何功能,与输入类似,您通过列表浏览器获得了灵活性并获得了其他类型的浏览器。
对于筛选器,我的建议是:
- 应始终使用命令性方法来定义各个筛选器。您什么也没有失去,并获得了许多功能。
- 如果需要对属性分组(对于相关的输出数据),则需要使用 ActivityData 特性,因此必须对这些属性使用 ActivityInput 特性,但这通常仅用于定义活动的输出数据的属性组。
输出属性
输出属性用于将发布的数据发送到数据总线。输出属性必须在设计时定义,否则将无法在 Runbook 的后续活动中从该活动访问发布的数据。
功能 | [ActivityOutput] 特性 |
AddOutput 方法 |
设置输出属性的名称 | 在特性的第一个字符串中指定。例如:[ActivityOutput("HWOutput") ] 如果未指定任何名称,将使用该类的名称。 | AddOutput(“HWOutput”) |
设置输出属性的描述 | [ActivityOutput("HWOutput", ”The property description goes here”) ] | AddOutput(“HWOutput”).WithDescription(=”The property description goes here”) |
将输出属性定义为日期/时间值 | 无等效项 | AddOutput(“HWOutput”).AsDateTime() |
将输出属性定义为数字值 | 无等效项 | AddOutput(“HWOutput”).AsNumber() |
将输出属性定义为布尔值 | 无等效项 | 无等效项 |
将输出属性定义为字符串值 | 无等效项 | AddOutput(“HWOutput”).AsString() |
将输出属性定义为筛选器值 | 将 [ActivityFilter] 特性添加到该属性。(参见下文) | AddOutput(“HWOutput”).WithFilter() 或 AddOutput(“HWOutput”).WithFilter(relation) 注意:使用此方法,您无法添加可以使用 AddFilter() 添加的所有功能。 |
与其他两种方法不同,AddOutput() 方法则允许您为输出属性定义描述。因此,对于输出属性,使用命令性方法绝对没有丢失任何功能。由于没有任何浏览器与输出属性关联,因此您没有获得任何功能,但确实获得了专门定义输出类型的能力,使用此特性则无法做到。例如,“1”应该是字符串还是数字?您可以使用命令方法对其进行定义。因此,命令性方法在灵活性和功能方面再次显示了它的优势。
对于输出属性,我的建议是:
- 应始终使用命令性方法定义各个属性。您什么也没有失去,并获得了额外的功能。
- 如果需要对属性分组(对于相关的输出数据),则需要使用 ActivityData 特性,因此必须对这些属性使用 ActivityOutput 特性,但这通常仅用于定义活动的输出数据的属性组。
无命令性等效项的特性
下面显示了没有可用接口等效项的特性,因此,如果需要该功能,必须使用此特性。
[ActivityMonitor]
此特性用于定义监视器而不是正常活动。与 [Activity] 特性类似,必须使用此特性定义实际的活动类。您仍可以从 IActivity 继承该类以获得动态输入和输出的好处。
[ActivityConfiguration]
此特性用于定义活动的配置设置。它们是显示在 [Options](选项)菜单和指定配置值的活动顶部的项目。
[ActivityData]
此特性用于定义组合在一起的输入、输出和/或筛选器类型的特殊类。在活动内部使用。它们经常在配置中使用,以定义一组与配置关联的属性。要将此组属性用作输出数据,必须使用 IActvity/IActivityDesigner 接口和 AddCorellatedData() 方法。注意,无法在此特性化类中定义动态属性,因此,如果希望组合属性和关联数据,则需要静态定义输出。
[ActivityMethod]
此特性用于定义一个或多个将在活动运行时调用的方法。它们的调用顺序不确定。最相关的方法将是 IActivity 接口中的 Execute() 方法,该方法始终在运行时调用。注意:您可以定义没有任何 ActivityMethod 方法的活动,但它们要么在输出属性的 getter 中具有执行逻辑,要么需要从 IActivity 继承并具有 Execute() 方法
无特性等效项的接口/方法
IActivityDesigner.AddCorrelatedData()
如上所述,您使用 ActivityData 特性来指定包含多个属性(如输出)的类,但为了将该数据用作发布的数据,需要使用类似表格的格式将其输出,这需要 AddCorellatedData() 方法,该方法仅在您的活动从 IActivity 继承并且位于 Design() 方法中才可用,该方法采用类型为 IActivityDesigner 的参数。
IActivityRequest
此接口实际上仅在从 IActivity 继承的活动中定义一个 Execute() 方法的输入参数。决不会直接使用此接口创建另一扩展类。不过,我提到它是因为您可以在 Execute 方法中使用该接口在运行时检查与该活动关联的输入和筛选器的值。
IActivityResponse
与 IActivityRequest 类似,您将无法从该接口继承,如果您的活动从 IActivity 继承,则将使用它,因为 Execute() 方法有一个此类型的参数,允许您作为该活动运行时执行的一部分将数据从中发布出去。您始终可以使用许多方法来发布数据,而且有大量的方法在处理错误和向用户报告错误方面确实非常有用。以下内容特别重要:
- Publish - 允许您发布使用 ActivityData 特性定义的单一值、单个相关的数据集,或 Dictionary 对象中名称/值对集合。
- PublishRange – 允许您发布使用 ActivityData 特性定义的关联数据对象的集合。
如果您没有发现上述区别(我第一次看到它们时用了一些时间才发现),Publish 处理单个对象,PublishRange 处理对象集合。此接口还为您提供以下方法(我将在后续文章中详细介绍):
- WithFiltering – 提供一种方法发布启用隐式筛选的数据。
- LogErrorMessage - 向 Runbook 创建的日志文件发送错误消息
- LogInfoMessage - 向 Runbook 创建的日志文件发送信息性消息
- LogWarningMessage - 向 Runbook 创建的日志文件发送警告消息
- ReportErrorEvent - 在 Orchestrator 事件日志中创建错误事件
- ReportInfoEvent - 在 Orchestrator 事件日志中创建信息性事件
- ReportWarningEvent - 在 Orchestrator 事件日志中创建警告事件
结束语
如果您从这篇相当长的文章中吸收到任何内容,则不应单独使用特性选择简单的声明性方法,并且不能仅使用命令性方法和仅使用接口。在需要时,应使用能够利用这些特性的混合方法,但在所有其他情况下使用接口。此方法在设计自定义活动时为您提供了最大的灵活性和功能。
请经常关注 Orchestrator 团队的博客,以获取许多有用的提示和示例,了解如何使用所有这些巧妙的技术来充分利用 SDK!