0.1.27 • Published 5 years ago

fabric-rx-cqrs v0.1.27

Weekly downloads
1
License
Apache-2.0
Repository
github
Last release
5 years ago

CircleCI

About

Hyperledge Fabric: make Reactive and CQRS-ES
A powerful and light-weight library, to empower middle-tier application of Hyperledger, to break through the barriers of Deeply Decentralized application.

Motivation

Why this project is created? And what problem does it solve? And how?

See Motivation under development

Philosophy

  • Simplicity. This is intentionally a library, instead of framework
  • Model-free
  • Deterministic
  • Strong Decoupling
  • Highly reactive

Key Concepts

  • Reactive Pattern (Reactivity)
  • Command Query Responsibility Segregation (CQRS)
  • Event Sourcing (ES)
  • Redux-like architecture (Redux)
  • End-to-end Type Coverage (Type-system)

Command-side is Fabric; query-side is in-memory database.

See How we may achieve deep decentralization under development

Prerequisite

  • Hyperledger Fabric V1.4; its prerequisite remains
  • Install Fabric-samples

Configuration

Env VariablesValues
CERT_PATHe.g. ~/fabric-samples/basic-network/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem
CHANNEL_HUBpeer name to be listened, e.g. peer0.org1.example.com
CHANNEL_NAMEe.g. mychannel
CONNECTION_PROFILE_PATHpath to connection profile e.g. ~/connection.yaml
IDENTITYregistered user name in Fabric, e.g. User1@example.com
KEY_PATHe.g. fabric-samples/basic-network/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/
MSPIDe.g. Org1MSP
PRIVATE_KEYfilename of private key under KEY_PATH
WALLETDirectory name of local wallet
WALLET_ROOTabsolute path of wallet

Note that all paths are absolute paths. And, all configurations are required.

The Basics

Step 1: Install Fabric, and fabric-samples

Step 2: Bootstrap the basic-network
Remember to run fabcar example, to validate the installation.

Step 3: get boilerplated application & set env variables
More examples will added, in Examples.

See Hyperledger documentation from step 1 to 2.
see Examples, from step 3.

Step 4: make default reducer for each peer application

Based on counter example:

// counter-reducer.ts

import { BaseEvent, Reducer } from 'fabric-rx-cqrs';

export interface CounterEvent extends BaseEvent {
  type: 'ADD' | 'MINUS';
}

export interface Counter {
  id: string;
  value: number;
}

export const counterReducer: Reducer<Counter> = (
  history: CounterEvent[],
  initial = { value: 0 }
): Counter => history.reduce(reducer, initial);

const reducer = ({ value }, e: CounterEvent) => {
  switch (e.type) {
    case 'ADD':
      value++;
      return { value };
    case 'MINUS':
      value--;
      return { value };
    default:
      return { value };
  }
};

Each peer application requires at least one reducer function, to compute the current state, from history of events.

type Reducer<T> = (history: { type: string; payload?: any }[], initial?: T) => T;

Similarly, one, or more events are required. Payload will be used to compute the currents state.
type BaseEvent = { type: string, payload: any}

Each entity must be given an unique identitier id, as entity Id.

Step 5: set the defaultReducer

// counter.spec.ts
import { Entity, setDefaultReducer, getRepository, channelEvent, container } from 'fabric-rx-cqrs';
import { Counter, CounterEvent, counterReducer } from './counter-reducer';

setDefaultReducer(container, 'counter', counterReducer);

Each peer application have only a single default reducer. The getProjection function depends on default reducer.

setDefaultReducer(container: Container, entityName:string, reducer: Reducer)

Step 6: listen to channel event hub

// counter.spec.ts
await channelEvent.invoke();

This invokes the channel event hub. Whenever new commits, it publishes to in-memory query-side database.

Step 7: get repository

// counter.spec.ts
const entityName = 'counter';
const id = 'counter_test' + Math.floor(Math.random() * 1000);

const counterRepository = getRepository<Counter, CounterEvent>(
  'counter',
  counterReducer
);

getRepository is the most commonly used function, which returns typed Repository; K being the type argument for typed BaseEvent.

getRepository<T, K>(entityName: string, reducer: () => void): Repository

Repository is function factory, which returns:

type Repository<T, K> = {
  create: Function; // create entity
  getById: Function; // return current state and save(), by entity id
  getByEntityName: Function; // return array of (currentState) entities by, entity name
  getCommitById: Function; // return commits by entity id
  getProjection: Function; // return array of entities, by projection criteria
}

Note that argument reducer of getRepository can be the same or different reducer from defaultReducer; referred as non-default reducer. getById, getByEntityName are computed based on non-default reducer. getProjection is based on default reducer.

Step 8: write event to Fabric repository

// counter.spec.ts
await counterRepository.create(id).save([{ type: 'ADD' }])

This writes to Fabric, returning type Entity; is a commit object. If write failure, it return null commit, and throw error.

type Repository<T, K> = {
  create: (id: string) => { save: (events: K[]) => Promise<Entity> }
}

Every successful write will return commit object.

type Entity = {
  id: string;
  commitId: string;
  version: number;
  events;
  entityId: string;
  committeAt: string;
}

Step 9: get current state by projection

// counter.spec.ts
counterRepository
  .getProjection({ where: { id } })
  .then(({ projections }) => {
    expect(projections).to.deep.equal([{ id, value: 2 }]);
  });

getProjection is similar to a search function, but implemented with selector. The computation is based on defaultReducer. Currently, there are three operator: where, all, contain.

type Repository<T, K> = {
  getProjection: ({ where, all, contain }: 
    { where?: Partial<T>; all?: boolean; contain?: string }
  ) => Promise<{ projections: T[] }>;
}

Reconcile
Lastly, reconcile() performs reconcilation from write-side Fabric, to in-memory database; is the key bootstraping procedure for each peer application.

const reconcile = async (entityName: string, reducer: () => void) => Promise<any>

In each peer application, it can run multiple reconcile, for different entityName. e.g.

reconcile('counterA', counterReducer);
reconcile('counterB', counterReducer);

GraphQL Support

It provides getQueryResolver, getSubscriptionResolver, which return high-order resolvers for GraphQL server. This is very convenient feature to generate CRUD resolvers, via decorated domain model. Thanks to Type-GraphQL.

It will generate executable resolver, for below example query.

getCounter(id: "counterId") {
  id
  value
}

getAllCounter {
  id 
  value
}

getCommits(id: "counterId") {
  id
  commitId
  version
  events
  entityId
  committeAt
}

It leverages pubSub(), as default publish-subscription engine, for GraphQL subscription.

GraphQL is the preferred API implementation; beyond the scope of this library.

GraphQL test-net is under development.

Technologies

  • GraphQL
  • Hyperledger Fabric
  • Inversify
  • Redux
  • Rxjs
  • Typescript

Getting Started: Basic Examples

See Basic Examples

0.1.27

5 years ago

0.1.26

5 years ago

0.1.25

5 years ago

0.1.24

5 years ago

0.1.23

5 years ago

0.1.22

5 years ago

0.1.21

5 years ago

0.1.20

5 years ago

0.1.19

5 years ago

0.1.18

5 years ago

0.1.17

5 years ago

0.1.16

5 years ago

0.1.15

5 years ago

0.1.14

5 years ago

0.1.13

5 years ago

0.1.12

5 years ago

0.1.11

5 years ago

0.1.10

5 years ago

0.1.9

5 years ago

0.1.8

5 years ago

0.1.7

5 years ago

0.1.6

5 years ago

0.1.5

5 years ago

0.1.4

5 years ago

0.1.3

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago