Document Properties
Kbid2488Z9
Last Modified04-Feb-2020
Added to KB11-Jul-2013
Public AccessEveryone
StatusOnline
Doc TypeGuidelines, Concepts & Cookbooks
Product
  • ICM 7.6
  • ICM 7.7
  • ICM 7.8
  • ICM 7.9
  • ICM 7.10

Guide - Pipelet Style Guide

1 Introduction

This section contains a quick overview of conventions and rules that are outlined and discussed in more detail in the remaining sections of this document. Use this checklist whenever developing / reworking pipelets.

The following things, you should do:

  • Make sure you always use Intershop Studio for editing pipelet descriptors to keep source code and descriptor in sync.
  • Make sure your pipelet name fits the pipelet-naming scheme.
  • Make sure you assigned the pipelet to a meaningful pipelet group.
  • Make sure you configured the correct pipelet transaction mode.
  • Make sure you configured whether or not the pipelet uses an error exit.
  • Make sure your pipelet does not transform strings into object types (i.e. integers, doubles).
  • Make sure you configured required parameters as such in the pipelet descriptor.
  • Make sure your pipelet correctly handles localizable custom attributes.
  • Make sure you provided a meaningful pipelet Javadoc.
  • Make sure you documented the individual I/O parameters.
  • Make sure you documented when the error exit is used (possible exception are the standard pipelets Create..., Get...).

Things you should not do:

  • Never transform strings into object representations (use formatter utility pipelets instead).
  • Do not pass parameters as String, but use the original type (e.g. int).
  • Never access form data in the pipeline dictionary (use form data utility pipelets instead).
  • Never change the pipelet I/O parameter set of existing pipelets in a way that is not backward compatible (see API migration section for rules).
  • Never introduce local pipelet variables that are not accessed in a read only manner (exceptions are caches).
  • Avoid subclassing of pipelets since this introduces unnecessary dependencies (use utility helper classes to extract code shared across different pipelets).
  • Using the error exit is no reason to log an error as it is part of the normal behavior for the pipelet.

2 Pipelet Modeling

To ensure synchronization between pipelet descriptor and implementation (i.e. Java source code), the use of tools is required. Manual pipelet descriptor changes should be avoided at all costs. The recommended tool for pipelet modeling and editing is Intershop Studio.

3 Pipelet Naming

3.1 Pipelet Class Naming

It is recommended to use a consistent naming scheme for pipelets that indicates the purpose of a pipelet based on its name.
The naming conventions outlined below are by no means complete but apply to about 80% of all cases.
The pipelets used most frequently are those managing the life cycle of persistent objects. The following naming scheme is recommended for the following pipelets:

  • Create<Object>
    Creates a new instance of the specified business object based on some sort of identification criteria, i.e., primary key attributes, retrieved from the pipeline dictionary. This pipelet should focus on object creation with the primary key attributes and other mandatory attributes. The pipelet should not take care of completely configuring the business object with all optional parameters. This should be done by a subsequent update pipelet. The pipelet should always provide an error connector that is used whenever the business object could not be created.
  • Update<Object>
    Updates a business object instance (available in the pipeline dictionary) with some new attributes (available in the pipeline dictionary). This pipelet should not have an error connector and should gracefully ignore errors that might come up when updating the object attributes. Update pipelets will usually not care about custom attributes. These are to be handled by a generic pipelet provided by the platform.
  • Delete<Object>
    Deletes a business object based on some sort of identification criteria, which is usually retrieved from the pipeline dictionary. The pipelet should not have an error connector and should gracefully ignore errors during the deletion process, e.g., a missing object.
    Once business objects have been created, it is essential to have a powerful means to identify them and look them up in other business processes. The following pipelets should be provided to accomplish this.
  • Get<Object>ByUUID
    Tries to identify a single object instance based on its primary key (i.e. the UUID for all subclasses of PersistentObject).
    The UUID is retrieved from the pipeline dictionary. The pipelet uses the error exit in case no object with the specified UUID could be found.
  • Get<Object>By<Attr>
    Tries to identify a single object instance based on some attribute(s) (that is not the primary key). The identifying attribute(s) are retrieved from the pipeline dictionary. Get pipelets always provide an error connector that is used whenever the requested business object could not be found.
  • Get<Object>By<Object>
    Refers to the same concepts as the Get<Object>By<Attr> pipelet. The only difference is that here another, usually associated, object is used for the lookup.
  • Get<Object>sBy<Attr>
    Tries to identify a single object or a collection of objects based on an attribute (which is not the primary key). The identifying attributes are retrieved from the pipeline dictionary. This pipelet is important whenever lists of business objects need to be processed, e.g., all departments of an organization. The pipelet does not provide an error connector. Instead, empty iterators are stored in the pipeline dictionary if errors occurred or empty result sets were returned.
  • Get<Object> s By<Object>
    Refers to the same concepts as the Get<Object>sBy<Attr> pipelet. The only difference is that here, another, usually associated, object is used for the lookup.

Beside standard life cycle and lookup pipelets, pipelets are required that do some actual processing on the business objects of the pipeline dictionary. The naming scheme for these pipelets depends on the type of processing done by the pipelet.
Below are some examples and recommendations that might be used as guidance:

  • Calculate<Object>
    Is used whenever some sort of calculation is done on the object. This usually leads to updates on the object attributes. This pipelet requires that the object in question was identified by an appropriate lookup pipelet before.
  • Process<Object>
    With this pipelet, some sort of processing takes place on the object that was identified previously by a lookup pipelet.

3.2 Pipelet Group Naming

Pipelet groups do not have a real function when it comes to pipelet or pipeline processing. They are purely declarative and are used by the Visual Pipeline Manager for an alternative grouping of the whole pipelet set. The feature is designed to help developers locate all pipelets that do something with certain business objects without looking at their name.

The VPM pipelet grouping only works successfully when all pipelets are categorized within a small but meaningful set of pipelet groups. Too many groups are less helpful than too few groups.

  • There should be at least one pipelet group for each business component used in a solution. One group is sufficient for smaller components that do not define many new business objects. For example, it is probably sufficient to have one pipelet group for the approval component. The group contains all approval process-related pipelets.
  • For larger components with lots of business objects, there should be more than one group. For such components, groups' names should be chosen that relate to the business objects being processed. There should, at least, be groups for pipelets that deal with departments, cost centers, budgets and organizations.
  • If pipelets extend platform functionality, e.g., additional user management functions, use the name of the pipelet groups that are used in the platform. This ensures that the pipelets show up in the appropriate section in the VPM even though they come from a different cartridge.
  • There should be exactly one pipelet group per project that holds all project-specific pipelets. No business pipelets should be assigned to this group. Usually, only a few presentation-related pipelets fall into that group.
  • Assign obsolete pipelets to a special pipelet group named "Deprecated" to indicate that support for the pipelet is not going to be continued for the next product releases.
  • Avoid having generic pipelet group names such as Common, Misc or Util.

Note that pipelet group names should start with a capital letter and consist of a single word only.

3.3 Pipelet Parameter Naming

Pipelet parameter names and types are an essential part of the pipelet API because they basically make up the pipelet signature. Whenever a pipelet name does not allow developers to infer the pipelet's meaning at first glance, the pipelet parameters will. Choosing an appropriate set of pipelet parameters is essential for pipelet reuse.
Pipelet parameter names need to make sense for the pipelet that uses the parameter. When choosing a name, do not focus on the pipeline the pipelet might be used in. Due to the dictionary aliasing feature of Intershop 7, there is no need to synchronize the pipelet parameter names of all pipelets that are used in a single pipeline. This gives more freedom to the developer for choosing semantically meaningful names. Below are some general parameter naming conventions that are mandatory:

  • Pipelet parameter names should always start with a capital letter. The general naming scheme follows the one for Java classes. Consequently, User, UserGroup, Basket are valid while usergroup, userGroup and BASKET are invalid.
  • Pipelet parameter names are usually named like the business object type the parameter refers to. For example, a parameter that reads a Basket instance form the dictionary should be named like the class: Basket.
  • Pipelet parameters for reading identification information should be named with the class name of the identified business object with an "ID" suffix. For example, a pipelet for looking up baskets by domain should have a parameter DomainID.

As a general rule, pipelet parameter sets should be designed in a way that good interoperability between the different pipelet groups, e.g., life-cycle, lookup, processing, is ensured. There is, for example, no point in having ID attributes in processing pipelets. Instead, processing pipelets should read business object instances from the pipeline dictionary that have to be provided by the lookup pipelets. The benefit is that you do not need to replicate the code required for retrieving the instances of business objects throughout all pipelets.
Although pipelet parameter naming is done with a pipelet focus only, there are parameters that are shared by pipelets and pipelines. To ease development, you should access and store standard objects with standard parameter names.
The following parameter names should be used for standard platform objects:

  • Domain
  • User
  • UserGroup
  • Permission

When accessing platform parameters, it is always a good idea to take a look at the naming conventions used in the platform. Although the conventions are not as strict as suggested here, they might still provide some guidance.

4 Pipelet Parameter Types

Like parameter names, parameter types are part of the pipelet's API. Hence, correct and consistent usage of parameter types is important. The following rules apply:

  • Pipelet dictionary I/O parameters should refer to CAPI interfaces only. A pipelet should never declare an object located in the internal packages as input or output type.
  • Pipelet dictionary I/O parameter representing primitive types should be read from the pipeline dictionary using their Java object representation (i.e. Integer, Double, String, Money, Quantity, BigDecimal, ...).
  • Pipelet configuration parameters always need to be declared as primitive types ( int, double, string).

In case a pipelet consumes numeric data (e.g. numeric type codes, dates, money values, ...) it must use the appropriate Java object types. A pipelet must not read numeric information as string and convert them internally into the corresponding object type (e.g. by using Integer.valueOf()). There are two very important reasons for this rule:

  • Localization
    Using standard string -> object conversion methods such as Integer.valueOf() fails as soon as the application supports multiple locales. In such a scenario, users will provide numeric input based on their locale setting (e.g. "20.50" for English and "20,50" for German). Pipelets that use the standard methods conversion will fail with a NumberFormatException.
  • Reusability
    Declaring numeric input as strings reduces reusability of the pipelets in other environments such as Web services and jobs. Retrieving string based numeric data from an HTTP/HTML based application is just one (very common) environment for a pipelet. However, suppose you want to use the same pipelet for implementing Web services. There is no point in transforming an integer retrieved from a Web service call into a string, which will then again be transformed to an integer within the pipelet.

If numeric data is posted as string content from a template (e.g. an HTTP POST of an update form) it needs to be converted using one of the following formatter utility pipelets before it can be used:

  • VerifyBoolean
  • VerifyInteger
  • VerifyDouble
  • VerifyDate
  • VerifyMoney
  • VerifyQuantity
    This rule ensures that localization support can be centralized in the formatter pipelets instead of being replicated into all business pipelets.
    As a special case, we often have to implement pipelets which need to support multiple types for a given input parameter. Consider the following wrong (but commonly used) example:
Wrong
UpdateCustomAttribute | <IN> Object : ExtensibleObject |
<IN> Locale : LocaleInformation | <IN> AttributeName : String |
<IN> AttributeType : String | <IN> AttributeValue : String

In this example, the attribute value needs to support string, integer and double. The (wrong) pipelet implementation reads those values as string and internally converts them by evaluating a numeric type code (that is also provided as a string).
This is very bad practice, due to the dangerous internal conversion of strings (see the discussion on localization issues above) and the limited reusability of the pipelet. A better API version of this example pipelet would look like this:

Correct
UpdateCustomAttribute | <IN> Object : ExtensibleObject |
<IN> Locale : LocaleInformation | <IN> AttributeName : String |
<IN> AttributeValue : Object

Note that the pipelet does not read a numeric type code any more. Instead, the pipelet reads the attribute value as an object. The implementation will use the instanceof operation to check for supported types (i.e. String, Integer, Double) and call the appropriate ExtensibleObject method. In an HTTP/HTML environment where the values are always posted as strings, an additional decision node should be used to evaluate the submitted type code in order to decide which conversion utility pipelet should be used. The following simplified pipeline view summarizes this approach.

Note that the type checks in this sample pipeline are just examples. In real pipelines, the type codes are likely read from somewhere else (i.e. from a webform or a form record).

5 Pipelet Parameter Fallbacks

Pipelets are supposed to be reusable across pipelines and even different projects. It is therefore important to design pipelets to be more generic than it might be required in the context of a certain use case. One important feature of generic pipelets is the support for statically configured configuration parameters that are used as fallbacks in case a declared I/O parameter is not available in the pipeline dictionary.
This issue is best discussed with an example. Consider a pipelet that can be used to remove a specified custom attribute for a provided ExtensibleObject instance.

Wrong
RemoveCustomAttribute | <IN> Object : ExtensibleObject | <IN> Locale : LocaleInformation |

For the original use case, the pipelet received the name of the attribute to be removed from the pipeline dictionary (i.e., the user selected the attribute to be removed explicitly). However, it is also perfectly possible that the pipeline designer needs to remove a custom attribute independently of the user interaction. Therefore, the attribute name should alternatively be configurable in the pipelet configuration.

Correct
RemoveCustomAttribute
| <CONFIG> DefaultAttributeName : String
| <IN> Object : ExtensibleObject
| <IN> Locale : LocaleInformation
| <IN> AttributeName : String

The pipelet implementation would select the attribute name to be used such that the pipeline dictionary value always takes precedence:

public void init()
throws PipelineInitializationException
{
cfg_defaultAttributeName = (String)getConfiguration(). ...
get("DefaultAttributeName");
...
}
public int execute(PipelineDictionary dict)
throws PipeletExecutionException
{
// lookup AttributeName in pipeline dictionary
String attributeName = (String)dict.get(IO_ATTRIBUTENAME_NAME);
//{{ execute
String attrName = attributeName != null
? attributeName
: cfg_defaultAttributeName;
if(attrName == null)
{
throw new PipeletExecutionException( ...
"Mandatory input parameter 'AttributeName' not available.");
}
...

This is a pattern that can be applied to many pipelets. All pipelet input parameters for which a static configuration at the pipeline level does make sense should complement the dictionary input parameters with appropriate 'Default' configuration parameters.
The following specific rules apply:

  • The name of configuration parameter is derived from the dictionary input parameter by adding the prefix 'Default'.
  • Parameter types of the input and configuration parameter need to match (i.e. String-->String, Integer{-}->int, Double-->double).
  • The dictionary input parameter always takes precedence over the configuration parameter.
  • Both parameters need to be declared as optional. For required parameters the existence check is implemented along with the parameter selection (see code sample above).
  • The parameter fallbacks only make sense for primitive input types that can be provided by the pipeline designer (i.e. numeric type codes, string IDs but no UUIDs or dates).
  • To enforce that a default value is used it must be ensured that the dictionary input returns null. Use the alias 'NULL' for the input parameter to insure the input value is null.

6 Pipelet Form Data Access

Pipelets must not make any assumptions about the environment they are used in. To be reusable across different projects, a pipelet must not make any assumptions about the UI that triggers the pipelet's execution. To be reusable in job- or web service-driven implementations, a pipelet must not even assume that there is an HTTP request triggering the pipelet.
It is therefore forbidden for pipelets to directly access HTTP form data stored in the pipeline dictionary. The following pipeline dictionary API calls should not be used:

Iterator createFormKeyIterator()
Iterator createFormPrefixedKeyIterator()
String getFormValue(String)
String getFormValues(String)
void removeFormValues(String)
void setFormValue(String,String)
void setFormValues(String,String)

When implementing pipelines for an HTTP/HTML based application, the following form handling utility pipelets should be used to access the form data and provide it in a controlled way for all other pipelets.

GetFormSelection
GetFormRecord
CreateWebForm
GetWebForm
UpdateWebForm
ValidateWebForm
UpdateClipboard
GetClipboard

7 Pipelet Error Handling

A consistent handling of errors across different pipelets is another important aspect of our pipelet API. The following basic rules apply:

  • Pipelets are supposed to throw a PipeletExecutionException in case a required input parameter is not available in the pipeline dictionary.
  • Pipelets are supposed to throw a PipelineInitializationException in case a required configuration parameter is not available.
  • Pipelets should not catch RemoteException, RuntimeExceptions and Throwable in their execution body.
  • Pipelets should deal with CreateException, FinderException, RemoveException and any other custom business exceptions (in case these exceptions are declared at the manager level) by using the error exit (if the pipelet declares one).

To decide whether a certain pipelet should declare an error exit at all, refer to the discussion in the pipelet naming scheme section ( #Pipelet Naming).
Even though pipelets often need to indicate detailed error information by storing certain error codes in the pipeline dictionary, this style guide does not provide a naming scheme for error codes, since this is an issue that needs to be addressed and better supported by the platform. In the meantime, error codes should be avoided (if possible) within pipelets. Instead use utility pipelets like SetDictionaryValue that should be connected to the error exit of pipelets to record a custom error code. Note that this only works for pipelets that can experience only one error situation. If it is required to return an error code from the pipelet, you should declare an output parameter ErrorCode and return a human-readable error name.

Disclaimer

The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.

Customer Support
Knowledge Base
Product Resources
Tickets