Implement domains

External entities

The external entity script contains 3 parts:

Constructor

The constructor is used to construct a local instance of the external entity and initialize the associated properties with certain pre-specified values that are provided as input properties.

//**
* constructor method of external entity reference
*/
public create(): void {
   const log = this.util.log;
   log.debug('Constructor.execute()');

   // Initialize the value of the associated properties to the input value store in the construc-tor parameter and to a predefined value
   this.instance.associatedProperty1 = this.input.constructorProperty1;
   this.instance.associatedProperty1 = value1;
}

Loader

The loader is used to load the external instance. This is normally done by calling a REST service of the integration namespace.

When calling a service (REST or normal), it is important to check whether the output exists, and it is not void. Due to type safety, if the check does not occur, then it is not possible to access the properties of the returned object. This is to prevent accessing properties that may not exist. This check is necessary for calling functions that are built to return two different types such as services that return either void or their output entity.

/**
* load method of external entity reference - loads external entity from external system
*/
public async load(): Promise<void> {
   const log = this.util.log;
   log.debug('Load.execute()');
   
  // Initialize the input of the REST integration service
   const input = this.factory.entity.cust. ServiceIdentifier_Input();

  // Initialize the input properties to their values
   input.property1 = “value1”; // or this.instance.(…)

  // Call the REST service and pass as input the the input entity created above
   const serviceOutput = await this.services.intnsacrnm.ServiceIdentifier (input);

   // Check if the output of the service is not void
   if(serviceOutput) {
     
     // Initialize the output of the loader of the external entity
     this.output = this.factory.nsacrnm.ExternalEntityIdentifier_Output()

     // Set the values of the output properties of the output
     this.output.property1 = serviceOutput.property1;
     this.output.property2 = serviceOutput.property2; 
   }
}

Validator

The validator gives information regarding the existence of a specific instance of an external entity. It usually returns true if the external instance exists and false if it does not exist. The validator can also be used for updating the local instance of an external entity if the update flag is set to true.

Tip: In a validator, it is recommended to try to load the instance. If the instance is returned successfully, then return true. A validator must always return a boolean.
/**
* validate method of external entity reference - checks if external entity still exists in external system
* @param update indicates whether local properties should be updated after validating entity on external system
* @returns boolean indicating whether external entity still exists in external system
*/
public async validate(update: boolean): Promise<boolean> {
    const log = this.util.log;
    log.debug('Validate.execute()');

    // trigger load function of external entity in order to validate whether the entity still exists
    const existence = await this.instance.load();

    // If it exists return true. Otherwise, return false.
    if(existence) {
      return true;
    } else {
      return false;
    }
}

Factory commands

The main purpose of a factory command is to create a new instance of a root entity with initial values and then persist those changes to the database. Each factory command provides input data depending on what has been modelled in the Solution Designer before. As a return value, the factory command will automatically return the instance id of the created instance.

Here you can see an example implementation of a factory command, creating a new instance of root entity Order:

  /**
   * Factory command execution
   */
  public async execute(): Promise<void> {
    // Read input properties
    const { inputProperty1, inputProperty2 } = this.input;

    // create a new instance of the Root entity
    this.instance = this.factory.entity.Order();

    // fill properties of the instance using the values from the input
    this.instance.property1 = inputProperty1;
    this.instance.property2 = inputProperty2;

    // Set the instance property to an initial value
    this.instance.property3 = 'value3';

    // Save changes to the database
    await this.instance.persist();
  }

Instance commands

Instance commands usually hold logic to modify the state of a root entity instance and persist the updated state in the database. The instance that can be modified as well as the input of the command is provided automatically.

Here you can see an example implementation of an instance command, modifying an existing instance of root entity Order and throwing a business error if some special conditino is met:

  /**
   * Instance command execution
   */
  public async execute(): Promise<void> {
    // get value from input
    const { newPropertyValue } = this.input;

    // Check current state of instance
    if (this.instance.property1 === 'SomeSpecialState'){
      // throw business error
      throw this.factory.error.nsacrnm.MyBusinessError(); 
    } else {
      // update property with new value
      this.instance.property1 = newPropertyValue;
    }

    // Save changes to the database
    await this.instance.persist();
  }

The deletion of a root entity should be also handled within an instance command:


  /**
   * Instance command deleting execution
   */
  public async execute(): Promise<void> {
    // Delete instance
    await this.instance.delete();
  }
Attention: When deleting an instance, no persist call is needed to delete the instance from the database. Using delete() is sufficient.

Events

An event using a schema from the schema registry as payload is published by using the structure of the following code.

Custom headers, and custom message key can be set for events (optional).

import { schemaRegistry } from 'solution-framework';

  /**
   * Event publishing in Command or Service
   */
  public async execute(): Promise<void> {

    // create a new event instance
    const event = this.factory.event.domainNs.OrderAccepted();

    // Create payload object using the type defined in schemaRegistry
    const payload: schemaRegistry.SchemaName = {
      property1: 'val'
    }
    event.payload = payload;

    // set message key (optional)
    event.messageKey = 'myMessageKey';
    
    // set message headers (optional)
    event.messageHeaders = {
      'myHeaderKey': 'my Header value',
    }

    // Publish the event
    await event.publish();
  }
Note: When setting custom message headers for schema type events, the header key 'apicurio.value.globalId' is reserved and will be overwritten by the sdk, as it is used for integration purposes.

An event using an entity as payload is published by using the structure of the following code, however this is deprecated and it is recommended to use schemas from the schema registry instead.

Custom headers can be set for events (optional).

  /**
   * Event publishing in Command or Service
   */
  public async execute(): Promise<void> {

    // create a new event instance
    const event = this.factory.event.domainNs.OrderAccepted();

    // Create payload entity for event and set values
    event.payload = this.factory.entity.domainNs.OrderAccepted_Payload();
    payload.property1 = value1;
    
    // set message headers (optional)
    event.messageHeaders = {
      'myHeaderKey': 'my Header value',
    }

    // Publish the event
    await event.publish();
  }

Agents

A published event will automatically trigger the agents where the event is assigned as trigger event. The payload provided in the event will be available as input in the agent. Besides that, any kind of logic can be implemented in an agent (e. g. calling another service).

Within the agent implementation, the message key, the message headers and the message timestamp are available and can be used. Both custom headers as well as commonly used headers can be accessed here.

  /**
   * Agent execution
   */
  public async execute(): Promise<void> {
    const log = this.util.log;

    // Get the payload from the event that triggered the agent
    const { property1 } =  this.input;

    // log message key
    log.info(`Message key: ${this.messageKey}`);

    // log value of my custom header, wich has a string as value
    const strHeaderValue = this.messageHeaders['customHeader1'].toString();
    log.info(`Custom header "customHeader1" has value: ${strHeaderValue}`);

    // log value of another header, which has Buffer as value
    const bufHeaderValue = this.messageHeaders['customHeader2'].toString();
    log.info(`Custom header "customHeader2" has value: ${bufHeaderValue}`);

    // log value of message timestamp in the format of an ISO String
    const msgTime = new Date(this.messageTimestamp);
    log.info(`Message timestamp: ${msgTime.toISOString()}`)

    // Initialize input for calling another service
    const input = this.factory.entity.order.ServiceIdentifier_Input();

    // Set the value from the event payload as input value
    input.property1 = property1;

    // Call service and pass as input the input entity created above
    await this.services.order.ServiceIdentifier(input);
  }

Domain services

Within a domain service implementation, you can access the modelled input properties and process any logic. Typical logic in domain services would be e. g. loading of data from the repo and processing the data or calling instance and factory commands. Additionally it is also possible to define the output of the service. Both input and output is restricted to what has been modelled in the Solution Designer.

  /**
   * Domain service execution
   */
  public async execute(): Promise<void> {

    // Get the input properties
    const { property1, property2, id } =  this.input;

    // Get an instance by ID
    const myInstance = await this.repo.nsacrnm.EntityIdentifier.findById(id);

    // trigger instanceCommand
    await myInstance.MyCommand();

    // fill output of service with some data
    this.output = this.factory.entity.order.ServiceIdentifier_Output();
    this.output.prop1 = property1;
    this.output.prop2 = property2;
  }
Note: If you are using the aggregate persistence functionality, please ensure that your code is compliant with the recommended restrictions of the used database. Consult MongoDB documentation for further information. For example: Because of the limitations of database operations and drivers, MongoDB does not recommend the use of timestamps, that are not within the year range of 0 - 9999, see Date and DateTime documentation.