Documentation for a newer release is available. View Latest

DSL 10 - Calling Other Flows

Getting Started

The tutorial step uses the add_subflow solution of the project as it’s starting point.

If at any time you want to see the solution to this step, this can be found on the add_flow solution

Scenario

So far, we’ve only considered a single flow, which takes in a pacs.008 and performs some actions on it. We could consider this to be an "execution" flow. Let’s now consider how we may do "initiation" and create a second flow that will:

  1. Receive a pain.001

  2. Convert the pain.001 to a pacs.008

  3. Forward the pacs.008 to the execution flow

  4. Determine the outcome of the execution flow and terminate appropriately.

Diagram

The key thing to consider here is how we communicate flow-to-flow. In this step, we’ll only look at flows that are deployed together - namely when two flows are both defined in the same model.

While this section discusses how to use the DSL itself to set up flow-to-flow communication, it’s important to realise that, in essence, flow-to-flow is effectively one domain calling another and, as such, it’d be just as valid (and possible) to simply model the flow-to-flow interaction using external domains.

DSL Setup

Adding the Initiation Flow

Let’s add a new flow by right-clicking on the model and selecting New  v2Flo  Flow. Then, in the flow editor we’ll set:

  • A name of "Initiation Flow"

  • A description of "Initiates payment processing"

  • A new state to represent flow rejection called "Rejected". This should have a REJECTED global code.

When done, it should look something like:

calling1 create flow

Now let’s move down to the Initiation Behaviour. As per our above-mentioned requirement, we need to receive a pain001. The first thing we’ll do here is use the pre-packaged initiation types by importing them into our solution.

To do this, press Ctrl+R, enter 'ISO Initiation Types' into the search and then select the resulting entry.

When importing models into your solution using this method, you may inadvertently import a model that you didn’t intend to, which can result in your application failing to start with errors such as:

An action processor has not been provided for external domain INITIATION_DOMAIN in model INITIATION

To remove this, and any other unused models you may have in your solution, click on the ipftutorialmodel in the left hand project window, hit Alt+Enter and then remove any light grey items (indicating they are unused) in the imported models list by selecting them and clicking on the 'minus' icon (or use the shortcut Alt+Delete). Alternatively, you can remove all unused imports in one go by clicking on the "Remove unused model imports" button remove unused model imports icon.

If we go to our Initiation Behaviour on the new flow, we should have the option to select "Payment Initiation" - so let’s do that.

calling1 init behaviour 1

Integrating the Flows

Now let’s integrate the two flows. To do this, we need to use another pseudo-state just like in DSL 5 - Using a Decision. In this case, it’s a "Flow State". So, in the "Move To State" box, select "Create Flow State".

Let’s call our Flow State "Call Execution".

Then in the perform action state, we’ll select "Call Flow" and select our ipftutorial flow. When you’ve done this, it should look something like:

calling1 init behaviour 2

Now we can see that the calling of the flow is underlined as an error. If you validate the flow (ALT+ENTER then Validate Flow) we’ll find:

calling1 validation errors

Let’s work through each of these. Firstly, we see "Called Flow requires missing data: [Customer Credit Transfer]". This is because our execution flow needs to be given a credit transfer object, but at the moment the Initiation Flow doesn’t have one to give. To fix this, we’ll create a new mapping function to map from the pain.001 to the pacs.008. If you need a reminder of mapping functions, review DSL 6 - Mapping Functions. Try and add this new mapping function yourself now. When you’re ready, you can compare to the solution provided below:

calling1 mapping function

We’ll then need to add the mapping function to our Initiation Behaviour. In this case, we’re going to use it as an input enricher:

calling1 init behaviour 3

If we now re-validate our flow, we should no longer see the missing data error.

The state issues are due to the fact we’ve not referenced the Complete and Rejected state definitions anywhere in the Initiation Flow yet. To fix this, we’ll begin by adding an Event Behaviour for when we’re in the "Call Execution" state we defined in our Initiation Behaviour. We first add the Complete state in the "Move To State" section of the Event Behaviour and then, in the "For Event" section, we select the special "On Flow State" event type, which provides us with a way to handle the states published by the Execution Flow (similar to how we handled Subflow completion states in DSL 9 - Using Subflows). After implementing this event type, we select Ipftutorialflow (V2) for the "When" part. You’ll notice that when trying to select an option for the "reaches" part, there are no options to choose from!

calling1 event behaviour 1

This is because, unlike for subflows, we need to explicitly select which flow states in the called flow (the Execution Flow here) we want to be published and available for any calling flows (the Initiation Flow here).

In this instance, we just want to make the terminal states in the Execution Flow available to the Initiation Flow, so we tick the "Is Publishing?" option for the Complete, Rejected and Timed Out states in the Execution Flow:

calling1 exec state definitions

Now we can select the Complete state published by the Execution Flow in the "reaches" part for this Event Behaviour:

calling1 event behaviour 2

We then follow the same process to add Event Behaviour for the Rejected terminal state in the Initiation Flow, which is transitioned to On Any Of the Rejected and Timed Out states being published by the Execution Flow:

calling1 event behaviour 3

Viewing the Flows

That’s all our DSL changes complete. Before moving on though, let’s consider the graph for our new Initiation Flow. As normal, let’s open the Flo Viewer (Tools > Open Flo Viewer):

calling1 flo viewer

Here we can see that our (called) Execution Flow is represented by the green box in the diagram. Note that, unlike for a subflow, we cannot expand this box, as it is a separate flow and is not considered as an embedded part of the (calling) Initiation Flow.

Java Implementation

Let’s now switch back to Intellij and look at how we plug this into our implementation code. As usual, we’ll start by running a build from a terminal window:

mvn clean install

This will successfully generate all of our DSL related components.

Previously, we were firing our requests directly into the ipf tutorial flow, whereas now we want to call the Initiation Flow. We’ll therefore need to change InitiateIpftutorialflowInput to InitiateInitiationflowInput in the InitiationController (ipftutorialsolution/app/controller/InitiationController.java). The InitiateInitiationflowInput expects a payment initiation object instead of a customer credit transfer, so we need to update our initiation definition accordingly. For now, we can use the Pain001Generator from within the ipftutorial-app to provide this for us.

Try and do this change yourself. When you’re ready, you can compare to the solution provided below:

var samplePain001 = Pain001Generator.generate();
 if (request != null && request.getValue() != null) {
    samplePain001.getPmtInf().get(0).getCdtTrfTxInf().get(0).getAmt().getInstdAmt().setValue(request.getValue());
}

return Mono.fromCompletionStage(IpftutorialmodelDomain.initiation().handle(new InitiateInitiationFlowInput.Builder(entityId)
        .withProcessingContext(ProcessingContext.builder()
                .unitOfWorkId(unitOfWorkId)
                .clientRequestId(clientRequestId)
                .build())
        .withPaymentInitiation(samplePain001)
        .build()).thenApply(done -> InitiationResponse.builder().requestId(clientRequestId).uowId(unitOfWorkId).aggregateId(done.getAggregateId()).build()));

Make sure you remember to clean up the imports in InitiationController after making this change (CTRL+SHIFT+O or CTRL+ALT+O on Windows)!

Next, we have to add the mapping function for the Initiation Flow. Let’s remind ourselves what we were trying to achieve with this function: a way of mapping the received pain.001 to a pacs.008 that we can then send to the Execution Flow.

You could use any approach you want to perform the mapping, but in this case, we’re going to use a pre-built library that provides us with a method to map the pain.001 to a pacs.008. So let’s add that now.

First, we need to add the dependency to the pom.xml within the ipf-tutorial-app module (ipf-tutorial-app/pom.xml).:

<dependency>
    <groupId>com.iconsolutions.ipf.core.mapper</groupId>
    <artifactId>mapping-library-spring</artifactId>
</dependency>

This will load in a Spring based implementation of various ISO to ISO mapping methods. Have a look at the IsoMappingService class that has been brought in, and you can see it has a method to apply a mapping from pain.001 to pacs.008:

public FIToFICustomerCreditTransferV08 mapPain001toPacs008(CustomerCreditTransferInitiationV09 initiation) {
    return (FIToFICustomerCreditTransfer)this.transformationService.mapThenEnrichWithDefault(initiation, FIToFICustomerCreditTransfer.class);
}

Here we can see that it is calling a transformation service to apply the mapping. This is using Icon’s "Mapping Framework" to perform the mapping. The mapping framework can be used to build your own custom mappings, but this is out of the scope of this tutorial section. Note that this implementation assumes as 1:1 ratio between input (pain.001) and output (pacs.008).

As we have chosen to use a spring based implementation, an instance of the IsoMappingService will be automatically injected into the spring context for us. Therefore, we simply need to add it to our ipf tutorial model Domain definition and use it to provide the implementation of our Initiation Flow mapping function port.

Try and do this yourself now, just as we did for the ipf tutorial flow mapping function port previously in DSL 6 - Mapping Functions. When you’re ready, you can compare to the solution provided below:

@Bean
public IpftutorialmodelDomain ipftutorialmodelDomain(ActorSystem actorSystem, IsoMappingService mappingService, SchedulerPort schedulerAdapter) {
    // All adapters should be added to the domain model
    return new IpftutorialmodelDomain.Builder(actorSystem)
            .withTutorialDomainFunctionLibraryAdapter(input -> CompletableFuture.completedStage(new DuplicateCheckResponseInput.Builder(input.getId(), AcceptOrRejectCodes.Accepted).build()))
            .withAccountingSystemActionAdapter(new SampleAccountingSystemActionAdapter())
            .withFraudSystemActionAdapter(new FraudSystemActionAdapter())
            .withDecisionLibraryAdapter(input ->
                    input.getCustomerCreditTransfer().getCdtTrfTxInf().get(0).getIntrBkSttlmAmt().getValue().compareTo(BigDecimal.TEN) > 0 ?
                            RunFraudCheckDecisionOutcomes.FRAUDREQUIRED : RunFraudCheckDecisionOutcomes.SKIPFRAUD)
            .withIpftutorialflowV1MappingAdapter(input -> new ExtractCreditorAccountForFlowIpftutorialflowV1MappingOutput(input.getCustomerCreditTransfer().getCdtTrfTxInf().get(0).getCdtrAcct()))
            .withIpftutorialflowV2MappingAdapter(input -> new ExtractCreditorAccountForFlowIpftutorialflowV2MappingOutput(input.getCustomerCreditTransfer().getCdtTrfTxInf().get(0).getCdtrAcct()))
            .withInitiationFlowMappingAdapter(parameters -> new MapPain001ToPacs008ForFlowInitiationFlowMappingOutput(mappingService.mapPain001toPacs008(parameters.getPaymentInitiation())))
            .withSchedulerAdapter(schedulerAdapter)
            .withCSMServiceActionAdapter(new SampleCSMServiceActionAdapter())
            .withSanctionsSystemActionAdapter(new SampleSanctionsSystemActionAdapter())
            .build();
}

That’s it! Now when our Initiation Flow calls the mapping function, it will take the input pain.001 and return a mapped pacs.008.

Checking Our Solution

As always, let’s start up our application (instructions are available in Reviewing the initial tutorial application if you need a refresher!) and check that our solution works.

Using our trusty curl command:

curl -X POST localhost:8080/submit | jq

we notice that our aggregate id in the response looks a bit different than before:

{
  "requestId": "1a53c51c-c96e-4786-9f3f-d0d91f80b973",
  "uowId": "ba0b5c6c-855b-41ef-98a7-9b0ee121e6da",
  "aggregateId": "InitiationFlow|176a4e23-6299-49be-89bf-891ca12740de"
}

and signifies that it is now the Initiation Flow that’s being hit via the request.

If we now bring up the payment in the Developer GUI and select the flow view (search by unit of work id, click view), we should see:

calling1 dev app summary

This is a really important moment to understand. We’ve brought back all the flows associated to our unit of work id here, and in doing so, we’re now seeing two flows rather than one.

Firstly we have the Initiation Flow. Let’s look at its full graph:

calling1 dev app init flow diagram

We can see here that it’s calling the Execution Flow AND receiving the completion response back from it. This indicates that our main Execution Flow must have completed correctly. We can validate that by clicking on the graph for the ipftutorialv2 flow and seeing what it looks like:

calling1 dev app exec flow diagram

As expected, it has completed successfully too.

Looking in the Message Data Structures tab, we can verify that the pain.001 file was passed to the flow:

calling1 dev app mds

It’s also worth checking the "Domain Events" tab, where you can observe the Events and the Process Flow from which those events originated:

calling1 dev app domain events

Conclusions

In this section, we’ve successfully:

  • Created an Initiation Flow

  • Added the integration between the Initiation and Execution Flow

  • Implemented the newly created ports

  • Deployed and tested the application

  • Observed that the two flows are linked via the unit of work id.