Skip to main content

Services

Services enable modules to inject custom logic into JDito processes without overriding them directly. They complement Extension Points (for data model extensions) and are ideal for reusable logic that must run across multiple modules or at specific runtime hooks such as insert, update, or delete.

tip

Services are the safest way to add and preserve new functions and their configurations.

There are two service scopes:

  • Global Services: Available throughout the project
  • Entity Services: Scoped to a specific entity and only available there

Before creating a new service, you should consider if this service is needed in only one entity or in multiple global processes or entities. If your service is only needed in one entity, consider using an Entity Services. Otherwise, you need to use a Global Service.

Terms

  • Service Definition: A named collection that groups multiple service implementations. This name is used to load all implementations.
  • Service Implementation: A single piece of JDito logic that belongs to a service definition.

Benefits

  • Encapsulates reusable logic across modules
  • Reduces the need for process overrides
  • Provides clean injection points in shared processes
  • Enables dynamic extension without breaking modular separation

Typical use cases:

  • Any process logic where multiple modules might need to contribute to.
  • onDBInsert, onDBUpdate, onDBDelete
  • Dependency calculation (e.g., Dependency.mapping)
  • Validation, enrichment, and transformation

Defining and Implementing Services

Global Services

Create global services under the service node in the Designer.

serviceImplementationGlobal
Figure: Global Service with two implementations

Entity Services

Entity Services are defined within an entity and are only available there. Creation and implementation follow the same procedure as Global Services.

serviceImplementationEntity
Figure: Multiple Entity Services inside a specific entity

Creating a Service Definition

  1. Right-click the service folder → "New"
  2. Select type service and use a name using the pattern lowerCamelCase_service

The Service Definition is now created and can be referenced in the process.

tip

Consider adding documentation to the Service Definition describing how it should be used across different packages.

Adding an implementation

note

You do not need to add an implementation in the same module as the service definition. If you want, you can add an implementation in the same module as the service definition.

You should be aware that service implementations can be added from other modules.

Steps:

  1. Right-click on the desired service → Add Implementation
  2. Name the implementation using the pattern lowerCamelCase_impl
  3. Provide the logic in the process property
note

ESLint or TypeScript may warn about the returned function. If necessary, place //@ts-ignore directly before return.


Calling Services

Use modules.loadService("serviceName_service") to retrieve all implementations.

This use case is the same for Global and Entity Services.

Example: Calling a Service with no parameters

Example process where a service is loaded
import { modules } from "@aditosoftware/jdito-types";

modules.loadService("serviceName_service").forEach(impl => impl());
info

At transpile time, each implementation becomes a generated function. The transpiler replaces loadService(...) with an array of those functions.

The generated result during transpilation:

transpiled process.js
import { modules } from "@aditosoftware/jdito-types";

[_____GENERATED_serviceName_service_first_impl, _____GENERATED_serviceName_service_second_impl].forEach(impl => impl());

function _____GENERATED_serviceName_service_first_impl()
{
console.log('hello from first implementation');
}

function _____GENERATED_serviceName_service_second_impl()
{
console.log('hello from second implementation');
}

As you can see, all generated functions are added to your process.js file with its content.


Example: Calling a service with parameters

If implementations need parameters, each implementation must return a function which is then invoked with the parameters.

Example process where a service is loaded with parameters
import { modules } from "@aditosoftware/jdito-types";

modules.loadService("myService_service").forEach(impl => impl()(param1, param2));

Implementation:

Service implementation with parameters
let fn = (p1, p2) => {
// logic
};

//@ts-ignore
return fn;

Usage Example of Services: AttributeUtil

This example demonstrates the benefits of using Services in a process. You can find this pattern in any xRM project.

Before Modularization

Previously, Attribute_lib contained a single array with getPossibleUsageContexts. This contained all contexts that are usable for attributes.

Whenever a new suitable context was added, the function had to be edited.

Attribute_lib.js before modularization
AttributeUtil.getPossibleUsageContexts = function() 
{
return [
"Organisation",
"Person",
"Contract",
"Product",
"Activity",
"Offer",
"Order",
"Employee",
"Salesproject",
"Campaign",
"DocumentTemplate",
"SupportTicket",
"Leadimport",
"ImportField",
"DSGVO"
];
}

After Modularization

The return function is now a simple call to modules.loadService and a simple object merge.

AttributeUtil.getPossibleUsageContexts = function() 
{
return modules.loadService("attributeUsageContexts_service")
.reduce((contextAccumulator, nextContextsFn) => contextAccumulator.concat(nextContextsFn()), []);
};

The global service attributeUsageContexts_service contains all implementations with possible contexts, and each module contributes any number of implementations that return their portion of the mapping.

services-attributeUsageContexts_service.png
Figure: Service Implementation for attributeUsageContexts_service

A single implementation looks like this:

offer_impl.js
//@ts-ignore
return [
"Offer"
];

As a result, no one needs to modify the lib, but can contribute a Service Implementation to add a new context. Each module can contribute its own implementation independently.