| id | api-reference |
|---|---|
| title | API Reference |
The table below lists functions a custom Read Model's connector should implement.
| Function Name | Description |
|---|---|
| connect | Initializes a connection to storage. |
| disconnect | Closes the storage connection. |
| drop | Removes the Read Model's data from storage. |
| dispose | Dispose of this connector's unmanaged resources. |
Initializes a connection to storage. An implementation should return a store object.
| Argument Name | Description |
|---|---|
| readModelName | A read model for which to establish a connection. |
const connect = async readModelName => {
fs.writeFileSync(`${prefix}${readModelName}.lock`, true, { flag: 'wx' })
readModels.add(readModelName)
const store = {
get() {
return JSON.parse(String(fs.readFileSync(`${prefix}${readModelName}`)))
},
set(value) {
fs.writeFileSync(`${prefix}${readModelName}`, JSON.stringify(value))
}
}
return store
}Closes the storage connection.
| Argument Name | Description |
|---|---|
| store | A store object. |
| readModelName | The read model to disconnect. |
const disconnect = async (store, readModelName) => {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
readModels.delete(readModelName)
}Removes the Read Model's data from storage.
| Argument Name | Description |
|---|---|
| store | A store object. |
| readModelName | A Read Model whose data to remove. |
const drop = async (store, readModelName) => {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
safeUnlinkSync(`${prefix}${readModelName}`)
}Dispose of all unmanaged resources provided by this connector.
const dispose = async () => {
for (const readModelName of readModels) {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
}
readModels.clear()
}The table below lists functions that you can use to communicate with a Read Model store through a store object.
| Function Name | Description |
|---|---|
| defineTable | Defines a new table within the store. |
| find | Searches for data items. |
| findOne | Searches for a single data item. |
| count | Returns the number of items that meet a condition. |
| insert | Inserts an item into a table. |
| update | Updates data items. |
| delete | Deletes data items. |
Defines a new table within the store.
| Argument Name | Description |
|---|---|
| tableName | The new table's name. |
| tableDescription | An object that describes the new table's structure. |
Init: async store => {
await store.defineTable('Stories', {
indexes: { id: 'string', type: 'string' },
fields: [
'title',
'text',
'link',
'commentCount',
'votes',
'createdAt',
'createdBy',
'createdByName'
]
})Searches for data items based on the specified expression.
| Argument Name | Description |
|---|---|
| tableName | A table name. |
| searchExpression | An object that defines a search expression. |
| fieldList | A list of fields to fetch. |
| sort | A sort order. |
| skip | A number of data items to skip. |
| limit | The maximum number of data items to fetch. |
const getStories = async (type, store, { first, offset }) => {
await store.waitEventCausalConsistency()
const search = type && type.constructor === String ? { type } : {}
const skip = first || 0
const stories = await store.find(
'Stories',
search,
null,
{ createdAt: -1 },
skip,
skip + offset
)
return Array.isArray(stories) ? stories : []
}Searches for a data item based on the specified expression.
| Argument Name | Description |
|---|---|
| tableName | A table name. |
| searchExpression | An object that defines a search expression. |
| fieldList | A list of fields to fetch. |
[STORY_UPVOTED]: async (store, { aggregateId, payload: { userId } }) => {
const story = await store.findOne(
'Stories',
{ id: aggregateId },
{ votes: 1 }
)
await store.update(
'Stories',
{ id: aggregateId },
{ $set: { votes: story.votes.concat(userId) } }
)
},Returns the number of items that meet the specified condition.
| Argument Name | Description |
|---|---|
| tableName | A table name. |
| searchExpression | An object that defines a search expression. |
const getStoryCount = async (type, store) =>
const count = await store.count('Stories', {})
return count
}Inserts an item into the specified table.
| Argument Name | Description |
|---|---|
| tableName | A table name. |
| document | An object that is an item to insert. |
[STORY_CREATED]: async (
store, { aggregateId, timestamp, payload: { title, link, userId, userName, text } }
) => {
const isAsk = link == null || link === ''
const type = isAsk ? 'ask' : /^(Show HN)/.test(title) ? 'show' : 'story'
const story = {
id: aggregateId,
type,
title,
text,
link: !isAsk ? link : '',
commentCount: 0,
votes: [],
createdAt: timestamp,
createdBy: userId,
createdByName: userName
}
await store.insert('Stories', story)
},Searches for data items and updates them based on the specified update expression.
| Argument Name | Description |
|---|---|
| tableName | A table name. |
| searchExpression | An object that defines a search expression. |
| updateExpression | An object that defines an update expression. |
| upsertOption | A boolean value that defines whether to create a new item if an existing item meeting the specified criteria was not found. |
[STORY_UPVOTED]: async (store, { aggregateId, payload: { userId } }) => {
const story = await store.findOne(
'Stories',
{ id: aggregateId },
{ votes: 1 }
)
await store.update(
'Stories',
{ id: aggregateId },
{ $set: { votes: story.votes.concat(userId) } }
)
},Deletes data items based on the specified search expression.
| Argument Name | Description |
|---|---|
| tableName | A table name. |
| searchExpression | An object that defines a search expression. |
[SHOPPING_LIST_REMOVED]: async (store, { aggregateId }) => {
await store.delete('ShoppingLists', { id: aggregateId })
}The code sample below demonstrates how to use this API to communicate with a store from a Read Model projection and resolver.
A saga's event handler receives an object that provides access to the saga-related API. This API includes the following objects:
| Object Name | Description |
|---|---|
| store | Provides access to the saga's persistent store (similar to the Read Model store). |
| sideEffects | Provides access to the saga's side effect functions. |
In addition to user-defined side effect functions, the SideEffects object contains the following default side effects:
| Function Name | Description |
|---|---|
| executeCommand | Sends a command with the specified payload to an aggregate. |
| scheduleCommand | Similar to executeCommand, but delays command execution until a specified moment in time. |
The sideEffects object's isEnabled field indicates whether or not side effects are enabled for the saga.
Sends a command with the specified payload to an aggregate.
| Argument Name | Description |
|---|---|
| command | Specifies a command object. Refer to the Write Side article for more information. |
await sideEffects.executeCommand({
aggregateName: 'User',
aggregateId: event.aggregateId,
type: 'requestConfirmUser',
payload: event.payload
})Similar to executeCommand but delays the command's execution until a specified moment in time.
| Argument Name | Description |
|---|---|
| command | Specifies a command object. Refer to the Write Side article for more information. |
await sideEffects.scheduleCommand(
event.timestamp + 1000 * 60 * 60 * 24 * 7,
{
aggregateName: 'User',
aggregateId: event.aggregateId,
type: 'forgetUser',
payload: {}
}
)Resolve provides a standard HTTP API that allows you to send aggregate commands and query Read and View Models. Refer to the Standard HTTP API topic for more information.
To query a Read Model from the client side, send a POST request to the following URL:
http://{host}:{port}/api/query/{readModel}/{resolver}
| Name | Description |
|---|---|
| readModel | The Read Model name as defined in config.app.js |
| resolver | The name of a resolver defined in the Read Model |
The request body should have the application/json content type and the following structure:
{
param1: value1,
param2: value2,
// ...
paramN: valueN
}The object contains parameters that the resolver accepts.
Use the following command to get 3 users from the with-saga example:
curl -X POST \
-H "Content-Type: application/json" \
-d "{\"page\":0, \"limit\":3}" \
"http://localhost:3000/api/query/default/users"To query a View Model from the client side, send a GET request to the following URL:
http://{host}:{port}/api/query/{viewModel}/{aggregateIds}
| Name | Description |
|---|---|
| viewModel | The View Model name as defined in config.app.js |
| aggregateIds | The comma-separated list of Aggregate IDs to include in the View Model. Use * to include all Aggregates |
Use the following command to get the shopping-list example application's state:
curl -g -X GET "http://localhost:3000/api/query/Default/shoppingLists"You can send a command from the client side as a POST request to the following URL:
http://{host}:{port}/api/commands
The request body should have the application/json content type and contain the command's JSON representation.
{
"aggregateName": aggregateName,
"type": commandType,
"aggregateId": aggregateID,
"payload": {
"param1": value1,
"param2": value2,
...
"paramN": valueN
}
}
| Name | Type | Description |
|---|---|---|
| aggregateId | string | The ID of an aggregate that should handle the command |
| aggregateName | string | The aggregate's name as defined in config.app.js |
| commandType | string | The command type that the aggregate can handle |
| payload | object | Parameters the command accepts |
Use the following command to add an item to the shopping-list example:
$ curl -X POST "http://localhost:3000/api/commands"
--header "Content-Type: application/json" \
--data '
{
"aggregateName":"Todo",
"type":"createItem",
"aggregateId":"root-id",
"payload": {
"id":`date +%s`,
"text":"Learn reSolve API"
}
}
'The reSolve framework includes the client resolve-redux library used to connect a client React + Redux app to a reSolve-powered backend. This library provides the following HOCs:
| Function Name | Description |
|---|---|
| connectViewModel | Connects a React component to a reSolve View Model. |
| connectReadModel | Connects a React component to a reSolve Read Model. |
| connectRootBasedUrls | Fixes URLs passed to the specified props so that they use the correct root folder path. |
| connectStaticBasedUrls | Fixes URLs passed to the specified props so that they use the correct static resource folder path. |
Connects a React component to a reSolve View Model.
export const mapStateToOptions = (state, ownProps) => {
const aggregateId = ownProps.match.params.id
return {
viewModelName: 'ShoppingList',
aggregateIds: [aggregateId]
}
}
export const mapStateToProps = (state, ownProps) => {
const aggregateId = ownProps.match.params.id
return {
aggregateId
}
}
export const mapDispatchToProps = (dispatch) =>
bindActionCreators(
{
replaceUrl: routerActions.replace
},
dispatch
)
export default connectViewModel(mapStateToOptions)(
connect(
mapStateToProps,
mapDispatchToProps
)(ShoppingList)
)Connects a React component to a reSolve Read Model.
import { sendAggregateAction } from 'resolve-redux'
import { bindActionCreators } from 'redux'
export const mapStateToOptions = () => ({
readModelName: 'ShoppingLists',
resolverName: 'all',
resolverArgs: {}
})
export const mapStateToProps = (state, ownProps) => ({
lists: ownProps.data
})
export const mapDispatchToProps = dispatch =>
bindActionCreators({
createStory: sendAggregateAction.bind(null, 'Story', 'createStory')
}, dispatch)
export default connectReadModel(mapStateToOptions)(
connect(
mapStateToProps,
mapDispatchToProps
)(MyLists)
)Fixes URLs passed to the specified props and ensures they use the correct root folder path.
export default connectRootBasedUrls(['href'])(Link)Fixes URLs passed to the specified props to correct the static resource folder path.
export default connectStaticBasedUrls(['css', 'favicon'])(Header)