Triarc and DQMH
LabVIEW has already existed for over 30 years and Triarc is not the first framework which has been proposed. Comparing frameworks is not a simple task as different frameworks have merits in different areas. There is simply no such thing as the best framework, it all comes down to who you ask. As of today, there are two frameworks which have achieved a somewhat broad user base. The first is NI Actor Framework is bundled with LabVIEW and is included in the installation of LabVIEW. The second is Delacor DQMH, which is an extenssion and improvement of the NI Queued Message Handler (QMH) template.
Disclaimer: I tend to be strongly opinionated and I am certainly biased, as I would not have created Triarc if I thought there was better alternatives out there for me. The purpose of this post is not to discourage the use of DQMH, it is not without reason it has become the most successful LabVIEW framework to date.
Brief introduction to DQMH
DQMH was invented by Fabiola De la Cueva and maintained by Delacor up until the DQMH consortium was founded. This framework is very approachable and builds on the QMH template shipped with LabVIEW and introduced in the LabVIEW training courses. The framework is built around modules, where each module is a library. Each module has a main.vi which, as the name suggests, is the main VI of the module. The main.vi contains two loops out of the box, where one handles events from the user interface or API calls, and the other loop is a message handling loop. The general idea is that events enter the module through the event loop of the main.vi and work is then enqueued to the message handling loop. A common design pattern is to implement helper loops in the main.vi which runs in parallel with the other loops. By heavily leveraging scripting, DQMH avoids the introduction of object orientation and greatly simplifies the creation of the events, which would otherwise be a quite tedious and error prone task. My personal favorite feature of DQMH is the Testers which comes with each module and facilitates manual testing of the module in isolation by making calls to the API.
DQMH is freely available, although not open source. A very big selling point of DQMH is the community and user base already committed to the framework. Some companies are qualified as DQMH trusted advisors and are certified to support customers using DQMH. The documentation is also extensive, including youtube videos, help files and comments in the framework code.
Comparing DQMH to Triarc
DQMH and Triarc are similar in many ways. A DQMH module corresponds to a Triarc Process, or to be more precise a Triarc View if the module UI is needed. DQMH goes pretty far to avoid object orientation, which may make the framework more approachable for very junior developers. Triarc is object oriented by design and each process is a subclass of the Triarc Process class.
This section will go through some of the design decissions in the two frameworks and how they differ. For a better understanding, it is worth exploring the Thermal Chamber example which ships with DQMH and the counterpart implemented in Triarc.
The Actor Model
Both Triarc and DQMH implements a version of the actor model, which is a well proven design pattern for concurrent systems. The main idea is that having an actor encapsulating a state and to only communicate with it by sending messages. In DQMH the actor role is taken by the DQMH module and the state is contained in the shift register of the message handling loop. Messages in DQMH are sent by generating events, which are captured by the event handling loop, and subsequently forwarded for processing in the message handling loop. This is very similar to the NI QMH template, but it has been generalized by allowing events to be generated externally using API calls.
In Triarc the process represents the actor in the Triarc framework and the state is maintained in the class private data of the process. Communication with the process is imlemented using API calls which enqueues messages to the process. If the process implements a user interface, it will inherit from the Triarc View class. The View class has a vi called View.vi which holds the user interface. Views may be composed in subpanels and the logic of each view is implemented in the processes of the independent views. The View.vi communicates with the process using regular API methods, or private methods for enqueueing to the process loop. The flow of information in the two paradigms are compared in the figure below.
Messaging
DQMH has two types of messages called requests and broadcasts. Requests are API calls typically sent into a known module from a caller and broadcasts are generated by the caller to respond to events without coupling to the listener of the broadcast.
In Triarc there is messages, requests and broadcasts. Messages are enqueued asynchronously, while requests are synchronous requsts blocking until a response is received. Broadcasts are very similar to the DQMH broadcasts, but they do not have the same type safety as the message data in a broadcast is a variant.
Hierarchical Structure
In DQMH there is no built in hierarchical structure for modules to adhere to. In good DQMH design however, modules are often ordered in a tree-like structure, but the responsibility for maintaining the order is on the developer. In Triarc the concept of an application and nested applications maintains the hierarchical structure and keeps them isolated from each other.
The Use of Global State
The DQMH module encapsulates framework references in a functional global variable (FGV) and this globally accessible variable is used to enqueue messages to the module. This is in fact global a global resource and using global resources requires careful consideration, especially when the application grows in size. As the references are accessible from anywhere through API calls, it is very easy to have modules call each other and the data flow may be broken up into many separate timelines. This might seem convenient, but it may also leat to the code being more obscure and API calls may be hidden within subVIs.
Modules calling API VIs on each other are explicitely coupled and if the design does not define the calling order for the modules, one might end up with very tight coupling between modules. This design makes it complicated to instantiate multiple instances of a module, which in the case of DQMH is solved by introducing a cloneable module which maintains an ID to filter messages by.
In Triarc the references used by the framework are kept within the class data of the process. This means that you are not able to interract with a process unless you have access to the wire of the process. This makes the API adhere to the principles of data flow even if the processes are running asynchronously.
The Triarc framework is design with great care to prevent shared global state. There is not a single FGV, named queue, global variable, etc. in Triarc. If something needs to be globally accessible, which would often be the case with e.g. a log handler or error handler, the resource is injected on the top level VI, which is responsible for the lifecycle of the global resource. The lack of global state makes it very straight forward to instantiate multiple copies of the same Triarc Process.
Dryness
In software engineering there is a principle of not repeating the logic when writing code (the Dry principle). While one has to be pragmatic and make decissions which fits the design, having duplicate logic means having duplicate bugs and duplicate code to maintain.
In DQMH every module carries with it the full framework. One might argue that this is not an issue, as all the framework VIs are generated through scripting. In my opinion, the generated contet clutters the project and makes it difficult to get a quick overview of what the module API looks like. To illustrate this, let's have a look at the module for the device under test (DUT) in the thermal chamber template and compare it to the Triarc process implementing the same functionality.
Even if we were to lock the libraries to hide the private methods, there is still many times more stuff going on in the DQMH module. The reason we only need process specific VIs in the Triarc process is because all framework features are inherited from the main Triarc Process and View classes.
One advantage of bundling the framework with each module created is that modules may use different versions of the framework and still work within the same application. The flipside is that each module must be updated when new versions of the framework is released. In Triarc breaking changes will need to be solved by namespacing the framework within versioned libraries with an incremental naming convention.
Object Orientation
Whether object orientation is good or bad is beyond the scope of this post, but it does have a significant impact on the design. Triarc relies heavily on object oriented design, while DQMH only uses objects for certain subtasks.
One clear drawback of using a library over a class in LabVIEW, is the fact tha libraries are not first class citizens in LabVIEW whilde classes are. This means that a class may be wired on the block diagram just as any other data type, while a library cannot be used in the same way. The implications of this is that libraries cannot be composed into larger composite structures. Triarc processes are classes and therefore composeable.
By using object oriented design, powerful abstractions may be created and a lot of flexibility to define behavior at run-time is possible through subclassing. It also reduces the amount of code duplication as descending classes may use the parent class methods. A good example of this is the Show Panel.vi which is included in every DQMH module. In triarc this functionality is only defined in the View class and subclasses uses the VI from the View class to open therir panels.
Dependency Inversion
One of the main benefits of object oriented design is the separation of source code dependencies from run time dependencies. As DQMH is not object oriented, it is not possible to use abstractions to invert dependencies without wrapping DQMH modules in classes. This is a major limitation of the framework and it misses out on one of the core ideas of object oriented design.
Type Safety
In DQMH each event has an associated data type defined by a typedefined cluster. The events are generated through scripting, so the tedious task of setting this up is handled by the framework. The outcome is that each event case has statically defined types which will give the correct types at edit time. This can be pretty useful to ensure bad type casts.
In Triarc the data is flattened to a variant in each API VI and sent to the process as a variant. This puts the responsibility on the developer to cast the variant to the expected data type. In practice this is not too painful, as the pipeline the data goes through is well defined. It is more complicated for broadcasts, as each listener needs to know what data type to expect.
The dynamic typing will require more testing and preferably automated testing to prevent regression issues. On the other hand, having the correct type is not equivalent with correct behavior and we should probably be doing the testing regardless.