68

Building a CQRS + ES App With reSolve

 5 years ago
source link: https://www.tuicool.com/articles/hit/2InIRrv
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Building a CQRS + ES App With reSolve

Learn how to build React+Redux Apps with the reSolve Framework

63meEnV.png!weba2iEniU.png!web

A couple of days back, someone asked me if I have worked with CQRS and ES. And honestly, I had no idea what that person was talking about. I quickly got in front of my laptop and started my quest of learning about these things.

CQRS stands for Command Query Responsibility Segregation . It is a programming principle that was originally introduced by Bertrand Meyer during his work on the Eiffel programming language.

In simple terms, CQRS states that everything in our code should either be a command that performs some action, or a query that that returns some data to the caller. CQRS provides a clear separation of concerns. The command can only change the application state and the query can only get the state to the user.

ES here stands for Event Sourcing . In event sourcing, the changes made to the state are stored as a sequence of events, where each event describes a change made to the application state.

Both ES and CQRS are most often grouped together. By applying event sourcing above CQRS, we can easily persist each event on the command side of the application. The query side of the app can then be easily derived from the sequence of events.

After getting a basic understanding of what Event Sourcing and Command Query Resposibility Segregation are, I set out to look for something that could help me easily build applications using these interesting principles.

That is when I came across reSolve!

jmqq2am.jpg!webBzqyiea.jpg!web

reSolve is an awesome new framework for building apps based on CQRS and ES.

In this post, I will show you how to get started with building applications with reSolve.

You can get the entire source code of this post here:

Tip: Use Bit to share and reuse React components. Create a collection you and your team can share, and use the components to build new apps faster. Try it.

Reusable UI components with Bit: Choose, try, install

Getting Started

Before you start following the code in this post, make sure that you have the following things installed on your system:

  • Node (version 8 or higher)
  • Yarn
  • A good code editor ( I prefer VS Code :heart:)

In order to create a reSolve application, we need to use the create-resolve-app CLI. A CLI is a command line interface which will help us install a few useful things to get started.

To install this CLI, open the command terminal on your system and run the command:

$ yarn create resolve-app my-app

This will create a new folder in your system named my-app and fill it with everything that we will need to build an app. Take a look under the hood by opening the package.json file inside app’s directory.

Jbe2Mzb.png!webVNRJjmq.png!web

The dependencies section tells us what under the hood of our app. You can see here that our app is basically a React app with Redux for State management, configured to work as a CQRS app using reSolve!

To locally deploy the app on your system’s browser, open the command terminal and run the dev script using Yarn as shown below:

$ yarn run dev

The app will then open on your browser at localhost:3000 :

eYBZZvM.png!webVFbyMnB.png!web

Write Side of the App

As mentioned before, the write side of the app handles the commands and emits events that are then saved to the event store.

According to the CQRS and Event Sourcing principles, commands are handled by Domain Objects, which are grouped inside aggregates . reSolve implements aggregates as static objects containing sets of functions, which can either of the following:

  • Command handlers — Handles commands and emit events in response.
  • Projections — Used to build aggregate state from events.

Let’s start actually start working on our app by building the App’s write side.

Step 1: Create an Aggregate

In this post, I am going to create a simple Todo app. To create an aggregate for this app, we first need to think out about the types of events that aggregate will produce. I can think of only two for now — An event where the Todo List is created, and another event where the actual Todo is created.

Inside the client folder, and create a file named eventTypes.js and write the following code inside it:

export const TODO_LIST_CREATED = "TODO_LIST_CREATED";
export const TODO_ITEM_CREATED = "TODO_ITEM_CREATED";

Next, we need to create a file that holds all the command handlers for the TodoList aggregate. So inside the common/aggregate , delete the pre-existing files and create a new one named todo_list.commands.js and write the following code inside it:

import {TODO_LIST_CREATED, TODO_ITEM_CREATED} from '../eventTypes';
export default {
  createTodoList: (state, {payload: {name}}) => {
    if (!name) throw new Error("name is required")
    return {
      type: TODO_LIST_CREATED,
      payload: {name}
    }
  },
  createTodoItem: (state, {payload: {id, text}}) => {
    if (!id || !text) throw new Error('Id or Text is not given')
    return {
      type: TODO_ITEM_CREATED,
      payload: {id, text}
    }
  }
}

Here we are exporting an object that contains two command handlers — createTodoList and createTodoItem . Each command handler receives the state and a payload . The createTodoList command contains the name of the Todo list as payload , and the createTodoList command contains a Todo item’s id and the task as payload .

The last thing that we need to do is register the aggregate in the config.app.js file. Inside this file, you will see the aggregates section. Rewrite it as shown below:

aggregates: [
  {
    name: 'ToDoList',
    commands: 'common/aggregates/todo_list.commands.js',
  }
],

To verify if our aggregate is working or not, open POSTMAN and send the following POST request to localhost:3000/api/commands .

3iaaMzy.png!webFRJjAnR.png!web

You have now created a Todo-list using the createTodoList command handler. To add a Todo item to this list, send the following POST on Postman:

UBNZJzF.png!web3IFFNzF.png!web

We can see here that the command handlers are working. But how we know if the data is getting stored in the event store? In your app’s root directory, open the file named event-storage.db , you will see all that there is a record of the event of Todo-list and Todo-item being created is stored there.

Our command handlers are now working beautifully. But there are a couple of loopholes that we need to take care of. They are:

aggregate-id

To solve these issues, we need to store the some data about previously performed operations. This is where the state comes in. The state is created by an aggregate projection instantly. Lets create a projection inside a new file named todo_list.projection.js inside the common/aggregates folder.

import {TODO_LIST_CREATED} from '../eventTypes';
export default {
  Init: () => ({}),
  [TODO_LIST_CREATED]: (state, {timestamp}) => ({
    ...state,
    createdAt: timestamp
  })
};

In any projection object, you should add a Init function and a set of projection functions. The Init function initializes the aggregate state, which is an empty object here. The projection functions build the aggregate state based on the aggregate’s events. The function receives the previous state and an event and returns a new state that is based on the input. Don’t forget to register this projection in the app.config.js file as shown below:

aggregates: [
{
name: 'ToDoList',
commands: 'common/aggregates/todo_list.commands.js',
projection: 'common/aggregates/todo_list.projection.js'
}
],

The TODO_LIST_CREATED projection function adds the event’s timestamp to the state. We will then use this info to find out whether the list has been already created or not. So go to the todo_list.commands.js file and rewrite the code as shown below:

import {TODO_LIST_CREATED, TODO_ITEM_CREATED} from '../eventTypes';
export default {
createTodoList: (state, {payload: {name}}) => {
if(state.createdAt) throw new Error("The list already exists")
if (!name) throw new Error("name is required")

return {
type: TODO_LIST_CREATED,
payload: {name}
}
},
createTodoItem: (state, {payload: {id, text}}) => {
if(!state || !state.createdAt) {
throw new Error("List does not exist")
}
if (!id || !text) throw new Error('Id or Text is not given')
return {
type: TODO_ITEM_CREATED,
payload: {id, text}
}
}
}

Read Side of the App

Till this point, our app can create data entries by sending commands to the backend. We now need to write some code that will allow us to fetch that data back. We will start by creating a View model. A View model can build the our application’s state instantly, helping us keep the implementation easy enough to get right on the first try.

Start by deleting everything inside the view-models and create a new file named todo_list.projection.js with the following code inside it:

import {TODO_ITEM_CREATED, TODO_LIST_CREATED} from '../eventTypes';
export default {
  Init: () => null,
  [TODO_LIST_CREATED]: (state, {aggregateId, payload: {name}}) => ({
    id: aggregateId,
    name,
    list: []
  }),
  [TODO_ITEM_CREATED]: (state, {payload: {id, text}}) => ({
    ...state,
    list: [
      ...state.list,
      {
        id,
        text,
        mark: false
      }
    ]
  })
};

Similar to commands, we need to register this View Model projection in the config.app.js as well. Search for the viewModels section in the file and rewrite it as shown as below:

viewModels: [
  {
    name: 'TodoList',
    projection: 'common/view-models/todo_list.projection.js'
  }
],

To get if the viewModel works, go to Postman and send a GET request as shown below:

2Ir2quQ.png!webM7Zjaim.png!web

And that's it! We are done building the Write and Read Side of the App. All that is left to is present it all on the application, A.K.A FrontEnd .

Display Data in the App

In this section, we will see how to build the frontend of the app using React and display the Todo-List in the app.

In the client/container folder, create a file named TodoList.js and write the following code inside it.

import { ListGroup, ListGroupItem, Checkbox} from 'react-bootstrap';
export class TodoList extends React.PureComponent {
  render() {
    const list = this.props.data.list
    return (
      <ListGroup style={{maxWidth: '500px', margin: 'auto'}}>
        {list.map(task => (
          <ListGroupItem key={task.id}>
            <Checkbox inline>{task.text}</Checkbox>
          </ListGroupItem>
        ))}
      </ListGroup>
    )
  }
}

The reSolve framework provides with a library called resolve-redux that connects the redux state management to the framework. Inside TodoList.js file, import the connectViewModel HOC and use it to bind the ToDoList component to the TodoList view model as shown below:

export const mapStateToOptions = (state, ownProps) => {
  return {
    viewModelName: 'TodoList',
    aggregateIds: ['Todo-list-1']
  }
}
export default connectViewModel(mapStateToOptions)(TodoList)

Finally, we need to import the TodoList component into the App.js file and insert it inside the App as shown below:

import React from 'react'
import TodoList from './TodoList'
const App = () => (
  <div>
    <h1 align="center">Todo App using reSolve</h1>
    <TodoList/>
  </div>
)
export default App

Rerun the dev script using yarn and you should be able to see the TaskList in the browser as shown below:

RbEzYz2.png!web

Updating Todos

One of the core features of any Todo App is the ability to mark the items as completed or not_completed . Currently, the only Todo in our app is Learn CQRS and it is marked as not completed by default. Let’s add some code that will allow us to toggle the Todo’s as completed and not completed.

First, we need to make some additions to the write side of the app. Go to the common folder and write the following code in eventTypes.js file:

export const TODO_COMPLETED = "TODO_COMPLETED";

Then import this type in the aggregates folder’s todo_list.command.js file and write the following command handler:

completeTodoItem: (state, {payload: {id}}) => {
  if(!state || !state.createdAt) {
    throw new Error(`List does not exist`)
  }
  if (!id) throw new Error('id not provided')
  return {
    type: TODO_COMPLETED,
    payload: {id}
  }
}

Next, we add the following code to the View Model inside the common/view-models/todo_list.projection.js . Don’t forget to import the TODO_COMPLETED type into this file as well.

[TODO_COMPLETED]: (state, {payload: {id}}) => ({
  ...state,
  list: state.list.map(task =>
    task.id === id ? {
      ...task,
      mark: !task.mark
    }
    : task
  )
})

Our backend is now ready! Next, we need to work on the user interface. We have already connected the TodoList to the reSolve View Model, using which the component’s props already include an array of action creators that will dispatch actions on the client and send the corresponding commands to the app.

To use the action creator to edit data in the app, write the following code at the bottom of TodoList.js file that is inside the client/containers folder:

export const mapDispatchToProps = (dispatch, {aggregateActions}) =>
  bindActionCreators(
    {
      ...aggregateActions
    },
    dispatch
  )
export default connectViewModel(mapStateToOptions)(
  connect(
    null,
    mapDispatchToProps
  )(TodoList)
)

Don’t forget to add the following imports to the top of the file as well:

import React from 'react';
import {connect} from 'react-redux';
import {connectViewModel} from 'resolve-redux';
import {bindActionCreators} from 'redux';
import { ListGroup, ListGroupItem, Checkbox} from 'react-bootstrap';

Here, we connected the TodoList component to the redux state using connect function from react-redux library. Then, the component is connected to the reSolve View Model. The connect function is called with mapDispatchToProps function, that takes reSolve aggregate actions from the component’s payload and wraps them into the dispatch function call using bindActionCreators . All this has allowed us to add the completeTodoItem to the component’s props. Inside the TodoList component, add the following code inside the render() function:

const completeTodoItem = this.props.completeTodoItem;

We can then use completeTodoItem to handle click events on the checkbox.

<Checkbox
  inline
  mark={task.mark}
  onChange={completeTodoItem.bind(null, 'Todo-list-1', {
    id: task.id
  })}
>
  {task.text}
</Checkbox>

We can now mark the Todo item as completed or not completed. Next, we will see how to use the createTodoItem function to add new Todo Items.

In the TodoList.js file, write the following code inside the component and below the ListGroup section.

<ControlLabel>Task Name</ControlLabel>
<Row>
  <Col md={8}>
    <FormControl
      className="example-form-control"
      type="text"
      value={this.state.itemText}
      onChange={this.updateItemText}
      onKeyPress={this.onItemTextPressEnter}
    />
  </Col>
  <Col md={4}>
    <Button
      className="example-button"
      bsStyle="success"
      onClick={this.createTodoItem}
    >
      Add Task
    </Button>
  </Col>
</Row>

Also, write the following methods inside the TodoList component (but not inside its render function):

state = {itemText: ''}
createTodoItem = () => {
  this.props.createTodoItem('Todo-list-1', {
    text: this.state.itemText,
    id: Date.now().toString()
  })
  this.setState({
    itemText: ''
  })
}
updateItemText = event => {
  this.setState({
    itemText: event.target.value
  })
}
onItemTextPressEnter = event => {
  if (event.charcode === 13) {
    event.preventDefault()
    this.createTodoItem()
  }
}

And That’s All! Our ToDo App is Ready!

ANzuArQ.png!webmYRRjq2.png!web

I would also like to mention that the View Model is reactive. Since it is using redux reducers, it can be updated in real time by applying events.

resolve-redux receives the events through websockets and updates the View Model.

Meaning if you open your app in two browsers and make some changes in one, it will be automatically updated in the other browser instantly!

You can check out the entire source code of this app here:

Conclusion

By following the CQRS principle, developers can avoid making their app’s unnecessarily complex.

The main benefit of following CQRS principle is that it allows us to create a simpler model by separating queries and commands.

CQRS is an exciting new approach to building apps, and reSolve provides us with an easier and simpler way to do so.

Be sure to check out this amazing framework! I enjoyed playing around with it while writing this post, and will be sure to write more posts on reSolve in the near future!

In the meantime, check out reSolve’s extremely helpful documentation here:

Thanks for reading this long post! I hope this post helped you understand CQRS, Event Sourcing and reSolve framework a little better. If you liked this post, then please do give me a few :clap: and please feel free to comment below. Cheers!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK