Documentation for a newer release is available. View Latest

Retrieving parent data from a child

By default, when a parent flow calls into a child flow, the data structures related to the parent flow are not available in the child flow.

One of the situations where we might wish to retrieve the parent data structures is in bulking/debulking; processing is split across levels with many C-Levels (creditor information) required per B-Level (debtor information). When processing C-Level it is necessary to access debtor information for booking/settlement.

Accessing the Parent Flow

In the absence of other alternatives, to access these structures (such as the MDS and PDS), a call would be required through an external domain to ODS to retrieve the information.

However, there are better and more efficient ways to retrieve this information:

1) If the parent is deployed on a separate processing service then instead we need to use transactionOperation API deployed on the parent service, which contains an endpoint which allows the aggregate to be retrieved.

2) If the parent is present in the same processing service. In this situation, the data structures can be retrieved from the flo-domain API.

Using the flo-domain API

This assumes the parent flow is available in the same processing service.

An alternative to a call to ODS to retrieve the data is using a call to getAggregate from the flo-domain API, where both the parent and the child flow are on the same processing service.

This makes use of the fact that a child action knows the flow Id that called it, and as such the data can be retrieved.

The Aggregate class specified as a parameter in the call belongs to the parent flow, although of course the retrieval is occurring during processing within the action of the child flow.

In the situation below, the TransactionFlow is being called from the BatchFlow, so the aggregate being retrieved is the BatchFlowAggregate.

import debulkexamplemodel.domain.DebulkexamplemodelModelOperations;
[...]
debulkexamplemodelModelOperations.getAggregate(parentId, BatchFlowAggregate.class)

Processing the values

MDS and PDS values are filtered from the parent aggregate for use:

return debulkexamplemodelModelOperations.getAggregate(parentId, BatchFlowAggregate.class)
                .thenAccept(batchFlowAggregate -> {
                    MdsWrapper<PaymentInstruction30> parentMdsValues = batchFlowAggregate.getPain001Instruction();
                    List<? extends DataElementWrapper<?>> parentPdsValues = batchFlowAggregate.businessData().values()
                            .stream()
                            .filter(dataElementWrapper -> dataElementWrapper.getCategory().equals("PROCESSING_DATA_STRUCTURE"))
                            .toList();
                });

Using the cache

In addition to this, if the same parent id is being retrieved multiple times, caching would be useful. ipf-cache should be used for this, to reduce the repeated use of getAggregate.

To facilitate this, the following entry can be added to the pom.xml

<dependency>
    <groupId>com.iconsolutions.ipf.core.platform</groupId>
    <artifactId>ipf-cache-api</artifactId>
</dependency>
<dependency>
    <groupId>com.iconsolutions.ipf.core.platform</groupId>
    <artifactId>ipf-cache-caffeine</artifactId>
</dependency>

Further documentation on how to set up the cache for use is here.

By doing this, an implementation can be used that avoids the pitfalls associated with re-reading from the aggregate. Further details here.

Implementing the cache

When this is all brought together in the ActionAdapter for the child flow, we can have something like the following:

    private final DebulkexamplemodelModelOperations debulkexamplemodelModelOperations;
    private final CacheAdapter<String, MdsPdsWrapper> cacheAdapter;
private Optional<MdsPdsWrapper> getFromCache(String parentId) {
    var mdsPdsWrapper = cacheAdapter.get(parentId);
    if (mdsPdsWrapper.isPresent()) {
        log.debug("Existing MdsPdsWrapper retrieved from cache; parentId:{} mdsPdsWrapper:{}", parentId, mdsPdsWrapper);
    }
    return mdsPdsWrapper;
}
CompletionStage<Void> getFromCacheOrLoad(String parentId) {
    return getFromCache(parentId).isPresent() ?
            CompletableFuture.completedFuture(null) :
            retrieveParentMdsPdsWrapper(parentId);
}
private CompletionStage<Void> retrieveParentMdsPdsWrapper(String parentId) {
    return debulkexamplemodelModelOperations.getAggregate(parentId, BatchFlowAggregate.class)
            .thenAccept(batchFlowAggregate -> {
                MdsWrapper<PaymentInstruction30> parentMdsValues = batchFlowAggregate.getPain001Instruction();
                List<? extends DataElementWrapper<?>> parentPdsValues = batchFlowAggregate.businessData().values()
                        .stream()
                        .filter(dataElementWrapper -> dataElementWrapper.getCategory().equals("PROCESSING_DATA_STRUCTURE"))
                        .toList();
                MdsPdsWrapper mdsPdsWrapper = new MdsPdsWrapper(parentMdsValues, parentPdsValues);
                cacheAdapter.put(parentId, mdsPdsWrapper);
                log.debug("Loaded into cache -- parentId:{} mdsPdsWrapper:{}", parentId, mdsPdsWrapper);
            });
}
The MdsWrapper class is a POJO which was added to simplify the values stored in the cache.