Events are undeniably a key aspect of event driven programming.  What is needed is a way of analysing the code and documenting the event flow.

Pulling the Events out of Event-Driven Programming Blog

Robert Bailes -  08 Dec, 2016

 
Events are undeniably a key aspect of event driven programming, but it can be difficult to keep track of all those events flying around. What with all the other stuff any decent sized application needs to do - business logic, persistence layer, mapping, adaptors, query model and such like -  those little events can get lost in all the hubbub. So, how to keep track of the little blighters?
 
What is needed is a way of analysing the code and documenting the event flow, from the process that creates the event to the outcome of the event being handled. Sequence diagrams provide a straightforward and intuitively representative way of documenting the context and outcome of an event's lifecycle and there are various technologies that can produce such digrams from textual representations of the interactions they describe. So if a way can be found to extract this information from the source code and generate the appropriate descriptions of where an event is created, where the event is handled and what happens as a result, we can automatically document the system from an event-driven viewpoint. 

Technologies

PlantUML

PlantUML Website 
Since the website has such horrific design, it may be preferable to use the language reference guide where possible.
 
UML defines many styles of diagram for different purposes, PlantUML is an open source Java project that allows these diagrams to be described using a simple and intuitive language. This description is then used to render the diagram in various image formats. UML sequence diagrams provide for detailed descriptions of the messages passed between different processes and PlantUML renders the results in an appealing visual style. As it is written in Java and available as an executable jar file it is easily incorporated into the build process. The participants can be given various representations, activation/deactivation and creation of participants can be shown, they can also be grouped together in a box (to denote different domains, for example), sequences of messages can also be grouped together, and notes in various colours and styles.
 
Given the slightly more detailed example:
 
@startuml
title Simple event lifecycle example
 
participant "Calling\nClass" as source
participant "Domain\nEvent\nBuilder" as builder
participant XxxEvent as event
control "Event\nPublisher" as queue
participant "Event\nHandler" as handler << Low Priority >>
participant "Domain\nService" as service
 
[-> source : initiatingMethod()
activate source
 
source -> builder  : newEvent()
activate builder
 
create event
builder -> event  : new()
 
builder --> source  : event
deactivate builder
 
source -> queue  : publish(event)
deactivate source
 
... event processed ...
 
queue -> handler  : handle(event)
activate handler
 
note left of handler #DEADB0
The diagram can also include notes
in different colours and styles
end note
 
handler -> service  : doStuff()
@enduml
 
 
 
the diagram produced is...
 
 

Doxygen

Doxygen is the de-facto standard tool for generating documentation from annotated C++ sources, but also supports various other languages including Java and javascript. In addition to generating online and offline documentation in various formats, much like JavaDoc, it can be used to extract the code structure from undocumented source files and visualise the relations between the various elements by means of dependency graphs, inheritance diagrams, and interaction diagrams, which are all generated automatically using GraphViz. In order to achieve this level of flexibility an intermediate XML format is used to describe the structure of the code and interactions that take place between methods. Amongst the many configuration options it is possible to define exclude patterns, for instance to ignore test classes.
 
Installers are available for Windows and various flavours of Linux, including OsX where Homebrew or MacPorts can be used - so GraphViz is automatically installed as a dependancy, and a GUI front-end is also installed on Macs. Plugins are available for various environments including Eclipse, Jakarta-Ant and Jenkins. The config file used to generate the xml and tglib files is referenced at the bottom of this blog post along with the tagfile produced for travel.

Doxygen XML Format

Once the source code is parsed, various xml files are created in an `xml/` subdirectory.
 
The root of the structure is an `index.xml` which includes a compound node for each class and interface found. This details the fields and methods of the class as nested `member` nodes, with types `variable` and `function` respectively, additionally every directory and source file have their own compound node. Each class, field and method is assigned a reference id that maps to the directory structure under the root `xml/` directory. 
 
<?xml version='1.0' encoding='UTF-8' standalone='no'?><doxygenindex xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="index.xsd" version="1.8.12">
<!-- ... -->
<compound refid="db/d39/classcom_1_1clicktravel_..._basket_validation_result" kind="class"><name>com::clicktravel::services::travel::domain::model::basket::BasketValidationResult</name>
    <member refid="db/d39/classcom_..._basket_validation_result_...5ad9" kind="variable"><name>productId</name></member>
    <member refid="db/d39/classcom_..._basket_validation_result_...191a" kind="variable"><name>subproductId</name></member>
    <member refid="db/d39/classcom_..._basket_validation_result_...140b" kind="variable"><name>error</name></member>
    <member refid="db/d39/classcom_..._basket_validation_result_...ef7e" kind="function"><name>getProductId</name></member>
    <member refid="db/d39/classcom_..._basket_validation_result_...1afa" kind="function"><name>setProductId</name></member>
    <member refid="db/d39/classcom_..._basket_validation_result_...4d7c" kind="function"><name>getSubproductId</name></member>
    <member refid="db/d39/classcom_..._basket_validation_result_...f883" kind="function"><name>setSubproductId</name></member>
    <member refid="db/d39/classcom_..._basket_validation_result_...8c59" kind="function"><name>getError</name></member>
    <member refid="db/d39/classcom_..._basket_validation_result_...4c57" kind="function"><name>setError</name></member>
  </compound>
  <!-- ... -->
</doxygenindex>
 
 
Each class and interface is described in more detail in its own xml file which can be located based on its `refid` which acts as the path to the file. Each directory in the source path is also described more extensively by a set of xml files with the prefix `dir_`, but these are of little interest in this instance. The xml describing a source file includes each line of source code marked up to highlight keywords and define references to methods fields and classes that are referred to (where these are accounted for by the parsed source code and any tag libraries supplied). The xml describing a class (or an interface) specifies each member variable and function including definitions of their type, visibility, initialisation (for variables), arguments (for functions). The descriptions of functions also include the refid of methods that the function calls and those that call the function in question.
 
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<doxygen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="compound.xsd" version="
1.8.12">
  <compounddef id="db/d39/classcom_1_1clicktravel_..._basket_validation_result" kind="class" language="Java" prot="public">
    <compoundname>com::clicktravel::services::travel::domain::model::basket::BasketValidationResult</compoundname>
      <sectiondef kind="private-attrib">
      <memberdef kind="variable" id="db/d39/classcom_..._basket_validation_result_1afd..." prot="private" static="no" mutable="no">
        <type>String</type>
        <definition>String com.clicktravel.services.travel.domain.model.basket.BasketValidationResult.productId</definition>
        <argsstring></argsstring>
        <name>productId</name>
        <!-- ... -->
        <location file="ClickPlatform/.../com/clicktravel/.../BasketValidationResult.java" line="5" column="1" bodyfile="ClickPlatform/.../com/clicktravel/.../BasketValidationResult.java" bodystart="5" bodyend="-1"/>
        <referencedby refid="db/d39/classcom_..._basket_validation_result_1a62..." compoundref="d2/d71/_basket_validation_result_8java"
startline="9" endline="11">com.clicktravel.services.travel.domain.model.basket.BasketValidationResult.getProductId</referencedby>
        <referencedby refid="db/d39/classcom_..._basket_validation_result_1a44..." compoundref="d2/d71/_basket_validation_result_8java"
startline="13" endline="15">com.clicktravel.services.travel.domain.model.basket.BasketValidationResult.setProductId</referencedby>
      </memberdef>
      <!-- ... -->
      <sectiondef kind="public-func">
      <memberdef kind="function" id="db/d39/classcom_..._basket_validation_result_1a62..." prot="public" static="no" const="no"
explicit="no" inline="yes" virt="non-virtual">
        <type>String</type>
        <definition>String com.clicktravel.services.travel.domain.model.basket.BasketValidationResult.getProductId</definition>
        <argsstring>()</argsstring>
        <name>getProductId</name>
        <briefdescription>
        </briefdescription>
        <detaileddescription>
        </detaileddescription>
        <inbodydescription>
        </inbodydescription>
        <location file="ClickPlatform/.../com/clicktravel/.../BasketValidationResult.java" line="9" column="1" bodyfile="ClickPlatform/.../com/clicktravel/.../BasketValidationResult.java" bodystart="9" bodyend="11"/>
        <references refid="db/d39/classcom_..._basket_validation_result_1afd..." compoundref="d2/d71/_basket_validation_result_8java" startline="5">com.clicktravel.services.travel.domain.model.basket.BasketValidationResult.productId</references>
        <referencedby refid="d7/dcb/classcom_..._1mapper_1_1domain_1_1to_1_1query_1_1model_1_1_ba43..._1a25..." compoundref="de/d38/_basket_validation_result_to_qm_mapper_8java" startline="13" endline="25">com.clicktravel.services.travel.mapper.domain.to.query.model.BasketValidationResultToQmMapper.map</referencedby>
      </memberdef>
 
 
References to external libraries can be included by providing a taglib file that defines the functions and variables involved. These taglib files can be generated by running Doxygen on the external library's source files. 

Where do they come from, where do they go and what do they do?

Given the xml generated by running Doxygen on Travel (and the taglib files produced by processing the other domains), it should be possible to determine the initiating method and primary outcome of every event sent or received by travel. From this we can generate a set of sequence diagrams which describe the lifecycle of each event.
 
The process would be as follows:
 
1. Run Doxygen on non-travel projects (and possibly Chedder, for the Metrics classes and such).

2. Run Doxygen on Travel project, providing the taglib files generated above.

3. Read index.xml file and locate classes of interest, primarily `DomainEventBuilder` and any `XxxEventHandler` classes.

4. For each, locate the xml file for the class from its refid and process that file as follows.

4.1 For each `newXxxEvent()` method in `DomainEventBuilder` identify the class of event returned and the callers of the method.

4.2 For each EventHandler class, identify its `getEventType()` method to identify the associated event class; locate the `handle()` method and identify the methods it refers-to; we could also identify the xml that relates to the source file for this class and extract the source code for the method (or extract it directly from the source code, as the line numbers are included in the `location` sub-node) to include in a note on the sequence diagram.

4.3 Optionally, we could consider each event class to ensure that only the `DomainEventBuilder` constructs the event and, perhaps identify the classes that refer to the event's `getXxx()` methods if a more detailed description of the handler's interaction are required.

5. Build a data structure that describes the sources of each event and links this to the appropriate event handle, with a description of the calls that result.

6. Produce PlantUML based descriptions of each event's lifecycle - from the method that creates it to the methods called by its event handler.

7. Run PlantUML on the descriptions to generate SVG images that can be included in our living documentation.

Where do we go from here?

With Doxygen to analyse the source code and extract the events' interactions with the rest of the system and a little scripting to produce PlantUML descriptions, it seems perfectly possible to produce sequence diagrams that describe the system from an event-driven perspective. In fact the whole thing could be integrated into the build process, perhaps running each night, to produce diagrams that could be integrated into our living documentation project. This could be a big win for fairly minimal effort, improving the learning curve for new SET employees and providing an overview of the system from an event-based point of view.
 
 
 
Subscribe to this blog

Use of this website and/or subscribing to this blog constitutes acceptance of the travel.cloud Privacy Policy.

Comments