Skip to content

Functional microservices framework - best served serverless

Notifications You must be signed in to change notification settings

jaystack/functionly

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

functionly

The functionly library lets you build serverless nodejs applications in an innovative, functional, and fun by abstraction way. Use the JavaScript language and the JSON syntax to describe infrastructure and entities, service dependencies, and to implement service code. Deploy your solution to cloud providers, or run containerized on your onprem servers, or locally during development time using the functionly CLI.

Defining a rest service which listens on /hello-world:

import { FunctionalService, rest, description, param } from 'functionly'

@rest({ path: '/hello-world' })
@description('hello world service')
export class HelloWorld extends FunctionalService {
    static async handle(@param name = 'world') {
        return `hello ${name}`
    }
}

export const helloworld = HelloWorld.createInvoker()

Running on localhost:

functionly start

Try it on http://localhost:3000/hello-world?name=Joe


Install from npm:

functionly CLI

npm install functionly -g

functionly library

npm install functionly

Getting started

Create an empty Functionly project

It's a simple npm project.

Dependencies

  • functionly
npm install --save functionly

Dev dependencies

Functionly uses webpack with babel for compile the code.

  • babel-core
  • babel-loader
  • babel-preset-functionly-aws
npm install --save-dev babel-core babel-loader babel-preset-functionly-aws

Babel configuration

Default .babelrc

{
    "presets": [ "functionly-aws" ]
}

Functionly configuration

Default functionly.json

{
    "awsRegion": "us-east-1",
    "main": "./src/index.js",
    "deployTarget": "aws",
    "localPort": 3000,
    "stage": "dev",
    "watch": true,
    "compile": "babel-loader"
}

Create a Hello world service

We need to create a FunctionalService to implement the business logic of hello world application

import { FunctionalService } from 'functionly'

export class HelloWorld extends FunctionalService {
    static async handle() {}
}

If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the rest decorator. We have to set the path property to define the rest endpoint. If we do not set the methods property that means it will accept GET requests. (default: methods: ['get'])

@rest({ path: '/hello-world' })

Define a description for the HelloWorld, which will make it easier to find in the AWS Lambda list.

@description('hello world service')

Now we have to create the business logic.

import { FunctionalService, rest, description } from 'functionly'

@rest({ path: '/hello-world' })
@description('hello world service')
export class HelloWorld extends FunctionalService {
    static async handle() {
        return `hello world`
    }
}

We are almost done, we just have to export our service from the main file.

export const helloworld = HelloWorld.createInvoker()

Resolve parameter values

In the handle method if you use the @param property decorator for a parameter then it resolves the value from a request context.

import { FunctionalService, rest, description, param } from 'functionly'

@rest({ path: '/hello-world' })
@description('hello world service')
export class HelloWorld extends FunctionalService {
    static async handle(@param name = 'world') {
        return `hello ${name}`
    }
}

export const helloworld = HelloWorld.createInvoker()

Create a TodoDB application with DynamoDB

Define a base class for FunctionalService to set basic Lambda settings in the AWS environment.

import { FunctionalService, aws } from 'functionly'

@aws({ type: 'nodejs20.x', memorySize: 512, timeout: 3 })
export class TodoService extends FunctionalService { }

Create a dynamo table

We need a DynamoTable, called TodoTable because we want to store todo items.

import { DynamoTable, dynamoTable, injectable } from 'functionly'

@injectable()
@dynamo()
export class TodoTable extends DynamoTable { }

Read todos

We need to create a service to read todo items.

export class GetAllTodos extends TodoService {
    static async handle() {}
}

If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the rest decorator. We have to set the path property to define the rest endpoint. If we do not set the methods property that means it will accept GET requests. (default: methods: ['get'])

@rest({ path: '/getAllTodos' })

Define a description for the TodoService, which will make it easier to find in the AWS Lambda list.

@description('get all Todo service')

Now we have to create the business logic. We want to read the todo items, so we need to inject the TodoTable. Get the items from it and return from our service.

import { rest, description, inject } from 'functionly'

@rest({ path: '/getAllTodos' })
@description('get all Todo service')
export class GetAllTodos extends TodoService {
    static async handle(@inject(TodoTable) db) {
        let items = await db.scan()
        return { ok: 1, items }
    }
}

We are almost done, we just have to export our service from the main file.

export const getAllTodos = GetAllTodos.createInvoker()

Create todo

We need a service to create todo items, so let's do this. We will also define a rest endpoint and a description.

import { rest, description } from 'functionly'

@rest({ path: '/createTodo', methods: ['post'] })
@description('create Todo service')
export class CreateTodo extends TodoService {
    static async handle() {}
}

We need some values to create a new todo item: name, description and status. Expect these with the param decorator, and it will resolve them from the invocation context.

import { rest, description, param } from 'functionly'

@rest({ path: '/createTodo', methods: ['post'] })
@description('create Todo service')
export class CreateTodo extends TodoService {
    static async handle(@param name, @param description, @param staus) {}
}

The business logic: save a new todo item. Inject the TodoTable and save a new todo item with the put function. We need an id for the new todo, in the example, we'll use shortid to generate them.

import { generate } from 'shortid'
import { rest, description, param } from 'functionly'

@rest({ path: '/createTodo', methods: ['post'] })
@description('create Todo service')
export class CreateTodo extends TodoService {
    static async handle(@param name, @param description, @param status, @inject(TodoTable) db) {
        let item = {
            id: generate(),
            name,
            description,
            status
        }

        await db.put({ Item: item })

        return { ok: 1, item }
    }
}

export const createTodo = CreateTodo.createInvoker()

Extend the example with Services

Optional

Create two services: validate and persist todo items. Then the CreateTodo has only to call these services.

Validate todo

It will be an injectable service and expect the three todo values, then implement a validation logic in the service.

import { injectable, param } from 'functionly'

@injectable()
export class ValidateTodo extends Service {
    static async handle( @param name, @param description, @param status) {
        const isValid = true
        return { isValid }
    }
}

Persist todo

It will be an injectable service and expect the three todo values and inject a TodoTable then implement a persist logic in the service.

import { injectable, param, inject } from 'functionly'

@injectable()
export class PersistTodo extends Service {
    static async handle( @param name, @param description, @param status, @inject(TodoTable) db) {
        let item = {
            id: generate(),
            name,
            description,
            status
        }
        await db.put({ Item: item })
        return item
    }
}

Changed CreateTodo FunctionalService

inject the two new services(ValidateTodo, PersistTodo) and change the business logic

import { rest, description, param, inject } from 'functionly'

@rest({ path: '/createTodo', methods: ['post'] })
@description('create Todo service')
export class CreateTodo extends TodoService {
    static async handle(
        @param name,
        @param description,
        @param status,
        @inject(ValidateTodo) validateTodo,
        @inject(PersistTodo) persistTodo
    ) {
        let validateResult = await validateTodo({ name, description, status })
        if (!validateResult.isValid) {
            throw new Error('Todo validation error')
        }
        let persistTodoResult = await persistTodo({ name, description, status })
        return { ok: 1, persistTodoResult }
    }
}

The source code of this example is available here

Install

npm install

Run and Deploy with CLI

The CLI helps you to deploy and run the application.

  1. CLI install
npm install functionly -g

Local deployment

  1. Create DynamoDB with Docker
docker run -d --name dynamodb -p 8000:8000 peopleperhour/dynamodb
  1. Deploy will create the tables in DynamoDB

Note: Create the functionly.json in the project for short commands. Also, you don't have to pass all arguments.

functionly deploy local

Run in local environment

During development, you can run the application on your local machine.

functionly start

AWS deployment

Disclaimer: As functionly provisions AWS services, charges may apply to your AWS account. We suggest you to visit https://aws.amazon.com/pricing/services/ to revise the possible AWS costs.

Set up AWS Credentials before deployment.

Note: Create the functionly.json in the project for short commands. Also, you don't have to pass all arguments. As the deployTarget is configured as aws (the default value configured) then the deploy command will use this as deployment target.

Functionly will create the package and deploy the application to AWS. The package is a CloudFormation template, it contains all the AWS resources so AWS can create or update the application's resources based on the template.

functionly deploy

Congratulations! You have just created and deployed your first functionly application!

Examples

Javascript

Typescript