Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/amancevice/yake
A Rake-like DSL for writing AWS Lambda handlers
https://github.com/amancevice/yake
api-gateway aws datadog dsl lambda rake ruby serverless
Last synced: 2 days ago
JSON representation
A Rake-like DSL for writing AWS Lambda handlers
- Host: GitHub
- URL: https://github.com/amancevice/yake
- Owner: amancevice
- License: mit
- Created: 2021-04-26T18:27:27.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-10-15T13:23:06.000Z (2 months ago)
- Last Synced: 2024-12-14T19:04:32.954Z (9 days ago)
- Topics: api-gateway, aws, datadog, dsl, lambda, rake, ruby, serverless
- Language: Ruby
- Homepage:
- Size: 200 KB
- Stars: 168
- Watchers: 5
- Forks: 7
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# λake
[![gem](https://img.shields.io/gem/v/yake?color=crimson&logo=rubygems&logoColor=eee&style=flat-square)](https://rubygems.org/gems/yake)
[![rspec](https://img.shields.io/github/actions/workflow/status/amancevice/yake/rspec.yml?logo=github&style=flat-square)](https://github.com/amancevice/yake/actions/workflows/rspec.yml)
[![coverage](https://img.shields.io/codeclimate/coverage/amancevice/yake?logo=code-climate&style=flat-square)](https://codeclimate.com/github/amancevice/yake/test_coverage)
[![maintainability](https://img.shields.io/codeclimate/maintainability/amancevice/yake?logo=code-climate&style=flat-square)](https://codeclimate.com/github/amancevice/yake/maintainability)[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/smallweirdnumber)
Write your AWS Lambda function handlers using a Rake-like declarative syntax:
```ruby
# ./lambda_function.rb
require 'yake'handler :lambda_handler do |event|
# Your code here
end# Handler signature: `lambda_function.lambda_handler`
```You can even declare Sinatra-like API Gateway routes for a main entrypoint:
```ruby
# ./lambda_function.rb
require 'yake/api'header 'content-type' => 'application/json'
get '/fizz' do
respond 200, { ok: true }.to_json
endhandler :lambda_handler do |event|
route event
rescue => err
respond 500, { message: err.message }.to_json
end# Handler signature: `lambda_function.lambda_handler`
```## Installation
Add this line to your application's Gemfile:
```ruby
gem 'yake'
```And then execute:
```bash
bundle install
```Or install it yourself as:
```bash
gem install yake
```## Why Is It Called "yake"?
"λ" + Rake, but "λ" is hard to type and I think "y" looks like a funny little upside-down-and-backwards Lambda symbol.
## Why Use It?
So why use `yake` for your Lambda functions?
### Event Logging
By default, the `handler` function wraps its block in log lines formatted to match the style of Amazon's native Lambda logs sent to CloudWatch. Each invocation of the handler will log both the _input event_ and the _returned value_, prefixed with the ID of the request:
```plaintext
START RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf Version: $LATEST
INFO RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf EVENT { … }
…
INFO RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf RETURN { … }
END RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf
REPORT RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf Duration: 43.97 ms Billed Duration: 44 ms Memory Size: 128 MB Max Memory Used: 77 MB
```Logging the request ID in this way makes gathering logs lines for a particular execution in CloudWatch much easier.
You can customize or disable the logger:
```ruby
logging :off # disables logging entirely
logging pretty: false # Logs event/result in compact JSON
logging :on, MyLogger.new # Use a custom logger
```Include `Yake::Logger` on a class to access this logger:
```ruby
class Fizz
include Yake::Logger
endFizz.new.logger == Yake.logger
# => true
```### API Routes
A common use of Lambda functions is as a proxy for API Gateway. Oftentimes users will deploy a single Lambda function to handle all requests coming from API Gateway.
Requiring the `yake/api` module will add the API-specific DSL into your handler.
Define API routes using Sinatra-like syntax
```ruby
any '/…' do |event|
# Handle 'ANY /…' route key events
enddelete '/…' do |event|
# Handle 'DELETE /…' route key events
endget '/…' do |event|
# Handle 'GET /…' route key events
endhead '/…' do |event|
# Handle 'HEAD /…' route key events
endoptions '/…' do |event|
# Handle 'OPTIONS /…' route key events
endpatch '/…' do |event|
# Handle 'PATCH /…' route key events
endpost '/…' do |event|
# Handle 'POST /…' route key events
endput '/…' do |event|
# Handle 'PUT /…' route key events
end
```Helper methods are also made available to help produce a response for API Gateway:
Set a default header for ALL responses:
```ruby
header 'content-type' => 'application/json; charset=utf-8'
header 'x-custom-header' => 'fizz'
```Produce an API Gateway-style response object:
```ruby
respond 200, { ok: true }.to_json, 'x-extra-header' => 'buzz'
# {
# "statusCode" => 200,
# "body" => '{"ok":true}',
# "headers" => { "x-extra-header" => "buzz" }
# }
```Route an event to one of the declared routes:
```ruby
handler :lambda_handler do |event|
route event
rescue Yake::Errors::UndeclaredRoute => err
respond 404, { message: err.message }.to_json
rescue => err
respond 500, { message: err.message }.to_json
end
```### Zero Dependencies
Finally, `yake` does not depend on any other gems, using the Ruby stdlib only. This helps keep your Lambda packages slim & speedy.
## Support Helpers
As of `~> 0.5`, `yake` comes with a support module for common transformations.
Enable the helpers by requiring the `support` submodule:
```ruby
require 'yake/support'
````Object` helpers:
```ruby
MyObject.new.some_method
# => NoMethodErrorMyObject.new.try(:some_method)
# => nil10.try(:some_method) { |x| x ** 2 }
# => 100
````Hash` helpers:
```ruby
{ a: { b: 'c', d: 'e' }, f: 'g' }.deep_keys
# => [:a, :b, :d, :f]left = { a: 'b', c: { d: %w[e] } }
right = { a: 'a', c: { d: %w[d] } }
left.deep_merge(right)
# => { :a => "a", :c => { :d => ["e", "d"] } }{ a: { b: 'c', d: 'e' }, f: 'g' }.deep_transform_keys(&:to_s)
# => { "a" => { "b" => "c", "d" => "e" }, "f" => "g" }hash = { a: { b: 'c', d: 'e' }, f: 'g' }
hash.deep_transform_keys!(&:to_s)
# => { "a" => { "b" => "c", "d" => "e" }, "f" => "g" }{ f: 'g', a: { d: 'e', b: 'c' } }.deep_sort
# => { a: { b: 'c', d: 'e' }, f: 'g' }{ fizz: 'buzz' }.encode64
# => "eyJmaXp6IjoiYnV6eiJ9\n"{ fizz: 'buzz', jazz: 'fuzz' }.except(:buzz)
# => { :fizz => 'buzz' }{ fizz: 'buzz' }.strict_encode64
# => "eyJmaXp6IjoiYnV6eiJ9"{ fizz: { buzz: %w[jazz fuzz] } }.stringify_names
# => { "fizz" => { "buzz" => ["jazz", "fuzz"] } }{ 'fizz' => { 'buzz' => %w[jazz fuzz] } }.symbolize_names
# => { :fizz => { :buzz => ["jazz", "fuzz"] } }{ fizz: 'buzz' }.to_form
# => "fizz=buzz"{ f: 'g', a: { d: 'e', b: 'c' } }.to_json_sorted
# => '{"a":{"b":"c","d":"e"},"f":"g"}'{ f: 'g', a: { d: 'e', b: 'c' } }.to_struct
# => #"e", :b=>"c"}>{ f: 'g', a: { d: 'e', b: 'c' } }.to_deep_struct
# => #>{ a: { b: 'c', d: 'e' }, f: 'g' }.to_dynamodb
# => { :a => { :M => { :b => { :S => "c" }, :d => { :S => "e" } } }, :f => { :S => "g" } }{ a: { M: { b: { S: 'c' }, d: { S: 'e' } } }, f: { S: 'g' } }.to_h_from_dynamodb
# => { :a => { :b => "c", :d => "e" }, :f => "g" }
````Integer` helpers:
```ruby
7.weeks
# => 4_233_6007.days
# => 604_8007.hours
# => 25_2007.minutes
# => 4201234567890.utc
# => 2009-02-13 23:31:30 UTC
````String` helpers:
```ruby
host = 'https://example.com/'
path = '/path/to/resource'
host / path
# => "https://example.com/path/to/resource"'snake_case_string'.camel_case
# => "SnakeCaseString""Zml6eg==\n".decode64
# => "fizz"'fizz'.encode64
# => "Zml6eg==\n"'fizz'.md5sum
# => "b6bfa6c318811be022d4f73070597660"'fizz'.sha1sum
# => "c25f5985f2ab63baeb2408a2d7dbc79d8f29d02f"'CamelCaseString'.snake_case
# => "camel_case_string"'Zml6eg=='.strict_decode64
# => "fizz"'fizz'.strict_encode64
# => "Zml6eg=="'{"fizz":"buzz"}'.to_h_from_json
# => { "fizz" => "buzz" }'fizz=buzz'.to_h_from_form
# => { "fizz" => "buzz" }'2009-02-13T23:31:30Z'.utc
# => 2009-02-13 23:31:30 UTC
````Symbol` helpers
```ruby
:snake_case_symbol.camel_case
# => :SnakeCaseSymbol:CamelCaseSymbol.snake_case
# => :camel_case_symbol
````UTC` Time helpers
```ruby
UTC.at 1234567890
# => 2009-02-13 23:31:30 UTCUTC.now
# => 2022-02-26 13:57:07.860539 UTC
```## Datadog Integration
As of `v1.0`, `yake` comes with a helper for writing Lambdas that integrate with Datadog's `datadog-lambda` gem.
As of `~> 0.8`, `yake` uses the v2 Datadog Lambda gem.
Creating a Lambda handler that wraps the Datadog tooling is easy:
```ruby
require 'aws-sdk-someservice'
require 'yake/datadog'# Configure Datadog to use AWS tracing
Datadog::Lambda.configure_apm { |c| c.tracing.instrument :aws }datadog :handler do |event|
# …
end
```## Deployment
After writing your Lambda handler code you can deploy it to AWS using any number of tools. I recommend the following tools:
- [Terraform](https://www.terraform.io) — my personal favorite Infrastructure-as-Code tool
- [AWS SAM](https://aws.amazon.com/serverless/sam/) — a great alternative with less configuration than Terraform
- [Serverless](https://www.serverless.com) — Supposedly the most popular option, though I have not used it## Development
After checking out the repo, run `bundle` to install dependencies. Then, run `rake spec` to run the tests.
## Contributing
Bug reports and pull requests are welcome on GitHub at [amancevice/yake](https://github.com/amancevice/yake).
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).