What is the Leap Execution Lifecycle?
By default, services developed using the Leap Framework and services generated by the Leap CLI implement all of the execution lifecycle phases. Each phase exists within a parent route, either the Entry Route or Execution Route. The purpose of the Leap Execution Lifecycle is two-fold:
- Enable the execution of logic via java-based handler or camel-based route at a specific time and place while processing a Leap Request.
- This is usually for plugging in additional functionality such as event publishing, logging, web-hooks, authentication, etc…
- Organizing the processing of Leap Requests into a series of steps that are predictable and easy to understand. Without this level of organization, routes would likely become a ball of mud with unnecessary variance from project to project.
The image below may seem a little bit daunting at first. By the time you’re done with this article, you will understand the diagram and the concepts it references.
Starting from the top of the diagram you can see that an HTTP request is being handled by the baseroute, some Request goes in and a Response is expected to come out of the other end. All of the phases that occur between the request and response are part of the Leap Execution Lifecycle and are executed in order. These steps are a combination of handler invocations and route calls, each of which is described by this document.
Routes
The lifecycle routes begin prior to the execution route of your service, although you may be able to augment their behavior through configuration, the route prior to and after the execution route is considered immutable – their implementation details are contained within the framework.
All of the routes located within the execution route can be customized to meet your needs. In most scenarios, these routes are customized to assist with organizing service functions. Adding logic to these routes is optional and may not be required for all services.
Immutable routes
These routes are invoked before and after the execution route and are usually associated with preparing the route to be called and dispatching actions after service execution is completed. As an example, the post-service handler or exit route could be used to track service usage – for specific tenants or sites – by incrementing a counter in your database.
Some routes cannot be changed without making changes to the source code of the framework. For developing regular Leap projects, these are considered immutable. In the diagram above, a few examples are:
- Entry route
- Feature Initializer
- Fetch handlers
Mutable routes
If you generate a new service using the Leap CLI these routes get created for you automatically, they can be found in the execution route and implementation route XML files.
- Execution Enrichment Route
- Execution Route XML file
- An excellent place for pulling in extra data to enrich the request or augmenting data before processing. This happens before the implementation route is selected so changes here may apply to all vendor implementations.
- Implementation Selection Route
- Execution Route XML file
- By default this route calls a bean that chooses which Implementation Route to call. Although it is uncommon, you could choose to use customized logic to control which routes are selected.
- Implementaton Enrichment Route
- Implementation Route XML file
- This is invoked just before the implementation route and allows for enrichment specific to the implementation. Most frequently, extra data will be fetched from cache or authentication may be done for a service called during the implementation.
- Implementation Route
- Implementation Route XML file
- The bulk of required service calls, transformations, taxonomies, and custom logic goes in this piece of the lifecycle.
- Execution Exit Route
- Execution Route XML file
- Called from within the implementation route
- Excellent place for implementation agnostic logging, transformations, and event production.
Route Examples
<route id="service-executionEnrichmentRoute"> <from uri="direct:service-executionEnrichmentRoute"/> <to uri="direct:service-ExecutionRoute"/> </route> <route id="service-ExecutionRoute"> <from uri="direct:service-ExecutionRoute"/> <to uri="bean:executionFeatureDynamic?method=route"/> <to uri="direct:service-ImplementationSelectionRoute"/> </route> <route id="service-ImplementationSelectionRoute"> <from uri="direct:service-ImplementationSelectionRoute"/> <log message="Implementation Route: ${in.header.implroute}"/> <toD uri="direct:${in.header.implroute}"/> </route> <route id="service-executionExitRoute"> <from uri="direct:service-executionExitRoute"/> <!-- Back to Baseroute and return to user --> <toD uri="direct:exitRoute"/> </route>
<route id="service-implEnrchimentRoute"> <from uri="direct:service-ImplementationEnrichmentRoute"/> <!-- Handle enrichment or auth --> <to uri="direct:service-IR"/> </route> <route id="service-implRoute"> <from uri="direct:service-IR"/> <!-- Do your work here, then go to the exit route --> <to uri="direct:service-executionExitRoute"/> </route>
Handlers
A handler is composed of two different parts:
- Handler Definition (XML)
- This is where you describe “how” a handler will behave, “when” it will take action, and how to identify it (“who“).
- Handler Implementation (Java)
- This is where you implement the logic that will execute, think of this as the “what” of your handler.
There are a few different types of handlers. They can be implemented with a common scope, application scope, or feature scope. Additionally, handlers can operate as either synchronous or asynchronous.
Handler Scope
Common Handlers
Handlers that are defined with the common scope are applied to all services that are deployed in the current Leap project (save JVM). By default, common handlers will run for all services unless the service is explicitly excluded in the service handler definition via <ExcludedServices>
.
Common handlers are defined via XML in the app_servicehandlerconfig.xml
file that is associated with the leap framework. All common handlers must be contained by the element <CommonServiceHandler>
.
Cross-Feature Handlers
In order to define a handler that can apply to any service deployed in the project, you define a Feature Service Handler. Unlike a common service handler, these handlers only apply to services that are explicitly included via <IncludedServices>
in the handler definition.
Just like the common handlers, Feature Service Handlers are registered in the app_servicehandlerconfig.xml
file. However, these are contained by the element <FeatureServiceHandler>
.
Feature Handlers
A Feature Handler is defined almost identically to a cross-feature handler. However, the difference lies in the scope of services that the handler has access to. Because the cross-feature handler is defined in a lower layer of the project, they have access to all services of all features. Yet, when a Feature Handler is defined, it is not defined in the app_servicehandlerconfig.xml file.
Feature Handlers are defined within the scope of a specific feature. You need to create an XML file that contains the handler definitions and then explicitly register that file in the FeatureMetaInfo.xml file of the specific feature.
Just like the `<FeatureServiceHandler>., all services that the handler applies to must be included using the element <IncludedServices>
.
Handler Examples
<?xml version="1.0" encoding="UTF-8"?> <ApplicationServiceHandlerConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="app_servicehandlerconfig.xsd"> <!-- app_servicehandlerconfig.xml --> <CommonServiceHandler> <ServiceHandler type="post-service" execute="async" handlerId="logginghandler"> <Description>this is async direct logging to cassandra handler ...</Description> <HandlerImplementation fqcn="com.attunedlabs.applicationservicehandlers.handler.SelfLinkLoggingStorage"> <HandlerConfig> {"driver_class":"com.github.adejanovski.cassandra.jdbc.CassandraDriver","url":"jdbc:cassandra://192.168.1.150:9042/selflink"} </HandlerConfig> </HandlerImplementation> <ExcludedServices/> </ServiceHandler> <ServiceHandler type="pre-service" execute="sync" handlerId="internalServiceHandler"> <Description>This is sync login handler for authentication...</Description> <HandlerImplementation beanId="servicehandlerappcomloggingbean" /> <ExcludedServices> <Service featureGroup="sample" featureName="helloworldservice" serviceName="internal2HelloMessage" /> </ExcludedServices> </ServiceHandler> </CommonServiceHandler> </ApplicationServiceHandlerConfiguration>
<?xml version="1.0" encoding="UTF-8"?> <ApplicationServiceHandlerConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="app_servicehandlerconfig.xsd"> <!-- app_servicehandlerconfig.xml --> <FeatureServiceHandler> <ServiceHandler type="pre-service" execute="async" handlerId="externalServiceHandler"> <Description>This is post sync event generation handler...</Description> <HandlerImplementation beanId="servicehandlerappfeatloggingbean" /> <IncludedServices> <Service featureGroup="sample" featureName="helloworldservice" serviceName="helloMessage" /> </IncludedServices> </ServiceHandler> </FeatureServiceHandler> <CommonServiceHandler> <ServiceHandler type="post-service" execute="async" handlerId="logginghandler"> <Description>this is async direct logging to cassandra handler ...</Description> <HandlerImplementation fqcn="com.attunedlabs.applicationservicehandlers.handler.SelfLinkLoggingStorage"> <HandlerConfig> {"driver_class":"com.github.adejanovski.cassandra.jdbc.CassandraDriver","url":"jdbc:cassandra://192.168.1.150:9042/selflink"} </HandlerConfig> </HandlerImplementation> <ExcludedServices/> </ServiceHandler> <ServiceHandler type="pre-service" execute="sync" handlerId="internalServiceHandler"> <Description>This is sync login handler for authentication...</Description> <HandlerImplementation beanId="servicehandlerappcomloggingbean" /> <ExcludedServices> <Service featureGroup="sample" featureName="helloworldservice" serviceName="internal2HelloMessage" /> </ExcludedServices> </ServiceHandler> </CommonServiceHandler> </ApplicationServiceHandlerConfiguration>
<?xml version="1.0" encoding="UTF-8"?> <ServiceHandlerConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="servicehandlerconfig.xsd"> <FeatureServiceHandler> <ServiceHandler type="*" execute="async" handlerId="AFSH-Test-kafka121"> <Description>This is Async event generation handler...</Description> <HandlerImplementation fqcn="com.reactiveworks.service.serviceHandler"> <HandlerConfig>{"brokerconfig":"localhost:9092","topic":"camel-test-0"} </HandlerConfig> </HandlerImplementation> <IncludedServices> <Service featureGroup="featureGroup" featureName="feature" serviceName="helloworld" /> </IncludedServices> </ServiceHandler> </FeatureServiceHandler> </ServiceHandlerConfiguration>
<?xml version="1.0" encoding="UTF-8"?> <Feature-Metainfo xmlns:fms="http://attunedlabs.com/internal/FeatureMetaInfoSupporting" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" productVersion="WMS2.0" xsi:noNamespaceSchemaLocation="featureMetaInfo.xsd"> <FeatureGroup> <Name>example</Name> <Features> <Feature implementationName="exampleimpl" vendorName="vendor" vendorTaxonomyId="test_QW" vendorVersion="1.0"> <Name>example</Name> <FeatureImplementations> <FeatureImplementation resourceName="example-featureservice.xml" /> </FeatureImplementations> <fms:ServiceHandlerConfiguration> <fms:HandlerConfiguration resourceName="servicehandler-impl.xml" /> </fms:ServiceHandlerConfiguration> </Feature> </Features> </FeatureGroup> </Feature-Metainfo>
package com.reactiveworks.service; import com.attunedlabs.servicehandlers.AbstractServiceHandler; public class ExampleServiceHandler extends AbstractServiceHandler { @Override public boolean initializeConfiguration(JSONObject jsonObject) { System.out.println("---------------------"); System.out.println("In initializeConfiguration json body is:- " + jsonObject); System.out.println("---------------------"); return true; } @Override public void postExec(Exchange exchange) { System.out.println("---------------------"); System.out.println("In post Execution .........."); System.out.println("---------------------"); } @Override public void postService(Exchange exchange) { System.out.println("---------------------"); System.out.println("In post Service method.........."); System.out.println("---------------------"); } @Override public void preExec(Exchange exchange) { System.out.println("---------------------"); System.out.println("pre Exec method....."); System.out.println("---------------------"); } @Override public void preExecEnrichment(Exchange exchange) { System.out.println("---------------------"); System.out.println("---------------------"); } @Override public void preImpl(Exchange exchange) { System.out.println("---------------------"); System.out.println("In pre impl method....."); System.out.println("---------------------"); } @Override public void preImplEnrichment(Exchange exchange) { System.out.println("---------------------"); System.out.println("In pre Impl Enrichment method......."); System.out.println("---------------------"); } @Override public void preImplSelection(Exchange exchange) { System.out.println("---------------------"); System.out.println("In pre Impl Selection method....."); System.out.println("---------------------"); } @Override public void preService(Exchange exchange) { System.out.println("---------------------"); System.out.println("In pre service method.........."); System.out.println("---------------------"); } }
When to use Handler vs Route?
A few main reasons you may choose to use a Handler:
- There are several services, in the same feature or cross-feature, that need to implement the same functionality. Code can be reduced by using a shared handler.
- If you need fine-grained control over which place the logic takes place – there are more handler steps than routes.
- When you need to handle the logic in an async way. Although routes can contain async calls, handlers are more clear to define.
A few reasons you may choose to use a Route:
- When you want to embed some logic that is specific to a single service OR is part of a vendor implementation.
- If the required logic can be done completely within a camel route without writing any “code”.
- When you are in the development step and you’re trying to evaluate whether or not some functionality should be used. Routes tend to be quicker to implment and reverse.
Service Handler Syntax Guide
Name | Type | Parent | Required | Description |
---|---|---|---|---|
<CommonServiceHandler> | element | <ApplicationServiceHandlerConfiguration> | N | This is used as the wrapper when defining common service handlers that impact all services |
<FeatureServiceHandler> | element | <ApplicationServiceHandlerConfiguration> | N | This is used as the wrapper when defining cross-feature service handlers or feature service handlers |
<ServiceHandler> | element | <FeatureServiceHandler> || <CommonServiceHandler> | Y | Contains several attributes and is responsible for holding all of the handler details |
execute | attribute | <ServiceHandler> | Y | “sync” or “async” |
handlerId | attribute | <ServiceHandler> | Y | Unique ID that is used to reference the handler and register it in Leap |
type | attribute | <ServiceHandler> | Y | Defines which of the lifecycle phases that trigger this handler. Possible values: pre-service, pre-exec, pre-exec-enrichment, pre-impl-selection, pre-impl-enrichment, pre-impl, post-exec, post-service, or all of them via * |
<Description> | element | <ServiceHandler> | Y | Leave a meaningful description of what the handler does and why it is being used |
<HandlerImplementation> | element | <ServiceHandler> | Y | Contains bean reference and configuration used to initialize the bean |
beanId | attribute | <HandlerImplementation> | N | the bean reference name for a bean that is already registered via beans.xml |
fqcn | attribute | <HandlerImplementation> | N | Fully Qualified Class Name that is dot-notation location of the class. Example: com.attunedlabs.handlers.impl.helloworld |
<HandlerConfig> | element | <HandlerImplementation> | N | JSONObject as a String. This map contains any data that you want to send to your Handlers initialization method for the first setup |
<ExcludeServices> | element | <CommonServiceHandler> | N | Only used for the common service handlers. Any <service> defined in this collection will not trigger the defined handler |
<IncludeServices> | element | <FeatureServiceHandler> | N | Only used for Feature Service Handlers. Any <service> defined in this collection will invoke the handler |
<Service> | element | <ExcludeServices> || <IncludedServices> | Y | Element that allows you to configure the service details of a service impacted by a handler |
featureGroup | attribute | <Service> | Y | The name of the featureGroup corresponding to the service being mentioned |
featureName | attribute | <Service> | Y | The name of the feature corresponding to the service being mentioned |
serviceName | attribute | <Service> | Y | The name of the service related to the defined handler |