Please fasten your seat belts… CQRS

The term saga is commonly used in terms of CQRS (Command Query Responsibility Segregation) pattern. More info about it you can find here. Generally term saga is referred to code that coordinates and routes messages between bounded contexts and aggregates. But in term of Redux Saga we can just simply name it as process manager. It’s completely covers all we need to know about sagas. More info about term definition can be found from original paper “Sagas”.

When you implement the CQRS pattern, you typically think about two types of messages to exchange information within your system: commands and events. Commands are requests to the system to perform some task or action. Commands are usually performed by one recipient and only once. Events are notifications. They inform interested parties that something has happened. They can have multiple subscribers.

Let’s describe ordering some product in online store.

In a complex system there may be some business processes that involve multiple aggregates. In such systems multiple messages of different types are exchanged by the participating aggregates. For example to order some goods in store you may proceed with such steps as on picture (1–8) just for successful result. They must all cooperate to enable a customer to complete a purchase. You can think about overhead of using such complex solution. It’s true for some small business process. But when you have tens aggregates and tons of messages inside, the benefits become more visible.

When to use process managers? When you have a large number of events and commands that is hard to manage as a collection of point-to-point interactions between aggregates. And when you want to make easier to modify message routing in your system. Process manager gives a single source of truth where all routing rules are defined.

When not to use a process managers? When you have a small numbers of aggregates with limited numbers of messages.

No business logic should be placed in process managers. All business logic belongs to concrete aggregate type.

Thunk and Saga

There are two common ways of dealing with side effects in Redux applications. Thunk is a function that already has everything it needs to execute. In Redux actions are defined with simple objects. And the main benefit of thunk that it allows to send a function instead. So you already able to write some logic to execute immediately and dispatch other actions.

This just exports a function that returns the store, which we will add to Provider.

In this actions creator file we have three simple actions and one action which will be managed over thunk middleware. Depending on fetch result fetchOrganization action function will dispatch FAIL or SUCCESS actions itself.

This solution is great for some small logic. But for bigger solutions you already need to use some greater middleware.

What is SAGA

A few short words about Saga approach. Saga is just a series of connected stories. Saga are Long Lived Transaction that can be written as a sequence of transactions that can be interleaved. All transactions in the sequence complete successfully or compensating transactions are ran to amend a partial execution. Compensation transaction are able to undo or add some info about transaction or it’s fail.

Sagas using yield keyword and it’s ability to halt execution within a function. So, by writing generator you just write steps necessary to complete your action.

Let’s begin from initial steps and add it to the project with npm install redux-saga .

Let’s define our fetchDataSaga with some comments and explanations:

So, we registered our saga in the system within middleware. This saga listens REQUEST_FETCH action and ready to go with its own flow to make everything we need while fetch and put data to the store after. In any case all sagas are implemented as generator functions that yield object to the redux-saga middleware. The yielded objects are kind of instructions which will be interpreted by the middleware in proper way. After first yield generator suspends and waits from environment for data resolving and command to continue saga till the next yield .

Flow orchestration inside Redux Saga implemented over using so called Effects. Each time we yield some object to outer environment within calling put , take , delay and so on, Saga knows what to do with such instructions. For example result of put({ type: 'FETCH_DATA' }) instructs the middleware to dispatch FETCH_DATA action.

What we have after? By dispatching only one action we performing complex async flow and getting payload to the store. Isn’t it amazing? This flow can be covered with tests as well to make sure we performing everything correct.

Covering the flow with tests

As we already know from generators each time we invoke objects with yield expression we getting back object with format {value: ..., done: [true|false]} . The last invocation will return {value: undefined, done: true} for us. So, keeping that in mind we’ll simply iterate over the returned generator and check the yielded values.

Helpers

All communication with redux-saga comes through helper effects which wrap internal functions to spawn tasks when some specific actions are dispatched.

put returns plain object that instructs redux-saga to dispatch action with type, mentioned in put argument

call calls function and returns result that will be passed back by redux-saga.

take , takeEvery , takeLatest returns plain object that instructs redux-saga to start listening some specific action in the system. take will listen only once, takeEvery will listen every action and takeLatest will listen only last one and cancel previous instruction.

fork used to spawn multiple watchers with built-in helpers to watch actions by multiple sagas in the background.

To create effects, you can use the functions provided by the library in the redux-saga/effects package.

Error handling

We can catch errors inside the saga using the familiar try/catch syntax easily.

In case we need to throw error inside our saga you can use .throw method to fake error. This will cause generator to break the current flow and execute the catch block.

Final world for this intro

Any side effects that comes from sagas always done by yield some declarative effects. This brings some wonderful advantages for us like easy testing. Saga composes all effects together to implement self descriptive and easy to read control flow. In this article described only the simplest kind of flow. You can use also if , while and for operators to implement much more complex flows.