0.1.2 • Published 2 years ago

cs-gen v0.1.2

Weekly downloads
-
License
BSD-2-Clause
Repository
github
Last release
2 years ago

cs-gen

Code generator for rapid development using Command Sourcing design pattern.

Inspired from CQRS (Command Query Responsibility Segregation) and ES (Event Sourcing) but simplified to allow less boilerplate.

Think it like the reducer in redux, but compatible to OOP (Object-oriented programming).

This is not a framework, instead it's a code generation tool to reduce the burden of boilerplate for common tasks in the development of web server and backend system with Typescript.

Any required library are injected to the target project, hence the resulting project does not have runtime dependency on this package.

This toolkit has been used in production by 30+ projects and micro-services since 2019. The rough edges are getting polished and patched overtime hence it is considered to be "production-ready" for small to middle scale of application.

It has gone through 2 years of active development.

System Architecture

Details analysis can be found in https://github.com/beenotung/cqrs-documents

Background: The typical architecture

Typical applications follows 3-tier web architecture which consists of the presentation tier, logic tier, and data tier. Namely, the web/app client, web server, and database (usually RDBMs or NoSQL Database).

The business logic is usually handled by the domain model on the web server and validation constraints in the RDBMs.

The web server usually implements the domain model using Object-Oriented-Programming (OOP) while the database usually model the dataset in normalized format.

The Problem with typical architecture

Database is optimized for writing but typical application area read-heavy. Usually over 90% traffics are read nature. reference: 1% rule (Internet culture)

Solution: A simplified architecture

Features

  • Model API calls as command, query, and subscription (live query)
  • Auto store API calls
  • Auto replay API calls when server restart
  • Customize which API calls to be stored and replayed
  • Auto reconnect when network restore from failure
  • Auto setup package dependencies and formatting (tsconfig, tslint, prettier, npm scripts, e.t.c.)

Installation

Option: Install with npm

npm i -g cs-gen

Option: Install with git

> git clone https://github.com/beenotung/cs-gen
> cd cs-gen
> pnpm i || npm i
> npm run build
> npm i -g .

The cs-gen command will be installed for cli

Usage

Show Available Commands

> cs-gen help
cs-gen [command]
Commands:
  init  : initialize project skeleton, setup package.json and create scripts/gen-project.ts with default settings
  gen   : generate the client-side SDK for *-client and *-admin project, and stub code for *-server project
  help  : show this help message

Create Project Template

> mkdir -p ~/workspace/myapp && cd $_

> cs-gen init
project name [myapp]:
production server domain [example.com]:
server origin [https://myapp.example.com]:
test server domain [example.net]:
server origin [https://myapp.example.net]:
port [8080]:
app id [com.example.myapp]:
initializing gen-project for 'myapp'
generated skeleton.

> find
.
./.editorconfig
./.prettierrc
./.gitignore
./package.json
./scripts
./scripts/gen-project.ts

Declare Project APIs

The APIs of the project can be declared in ./scripts/gen-project.ts. Depending on project the scale, the APIs can be declared across multiple files, then imported from the gen-project.ts.

When the project skeleton is generated, there are some example Commands and Queries. Below shows a part of gen-project.ts:

commandTypes.push(
  { Type: 'CreateUser', In: `{ UserId: string }`, Out: ResultType([UserNotFound]) },
  { Type: 'CreateAdmin', In: `{ UserId: string }`, Out: ResultType([UserNotFound]), Admin },
);
queryTypes.push(
  { Type: 'GetUserList', In: 'void', Out: ResultType([NoPermission], `{ Users: ${ArrayType(`{ UserId: string }`)} }`) },
  { Type: 'HasUser', In: `{ UserId: string }`, Out: ResultType([NoPermission], `{ HasUser: boolean }`), Admin },
);

catchMain(genProject({
  outDirname: '.',
  baseProjectName: "myapp",
  injectTimestampField: true,
  timestampFieldName: 'Timestamp',
  callTypes: flattenCallMetas({ commandTypes, queryTypes, subscribeTypes }),
  serverOrigin: {
    port: 8080,
    prod: "https://myapp.example.com",
    test: "https://myapp.example.net",
  },
  /* default is the inverse, uncomment to reverse the setting */
  // replayCommand: false,
  // replayQuery: true,
  // storeCommand: false,
  // storeQuery: false,
}));

Declare Type Alias, constant, and authentication plugin

import {
  commandTypes, queryTypes, subscribeTypes,
  alias,
  authConfig,
  def,
  typeAlias,
  authCommand as _authCommand,
  authQuery as _authQuery,
} from 'cs-gen/dist/helpers/gen-project-helpers';
import { flattenCallMetas, genProject } from 'cs-gen'

const app_id = "com.example.myapp";
def({ app_id });

let ProfileType = `{
  UserId: string
  Nickname: string
  Bio?: string
  Avatar?: string
  Timestamp: number
}`;
let { type, typeArray } = alias({
  ProfileType,
});

// custom wrapper is possible
function authCommand(Type: string, In: string, Reasons: string[] = []) {
  _authCommand({ Type, In, Reasons });
}
function authQuery(Type: string, In: string, DataType: string, Reasons: string[] = []) {
  _authQuery({ Type, In, Reasons, Out: DataType });
}

// token will be injected as part of API params
authCommand('SetProfile', `{Profile: ${type(ProfileType)}}`, ['UserNotFound']);

// similar for queries
authQuery('GetProfile', `{ProfileUserId: string}`, `{Profile: ${type(ProfileType)}}`, [QuotaExcess, UserNotFound]);

genProject({
  ...
  asyncLogicProcessor: true, // for async auth check with external service / database
  callTypes: flattenCallMetas({ commandTypes, queryTypes, subscribeTypes }),
  typeAlias,
  plugins: { auth: authConfig },
  ...
})

Options of genProject

The complete list of options can be found in cs-gen/dist/gen/gen-file.d.ts in the node.js module, or src/gen/gen-file.ts in the source code.

Some of them are optional with default value stored in defaultGenProjectArgs in the same file.

Generate client-side SDK and server side stub code

> cs-gen gen

The myapp-client, myapp-server, and myapp-admin projects will be created / updated accordingly. (The paths and controller names are configurable in the ./scripts/gen-project.ts)

Todo

  • Rewrite string-based code generation to use macro (e.g. tsc-macro or TypeDraft)
  • Change the example APIs from UpperCase to javascript convention
  • Expose as express middleware
  • Expose as koa middleware
  • Explain it's good at supporting testing snapshot of data with different versions of build, ref: wiki

License

This is free and open-source software (FOSS) with BSD-2-Clause License