/light-workflow-js

Lightweight Framework for Amazon Simple Workflow Service written in TypeScript and Rx

Primary LanguageTypeScriptMIT LicenseMIT

light-workflow-js

Build Status

A library to create, run and orchestrate AWS SWF workflow. Written in TypeScript and heavily relies on RxJS.

Documentation - You can find a documentation here

Getting Started

  $ git clone https://github.com/adamrecsko/light-workflow-js.git
  $ cd light-workflow-js
  $ yarn install

Prerequisites

You need node.js and yarn to be installed before a start.

    $ apt-get install nodejs
    $ npm install yarn -g

For running a workflow on AWS you need aws credentials properly setup on your development environment and a registered AWS SWF domain.

Running the tests

    $ yarn test

Running linter

Explain what these tests test and why

   $ yarn lint

Example

HelloWorld Application

For examples please check the examples folder.

Actor implementation

import 'reflect-metadata';
import 'zone.js';

const HELLO_WORLD_ACTOR = Symbol('HELLO_WORLD_ACTOR');

export interface HelloWorld {
  formatText(text: string): Observable<string>;
  printIt(text: string): Observable<string>;
}

@injectable()
export class HelloWorldImpl implements HelloWorld {
  private printer(text: string) {
    console.log(text);
  }

  @activity()
  @version('1')
  formatText(text: string): Observable<string> {
    return of('Hello' + text);
  }

  @activity()
  @version('1')
  @description('print the text out')
  printIt(text: string): Observable<string> {
    return of(text).do((text: string) => {
      this.printer(text);
    });
  }
}

Workflow implementation

export const HELLO_WORLD_WORKFLOW = Symbol('HELLO_WORLD_WORKFLOW');


export interface HelloWorldWorkflow {
  helloWorld(text: string): Promise<string>;
}


@injectable()
export class HelloWorldWorkflowImpl implements HelloWorldWorkflow {
  @actorClient
  @inject(HELLO_WORLD_ACTOR)
  private actor: HelloWorld;

  @workflow()
  async helloWorld(text: string) {
    const formattedText = await this.actor.formatText(text).toPromise();
    const printedText = await this.actor.printIt(formattedText).toPromise();
    return printedText;
  }
}

You can handle activity exceptions with try catch

  @workflow()
  async helloWorldWithErrorHandling() {
    try {
      await this.actor.throwException().toPromise();
    } catch (e) {
      if (e instanceof FailedException) {
        return 'Error handled';
      }
      throw e;
    }
  }
  

Or you can use observables to create a flow

  @workflow()
  helloWorldHandleErrorWithObservables() {
    return this.actor.throwException().catch((err) => {
      if (err instanceof FailedException) {
        return Observable.of('Error handled, no problem!');
      }
      return Observable.throw(err);
    });
  }

Or with lettable operators

  @workflow()
  helloWorldHandleErrorWithObservables() {
    const errorHandler = catchError(err => of('Error handled, no problem!'));
    return this.actor.throwException().let(errorHandler);
  }

You can also reschedule an activity

  
   @workflow()
   async helloWorkflowWithRetry() {
      try {
        await this.actor.firstTryTimeOut(0).toPromise();
      } catch (e) {
        if (e instanceof TimeoutException) {
          // Don't worry it is just a timeout
          return await this.actor.firstTryTimeOut(1).toPromise();
        }
        throw Error('Unknown error');
      }
    }
  

For more examples please check the examples folder

Application implementation

@injectable()
@configuration(new ApplicationConfiguration(new SWF({ region: 'us-east-1' })))
@services([
  {
    impl: HelloWorldImpl,
    key: HELLO_WORLD_ACTOR,
  },
  {
    impl: HelloWorldWorkflowImpl,
    key: HELLO_WORLD_WORKFLOW,
  },
])
export class MyApp {
  public static domain = 'test-domain';
  @inject(HELLO_WORLD_WORKFLOW)
  @workflowClient
  private workflow: HelloWorldWorkflow;
  @inject(WORKFLOWS)
  private workflows: Workflows;
  @inject(WORKFLOW_WORKER_FACTORY)
  private workerFactory: WorkflowWorkerFactory;
  @inject(ACTOR_WORKER_FACTORY)
  private actorWorkerFactory: ActorWorkerFactory;

  public async startHelloWorld(text: string): Promise<string> {
    const start = this.workflows.createStarter(MyApp.domain, 'default');
    const workflowResult = await start(this.workflow.helloWorld, text);
    return workflowResult.runId;
  }

public createWorkflowWorker(): WorkflowWorker<HelloWorld> {
    return this.workerFactory.create(MyApp.domain, [{
      key: HELLO_WORLD_WORKFLOW,
      impl: HelloWorldWorkflowImpl,
    }]);
  }

  public createActorWorker(): ActorWorker {
    return this.actorWorkerFactory.create(MyApp.domain, [{
      key: HELLO_WORLD_ACTOR,
      impl: HelloWorldImpl,
    }]);
  }
}

How to start workers and workflow

async function boot() {
  const app = createApplication<MyApp>(MyApp);

  const workflowWorker = app.createWorklfowWorker();
  const actorWorker = app.createActorWorker();


  await workflowWorker.register().toPromise();
  await actorWorker.register().toPromise();

  workflowWorker.startWorker();
  actorWorker.startWorker();

  await  app.startHelloWorld('World');

}

// boot
// register workflow and activities

boot().catch(err => console.error(err));

Built With

Authors

  • Adam Recsko - Initial work

License

This project is licensed under the MIT License - see the LICENSE.md file for details

Acknowledgments

  • This library is under development and not production ready yet.