Persistent Queries


Version 7.1.0 of NactJS and version 4.1.0 of ReasonNact added a new feature, Persistent Queries, which should go some way to supporting patterns like CQRS (which is something I have been asked a few times about).

I think the way the feature achieves its goals may be a little surprising for some people, who are used to a more global solution to CQRS; an example being events streams which pipe to some form of database. I decided to take a more localised, lazy approach to read models, because I feel that this is truer to what Nact is about (small, simple, functional). The global model is still useful in many instances, but it is quite a prescriptive approach and can anyway be modeled using the primitives Nact offers.

As the documentation explains:

Nact provides the persistent query feature as a light-weight (but powerful) form of CQRS. A persistent query takes in a persistent key, and returns a function which when invoked replays the persisted events, building a result which is finally returned as a promise. A persistent query is lazy in that it only retrieves events when forced. It may be invoked any number of times, each time checking for new events. The result and sequence number of the query are cached with an optional timeout, and the result may optionally also be snapshotted every given number of messages.

The rest of the documentation for persistence queries in js and reason can be found here and here.

January 2018 Update


New features

This month a few new features were added to the framework. JavaScript gained logging, while Reason added logging, encoders and decoders, and type adapters. The ReasonML additions were largely focused on improving ergonomics.

Thanks to @iskandersierra for making logging in Nact JS happen. Iskander Sierra is Nact's first external contributor. Iskander was patient despite my fussiness, so huge thanks. He definitely deserves a follow ๐Ÿ˜‰.

A preview of the future

One of the main innovations of the actor model was moving the idea of a process from a physical construct to a logical one. This abstraction allowed developers to focus on writing domain code without worrying about locking, staleness, or even on what core/machine the process was located. A cluster can be similarly abstracted. This allows developers to worry less about networking, load balancing, and service discovery, and more about solving domain problems at scale.

I've been thinking hard about what such an abstraction would look like, and think I have something which could work. Obviously this is all subject to change, and a lot of hard work will need to be done to make it a reality (failure detection, gossiping, sharding, membership and so on) but I'd be very happy if Nact clustering could look something like this:

This is the ReasonML version:

let system = start(~seeds=["https://system-1","https://system-2"]);

let (actorRef, cluster) = spawnCluster(~key="abc", system, RoundRobin);

let member = spawnStateless(system, (msg, ctx) => resolve(Js.log(msg)));

cluster +@ member; 
actorRef <-< "Hello Cluster!";

...and the JavaScript one:

const system = start({seeds: ["https://system-1", "https://system-2", "https://system-3"]});

const cluster = spawnCluster("abc", system, roundRobin);

let member = spawnStateless(system, (msg, ctx) => console.log(msg));

join(cluster, member); 
dispatch(cluster, "Hello Cluster!");

In the example, the system is fed a set of well known addresses to bootstrap discovery of peer nodes. Then a virtual cluster is created. This cluster has a particular routing strategy (in this case round robin) as different routing strategies have different consistency requirements.

The clusterRef supports two additional operations: join and leave or the [email protected] and [email protected] operators in Reason respectively. Messages can be dispatched to the cluster as per usual. In the Reason version, the clusterRef and actorRef are separate so as to simplify typing.

This clustering design feels ergonomic, fairly simple for the end user and flexible. If you spot a fatal flaw, please message me on discord; I like ideas that have a hope of actually working.

2017: The beginning


Midway through 2017, I was due to start a job at Root. My previous job had a .NET stack, which is where I'd learnt Akka. I'd come to really appreciate the benefits and constraints provided by an actor system. Root's stack used Node.js however, and while there are one or two actor frameworks available, it felt that they had quite different goals: focusing on performance first. From my experience introducing Akka to my colleagues, I feel that getting the ergonomics and pedagogical aspects right from the start is of greater importance. Nact is thus being designed to make it easy for software teams to fall into the pit of success.

This year most of the important building blocks were added to the framework, first class ReasonML bindings were released, and the website you're reading this on was published.

I was fortunate to join Root at a stage where the company doesn't face scaling problems. Thus we were able to adopt nact purely for it's event sourcing capabilities. Adding clustering is a priority for 2018, especially to support Root's move to kubernetes.

Other goals for 2018 are:

  • Further improvement to the documentation
  • Tooling for logging and monitoring
  • Better error messages
  • Support for more storage providers
  • A library which implements common patterns (such as at-least once delivery, scheduled jobs, the repository pattern, etc.) to reduce boilerplate code
  • Eliminating the rxjs dependency.

If you're interested in nact, would like to contribute to the framework, have suggestions, complaints or any comments, please reach out. I can be found on the nact discord or alternatively you can email me at [email protected].

Wishing you a happy holiday,

P.S: Root is hiring