Exchange Framework 1.6.0 upgrade guide
Configuration Service support
In Exchange Framework 1.6.0, we added the configuration service support in the FluentFunction
base class so that any service that extends from it is compatible with the configuration service. We have encapsulated most configuration service features in the preProcess()
method in the base class so that developers don't have to fetch config service data and consider the override logic manually on condition. It's all being taken care of by the Exchange Framework.
Overview
The basic idea behind the Configuration Service is that instead of configuring the individual service's data (service connection and service parameters), there is a single configuration service per organization that defines and returns the config data for all services inside the same organization. If the configuration service is enabled, all the original service connection and service parameters of a service will be ignored and replaced by the same related data directly from the configuration service. This requires that the fluent function is able to:
- know whether the configuration service is enabled and the configuration service's name;
- trigger the configuration service and get the config data for the current service;
- merge this data into the original service's data; and
- fall back to the original config data from service connection and service parameters if config data from the configuration service is unavailable.
info
All the above logic is provided by the Exchange Framework so that your fluent function can just concentrate on the business logic and consume the config data without knowing whether it's from the original service or from the config service. This also guaranties that future changes related to the configuration service don't require the individual fluent function to change.
Service Config object
Before this, we normally used the SvcDef
object to access the service connection (svcDef.svcConn
) and service parameters (svcDef.paramsMap
) for a fluent function. As the SvcDef
is immutable, which doesn't allow the service connection data from the configuration service to override the existing data, a new TifServiceConfig
object has been created in the Exchange Framework in order to solve this problem.
Basically, a TifServiceConfig
object is a class that contains both the service connection and service parameters data. It has service connection data such as endpoint, username, password, and authKey, as well as the service parameters. The main structure of the TifServiceConfig
class is shown below.
class TifServiceConfig {
// http connection info
String endpoint
String username
String password
String authKey
String fileName
byte[] fileData
// service parameters
Map<String, String> serviceParams
}
Sometimes, the vendor requires more connection data beyond the typical endpoint and basic crendentials. You can extend the TifServiceConfig
class with additional variables according to the vendor's needs. Below is an example class, AuthenticIDServiceConfig
, with an account code provided by the vendor.
class AuthenticIDServiceConfig extends TifServiceConfig {
// account code (Location code) configured in AuthenticID system
public String accountCode
}
What you need to do
1. Use TifServiceConfig instead of SvcDef
Starting with this release, we generate the TifServiceConfig
object (variable name serviceConfig
) in the FluentFunction
base class that stores the latest config data (from configuration service if it's enabled, from original service connection/service parameters in case the config service is disabled), so you might want to start using TifServiceConfig
to obtain service connection and service parameter info if you want your service to be compatible with configuration service.
For example, you can access service connection information by adding the following code in your fluent function:
String endpoint = serviceConfig.endpoint
String username = serviceConfig.username
String password = serviceConfig.password
String authKey = serviceConfig.authKey
Similarly, you can access service parameters as follows:
String sandboxUuid = serviceConfig.serviceParams.get('sandboxUuid')
2. App Name for configuration service
A service parameter needs to be created in your fluent function so that the Exchange Framework knows which App Name belongs inside the configuration service. In Exchange packages, we typically use vendor name as the App Name. Here's an example for the AuthenticID Document verification fluent function in service-def.json
:
{
"name": "configServiceAppName",
"description": "AppName for configuration service. All exchange services use vendor code for AppName.",
"value": "authenticid",
"type": "String",
"bind": false,
"required": true,
"clearOnExport": false,
"readOnly": false
},
info
The TPac archetype will generate this service parameter automatically with the correct value for your first fluent function so you don't have to worry about this step.
Using configuration service in your application
In order to use configuration service in your application, you need to define the organization property ConfigServiceName
in your organization. This defines which configuration service you want to use in your organization. For example, if you decide to trigger BankwestServiceConfiguration
as the configuration service, set the following organization property:
ConfigServiceName=BankwestServiceConfiguration
capitalizedKey annotation
In this release, we added a new capitalizedKey
annotation in @ResponsePath
to match the capitalized tag name convention in the Txn
XML generated by Server VO and Client VO. The default value is true
which ensures the XML tag generated by the VO's is always capitalized to match the Txn
XML naming convention.
For example, we could have a Socure Client VO class defined as follows:
@XPath('Socure/DV/Result')
public class SocureDvResultClientVO extends MaxAttemptsClientVO {
@ResponsePath(value='documentVerification.decision.value', capitalizedKey=true)
public String decisionValue
}
Note that the VO property decisionValue
isn't capitalized, but as the captializedKey
is set to true, the generated Txn
XML will be:
<Root>
<AvokaSmartForm>
<PrimaryApplicant>
<Socure>
<DV>
<Result>
<DecisionValue>VERIFIED</DecisionValue>
</Result>
</Product>
</Provider>
</PrimaryApplicant>
</AvokaSmartForm>
</Root>
Note that the DecisionValue
tag is capitalized. This is especially important for Client VO because failure to capitalize the tag may cause an issue later getting the value from the Maestro component using Maestro Entity.
Since the default value for captitalizedKey
is true, the above ClientVO definition can be simplified further with the same behaviour.
@XPath('Socure/DV/Result')
public class SocureDvResultClientVO extends MaxAttemptsClientVO {
@ResponsePath('documentVerification.decision.value')
public String decisionValue
}
Mock a fluent function or Groovy service
While debugging code on your local computer, if your unit test contains a fluent function that calls another fluent function or groovy service, you might find that it fails due to the JM SDK being unable to find the second service. Starting with this release, you can mock a response and specify the expected result from the second service so that you can test behaviour of your first fluent function.
For example, you can mock a response from a service called EXCHANGEServiceConfiguration
as follows.
MockUtils.registerMockService('EXCHANGEServiceConfiguration', null, txn.clientCode, ["endpoint": "http://mock", "username": 'danbo'])
Then, when your code triggers the EXCHANGEServiceConfiguration
service, you always get the mocking result you specified. The full test case is shown below.
package com.avoka.tif.util
import com.avoka.tif.test.BaseUnitTest
import com.avoka.tm.svc.GroovyServiceInvoker
import com.avoka.tm.test.MockVoBuilder
import com.avoka.tm.vo.Txn
import org.junit.Test
class MockUtilsTest extends BaseUnitTest {
MockUtilsTest() {
this.requireMock = true
}
@Test
void testMockingService() {
Txn txn = new MockVoBuilder().createTxnSavedWithXml('<Root><AvokaSmartForm></AvokaSmartForm></Root>')
MockUtils.registerMockService('EXCHANGEServiceConfiguration', null, txn.clientCode, ["endpoint": "http://mock", "username": 'danbo'])
Map configMap = (Map) new GroovyServiceInvoker()
.setServiceName('EXCHANGEServiceConfiguration')
.setClientCode(txn.clientCode)
.invoke(null)
assert configMap.endpoint=='http://mock'
}
}