Plugin Development Best Practices

Planning your plug-in

Appian plug-ins allow you to extend the base Appian product with the powerful and specific functionality required by your use cases. When developing your plug-in, it is important to ensure that the planned functionality does not replicate out of the box (OOTB) functionality.

Plug-in fixes and development are not supported by Appian Support, so using OOTB features means that you can get the best support and fixes for your applications.

Sometimes, specific functionality intersects with functionality provided by an existing plug-in. In these instances, instead of developing a completely new plug-in, it is best to add functionality to the existing plug-in. This can reduce duplication, and reduce the development effort required as the new functionality can leverage utilities and work done for the existing component.

Finally, when defining the use case for your plug-ins, it is important to consider how the functionality addressing the use case could be expanded to other, similar use cases with minimal changes and effort. Spending time early in the development cycle reasoning about this can allow plug-ins to be developed against the “generic use case” instead of being tightly coupled to your business specific use case. Plug-ins that apply to the generic use case provide more longevity for your organization, and are more resilient to evolving requirements. Plug-ins that address generic use cases can also be submitted as Shared Components for community use and enhancement.

For more details regarding the different ways to extend Appian and general information about plugins, please reference the Appian documentation - Extending Appian.

Integrating with external systems

Some plug-ins will require integration with other applications and services in order to fulfill your use cases. It is essential to remember the following points when integrating with external services, and to note that they also apply to connections obtained for any database access requirements:

  • All connections opened to external services should be flushed and closed. This prevents resources from becoming over-utilized and assists in keeping your applications stable. If established connections are not released properly, new connections may be refused which can cause a partial outage for that service.
  • The code that interacts with the external services, databases or other items should ensure that it does not allow for external, unintended data injection. For example, a database integration with SQL that is generated from any part of user input should be sanitized, and the statement shouldn’t be assembled purely based on user input. Values that are being passed to other systems should also be sanitized, and the plug-in should not allow any inputs to perform malicious actions against the target system. The best way to achieve this in a database integration is through appropriate use of Java’s PreparedStatement.
  • Results from calls to integration points should not be directly returned as outputs from Smart Service (SS) plug-ins. Doing so without additional parsing can cause process memory usage to be increased unnecessarily which can cause performance and capacity issues in the environment. All results should be parsed to extract only the requisite data which can then be returned as needed. For example, if a SS plug-in invokes some web service returning a JSON response, instead of returning the entire JSON response, return a dictionary/CDT with only the required fields. If returning documents, see below section on “Dealing with Documents”.
  • When reading the response of a call to some external integration point, it is important to ensure that the code does not attempt to read the entire response into memory at once. Doing so can cause performance and stability issues as the application server heap may be exhausted, rendering it unable to handle requests.
  • When using a SS plug-in that wraps an external integration point in your process models, you should ensure that you use a sub-process from the primary process to invoke the Smart Service plug-in. Doing this allows you to easily replace usage of the Smart Service plug-in with OOTB functionality when available, or an updated/different Smart Service plug-in should the need arise. The sub-process reduces the coupling of your primary process on the specific implementation of the Smart Service plug-in, instead, allowing you to control how the functionality is invoked through the configuration of the sub-process.

Dealing with documents

A common use case for many plug-ins is advanced document manipulation and processing that often provides mission critical functionality for business operations. As such, when developing these plug-ins, special care needs to be taken to maintain the integrity of the operation and the system. The key things to keep in mind are:

  • Any files created in temporary file storage (/tmp) should be deleted as soon as they are no longer needed. Failing to do this can exhaust the space available which can cause performance issues and application and platform issues for functionality that relies on being able to create files in this directory.
  • All file and document manipulations should be done using Appian public API’s to identify, locate, load and manipulate the document contents. It is not supported and can be dangerous to load files outside of what is offered through the API. Manipulating files in this way can put the platform into an unsupported state. Document files returned into Appian should be stored in the Appian Document Knowledge Center.
  • Document contents or log file contents should not be returned as the output of a Smart Service shared component, or other plug-in functionality such that it would become stored in an activity class parameter or process variable. Doing so without additional parsing can cause process memory usage to be increased unnecessarily which can cause performance and capacity issues in the environment. This includes documents in a base64 format or other such representations.
  • Document manipulation must not be encoded into expression function plug-ins as expression functions must not have side-effects.

Developing for platform stability and troubleshooting

Plug-ins in Appian are treated in the same manner by the application server as in-platform design objects in terms of execution and resource allocation. This means that a plug-in can cause a poor user experience when it is costly to run, poorly designed, or used incorrectly. Keep the following items in mind when developing your plug-ins:

  • Where possible, use BufferedInputStreams when handling responses obtained from external systems, loading documents, or otherwise processing large amounts of data. BufferedInputStream provides a performance and resource benefit over using standard InputStream based functions.
  • When operating with large sets of data, always use a batching or paged approach to process through subsets of entries. Attempting to load an overly large dataset into memory all at once can cause the application server heap to reach maximum capacity and render the platform unstable.
  • Ensure that the code has a suitable amount of DEBUG and INFO logging statements that capture execution state and operations being executed. Troubleshooting without this logging can be challenging and time consuming. NOTE: INFO statements should be used sparingly and should never be used for debugging output.
  • Exceptions should be caught and handled at appropriate points in the plug-in code. It is poor practice to catch only the top-level exception class and log a generic error. Handling individual exceptions close to where they are thrown allows the developer to log the context of the error and provide suitable information for troubleshooting or other error handling.
  • Ensure that the only logging output that the plug-in generates is performed through the log4j LOG.* method calls. Plug-ins should not print using System.out.println() or exception.printStackTrace().
  • Due to the threaded nature of the Appian Platform, expression functions and Smart Services are required to be developed in a thread safe manner. This is especially important with modern versions of Appian that are equipped with the parallel expression evaluation feature.
  • When using functionality provided by external libraries, it is essential to ensure that the developers understand the limitations imposed by the library, and the impact the library can have on the executing environment. It is especially important to evaluate the library against:
    • Thread safety
    • Ability to scale against its expected usage
    • Any extra resource requirements

Avoiding known vulnerabilities

To keep your Appian platform stable and secure, Appian regularly performs checks on all plug-ins for known vulnerabilities in third party libraries used by the plug-ins. If a vulnerability is uncovered, the corresponding plug-in may be removed from Appian Community until it is fixed.

In order to avoid known vulnerabilities, it is recommended that you use Common Vulnerabilities and Exposures (CVE) detection tools when developing your plug-ins. Tools such as OWASP Dependency Check are designed to detect all publicly disclosed vulnerabilities contained in a project dependencies. Fixing such vulnerability in a dependency library often involves updating to a newer version of said library that has been fixed by the library provider.

Developing a plug-in for your cloud instance

Plug-ins developed with the intention of being deployed on Appian Cloud have some additional restrictions on top of the best practice items outlined here. Please visit the documentation for additional details and guidelines.

Developing a plug-in for the Community

As discussed in the Planning your Plug-in section, a well designed plug-in can extend to use cases beyond that that the plug-in was originally developed for. Suitably generic plug-ins that can address a variety of similar use cases should be considered for release on the Appian Community platform.

Releasing a plug-in to Appian Community means that it will be available as a Shared Component for other practitioners to use as part of their applications. Not only does this raise your own profile among other practitioners, but it allows others to contribute to the development and upkeep of the component, resulting in reduced development efforts and continued improvement of the component.

Find more details about submitting a plug-in as a Shared Component here and here

Updating an existing plug-in

Plug-ins listed on Appian community are community supported. It is encouraged that practitioners maintain and update existing plug-ins as they see fit. Even if a plug-in has originally been developed and released by an Appian partner or by an Appian employee, practitioners can take an active role in fixing and enhancing existing plug-ins based on their needs and requirements.

When updating an existing plug-in, it is critical to keep the following in mind:

  • Backward compatibility. Changes made to the code and logic of a function or smart service should be thoroughly tested to ensure that they are backward compatible and do not break existing applications. If the changes affect backward compatibility, create a new version of the smart service or a new function instead of updating the original one.
  • Deprecation instead of deletion. When creating a new version of a smart service or function, do not delete the current version as this will break existing applications and process models. Instead deprecate the current version by doing the following:
    • Move the current smart service to the deprecated palette or move the current function to the hidden category.
    • Do not change the key of the smart service or function in the appian-plugin.xml file.
    • Create a new smart service or function as described in the documentation.
  • New inputs or outputs. Adding inputs and/or outputs requires the creation of a new version of a smart service. Deprecate the current version, as outlined above.
  • Dependencies update. Updating dependencies must be thoroughly tested to ensure that the code compiled successfully and the outputs of the plug-in remain unchanged.