Query events with AQL
The Actyx Query Language (AQL) allows you to query, filter, transform, and aggregate events in a structured fashion. For a quickstart please refer to the tutorial whereas all details are documented in the reference. In this section we take a look at how Actyx helps you access exactly those events you need and why that is important. As we will see, this has implications on how events should be tagged when they are emitted; best practices for this are discussed in the next section.
AQL evaluation explained
When you send a query to one of the API endpoints Actyx will — after a syntax check — do two things:
first it will instruct the local event store to retrieve all events matching the FROM
clause, and second it will create an evaluation engine for the following query steps.
Then, matching events will be streamed into that engine, always completing all steps before continuing with the next event.
Whenever an output emerges from the last query step it is serialised to JSON and sent to your app.
Assuming you model some entities — like TODO items — with a lifecycle we might store events for creating, editing, progress tracking, and completing them. If you want to get the IDs of all items created within the past month you might do the following:
FROM 'item' & TIME > 1M ago -- get all item events
FILTER _.type = 'created' -- skip all but creations
SELECT _.id -- pick out the ID
Assuming a couple of edits and lots of progress tracking it may well be that the filter step throws away >90% of the work done by the local event store for streaming the events. This can be avoided by tagging the creation events with an additional tag, so that we can write:
FROM 'item' & 'created' & TIME > 1M ago
SELECT _.id
The performance difference between these can be quite noticeable.
Excessive event retrieval is the #1 performance sin in Actyx, comparable to full table scans in relational databases. Event tagging fulfils the same function as indexing in those databases.
Queries sent from TypeScript apps
When you're using the machine-runner you are using AQL queries under the hood:
a machine's react
nextFactory
parameter directly corresponds to a query with only a FROM
clause.
The same goes for the SDK methods like Actyx.queryAllKnown
, which take a Where
instance to select events.
const itemTag = Tag<ItemEvent>('item') // entity type tag for “Items”
const createdTag = Tag('created') // additional tag, can be attached elsewhere
itemTag // corresponds to `FROM 'item'`
itemTag.withId('1234') // corresponds to `FROM 'item' & 'item:1234'`
itemTag.and(createdTag) // corresponds to `FROM 'item' & 'created'`
const item1 = itemTag.withId('1')
const item2 = itemTag.withId('2')
item1.or(item2) // corresponds to `FROM 'item' & 'item:1' | 'item' & 'item:2'`
// which is the same as `FROM 'item' & ('item:1' | 'item:2')`
const userTag = Tag<UserEvent>('user')
const myItem = itemTag.id('1234') // note that `.id()` omits the 'item' tag and doesn’t
// require matching events to be of type `ItemEvent`
userTag.withId('Fred').and(myItem) // => `FROM 'user' & 'user:Fred' & 'item:1234'`
Note the different roles of tags:
'item'
marks events emitted by an “item” entity and constrains the event type'item:1234'
is an additional tag that marks one particular “item” entity; it can be created withitemTag.id('1234')
and does not by itself constrain the event type'created'
marks the occasion and could be combined with items, users, etc.
Note that the createdTag
could be declared as Tag<{ id: string }>('created')
to constrain its application to events that do have an id
property of type string
.
The Where
interface correctly tracks these constraints through .and()
and .or()
combinations of tags.
Check out our reference documentation on AQL for more info on expressions and data transformations!