Skip to content

Commit

Permalink
Chapter 4
Browse files Browse the repository at this point in the history
  • Loading branch information
keithrozario committed May 13, 2020
1 parent 90dfca6 commit 300941c
Show file tree
Hide file tree
Showing 15 changed files with 199 additions and 18 deletions.
20 changes: 20 additions & 0 deletions code_examples/04.first_api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# Serverless directories
.serverless
9 changes: 9 additions & 0 deletions code_examples/04.first_api/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import json

def get_item(event, context):
items = ['Apple', 'Oranges' , 'Lemons']
api_response = {
"statusCode": 200,
"body": json.dumps(items)
}
return api_response
24 changes: 24 additions & 0 deletions code_examples/04.first_api/item_quantity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import json

# define the item quantities
item_quantity = {
"Apple": 3,
"Orange": 5,
"Lemon": 7
}


def get_item(event, context):

item = event['queryStringParameters']['item']
quantity = item_quantity[item]

api_response = {
"statusCode": 200,
"body": {
"item": item,
"quantity": quantity
}
}

return api_response
13 changes: 13 additions & 0 deletions code_examples/04.first_api/serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
service: first-api

provider:
name: aws
runtime: python3.8

functions:
hello:
handler: handler.get_item
events:
- httpApi:
method: GET
path: /api/v1/items
11 changes: 9 additions & 2 deletions docs/00_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

* Chapter 25: The one with a Step Function
* Chapter 26: Nested Step Functions
* Chapter 27: Custom runtimes (Bash!!)
* Chapter 27: Custom runtimes in our Step Functions

## Databases

Expand Down Expand Up @@ -88,4 +88,11 @@

* Chapter 45: S3 Select
* Chapter 46: Athena
* Chapter 47: Fargate
* Chapter 47: Fargate
* Chapter 48: AWS Glue

## Python Frameworks

* Chapter 48: Running Flask app using Zappa
* Chapter 50: Running Django with Zappa
* Chapter 51: Running Flask app using Chalice
2 changes: 2 additions & 0 deletions docs/01_console.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,6 @@ With Lambda we don't need keypairs, Elastic IPs or Dockerfiles. You can literall

If you haven't had AWS experience before -- congratulations, you're jumping onto the bandwagon at the precise time when it's easiest and most beneficial, and this is why lambda functions are a powerful tool to learn.

10 years ago, learning AWS exclusively meant learning to run VMs on the EC2. Today, we have a wealth of options, and Lambda is the best place to start as it has a much lower learning curve, and actually prepares students to design more modern applications from the get-go.

So let's get learning.
39 changes: 23 additions & 16 deletions docs/02_event_and_context.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# What is the Handler?

When we changed out Function Code in the previous chapter, you might have noticed the `lambda_handler` function. But just what is a lambda handler?
When we changed out Function Code in the previous chapter, you might have noticed the `lambda_handler` function, and thought to yourself "hmmm... that looks important". And you're right -- it is important.

All lambda functions have must have a handler.
The handler function is the starting point of your code. It's the *python* function that is executed when your *lambda* function runs.

The handler is the piece of code that runs when the lambda function executes. The handler is the **python** function that is executed when your **lambda** function runs.

So let's go back to our **Function Code** from chapter 1:
So let's go back to our Function Code from chapter 1:

```python
def lambda_handler(event, context):
Expand All @@ -18,13 +16,19 @@ def lambda_handler(event, context):

```

Here the handler is the function `lambda_handler`. When you create a lambda function, Lambda automatically creates a single .py file and populates it with a generic handler function. But you can provide lambda any number of .py files as your code, and then specify the handler using the following convention **[file_name].[function_name]**.
Here the handler is the function `lambda_handler`. When you create a lambda function from the console, it is automatically populated with a single .py file with generic handler function. But we'll learn in later chapters how you can provide lambda any number of .py files as your code, and then specify the handler using the following convention:

> **<file_name_without_extension>.<function_name>**
Let's take a look in the console again:
Let's take a look in the console again, notice 3 things:

* The Handler is set to lambda_function.lambda_handler
* lambda_function.py is the name of our .py file
* lambda_handler is the name of function in our code

![handler_settings](images/02/Handler_Function.png)

Now, handler functions must always two arguments, `event` and `context`, and it may return a value, i.e. they always have to look like this:
Handler functions must always take two arguments, `event` and `context`, and they may return a value, in short they always have to look like this:

```python
def lambda_handler(event, context):
Expand All @@ -36,7 +40,7 @@ Now let's look at the `event`, `context` and return value individually.

## Event

`event` is the data that's passed to the function upon execution. In the previous chapter, we used the console to create an test `event` to be passed to the Lambda Function, and the function could then use that data to perform a simple print statement.
`event` is the data that's passed to the function upon execution. In the previous chapter, we used the console to create a test `event` to be passed to the Lambda Function.

In real-life, the event can come from whatever triggers the lambda, a good example, if the lambda were triggered from an HTTP api call via API Gateway, then the event object would look something like this:

Expand Down Expand Up @@ -78,13 +82,14 @@ In real-life, the event can come from whatever triggers the lambda, a good examp
}
```

As you can see it's a very rich event with a lot of data, including the Http Method used, the User-Agent, QueryStringParameters etc. `event` is usually a Python dictionary, from which data can easily be extracted, for example:
As you can see it's a very rich event, including the Http Method used, the User-Agent, QueryStringParameters etc. `event` is usually a Python dictionary, from which data can easily be extracted using the tools we're already familiar with:

```python
postcode = event['QueryStringParameters']['postcode']
postcode = event['QueryStringParameters']['postcode'] # or if we're not sure if postcode is present:
postcode = event.get('QueryStringParameters',{}).get('postcode','n/a')
```

`event` can be thought of as the 'purpose' the lambda. When you hit the `Test` button on the console to test the lambda, you invoked the lambda and passed it the test `event` -- the lambda then took that event and performed some operation on it.
`event` can be thought of as the 'purpose' the lambda. When you hit the `Test` button on the console, you invoked the lambda and passed it the test `event` -- the lambda then took that event and printed it out.

As we dive deeper into AWS Lambda, we'll find more and more possible events that can trigger lambdas, such as:

Expand All @@ -95,13 +100,13 @@ As we dive deeper into AWS Lambda, we'll find more and more possible events that
* An event from cloudwatch
* .... and the list goes on...

Each event will have a different format and carry different payloads, but the pattern is always the same. The lambda function is triggered, it will inspect it's event and perform a logic on the event before returning and terminating.
Each event will have a different format and carry different payloads, but the pattern is always the same. The lambda function is triggered, it will inspect its event and perform a logic on the event before returning and terminating.

Now that we've covered `event`, let's move onto `context`s
Now that we've covered `event`, let's move onto `context`.

## Context

`context` is a Python Class that implements methods and has attributes. It's main role is to provide information about the current execution environment.
`context` is a Python objects that implements methods and has attributes. It's main role is to provide information about the current execution environment. Unlike `event`, the methods and properties of the `context` object remain the same regardless of the lambda was invoked or triggered.

### Context methods

Expand All @@ -123,10 +128,12 @@ We will use the `context` object later on in the book, but for now we can likely

Lambda functions can be triggered both synchronous and asynchronous fashion, which means sometimes it doesn't makes sense to provide a return value.

However when invoked synchronously (e.g. via API), the return value will be returned to the calling application.
However when invoked synchronously (e.g. via API), the return value will be returned to the calling application, and usually must be returned in a specific structure.

For example, AWS Lambda console uses the synchronous invocation type, so when you invoke the function using the console, the console will display the returned value (serialized into json)

> Return values must always
## Conclusion

So to conclude
Expand Down
99 changes: 99 additions & 0 deletions docs/04_our_first_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Our First API

## What is an API?

Let's say we're looking to do the following:

* Build a HTTP API that responds to GET requests on `/api/v1/item`
* GET request will have no body, query string or path parameter
* The API will respond in json with a list of the following:
* Apple
* Oranges
* Lemons
* The HTTP response will have a status code of 200.

For those familiar with frameworks like Flask or Django is an easy task, but let's see how to do this with Lambda and API Gateway.

## Write the Python Code

First let's start with what we know, let's write a python function that will return the json.

```python
import json

def get_item(event, context):
items = ['Apple', 'Oranges' , 'Lemons']
api_response = {
"statusCode": 200,
"body": json.dumps(items)
}
return api_response
```

The code is simple enough, it returns a list of items (serialized in JSON), but wrapped up in a python dictionary. We'll cover why we need the dictionary later, for now what's important to note is that:

* We have a lambda function with a handler called 'get_item'
* The handler expects two arguments `event` and `context`
* It returns an object with a list of items in the `body`
* The return object has an attribute `statusCode` with a value of 200

We save this to a file called `handler.py`

## API Configuration

For API configuration, we use serverless framework, and populate our serverless.yml file with the following:

```yaml
service: first-api

provider:
name: aws
runtime: python3.8

functions:
hello:
handler: handler.get_item
events:
- httpApi:
method: GET
path: /api/v1/items
```

## Deploying our API

With just these two files in the same directory:

* handler.py
* serverless.yml

We can now deploy our function with the `sls deploy`.

![sls deploy](images/04/sls_deploy.png)

That's it.

We now have a working API on AWS!!

No really -- you've just deployed your first API!!

## Test the API

You probably don't trust me -- surely a handful lines of code can't create an API out of thin-air. So let's test this out, if you look at the output of our `sls deploy` command you'll see an entry called endpoints, under which there is a url:

> GET - https://f4bhj5qndj.execute-api.us-east-1.amazonaws.com/api/v1/items
When you run this on your own, you'll get a different url or couse, but this url is where our API was deployed. We can now easily test if indeed there's a working API by pasting the url in our browser, or performing the curl command:

![curl](images/04/curl.png)

That really is it, we now have a working API that can respond to HTTP requests deployed on AWS.

## Conclusion

In 20 lines of code, we setup a complete API, that's really impressive.

The API isn't just functional, it has TLS encryption, is completely scalable, and even deployed to multi-AZ. If you didn't understand all of that, don't worry, the lambda functions take away the heavy lifting developers had to previously do, which means you don't have dig deep on multi-AZ deployments anymore -- they happen by default.

We now can focus on writing business logic, and not be worried about Apache Webserver configurations, or how to configure WSGI or Gunicorn.

But this API is boring, it returns a static list of items. Let's spice things up a bit.
Empty file added docs/05_our_second_api.md
Empty file.
Binary file added docs/images/04/curl.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/04/lambda_code_plus_settings.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/04/sls_deploy.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added misc/api.sdxml
Binary file not shown.
Binary file added misc/control_planes.sdxml
Binary file not shown.
Binary file added misc/lambda.sdxml
Binary file not shown.

0 comments on commit 300941c

Please sign in to comment.