<img height="1" width="1" style="display:none;" alt="" src="https://px.ads.linkedin.com/collect/?pid=1195114&amp;fmt=gif">

Mendix Design Patterns - Callback

Late last year I wrote a blog post on describing two design patterns Strategy and Visitor and how they can be implemented in Mendix. Now, I would like to continue by taking a look at a third design pattern known as Callback. To help understand the problem that Callback is trying to solve, I think it is best to take a look at the following sample project management application.

Like most project management applications, our sample application allows managers to assign people to different projects. For our purposes, let’s focus on the functionality provided to reassign/remove people from a project. Click on this short video clip to get an idea of how these functionalities behave.

Mar_25_2019_10_48_AM_(online-video-cutter-2d2a60db-8e4d-432b-a453-296a38f1e045.com)

As you can see both operations (reassign and remove) are subject to confirmation. Only after the user has confirmed the operation are people either removed from a project or reassigned to another project. The number of people assigned to the project is then updated. This is a very common scenario in software development. Often, an operation cannot be executed immediately i.e. synchronously. Rather it has to wait for another operation to finish before it can be executed. Some examples of such asynchronous operations are: user input, IO operation, network request, a background job etc.

The simplest way to implement this functionality in Mendix would be to have two separate almost identical confirmation dialog which call different microflows, one for each operation.

duplicate_dialog_bad_software_design

However, this introduces duplication in the code base. More challenging still, if we get a requirement to add a new operation we need to make yet another copy of the dialog.

A better approach that eliminates most of the duplication would be to use an enum attribute to denote the operation we want to perform. Then we can implement both processes with a single dialog and a single microflow. We can use conditional visibility to show correct information in the UI and exclusive split to trigger the correct logic in the microflow.

callback_with_enumaration_dialog_and_microflow

 

The issue still remains that adding another operation requires adding another enum option and another flow out of the split. This is less then ideal even when you own the code, but becomes a real issue when that code is provided by a third party e.g. an app store module. By coupling the confirmation logic with the operation logic (remove/reassign), it also breaks the single responsibility principle, .

The ideal solution would allow us to define the operational logic that needs to be executed at the end of the confirmation process without having to change any of the confirmation process code, especially the confirmation dialog. The callback pattern allows us to accomplish just that.

Callback

A callback, aka "call-after", is any executable logic that is passed as an argument to other logic that is expected to call back (execute) the argument at a given time. 

As with my previous blog post we will first take a look at how this pattern can be implemented in "classical" programming languages. Here is a very basic example of a callback in javascript that I am certain everyone who has ever used JavaScript can understand.


// third_party_library.js
function executeWithConfirmation(message, callback) {
	  showConfirmationDialog(message);
    confirmBtn.addEventListener("click", callback);
}

// bussiness_logic.js
function removeFromProject() {
    //logic to remove the people from the project
}

// index.js
removeBtn.addEventListener("click", function () {
    executeWithConfirmation("Are you sure?", removeFromProject); // <--passing a function pointer as parameter
});

 As you can see from the example the benefit of a callback is that third_party_library.js code does not know or care what the callback is doing. This makes it possible to define the callback function even in a different file. On top of that, changing the callback to accommodate different use cases is trivial. This second part is especially useful if third_party_library.js code is part of a library that we do not want to edit. In essence, callbacks allow us to change part of the behavior of the library without changing its source code.

Callbacks in Mendix

The javascript example is simple and elegant. However, if you try to port the above code to Mendix you will stumble upon the following blocker: Mendix does not have good support for function i.e. microflow pointers. In other words, in Mendix it is really hard to pass a microflow around to be invoked at a later point. There are several ways around this. I will list the three most common ones:

  • pass microflow name as string

This is used in the community commons module in several actions e.g. RunMicroflowAsyncInQueue. There are three main disadvantages here, all of which result in a fail at runtime: 1) there is no check on the microflow name until the action is executed. If you have a typo in the name the call will fail at runtime. 2) If the name of the microflow or the module is changed, or the microflow is deleted the project will still compile but the call will fail at runtime. 3) If the number and type of parameters of the callback microflow are changed, the code will fail at runtime.

  • pass microflows as model reflection object

The model reflection module makes all microflows in a Mendix project available as objects of the Microflow entity. This is especially useful if a microflow needs to be configured in the UI as is done for example in the Process Queue and Excel Import/Export modules. Unfortunately, this approach is only slightly better then passing the microflow name as string since it only addresses the first problem mentioned above, i.e. accidental typos in the name of the microflow. Renaming or deleting the microflow or changing the parameters still results in a runtime crash.

  • pass microflow as java action parameter

In version 7.18 Mendix introduces the concept of Microflow parameters for java actions.  If the microflow is moved to another module, renamed or deleted we get a compilation error. That's a good thing. However, there is no check on the number and types of parameters which means that any changes made there will still lead to an error during runtime. 

As you can see all of the above options have serious drawbacks. However, if you have read my previous blog post you will know that there is yet another option which makes use of object events. Here is a quick refresher:

We start by defining a hierarchy of entities where each specialization has a different event microflow configured for a certain object event e.g. before commit. Then by passing an instance of the specialization entity we are in a sense passing a microflow pointer. To trigger the microflow we need only to trigger the corresponding event e.g. by committing the object.

Now that we have solved the problem of how to pass a microflow pointer in Mendix let us go back to the sample app and see how we can implement the callback pattern.

Sample app callback implementation

Here is an abstract view of the new process flow in Mendix which uses callbacks.

callback_design_pattern_concept

To get started, we need to define an entity hierarchy. We define an 'abstract' entity called PersonListOperation which has two specializations ReassignOperation and RemoveOperation. 

entity_model_mendix_callback

Each specialization is configured with a different microflow for the before commit event. Note, how we used the `PersonListOperation` object to store the necessary information such as the project, the list of people, a string label etc. The specializations can in turn store information that is relevant only for that particular callback. For example, `ReassignOperation` has a reference to the new project to which people will be reassigned to.

When the user clicks one of the buttons we create the corresponding callback object:

dependency_injection_inversion_of_control_mendix

We use a single confirmation dialog based on the generalization entity PersonListOperation for both scenarios:

dialog_do_not_repeat_yourself

Finally, on click on Yes we execute the following microflow which invokes the proper callback microflow by committing the helper NPE object.

invoking_fynamic_function_as_callback_from_microflow

This concludes the example. You can find the complete source code for the sample project here.

callback.mpk

Wrapping up

By using the callback pattern developers can write modules that run asynchronous processes that are maintainable and extensible. Anyone who uses these modules can easily define custom logic to be executed after the asynchronous operation has completed without having to make changes to the module code. Thanks to the use of event handlers if the callback microflow is deleted or has incorrect parameters this will be caught at compilation time instead of at runtime thanks to the use of event handlers.

Thanks for taking the time to read this blog post, and I hope it helps you develop great applications in Mendix!

Extra - Calling a microflow from a nanoflow

Event handlers give us a way to pass around and invoke microflows without doing that explicitly. This comes in handy in situations where invoking a microflow explicitly is not possible, most notably in the context of a nanoflow. You might think that this defeats the point of using a nanoflow in the first place, as means to run logic on the client side. You are correct. Still I would argue that in many cases you have some checks/validations that can be done on the client side before sending a request to the server to run a microflow. In such cases the microflow is only invoked if all checks pass. This allows you to use a nanoflow to give quick feedback to users and to save some processing time on the server, while still delegating the complex logic to be executed as part of a microflow.New call-to-action

 

Andrej Gajduk

Andrej Gajduk is a consultant at Mansystems with over 5 years of experience in software development. Currently, he is a lead developer for Application Test Suite.