Best Practices
This guide is not about the syntax or semantics of the DimML language, but focuses on usage patterns and anti-patterns.
1. Favor asynchronous processing
Data collection in DimML is asynchronous by default. Take for example the following code:
concept Page {
match '*'
val title = `document.title`
flow
=> delay['5s']
=> code[title = `'Delayed: '+title`]
=> console
flow
=> code[title = `'Default: '+title`]
=> console
}
In the console you will notice that the second flow shows output before the first one. Furthermore, the browser's HTTP request to DimML containing the data finishes long before all the flows have completed (look for a request containing the string '/flow/' in your browser's development tools). The default asynchronous behavior ensures that no component is waiting unnecessarily.
Sometimes you do need to wait on processing. To use the output of a flow on the website, the HTTP exchange should wait on a flow to reach a specific point. The 'out' flow is a special instruction to indicate to any waiting party what the result of flow processing is. To start waiting on a flow, you could use the 'output' plugin:
concept Page {
match '*'
val title = `document.title`
flow
=> delay['5s']
=> code[title = `'Delayed: '+title`]
=> out
output `console.log(title)`
}
Don't make the HTTP exchange wait on things that are not required to produce output. Assume you need to do a complex operation to produce output and then you want to store the result in a database as well. A naive approach would be:
flow
=> delay['5s'] // complex operation here
=> sql[...] // log in DB
=> out
output `...`
This approach would wait for a response from the database before finishing the HTTP exchange. If the database response is not required, don't wait for it:
flow
=> delay['5s'] (complex-done) // complex operation here
=> out
flow (complex-done)
=> sql[...] // log in db
output `...`
It is best to keep the flows between start and the 'out' flow as short as possible. You can always do part of the processing asynchronously by branching the flows like above.
Similarly avoid unnessacery steps when using the 'call' flow. 'call' allows dynamic invocation of another flow. The first flow will be 'put on hold' until the second flow reaches an 'out' statement. In the following example the 'logln' function is defined to log debug messages in a database:
def logln = {msg => `:logln`@ref}
flow (logln) sql[...] => out
This implementation makes the calling flow wait on a successful database call before continuing. In most cases this is unnecessary. Favor the following implementation:
def logln = {msg => `:logln`@ref}
flow (logln) out
flow (logln) sql[...]
2. Avoid large objects in memory
Don't use camel:from or http to load megabyte files in a string: use streaming mode / stream:in instead. Don't use sql to load 10000 or more rows in memory. Rewrite solution to avoid these types of queries.
3. Minimize Javascript requests
Explain how multi-app concepts work (generating more javascript requests). Every dimml.event can lead to more Javascript requests. Strategies to avoid overuse. Show server-side routing.
4. Use rate limiting when required
Be aware of external systems that have a maximum rate of processing. Use rate limiting on stream:in flow. Slow down calls to database by using the 'call' flow in serial mode and the delay flow. Sample similar data.
5. Favor static flows privately and dynamic flows publicly
In files that are all under your control and form a logical application, it is more efficient to use static flow connections: Name the output of a flow and start new flows with that name. But for readability it is best to only use this mechanic in one file. Files become difficult to read when it is not clear what happens next. Tapping into a named flow in multiple files complicates that understanding.
Use dynamic flows to refer to library functions: The 'call' flow processor. It explicitely tells the reader what happens next and has better code isolation semantics. The only exception should be when you want to make data at a specific point in a flow publically available for asynchronous processing.
6. Use dimml.js standard functions
When using the DimML SLOC (//cdn.dimml.io/dimml.js), the dimml library provides a series of standard functions in the
window.dimml
object. Some examples are:
- test
A full reference can be found here: ...
7. Create your own service logging
It can be difficult to debug services that did not perform as expected. Show a 'log' flow that can be used in a 'call' statement to log various steps in a service flow. This helps in analyzing whether the service actually ran at the expected time and how far things operated normally.
8. Use preMatch to ensure libraries are loaded
When client-side code generated by DimML requires certain libraries (e.g. jQuery), you can use the preMatch function to wait for the availability of such libraries before continuing the matching process.
9. Don't transfer more data than necessary
Some elements are contained in the HTTP request, e.g. user-agent. No need to collect it is a 'val'. Don't process the user-agent client-side and send derived vals to DimML. Don't expose business logic just because Javascript is easier to write than Groovy (use server-side Javascript). Don't collect kilobytes of data with every request that stays mostly the same throughout a session (store in session flow instead).