Skip to content
logoBack to home screen

Service Processor

A service processor is a custom transformation engine that receives a request, processes it (hence the name) and returns a response.

General

A service processor is an essential part of creating a custom DDSA service in Tribefire. A custom service is normally a functionality not available using any of the default extension points and consists of the following elements:

  • service request
  • service response

    Modeling a custom service response is optional.

  • service processor

A service processor is essentially a processing engine which takes input arguments (service request), processes them (service processor with custom logic), and returns them (service response).

For more information on DDSA, see Denotation-driven Service Architecture (DDSA).

This document describes a service processor implemented in Java.

For more information, see:

Service Processors and Custom Services

By creating the service model, you create the data the service processor will work on, but it is the processor that does the actual work, i.e. processes the request according to your custom logic.

For more information on creating a service model, see Creating a Service Model - Best Practices.

Each extension point in tribefire consists of two main parts: the denotation type and the expert type. A service processor is no different.

Service Processor Denotation Type

Every service processor must have a denotation type and an expert. The denotation type must be an interface extending one or more available processor denotation types. See our API documentation for the list of all available denotation types.

You can also simply extend the com.braintribe.model.extensiondeployment.ServiceProcessor if you don't want to work in a predefined context the specializations of ServiceProcessor provide. In terms of code, your service processor's denotation type is an interface. If you want you can add properties to the denotation type, for example when you know your processor will use some form of an authentication token.

Let's see how the FindByTextProcessor denotation type looks like:

package tribefire.demo.model.deployment;
import //...

public interface FindByTextProcessor extends AccessRequestProcessor {
	
	EntityType<FindByTextProcessor> T = EntityTypes.T(FindByTextProcessor.class);
}

As you can see, the FindByTextProcessor extends the com.braintribe.model.extensiondeployment.access.AccessRequestProcessor type, which is the denotation type for a processor that can operate in an access.

Service Processor Expert Type

The expert type for your processor must implement one of the available expert interfaces. Even though they may provide different functionality, there are some areas in which they do not differ. Every service processor expert interface defines the process() method and all processors receive a ServiceRequestContext object as a parameter. It is then passed to the respective processor interfaces' process() method to supply it with meta information about the request. Furthermore, the ServiceRequestContext is an Evaluator that allows to invoke further requests of any kind. Thus, any implementation can build its functionality based on other DDSA functionality.

Your service processor expert type must know what its incoming type and the return types are. It is in the class signature that you define the request and response. In the example below, FindByTextProcessor implements the com.braintribe.model.processing.accessrequest.api.AccessRequestProcessor interface and takes the FindByText request as the incoming type and uses List<GenericEntity> as the return type.

package tribefire.demo.impl.extensions;
import //...

public class FindByTextProcessor implements AccessRequestProcessor<FindByText,  List<GenericEntity>>  {

    // Define the Expert Registry
    private GmExpertRegistry registry = null;

    public void setRegistry(GmExpertRegistry registry) {
        this.registry = registry;
    }

    public FindByTextProcessor() {
        
        // Fill the Expert Registry
        if (this.registry == null) {
            ConfigurableGmExpertRegistry registry = new ConfigurableGmExpertRegistry();
            registry.add(FindByTextExpert.class, Person.class, new PersonFinder());
            registry.add(FindByTextExpert.class, Company.class, new CompanyFinder());
            registry.add(FindByTextExpert.class, Department.class, new DepartmentFinder());
            this.registry = registry;
        }
    }
	
    // Implement the process-method
    @Override
    public List<GenericEntity> process(AccessRequestContext<FindByText> context) {

        // Get request, type, text, and session from the context-object
        FindByText request = context.getOriginalRequest();
        String type = request.getType();
        String text = request.getText();
        PersistenceGmSession session = context.getSession();

        // Call the expert respective to the type
        FindByTextExpert finder = registry.getExpert(FindByTextExpert.class).forType(type);
        return finder.query(text, session);
    }

    // Define an interface declaring a query-method                                   
    public interface FindByTextExpert {
        List<GenericEntity> query (String text, PersistenceGmSession session);
    }

    // Expert implementation of the FindByTextExpert-interface specific to Person
    public class PersonFinder implements FindByTextExpert {
        @Override
        public List<GenericEntity> query(String text, PersistenceGmSession session) {

            EntityQuery query = EntityQueryBuilder
                .from(Person.T)
                .where()
                    .disjunction()
                        .property(Person.firstName).like(text+"*")
                        .property(Person.lastName).like(text+"*")
                    .close()
                .done();

            return session.query().entities(query).list();
        }
    }
                                                                                                       
    // Expert implementation of the FindByTextExpert-interface specific to Company
    public class CompanyFinder implements FindByTextExpert {
        @Override
        public List<GenericEntity> query(String text, PersistenceGmSession session) {

            EntityQuery query = EntityQueryBuilder
                .from(Company.T)
                .where()
                    .property(Company.name).like(text+"*")
                .done();

            return session.query().entities(query).list();
        }
    }
                                                                                                       
    // Expert implementation of the FindByTextExpert-interface specific to Department
    public class DepartmentFinder implements FindByTextExpert {
    @Override
    public List<GenericEntity> query(String text, PersistenceGmSession session) {
        EntityQuery query = EntityQueryBuilder
            .from(Department.T)
            .where()
                .property(Department.name).like(text+"*")
            .done();

        return session.query().entities(query).list();
        }
    }	
}

If you are working with data in an access, it is advisable to implement the com.braintribe.model.processing.accessrequest.api.AccessRequestProcessor< P extends AccessRequest, R > interface as it uses the AccessRequestContext to operate within an access. If you are not working with accesses, we recommend to implement the com.braintribe.model.processing.service.api.ServiceProcessor< P extends ServiceRequest, R extends Object > interface.

In terms of code, your custom service processor's expert is a class implementing one of the available expert interfaces. Moreover, every service processor expert type must have the process() method, which should return the result of the processing.

Service Processors and Wire

Wiring the denotation type and the expert type is how you let tribefire know what logic to apply when a custom service processor is invoked. You can have one processor bound to several denotation types.

Before you do that though, you must register your service processor's expert type as a managed instance. You do that by adding a proper declaration in a Wire space.

Let's see how the expert type for the FindByTextProcessor is declared. The following is a snippet from the com.braintribe.cartridge.extension.wire.space.DeployablesSpace class:

@Managed
public FindByTextProcessor findByTextProcessor() {
    FindByTextProcessor bean = new FindByTextProcessor();
    return bean;
}

Note that it is the expert type that is being declared.

Within the context of the findByTextProcessor, you must implement the findByTextProcessor() method in your Wire space to instantiate your service processor expert. Without that, your custom extension is not complete.

When you declared your expert type as a managed instance, you must then bind a denotation type to an expert implementation. You start by passing the expert type's T value to the bind() method of a DenotationTypeBindingsConfig instance. Then, you follow with a component declaration taken from a component contract. The component declaration is the interface your denotation type extends. In the FindByText example, binding the denotation type to an expert implementation is done in the com.braintribe.cartridge.extension.wire.space.spacevariants.CustomCartridgeSpaceMinimal class, but you can register your expert type as a managed instance and bind the denotation type to the expert type in the same Wire space.

@Managed
public DenotationTypeBindings extensions() {

    DenotationTypeBindingsConfig bean = new DenotationTypeBindingsConfig();

    //..

    bean.bind(FindByTextProcessor.T)
        .component(commonComponents.accessRequestProcessor())
        .expertSupplier(deployables::findByTextProcessor);

    //..

    return bean;
}

Available Metadata

Assigning proper metadata is an essential step in creating a service processor because it is the metadata that binds a service request to a service processor.

You can map different service requests to the same processor.

You can use metadata selectors to make the mapping context-sensitive, for example role- or date-specific.

For more information on available selectors, see Metadata Selectors.

The available metadata are:

  • com.braintribe.model.extensiondeployment.meta.ProcessWith ProcessWith is an exclusive meta data whose resolution always results in maximum one processor used as a handler. It has a processor property that maps to a ServiceProcessor.

  • com.braintribe.model.extensiondeployment.meta.InterceptWith

    • com.braintribe.model.extensiondeployment.meta.PreProcessWith
    • com.braintribe.model.extensiondeployment.meta.AroundProcessWith
    • com.braintribe.model.extensiondeployment.meta.PostProcessWith

    InterceptWith, PreProcessWith, AroundProcessWith and PostProcessWith are multi meta-data that allow for multiple results on resolution. Therefore a number of cross-cutting handlers can be assigned on the meta data's individual processor property. Their call sequence is determined by the value of the conflict priority parameter of the respective metadata.

    For more information on conflict priority, see General Metadata Properties.

Using the ProcessWith metadata, you can define which service processor is used for which request on the service model level. To assign the metadata, open your service model and find your service request entity type. Then, add a new instance of the processWith metadata and make sure to assign your service processor as the processor for the metadata.

Triggering a Service Processor

You can trigger a service processor in the following ways:

  • programmatically in Java

    In Java, you will need an Evaluator<ServiceRequest> to invoke requests. If you have a processor that has a ServiceRequestContext you have that evaluator because the context is such.

    To trigger a service request, you simply need a prepared request instance. With the evaluator, you actually invoke the request and receive the response.

    Evaluator<ServiceRequest> evaluator = ... // get it from somewhere ;-)
    ValidateUserSession request = ValidateUserSession.T.create();
    request.setSessionId(“someSessionId”);
    
    UserSession userSession = request.eval(evaluator).get();
    
  • using a REST call

  • using Control Center by finding the service request and evaluating it

Service Processor Metadata Validation

You might want to validate the values for certain metadata that introduce constraints before the service processor is triggered. Current metadata where validation is supported include:

To enable basic validation against those metadata you must attach the com.braintribe.model.cortex.preprocessor.RequestValidatorPreProcessor service preprocessor to the main entity in your service request (the one that derives from ServiceRequest) using a PreProcessWith metadata.

You can use the following information to find the RequestValidatorPreProcessor:

PropertyValue
globalIddefault:preprocessor/requestValidator
externalIdpreProcessor.requestValidator.default
nameDefault Request Validator
typecom.braintribe.model.cortex.preprocessor.RequestValidatorPreProcessor
declaredModelcortex-deployment-model

For more information about the PreProcessWith metadata, see ProcessWith as both metadata work in a similar fashion.

This metadata (and the validation it introduces) is automatically propagated to requests derived from that entity type so you can use an abstract supertype for all your requests that you want to have validated.

The validation might increase the total time of evaluating a service request.

What's Next?

For more information, see Creating Service Requests.