Serverless Design Patterns and Best Practices
上QQ阅读APP看书,第一时间看更新

Application entrypoint

Every application, web or otherwise, needs a primary entry point. In our case, we'll use handler.py to begin application execution when a Lambda function is invoked. Serverless Framework applications will generate a handler.py file when you bootstrap a new project, so this pattern should be familiar to anyone who has used Serverless before. If you've never worked with the Serverless Framework, what follows will be a thorough introduction:

import sys

from pathlib import Path

# Munge our sys path so libs can be found
CWD = Path(__file__).resolve().cwd() / 'lib'
sys.path.insert(0, str(CWD))

import simplejson as json

from cupping.handlers.session import (
        handle_session,
        handle_session_detail,
)

from cupping.exceptions import Http404

CORS_HEADERS = {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Credentials': True
}

def session(event, context):
    """/session endpoint for POST or GET"""
    http_method = event['httpMethod']

    status_code = 200
    response = {}

    try:
        response = handle_session(http_method, event)
    except Exception as e:
        status_code = 500
        # TODO - log error
        response = {'errors': ['Unexpected server error']}

    response = {
        'statusCode': status_code,
        'body': json.dumps(response),
        'headers': CORS_HEADERS,
    }

    return response


def session_detail(event, context):
    http_method = event['httpMethod']

    status_code = 200
    response = {}

    try:
        response = handle_session_detail(http_method, event)
    except Http404 as e:
        status_code = 404
        response = {'errors': [str(e)]}
    except Exception as e:
        status_code = 500
        # TODO - log error
        response = {'errors': ['Unexpected server error']}

    response = {
        'statusCode': status_code,
        'body': json.dumps(response),
        'headers': CORS_HEADERS,
    }

    return response

Our handler.py code isn't very complicated and delegates most application logic to different parts of our application code (namespaced into the cupping package). This pattern of having a single entry point for all Lambda functions is advantageous for a few reasons.

When Lambda functions execute, they only know what they know. That is, we as application developers are used to installing extra packages in some known location (which is the default system package location) or perhaps creating a Python virtualenv and configuring our server to look there during the import cycle. During a Lambda, we are responsible for managing this ourselves. Application code has no idea where to look for packages beyond the built-in libraries without being told where to look. The code block below shows how to manipulate Python's path so that it can find any extra packages we wish to use.

    import sys

from pathlib import Path

# Munge our sys path so libs can be found
CWD = Path(__file__).resolve().cwd() / 'lib'
sys.path.insert(0, str(CWD))

These four lines of code accomplish the task of resolving the current directory of our handler.py file and appending a /lib onto it. The result is the absolute path of the lib directory where we've installed all of our system packages. During the deployment step, the Serverless Framework will package all directories and files that reside at or below the same directory level as the serverless.yml file, resulting in our lib being available to our application code during runtime. Any import statement for a third-party library will work as expected, only after the addition of the full path to lib being manually added to the system path. In the preceding example, there is an import for the third-party simplejson module. Had this import been placed above the sys.path.insert call, it would have failed.

When this path manipulation occurs as soon as possible (that is, as soon as handler.py is invoked), other parts of our application code can import packages without the danger of the import failing. If this path manipulation is done across different files only when a particular package is needed, errors will be inevitable as you will at some point forget to include this logic. Additionally, doing this work in a single place means there is no duplication of logic.