Skip to content

Commit

Permalink
Merge branch 'master' of github.com:wesone/blackrik
Browse files Browse the repository at this point in the history
  • Loading branch information
hvipana committed May 5, 2021
2 parents 5abd521 + d292cfd commit 95dfa7a
Show file tree
Hide file tree
Showing 44 changed files with 684 additions and 169 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Changed
- Renamed workflow property `currentEvent` to `event`
- Renamed workflow function `transition` to `trigger`

### Added
- Alternative HTTP route to execute commands
- New property `latestEventPosition` inside the context of commands

## [1.1.1] - 2021-04-19
### Changed
Expand Down
25 changes: 19 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,44 @@
If you'd like to contribute to Blackrik, please make sure you follow our [guidelines](#guidelines).

# Guidelines
To make development easier, there are a few rules that should be respected (yeah I know... rules 😒 but without them it would turn into an anarchy).

Just stick to our [workflow](#Workflow) and you should be fine.

## Workflow
Before you start, make sure there is no open issue that proposes your feature or fix (or whatever).
If there is already an issue that kinda fits but does not exactly match your proposal, hook into the discussion.

Otherwise
1. Create a new [issue](https://guides.github.com/features/issues/) so your proposal can be discussed first.
2. Create a new [branch](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-branches) of this repository to work in.
2. Create a new [branch](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-branches) of this repository to work in (see [branches](#Branches) for naming).
3. Implement your feature or fix or enhancement.
4. Add (or update) [tests](#testing) if needed.
5. Make sure your code complies with our [codestyle](#codestyle): `$ npx eslint .`.
4. Make sure your code complies with our [codestyle](#codestyle): `$ npx eslint .`.
5. Add (or update) [tests](#testing) if needed.
6. Make sure all [tests](#testing) pass: `$ npm run test`.
7. Commit all changes to your branch (from step 2) and write useful commit messages for others to be able to undestand what changed.
8. Create a [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) (PR) to inform others about your changes.
9. Your PR will be reviewed. If changes are requested, simply commit these to your branch.
10. After everything's right, someone will merge your branch into the master branch.

## Branches
When creating a new branch use the pattern `<category>/<topic>` or `<category>/<issue>/<topic>` for its name. Category may be one of the following `feature`, `bugfix`, `hotfix`, `docs`, ...
While the topic describes what exactly the branch is about.
You may add an issue id in-between e.g. `feature/42/my-feature` if issue #42 belongs to "my-feature".

Try to keep the name short e.g. instead of:
`bugfix/there-is-a-bug-where-the-api-explodes-when-calling-specific-routes`
just use:
`bugfix/api-explosion`

## Codestyle
We use [ESLint](https://eslint.org/) to make sure the code has a constant style. That style is described via rules inside the [.eslintrc.js](https://github.com/wesone/blackrik/blob/master/.eslintrc.js) file.
We use [ESLint](https://eslint.org/) to make sure the code has a consistent style. That style is described via rules inside the [.eslintrc.js](https://github.com/wesone/blackrik/blob/master/.eslintrc.js) file.

To test if all rules are satisfied run `$ npx eslint .` from the project's root directory (or `$ npx eslint <dir>`).
To test if all rules are satisfied run `$ npx eslint .` from the projects root directory (or `$ npx eslint <dir>`).
ESLint can fix some problems automatically if you use `$ npx eslint --fix .` but be advised that the `--fix` flag might manipulate your code.

### Naming
Please use descriptive names (for functions, parameters, classes, variables, ...) in [camel case](https://en.wikipedia.org/wiki/Camel_case) and avoid abbreviations when possible.
Please use descriptive names (for functions, parameters, classes, variables, ...) in [camel case](https://en.wikipedia.org/wiki/Camel_case) (capital first letter for classes) and avoid abbreviations when possible.

## Testing
We use [Jest](https://jestjs.io/) as our testing framework. All unit tests can be found inside the `test/` folder that reflects the structure of the `src/` folder.
Expand Down
20 changes: 11 additions & 9 deletions docs/Aggregates.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Introduction
An aggregate represents a state of a single unit inside your system (e.g. user).
It consists of a name, [commands](Aggregates#Commands) and a [projection](Aggregates#Projection).
It consists of a name, [commands](#Commands) and a [projection](#Projection).
A specific instance of an aggregate (e.g. an actual user) is identified by `aggregateId`.

# Commands
A command is a request to change/update an aggregate's state (e.g. update the user's name).
A command is a request to change/update an aggregates state (e.g. update the users name).
It can be triggered through the [HTTP API](HTTP-API#Commands) or locally through [executeCommand](Blackrik#executeCommand).

## Callback
Expand All @@ -15,7 +15,7 @@ To reject a command (e.g. in case the validation of the payload failed) the call
Name | Type | Attribute | Description
:--- | :--- | :--- | :---
command | object | | The [command](#Command) to be processed by the callback
state | object | | The calculated state that is created with the help of the aggregate's projection and that is based on all events that belong to the aggregateId
state | object | | The calculated state that is created with the help of the aggregates [projection](#Projection) and that is based on all events that belong to the aggregateId
context | object | | An object that contains [additional information or helper functions](#Context)

### Return
Expand All @@ -38,18 +38,20 @@ Custom properties can be added with the [contextProvider](Config#contextProvider

Property | Type | Attribute | Description
:--- | :--- | :--- | :---
causationEvent | object | optional | An event that caused this command. If the command returns a new event, the causationEvent will be the event's "parent"
aggregateVersion | number | `0` <small>for a new aggregate id</small> | A number indicating the amount of events that belong to the aggregate
latestEventPosition | number \| null | `null` <small>for a new aggregate id</small> | The position of the latest event inside the event store that belongs to the aggregate
causationEvent | object | optional | An event that caused this command. If the command returns a new event, the causationEvent will be the events "parent"

## Event
The object that may be returned by the command's callback initiates a new event.
The object that may be returned by the commands callback initiates a new event.
That event will automatically be assigned to the aggregateId and will be populated with additional information (timestamp, aggregateVersion, ...)

Property | Type | Attribute | Description
:--- | :--- | :--- | :---
type | string | | The type of event
payload | object | optional<br>default: `null` | An optional payload that contains additional information for the event

Example:
## Examples
```javascript
module.exports = {
create: async (command, state, context) => {
Expand Down Expand Up @@ -85,12 +87,12 @@ module.exports = {
```

# Projection
The projection will reduce all events that belong to the aggregateId and the resulting state will be passed to the command's callback.
The projection will reduce all events that belong to the aggregateId and the resulting state will be passed to the commands callback.
To achieve this, the projection contains a function for each event type of the aggregate.
The function will receive the previous state and the event and returns the new state.
A projection can also have a function called `init` that returns a starting state. Without an init-function the first state will be an empty object (`{}`).

Example:
## Examples
```javascript
module.exports = {
init: () => ({
Expand All @@ -106,4 +108,4 @@ module.exports = {
...payload
})
};
```
```
33 changes: 25 additions & 8 deletions docs/Blackrik.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ An object containing the default adapters that can be used inside read model sto
}
}
```
Example:

### Examples
```javascript
eventBusAdapter: {
module: Blackrik.ADAPTERS.EVENTBUS.Kafka
Expand All @@ -50,7 +51,8 @@ When used inside the routes array lower or upper case does not matter.
'all'
]
```
Example:

### Examples
```javascript
routes: [
{
Expand Down Expand Up @@ -83,7 +85,8 @@ ForbiddenError // 403 Forbidden
NotFoundError // 404 Not Found
ConflictError // 409 Conflict
```
Example:

### Examples
```javascript
const {BadRequestError, BaseError} = require('blackrik').ERRORS

Expand All @@ -98,7 +101,8 @@ if(userWantsCoffee())
# constructor
`constructor(...configs: object)`
Creates a new Blackrik instance based on the provided [config(s)](Config).
Example:

### Examples
```javascript
const Blackrik = require('blackrik');

Expand All @@ -124,7 +128,7 @@ command | object | | The command to be executed
`true` if an event was created, otherwise `false`
May throw an error

Example:
### Examples
```javascript
try
{
Expand All @@ -147,7 +151,7 @@ catch(e)
// inside a saga
'USER_CREATED': async (store, {aggregateId, payload: {email}}, sideEffects) => {
if(await store.findOne('emails', {email}))
return await executeCommand({
return await sideEffects.executeCommand({
aggregateName: 'User',
aggregateId,
type: 'reject',
Expand All @@ -168,6 +172,19 @@ Under the hood scheduleCommand is implemented using [setTimeout](https://nodejs.
### Return
`true` if the command was successfully scheduled.

### Examples
```javascript
// inside a saga
'USER_REGISTERED': async (store, {timestamp, aggregateId}, {scheduleCommand}) => {
// send the first newsletter 7 days after the registration
await scheduleCommand(timestamp + 1000*60*60*24*7, {
aggregateName: 'User',
aggregateId,
type: 'sendNewsletter'
});
}
```

# executeQuery
`async executeQuery(readModel: string, resolver: string, ?query: object): mixed`
Executes a query on the [read model's](ReadModels#Introduction) [resolver](ReadModels#Resolver).
Expand All @@ -183,7 +200,7 @@ query | object | optional<br>default: `{}` | A query for the resolver
The result of the resolver
May throw an error

Example:
### Examples
```javascript
const response = await executeQuery('users', 'get', {id: 42});
```
```
25 changes: 19 additions & 6 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,44 @@
If you'd like to contribute to Blackrik, please make sure you follow our [guidelines](#guidelines).

# Guidelines
To make development easier, there are a few rules that should be respected (yeah I know... rules 😒 but without them it would turn into an anarchy).

Just stick to our [workflow](#Workflow) and you should be fine.

## Workflow
Before you start, make sure there is no open issue that proposes your feature or fix (or whatever).
If there is already an issue that kinda fits but does not exactly match your proposal, hook into the discussion.

Otherwise
1. Create a new [issue](https://guides.github.com/features/issues/) so your proposal can be discussed first.
2. Create a new [branch](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-branches) of this repository to work in.
2. Create a new [branch](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-branches) of this repository to work in (see [branches](#Branches) for naming).
3. Implement your feature or fix or enhancement.
4. Add (or update) [tests](#testing) if needed.
5. Make sure your code complies with our [codestyle](#codestyle): `$ npx eslint .`.
4. Make sure your code complies with our [codestyle](#codestyle): `$ npx eslint .`.
5. Add (or update) [tests](#testing) if needed.
6. Make sure all [tests](#testing) pass: `$ npm run test`.
7. Commit all changes to your branch (from step 2) and write useful commit messages for others to be able to undestand what changed.
8. Create a [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) (PR) to inform others about your changes.
9. Your PR will be reviewed. If changes are requested, simply commit these to your branch.
10. After everything's right, someone will merge your branch into the master branch.

## Branches
When creating a new branch use the pattern `<category>/<topic>` or `<category>/<issue>/<topic>` for its name. Category may be one of the following `feature`, `bugfix`, `hotfix`, `docs`, ...
While the topic describes what exactly the branch is about.
You may add an issue id in-between e.g. `feature/42/my-feature` if issue #42 belongs to "my-feature".

Try to keep the name short e.g. instead of:
`bugfix/there-is-a-bug-where-the-api-explodes-when-calling-specific-routes`
just use:
`bugfix/api-explosion`

## Codestyle
We use [ESLint](https://eslint.org/) to make sure the code has a constant style. That style is described via rules inside the [.eslintrc.js](https://github.com/wesone/blackrik/blob/master/.eslintrc.js) file.
We use [ESLint](https://eslint.org/) to make sure the code has a consistent style. That style is described via rules inside the [.eslintrc.js](https://github.com/wesone/blackrik/blob/master/.eslintrc.js) file.

To test if all rules are satisfied run `$ npx eslint .` from the project's root directory (or `$ npx eslint <dir>`).
To test if all rules are satisfied run `$ npx eslint .` from the projects root directory (or `$ npx eslint <dir>`).
ESLint can fix some problems automatically if you use `$ npx eslint --fix .` but be advised that the `--fix` flag might manipulate your code.

### Naming
Please use descriptive names (for functions, parameters, classes, variables, ...) in [camel case](https://en.wikipedia.org/wiki/Camel_case) and avoid abbreviations when possible.
Please use descriptive names (for functions, parameters, classes, variables, ...) in [camel case](https://en.wikipedia.org/wiki/Camel_case) (capital first letter for classes) and avoid abbreviations when possible.

## Testing
We use [Jest](https://jestjs.io/) as our testing framework. All unit tests can be found inside the `test/` folder that reflects the structure of the `src/` folder.
Expand Down
30 changes: 15 additions & 15 deletions docs/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Property | Type | Attribute | Description
[readModelStoreAdapters](Config#readModelStoreAdapters) | object | | An object containing available read model store adapter definitions
[eventStoreAdapter](Config#eventStoreAdapter) | object | | An event store adapter definition
[eventBusAdapter](Config#eventBusAdapter) | object | | An event bus adapter definition
[contextProvider](Config#contextProvider) | function | | A function to create a custom context for commands and queries
[contextProvider](Config#contextProvider) | function | optional | A function to create a custom context for commands and queries
[server](Config#server) | object | optional | An object containing additional properties for the server
[server.config](Config#config) | object | optional | A config used by the server
[server.middlewares](Config#middlewares) | array | optional | An array of middlewares for the server
Expand All @@ -40,7 +40,7 @@ Property | Type | Attribute | Description
An array of objects where each object represents an aggregate.
Each aggregate needs to have a unique `name`, [commands](Aggregates#Commands) and a [projection](Aggregates#Projection).

Example:
### Examples
```javascript
aggregates: [
{
Expand All @@ -63,7 +63,7 @@ The projection and the resolvers depend on a store that is accessed through an a
By default the adapter `'default'` will be used.
You can reference any adapter from the [readModelStoreAdapters](Config#readModelStoreAdapters) object.

Example:
### Examples
```javascript
readModels: [
{
Expand All @@ -87,7 +87,7 @@ Just like [read models](Config#ReadModels), sagas depend on a store that is acce
By default the adapter `'default'` will be used.
You can reference any adapter from the [readModelStoreAdapters](Config#readModelStoreAdapters) object.

Example:
### Examples
```javascript
sagas: [
{
Expand All @@ -99,11 +99,11 @@ sagas: [
```

# adapter
The framework needs a store for it's core stuff (e.g. to persist [scheduled commands](Blackrik#schedulecommand)).
The framework needs a store for its core stuff (e.g. to persist [scheduled commands](Blackrik#schedulecommand)).
The specified adapter needs to be the name of one of the stores from the [readModelStoreAdapters](#readModelStoreAdapters) object.
By default the adapter `'default'` will be used.

Example:
### Examples
```javascript
adapter: 'MyCustomAdapter'
```
Expand All @@ -115,9 +115,9 @@ Refer to [read model store adapter](ReadModelStoreAdapter) to see how to create

The property `module` is a string that contains the full path to the adapter (or just the name of the module if the module is a stand-alone package).

The property `args` will be used as a configuration for the adapter and it's content will depend on the adapter.
The property `args` will be used as a configuration for the adapter and its content will depend on the adapter.

Example:
### Examples
```javascript
readModelStoreAdapters: {
default: {
Expand All @@ -141,9 +141,9 @@ Refer to [event store adapter](EventStoreAdapter) to see how to create an event

The property `module` is a string that contains the full path to the adapter (or just the name of the module if the module is a stand-alone package).

The property `args` will be used as a configuration for the adapter and it's content will depend on the adapter.
The property `args` will be used as a configuration for the adapter and its content will depend on the adapter.

Example:
### Examples
```javascript
eventStoreAdapter: {
module: Blackrik.ADAPTERS.EVENTSTORE.MySQL
Expand All @@ -162,9 +162,9 @@ Refer to [event bus adapter](EventBusAdapter) to see how to create an event bus

The property `module` is a string that contains the full path to the adapter (or just the name of the module if the module is a stand-alone package).

The property `args` will be used as a configuration for the adapter and it's content will depend on the adapter.
The property `args` will be used as a configuration for the adapter and its content will depend on the adapter.

Example:
### Examples
```javascript
eventBusAdapter: {
module: Blackrik.ADAPTERS.EVENTBUS.Kafka
Expand All @@ -179,7 +179,7 @@ An optional function to create a custom context to use inside [commands](Aggrega
The function receives the `req` object (just like [routes](#routes) and [middlewares](#middlewares)) which may be `null` if the command or query was not called by the HTTP API.
The function should return an object that holds infos or helper functions. Reserved properties (e.g. `causationEvent`) may be overridden.

Example:
### Examples
```javascript
contextProvider: (req = null) => ({
user: req?.user ?? 'system'
Expand All @@ -189,7 +189,7 @@ contextProvider: (req = null) => ({
# server
Blackrik utilizes [Express](https://expressjs.com/) as a server and you are able to affect the creation of the server by using the `server` property.
Example:
### Examples
```javascript
server: {
config: {
Expand Down Expand Up @@ -237,4 +237,4 @@ Name | Type | Attribute | Description
:--- | :--- | :--- | :---
method | string | | The HTTP method to use for a request to this route (`all` is also allowed to use every HTTP method, see [HTTP methods](Blackrik#HTTP_METHODS))
path | string \| array | | The path or an array of paths to request (it may contain a pattern e.g. `/user/:id` will match `/user/42`, `/user/21`, ...)
callback | function | | The callback to execute if the `path` was requested using the HTTP method in `method`
callback | function | | The callback to execute if the `path` was requested using the HTTP method in `method`
Loading

0 comments on commit 95dfa7a

Please sign in to comment.