Skip to content

serverless design

devonfw-core edited this page Jan 27, 2022 · 10 revisions

Serverless design (deprecated)

Introduction

The NodeJS back-end for My Thai Star application is going to be based on:

  • Serverless as serverless framework

  • devon4node as data access layer framework

  • DynamoDB as NoSQL Database

To know more details about the above technologies please visit the following documentation:

Basic architecture details

This structure can be shown in the following example image:

folder organization
  • handlers - All function handlers following devon4node structure

  • src

    • model - Folder with all data model

    • utils - Folder with all utils like classes and functions

    • config.ts - File with server configs

    • logic.ts - File with the business logic

  • test - Folder with all tests

Layers

  • Service Layer: this layer will expose the REST API to exchange information with the client applications.

  • Logic Layer: the layer in charge of hosting the business logic of the application.

  • Data Access Layer: the layer to communicate with the data base.

Service layer

The services layer will be solved using REST services with Serverless

To give service to the defined User Stories we will need to implement the following services:

  • provide all available dishes.

  • save a booking.

  • save an order.

  • provide a list of bookings (only for waiters) and allow filtering.

  • provide a list of orders (only for waiters) and allow filtering.

  • login service (see the Security section).

  • provide the current user data (see the Security section)

In order to be compatible with the other back-end implementations, we must follow the naming conventions proposed for Devon4j applications. We will define the following end points for the listed services.

  • (POST) /mythaistar/services/rest/dishmanagement/v1/dish/search.

  • (POST) /mythaistar/services/rest/bookingmanagement/v1/booking.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order.

  • (POST) /mythaistar/services/rest/bookingmanagement/v1/booking/search.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order/search.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order/filter (to filter with fields that does not belong to the Order entity).

  • (POST) /mythaistar/login.

  • (GET) /mythaistar/services/rest/security/v1/currentuser/.

You can find all the details for the services implementation in the Swagger definition included in the My Thai Star project on Github.

To treat these Http services, we must define the handlers following the devon4node convention:

  • (handlers/Http/POST/dish-search-handler) /mythaistar/services/rest/dishmanagement/v1/dish/search.

  • (handlers/Http/POST/booking-handler) /mythaistar/services/rest/bookingmanagement/v1/booking.

  • (handlers/Http/POST/order-handler) /mythaistar/services/rest/ordermanagement/v1/order.

  • (handlers/Http/POST/booking-search-handler) /mythaistar/services/rest/bookingmanagement/v1/booking/search.

  • (handlers/Http/POST/order-search-handler) /mythaistar/services/rest/ordermanagement/v1/order/search.

  • (handlers/Http/POST/order-filter-handler) /mythaistar/services/rest/ordermanagement/v1/order/filter (to filter with fields that does not belong to the Order entity).

  • (handlers/Http/POST/login-handler) /mythaistar/login.

  • (handlers/Http/GET/current-user-handler) /mythaistar/services/rest/security/v1/currentuser/.

These handlers will define the behavior for each service and use the logical layer.

An example of handler definition:

oasp4fn.config({ path: '/mythaistar/services/rest/bookingmanagement/v1/booking/search' });
export async function bookingSearch(event: HttpEvent, context: Context, callback: Function) {
    try {
        const search = <types.SearchCriteria>event.body;
        const authToken = event.headers.Authorization;
        // falta lo que viene siendo comprobar el token y eso

        auth.decode(authToken, (err, decoded) => {
            if (err || decoded.role !== 'WAITER') {
                throw { code: 403, message: 'Forbidden'};
            }

            // body content must be SearchCriteria
            if (!types.isSearchCriteria(search)) {
                throw { code: 400, message: 'No booking token given' };
            }

            business.searchBooking(search, (err: types.Error | null, bookingEntity: types.PaginatedList) => {
                if (err) {
                    callback(new Error(`[${err.code || 500}] ${err.message}`));
                } else {
                    callback(null, bookingEntity);
                }
            });
        });
    } catch (err) {
        callback(new Error(`[${err.code || 500}] ${err.message}`));
    }
}

The default integration for a handler is lambda. See devon4node documentation for more information about default values and how to change it.

Note

If you change the integration to lambda-proxy, you must take care that in this case the data will not be parsed. You must do JSON.parse explicitly

After defining all the handlers, we must execute the fun command, which will generate the files serverless.yml and webpack.config.js.

Logic layer and Data access layer

Security with Json Web Token

For the Authentication and Authorization the app will implement the json web token protocol.

JWT basics

Refer to JWT basics for more information.

JWT implementation details

The Json Web Token pattern will be implemented based on the JSON web token library available on npm.

Authentication

Based on the JSON web token approach, we will implement two methods in order to verify and user + generate the token and decode the token + return the user data. Also, as My Thai Star is a mainly public application, we will define here the resources that won’t be secured.

List of unsecured resources:

  • /services/rest/dishmanagement/**: to allow anonymous users to see the dishes info in the menu section.

  • /services/rest/ordermanagement/v1/order: to allow anonymous users to save an order. They will need a booking token but they won’t be authenticated to do this task.

  • /services/rest/bookingmanagement/v1/booking: to allow anonymous users to create a booking. Only a booking token is necessary to accomplish this task.

  • /services/rest/bookingmanagement/v1/booking/cancel/**: to allow canceling a booking from an email. Only the booking token is needed.

  • /services/rest/bookingmanagement/v1/invitedguest/accept/**: to allow guests to accept an invite. Only a guest token is needed.

  • /services/rest/bookingmanagement/v1/invitedguest/decline/**: to allow guests to reject an invite. Only a guest token is needed.

To configure the login we will create a handler called login and then we will use the method code to verify the user and generate the token.

app.post(oasp4fn.config({ integration: 'lambda-proxy', path: '/mythaistar/login' });
export async function login(event: HttpEvent, context: Context, callback: Function) {
.
.
.
.
}

We have two default users created in the database:

  • user: waiter

  • password: waiter

  • role: WAITER

  • user: user0

  • password: password

  • role: CUSTOMER

Token set up

Current User request

To provide the client with the current user data our application should expose a service to return the user details. In order to do this, we must define a handler called current-user-handler. This handler must decode the Authorization token and return the user data.

oasp4fn.config({
    path: '/mythaistar/services/rest/security/v1/currentuser',
});
export async function currentUser(event: HttpEvent, context: Context, callback: Function) {
    let authToken = event.headers.Authorization;
    try {
        auth.decode(authToken, (err: any, decoded?: any) => {
            if (err) {
                callback(new Error(`[403] Forbidden`));
            } else {
                callback(null, decoded);
            }
        });
    } catch (err) {
        callback(new Error(`[${err.code || 500}] ${err.message}`));
    }
}

Authorization

We need to secure three services, that only should be accessible for users with role Waiter:

  • (POST) /mythaistar/services/rest/bookingmanagement/v1/booking/search.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order/search.

  • (POST) /mythaistar/services/rest/ordermanagement/v1/order/filter.

To ensure this, we must decode the Authorization token and check the result. As the role is included in the token, once validated we will have this information and can guarantee access or return a 403 error.

oasp4fn.config({ path: '/mythaistar/services/rest/bookingmanagement/v1/booking/search' });
export async function bookingSearch(event: HttpEvent, context: Context, callback: Function) {
    const authToken = event.headers.Authorization;
    auth.decode(authToken, (err, decoded) => {
        try {
            if (err || decoded.role !== 'WAITER') {
                throw { code: 403, message: 'Forbidden' };
            }

            [...]

        } catch (err) {
            callback(new Error(`[${err.code || 500}] ${err.message}`));
        }
    });
}