├── .dockerignore ├── .gitignore ├── Dockerfile ├── Gopkg.lock ├── Gopkg.toml ├── README.md ├── configs └── dev.yml ├── go.mod ├── go.sum ├── mage.go ├── magefile.go ├── modules └── books │ ├── endpoints │ ├── create.go │ ├── delete.go │ ├── detail.go │ ├── read.go │ └── update.go │ └── functions │ └── worker.go ├── package-lock.json ├── pkg ├── libs │ ├── dynamoclient │ │ └── dynamoclient.go │ └── sqsclient │ │ └── sqsclient.go └── models │ └── book │ └── book.go └── serverless.yml /.dockerignore: -------------------------------------------------------------------------------- 1 | .git -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Serverless directories 2 | .serverless 3 | 4 | # golang output binary directory 5 | bin 6 | 7 | # golang vendor (dependencies) directory 8 | vendor 9 | 10 | # Binaries for programs and plugins 11 | *.exe 12 | *.exe~ 13 | *.dll 14 | *.so 15 | *.dylib 16 | 17 | # Test binary, build with `go test -c` 18 | *.test 19 | 20 | # Output of the go coverage tool, specifically when used with LiteIDE 21 | *.out 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.13.8-alpine3.10 2 | 3 | WORKDIR /go/src/app 4 | 5 | RUN apk add make git 6 | 7 | RUN go get -u github.com/golang/dep/cmd/dep 8 | 9 | ADD . . 10 | 11 | RUN make build -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:c8bc6cc8a13405a9ebfdc76a4279d52ff3e13dd4f73766b7a1ff6b45aa609fc3" 6 | name = "github.com/aws/aws-lambda-go" 7 | packages = [ 8 | "events", 9 | "lambda", 10 | "lambda/handlertrace", 11 | "lambda/messages", 12 | "lambdacontext", 13 | ] 14 | pruneopts = "UT" 15 | revision = "b5b7267d297de263cc5b61f8c37543daa9c95ffd" 16 | version = "v1.13.3" 17 | 18 | [[projects]] 19 | digest = "1:c9384787d3e6c3101f9727b7ecc9d802d698eb3b6c60d075c8a7d49aa75fc50c" 20 | name = "github.com/aws/aws-sdk-go" 21 | packages = [ 22 | "aws", 23 | "aws/awserr", 24 | "aws/awsutil", 25 | "aws/client", 26 | "aws/client/metadata", 27 | "aws/corehandlers", 28 | "aws/credentials", 29 | "aws/credentials/ec2rolecreds", 30 | "aws/credentials/endpointcreds", 31 | "aws/credentials/processcreds", 32 | "aws/credentials/stscreds", 33 | "aws/crr", 34 | "aws/csm", 35 | "aws/defaults", 36 | "aws/ec2metadata", 37 | "aws/endpoints", 38 | "aws/request", 39 | "aws/session", 40 | "aws/signer/v4", 41 | "internal/ini", 42 | "internal/sdkio", 43 | "internal/sdkmath", 44 | "internal/sdkrand", 45 | "internal/sdkuri", 46 | "internal/shareddefaults", 47 | "internal/strings", 48 | "private/protocol", 49 | "private/protocol/json/jsonutil", 50 | "private/protocol/jsonrpc", 51 | "private/protocol/query", 52 | "private/protocol/query/queryutil", 53 | "private/protocol/rest", 54 | "private/protocol/xml/xmlutil", 55 | "service/dynamodb", 56 | "service/dynamodb/dynamodbattribute", 57 | "service/dynamodb/expression", 58 | "service/sqs", 59 | "service/sts", 60 | "service/sts/stsiface", 61 | ] 62 | pruneopts = "UT" 63 | revision = "2fa828ed93ccc746b97509ba21309c4035f22009" 64 | version = "v1.27.2" 65 | 66 | [[projects]] 67 | digest = "1:582b704bebaa06b48c29b0cec224a6058a09c86883aaddabde889cd1a5f73e1b" 68 | name = "github.com/google/uuid" 69 | packages = ["."] 70 | pruneopts = "UT" 71 | revision = "0cd6bf5da1e1c83f8b45653022c74f71af0538a4" 72 | version = "v1.1.1" 73 | 74 | [[projects]] 75 | digest = "1:bb81097a5b62634f3e9fec1014657855610c82d19b9a40c17612e32651e35dca" 76 | name = "github.com/jmespath/go-jmespath" 77 | packages = ["."] 78 | pruneopts = "UT" 79 | revision = "c2b33e84" 80 | 81 | [solve-meta] 82 | analyzer-name = "dep" 83 | analyzer-version = 1 84 | input-imports = [ 85 | "github.com/aws/aws-lambda-go/events", 86 | "github.com/aws/aws-lambda-go/lambda", 87 | "github.com/aws/aws-sdk-go/aws", 88 | "github.com/aws/aws-sdk-go/aws/session", 89 | "github.com/aws/aws-sdk-go/service/dynamodb", 90 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute", 91 | "github.com/aws/aws-sdk-go/service/dynamodb/expression", 92 | "github.com/aws/aws-sdk-go/service/sqs", 93 | "github.com/google/uuid", 94 | ] 95 | solver-name = "gps-cdcl" 96 | solver-version = 1 97 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/aws/aws-lambda-go" 30 | version = "1.13.3" 31 | 32 | [[constraint]] 33 | name = "github.com/aws/aws-sdk-go" 34 | version = "1.27.2" 35 | 36 | [[constraint]] 37 | name = "github.com/google/uuid" 38 | version = "1.1.1" 39 | 40 | [prune] 41 | go-tests = true 42 | unused-packages = true 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://cdn-images-1.medium.com/max/1600/1*OezhU9lHTNCk6O6FCUL5fQ.png) 2 | 3 |

Serverless Architecture Boilerplate (GoLang) 👋

4 |

5 | 6 | 7 | Documentation 8 | 9 | 10 | Twitter: fidelissauro 11 | 12 |

13 | 14 | > Boilerplate to organize and deploy big projects using AWS API Gateway and AWS Lambda with Serverless Framework 15 | 16 | ### 🏠 [Homepage](https://github.com/msfidelis/serverless-architecture-boilerplate-go) 17 | 18 | ## Structure 19 | 20 | ``` 21 | . 22 | ├── Dockerfile 23 | ├── Gopkg.lock 24 | ├── Gopkg.toml 25 | ├── README.md 26 | ├── bin (output for go binaries) 27 | ├── configs (configs folders for environment) 28 | │ └── dev.yml 29 | ├── go.mod 30 | ├── go.sum 31 | ├── modules (modules folder) 32 | │ └── books (module / context) 33 | │ ├── endpoints (api endpoints) 34 | │ │ ├── create.go 35 | │ │ ├── delete.go 36 | │ │ ├── detail.go 37 | │ │ ├── read.go 38 | │ │ └── update.go 39 | │ └── functions (workers / background functions) 40 | │ └── worker.go 41 | ├── package-lock.json 42 | ├── pkg (shared components) 43 | │ ├── libs (libraries) 44 | │ │ ├── dynamoclient 45 | │ │ │ └── dynamoclient.go 46 | │ │ └── sqsclient 47 | │ │ └── sqsclient.go 48 | │ └── models (models) 49 | │ └── book 50 | │ └── book.go 51 | ├── magefile.go 52 | ├── mage.go 53 | └── serverless.yml 54 | ``` 55 | 56 | ## Install 57 | 58 | ```sh 59 | go run mage.go build 60 | ``` 61 | 62 | ## Usage 63 | 64 | ```sh 65 | go run mage.go deploy 66 | ``` 67 | 68 | ## Run tests 69 | 70 | ```sh 71 | go run mage.go test 72 | ``` 73 | 74 | # Testing boilerplate 75 | 76 | ### Listing books 77 | 78 | ```bash 79 | curl -i https://5pas07ibi2.execute-api.us-east-1.amazonaws.com/dev/books 80 | ``` 81 | 82 | ```json 83 | HTTP/2 200 84 | content-type: application/json 85 | content-length: 122213 86 | date: Sun, 23 Feb 2020 19:42:16 GMT 87 | x-amzn-requestid: 09b93530-dda7-46b0-a916-fcb4ae59c0bc 88 | x-amz-apigw-id: IXZP3EspoAMFyFQ= 89 | x-amzn-trace-id: Root=1-5e52d598-b446d86c871e237cf838922c;Sampled=0 90 | x-cache: Miss from cloudfront 91 | via: 1.1 37a135c363e9512a2b27aa63bc837339.cloudfront.net (CloudFront) 92 | x-amz-cf-pop: GRU50-C1 93 | x-amz-cf-id: vhEGO9RiFXPQw5q4GWRxd-w9l5Klg8NqI4u9-hbC231q9okFLI4Pbw== 94 | 95 | [{"hashkey":"ccd31b13-4945-11ea-a024-da7fa31ac789","title":"D(jwItrDnA","author":"prSa(j)kSD","price":10.1,"updated" 96 | :"","created":"","processed":true},{"hashkey":"cc867cb1-4945-11ea-8754-a2fe55138192","title":"pDy@eghdyS","author":" 97 | m*ikSdohth","price":10.1,"updated":"","created":"","processed":true},{"hashkey":"c2ff73e9-4945-11ea-a024-da7fa31ac78 98 | 9","title":"DFse*wsa)(","author":"DpFoqHISd*","price":10.1,"updated":"","created":"","processed":true},{"hashkey":"8 99 | e139c9a-4f9c-11ea-9f24-72f20a774bc0","title":"","author":"","price":0,"updated":"","created":"","processed":true},{" 100 | hashkey":"6e3f7b60-4f9c-11ea-9f24-72f20a774bc0","title":"","author":"","price":0,"updated":"","created":"","processe 101 | d":true},{"hashkey":"708117bf-4f9c-11ea-b2af-6e3ae06015d9","title":"","author":"","price":0,"updated":"","created":" 102 | ","processed":true}] 103 | ``` 104 | 105 | ### Creating new Book 106 | 107 | ```bash 108 | curl -X POST https://5pas07ibi2.execute-api.us-east-1.amazonaws.com/dev/books -H 'Content-type:application/json' -d '{"title": "American Gods", "price": 10.50, "author": "Neil Gaiman"}' 109 | ``` 110 | 111 | 112 | ```json 113 | curl -i -X POST https://5pas07i 114 | bi2.execute-api.us-east-1.amazonaws.com/dev/books -H 'Content-type:application/json' -d '{"title": "American Gods", 115 | "price": 10.50, "author": "Neil Gaiman"}' 116 | HTTP/2 201 117 | content-type: application/json 118 | content-length: 265 119 | date: Sun, 23 Feb 2020 19:45:41 GMT 120 | x-amzn-requestid: 03f52228-3df8-413e-a22f-12877e41ebb3 121 | x-amz-apigw-id: IXZv2FOXIAMFgDw= 122 | x-amzn-trace-id: Root=1-5e52d665-f99746e82b4797d024a9d196;Sampled=0 123 | x-cache: Miss from cloudfront 124 | via: 1.1 aa3fc654df34a675869d8ecab4dd6bab.cloudfront.net (CloudFront) 125 | x-amz-cf-pop: GRU50 126 | x-amz-cf-id: NasPt3E-aQBmufDdZ3fVjCSaYYVHTnicDcyib1ZoAgqdkHCcwySxGA== 127 | 128 | {"hashkey":"12bfd98c-5675-11ea-94ea-5ec3dff6689d","title":"American Gods","author":"Neil Gaiman","price":10.5,"updat 129 | ed":"2020-02-23 19:45:41.394682371 +0000 UTC m=+329.476777607","created":"2020-02-23 19:45:41.39467731 +0000 UTC m=+ 130 | 329.476772546","processed":false} 131 | ``` 132 | 133 | ### Detail a book 134 | 135 | ```bash 136 | curl -i https://5pas07ibi2.execu 137 | te-api.us-east-1.amazonaws.com/dev/books/12bfd98c-5675-11ea-94ea-5ec3dff6689d 138 | ``` 139 | 140 | ```json 141 | HTTP/2 200 142 | content-type: application/json 143 | content-length: 265 144 | date: Sun, 23 Feb 2020 19:47:24 GMT 145 | x-amzn-requestid: 7de185ab-a088-4bcd-8da8-5b8e249adfb7 146 | x-amz-apigw-id: IXZ_0G4jIAMFfzA= 147 | x-amzn-trace-id: Root=1-5e52d6cb-096a4604266412f06b182418;Sampled=0 148 | x-cache: Miss from cloudfront 149 | via: 1.1 356de6a1dc9aa6df67447cdc3e65d45e.cloudfront.net (CloudFront) 150 | x-amz-cf-pop: GRU1-C1 151 | x-amz-cf-id: cH8MwkQU9WLfuEkpNZCXYYS8QQGU0uQ5CsdmIfTEVn8lVrR9wMtoRg== 152 | 153 | {"hashkey":"12bfd98c-5675-11ea-94ea-5ec3dff6689d","title":"American Gods","author":"Neil Gaiman","price":10.5,"updat 154 | ed":"2020-02-23 19:45:41.526980165 +0000 UTC m=+328.914267950","created":"2020-02-23 19:45:41.39467731 +0000 UTC m=+ 155 | 329.476772546","processed":false}% 156 | ``` 157 | 158 | 159 | ### Updating a book 160 | 161 | ```bash 162 | curl -i -X PUT https://5pas07 163 | ibi2.execute-api.us-east-1.amazonaws.com/dev/books/12bfd98c-5675-11ea-94ea-5ec3dff6689d -H 'Content-type:application 164 | /json' -d '{"title": "American Gods - Updated", "price": 20.00, "author": "Neil Gaiman"}' 165 | ``` 166 | 167 | ```json 168 | HTTP/2 200 169 | content-type: application/json 170 | content-length: 179 171 | date: Sun, 23 Feb 2020 19:50:51 GMT 172 | x-amzn-requestid: 6720a008-07b5-4f0c-81de-3ee3bc2ed93b 173 | x-amz-apigw-id: IXagXGkmoAMFwvA= 174 | x-amzn-trace-id: Root=1-5e52d79b-b689cd30ffe1e9dc5ef25256;Sampled=0 175 | x-cache: Miss from cloudfront 176 | via: 1.1 a43a4e3a015929f71d6fc6fa15418703.cloudfront.net (CloudFront) 177 | x-amz-cf-pop: GRU1-C1 178 | x-amz-cf-id: 2W2jS7yoAbPsICQ7C3EMMKQl827HdbFPkb_WdsztzUwmstH7U4nIMw== 179 | 180 | {"hashkey":"","title":"American Gods - Updated","author":"Neil Gaiman","price":20,"updated":"2020-02-23 19:50:51.896 181 | 7559 +0000 UTC m=+48.464494766","created":"","processed":false} 182 | ``` 183 | 184 | 185 | ### Deleting a book 186 | 187 | ```bash 188 | curl -i -X DELETE https://5pas 189 | 07ibi2.execute-api.us-east-1.amazonaws.com/dev/books/12bfd98c-5675-11ea-94ea-5ec3dff6689d 190 | ``` 191 | 192 | ```json 193 | HTTP/2 200 194 | content-type: application/json 195 | content-length: 69 196 | date: Sun, 23 Feb 2020 19:52:48 GMT 197 | x-amzn-requestid: 19d8bf9a-9a9f-41f9-9558-481baae891d9 198 | x-amz-apigw-id: IXaybEqAoAMFuAg= 199 | x-amzn-trace-id: Root=1-5e52d80f-40cde16afd20a6ce870e7b0e;Sampled=0 200 | x-cache: Miss from cloudfront 201 | via: 1.1 32063733c6b1049f7b777e1f8ac028ad.cloudfront.net (CloudFront) 202 | x-amz-cf-pop: GRU50 203 | x-amz-cf-id: lNTlUeTq4RGRD1Us5cHgZ1spPqhzE91asXdSyci8k0IuISUuMOR_4w== 204 | 205 | {"hashkey":"12bfd98c-5675-11ea-94ea-5ec3dff6689d","status":"deleted"}ls 206 | 207 | ``` 208 | 209 | # Creating a new function 210 | 211 | ### 1) create new function inside `modules` path 212 | 213 | ```bash 214 | touch modules/mymodule/endpoints/myfunction.go 215 | ``` 216 | 217 | ### 2) Add build instructions on Makefile 218 | 219 | ```bash 220 | vim Makefile 221 | ``` 222 | 223 | ```Makefile 224 | build: 225 | dep ensure 226 | // ... 227 | env GOOS=linux go build -ldflags="-s -w" -o bin/mymodule/endpoints/myfunction modules/mymodule/endpoints/myfunction 228 | ``` 229 | 230 | ### 3) Add function mapping on `serverless.yml` file 231 | 232 | ```bash 233 | vim serverless.yml 234 | ``` 235 | 236 | ```yml 237 | # ... 238 | functions: 239 | create: 240 | handler: bin/mymodule/endpoints/myfunction 241 | events: 242 | - http: 243 | path: /services/mypath 244 | method: get 245 | tags: 246 | TAGFUNCTION: Tag Value 247 | ``` 248 | 249 | ### 4) Delete stack 250 | 251 | ```bash 252 | go run mage.go remove 253 | ``` 254 | 255 | ## Author 256 | 257 | 👤 **Matheus Fidelis** 258 | 259 | * Twitter: [@fidelissauro](https://twitter.com/fidelissauro) 260 | * Github: [@msfidelis](https://github.com/msfidelis) 261 | 262 | ## 🤝 Contributing 263 | 264 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/msfidelis/serverless-architecture-boilerplate-go/issues). 265 | 266 | ## Show your support 267 | 268 | Give a ⭐️ if this project helped you! 269 | 270 | *** 271 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 272 | -------------------------------------------------------------------------------- /configs/dev.yml: -------------------------------------------------------------------------------- 1 | ENV: dev 2 | REGION: 'us-east-1' 3 | DYNAMO_TABLE_BOOKS: ${self:custom.dynamo-books-name} 4 | SQS_QUEUE_BOOKS: ${self:custom.sqs-books-name} -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module serverless-architecture-boilerplate-go 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/aws/aws-lambda-go v1.13.3 7 | github.com/aws/aws-sdk-go v1.27.2 8 | github.com/google/uuid v1.1.1 9 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af 10 | github.com/magefile/mage v1.11.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY= 3 | github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= 4 | github.com/aws/aws-sdk-go v1.27.2 h1:yr0Lp4bcrIiP8x4JI9wPG+/t4hjdNJghmYJcKX4wh/g= 5 | github.com/aws/aws-sdk-go v1.27.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 6 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 10 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 12 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 13 | github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls= 14 | github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 18 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 19 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 20 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 21 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 22 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 23 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 24 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 25 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 26 | -------------------------------------------------------------------------------- /mage.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "os" 7 | 8 | "github.com/magefile/mage/mage" 9 | ) 10 | 11 | func main() { 12 | os.Exit(mage.Main()) 13 | } 14 | -------------------------------------------------------------------------------- /magefile.go: -------------------------------------------------------------------------------- 1 | //go:build mage 2 | // +build mage 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/magefile/mage/mg" 10 | "github.com/magefile/mage/sh" 11 | ) 12 | 13 | var Default = PHONY 14 | 15 | func PHONY() { 16 | mg.Deps(Clean, Build, Deploy, Remove) 17 | } 18 | 19 | // clean remove all bin 20 | func Clean() error { 21 | return sh.Rm("bin") 22 | } 23 | 24 | // build build each module into "bin" 25 | func Build() error { 26 | mg.Deps(Clean) 27 | os.Setenv("GO111MODULE", "on") 28 | os.Setenv("GOOS", "linux") 29 | gobuild := sh.RunCmd("go", "build", "-ldflags", "-s -w", "-o") 30 | 31 | bins := []struct { 32 | output string 33 | source string 34 | }{ 35 | {"bin/books/endpoints/create", "modules/books/endpoints/create.go"}, 36 | {"bin/books/endpoints/read", "modules/books/endpoints/read.go"}, 37 | {"bin/books/endpoints/detail", "modules/books/endpoints/detail.go"}, 38 | {"bin/books/endpoints/update", "modules/books/endpoints/update.go"}, 39 | {"bin/books/endpoints/delete", "modules/books/endpoints/delete.go"}, 40 | 41 | {"bin/books/functions/worker", "modules/books/functions/worker.go"}, 42 | } 43 | for _, bin := range bins { 44 | err := gobuild(bin.output, bin.source) 45 | if err != nil { 46 | return err 47 | } 48 | } 49 | return nil 50 | } 51 | 52 | // test run all go tests 53 | func Test() error { 54 | return sh.Run("go", "test", "./...") 55 | } 56 | 57 | // deploy force deployment to default 58 | func Deploy() error { 59 | mg.Deps(Clean, Build) 60 | return sh.Run("serverless", "deploy", "--verbose", "--force") 61 | } 62 | 63 | func Remove() error { 64 | mg.Deps(Clean) 65 | return sh.Run("serverless", "remove") 66 | } 67 | -------------------------------------------------------------------------------- /modules/books/endpoints/create.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "os" 8 | "time" 9 | 10 | "github.com/aws/aws-lambda-go/events" 11 | "github.com/aws/aws-lambda-go/lambda" 12 | "github.com/google/uuid" 13 | 14 | "serverless-architecture-boilerplate-go/pkg/libs/dynamoclient" 15 | "serverless-architecture-boilerplate-go/pkg/libs/sqsclient" 16 | "serverless-architecture-boilerplate-go/pkg/models/book" 17 | ) 18 | 19 | type Response events.APIGatewayProxyResponse 20 | 21 | func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (Response, error) { 22 | var buf bytes.Buffer 23 | 24 | dynamoTable := os.Getenv("DYNAMO_TABLE_BOOKS") 25 | client := dynamoclient.New(dynamoTable) 26 | 27 | sqsQueue := os.Getenv("SQS_QUEUE_BOOKS") 28 | sqs := sqsclient.New(sqsQueue) 29 | 30 | id, _ := uuid.NewUUID() 31 | 32 | book := &book.Book{ 33 | Hashkey: id.String(), 34 | Created: time.Now().String(), 35 | Updated: time.Now().String(), 36 | Processed: false, 37 | } 38 | 39 | json.Unmarshal([]byte(request.Body), book) 40 | 41 | client.Save(book) 42 | sqs.SendMessage(book) 43 | 44 | body, err := json.Marshal(book) 45 | 46 | if err != nil { 47 | return Response{StatusCode: 404}, err 48 | } 49 | 50 | json.HTMLEscape(&buf, body) 51 | 52 | resp := Response{ 53 | StatusCode: 201, 54 | IsBase64Encoded: false, 55 | Body: buf.String(), 56 | Headers: map[string]string{ 57 | "Content-Type": "application/json", 58 | }, 59 | } 60 | 61 | return resp, nil 62 | } 63 | 64 | func main() { 65 | lambda.Start(Handler) 66 | } 67 | -------------------------------------------------------------------------------- /modules/books/endpoints/delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "os" 8 | "serverless-architecture-boilerplate-go/pkg/libs/dynamoclient" 9 | 10 | "github.com/aws/aws-lambda-go/events" 11 | "github.com/aws/aws-lambda-go/lambda" 12 | 13 | "github.com/aws/aws-sdk-go/aws" 14 | "github.com/aws/aws-sdk-go/service/dynamodb" 15 | ) 16 | 17 | type Response events.APIGatewayProxyResponse 18 | 19 | func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (Response, error) { 20 | var buf bytes.Buffer 21 | var body []byte 22 | var statusCode int 23 | 24 | dynamoTable := os.Getenv("DYNAMO_TABLE_BOOKS") 25 | client := dynamoclient.New(dynamoTable) 26 | 27 | hashkey := request.PathParameters["hashkey"] 28 | 29 | key := map[string]*dynamodb.AttributeValue{ 30 | "hashkey": { 31 | S: aws.String(hashkey), 32 | }, 33 | } 34 | 35 | removed := client.RemoveItem(key) 36 | 37 | if removed == true { 38 | statusCode = 200 39 | payload, err := json.Marshal(map[string]interface{}{ 40 | "hashkey": hashkey, 41 | "status": "deleted", 42 | }) 43 | if err == nil { 44 | body = payload 45 | } 46 | } else { 47 | statusCode = 404 48 | payload, err := json.Marshal(map[string]interface{}{ 49 | "hashkey": hashkey, 50 | "status": "not found", 51 | }) 52 | if err != nil { 53 | body = payload 54 | } 55 | } 56 | 57 | json.HTMLEscape(&buf, body) 58 | 59 | resp := Response{ 60 | StatusCode: statusCode, 61 | IsBase64Encoded: false, 62 | Body: buf.String(), 63 | Headers: map[string]string{ 64 | "Content-Type": "application/json", 65 | }, 66 | } 67 | 68 | return resp, nil 69 | } 70 | 71 | func main() { 72 | lambda.Start(Handler) 73 | } 74 | -------------------------------------------------------------------------------- /modules/books/endpoints/detail.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "os" 9 | 10 | "serverless-architecture-boilerplate-go/pkg/libs/dynamoclient" 11 | "serverless-architecture-boilerplate-go/pkg/models/book" 12 | 13 | "github.com/aws/aws-lambda-go/events" 14 | "github.com/aws/aws-lambda-go/lambda" 15 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" 16 | "github.com/aws/aws-sdk-go/service/dynamodb/expression" 17 | ) 18 | 19 | type Response events.APIGatewayProxyResponse 20 | 21 | func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (Response, error) { 22 | 23 | var buf bytes.Buffer 24 | var books []book.Book 25 | 26 | dynamoTable := os.Getenv("DYNAMO_TABLE_BOOKS") 27 | client := dynamoclient.New(dynamoTable) 28 | 29 | hashkey := request.PathParameters["hashkey"] 30 | 31 | // Values to return from table 32 | proj := expression.NamesList( 33 | expression.Name("hashkey"), 34 | expression.Name("title"), 35 | expression.Name("author"), 36 | expression.Name("price"), 37 | expression.Name("updated"), 38 | expression.Name("created"), 39 | ) 40 | 41 | // Filter to return 42 | filt := expression.Name("hashkey").Equal(expression.Value(hashkey)) 43 | 44 | // Initialize Query Builder 45 | expr, errBuilder := expression.NewBuilder(). 46 | WithFilter(filt). 47 | WithProjection(proj). 48 | Build() 49 | 50 | if errBuilder != nil { 51 | fmt.Println("Got error building expression:") 52 | return Response{StatusCode: 500}, errBuilder 53 | } 54 | 55 | // Scan dynamoDB table 56 | result := client.Scan(expr) 57 | 58 | if len(result.Items) <= 0 { 59 | 60 | body, err := json.Marshal(map[string]interface{}{ 61 | "hashkey": hashkey, 62 | "message": "Book not found", 63 | }) 64 | 65 | if err != nil { 66 | return Response{StatusCode: 500}, err 67 | } 68 | 69 | return Response{ 70 | StatusCode: 404, 71 | IsBase64Encoded: false, 72 | Body: string(body), 73 | Headers: map[string]string{ 74 | "Content-Type": "application/json", 75 | }, 76 | }, nil 77 | 78 | } 79 | 80 | errUnmarsh := dynamodbattribute.UnmarshalListOfMaps(result.Items, &books) 81 | 82 | if errUnmarsh != nil { 83 | return Response{StatusCode: 500}, errUnmarsh 84 | } 85 | 86 | body, err := json.Marshal(books[0]) 87 | 88 | if err != nil { 89 | return Response{StatusCode: 500}, err 90 | } 91 | 92 | json.HTMLEscape(&buf, body) 93 | 94 | resp := Response{ 95 | StatusCode: 200, 96 | IsBase64Encoded: false, 97 | Body: string(body), 98 | Headers: map[string]string{ 99 | "Content-Type": "application/json", 100 | }, 101 | } 102 | 103 | return resp, nil 104 | } 105 | 106 | func main() { 107 | lambda.Start(Handler) 108 | } 109 | -------------------------------------------------------------------------------- /modules/books/endpoints/read.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "strconv" 9 | 10 | "serverless-architecture-boilerplate-go/pkg/libs/dynamoclient" 11 | "serverless-architecture-boilerplate-go/pkg/models/book" 12 | 13 | "github.com/aws/aws-lambda-go/events" 14 | "github.com/aws/aws-lambda-go/lambda" 15 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" 16 | "github.com/aws/aws-sdk-go/service/dynamodb/expression" 17 | ) 18 | 19 | type Response events.APIGatewayProxyResponse 20 | 21 | func Handler(request events.APIGatewayProxyRequest) (Response, error) { 22 | var buf bytes.Buffer 23 | var filt expression.ConditionBuilder 24 | 25 | dynamoTable := os.Getenv("DYNAMO_TABLE_BOOKS") 26 | client := dynamoclient.New(dynamoTable) 27 | 28 | filterByProcessed := request.QueryStringParameters["processed"] 29 | fmt.Println(filterByProcessed) 30 | 31 | if filterByProcessed == "true" || filterByProcessed == "false" { 32 | b, err := strconv.ParseBool(filterByProcessed) 33 | if err != nil { 34 | fmt.Println("Got error building expression:") 35 | fmt.Println(err.Error()) 36 | } 37 | filt = expression.Name("processed").Equal(expression.Value(b)) 38 | } else { 39 | filt = expression.AttributeExists(expression.Name("hashkey")) 40 | } 41 | 42 | proj := expression.NamesList(expression.Name("hashkey"), expression.Name("title"), expression.Name("author"), expression.Name("price"), expression.Name("processed")) 43 | expr, errBuilder := expression.NewBuilder().WithProjection(proj).WithFilter(filt).Build() 44 | 45 | if errBuilder != nil { 46 | fmt.Println("Got error building expression:") 47 | fmt.Println(errBuilder.Error()) 48 | os.Exit(1) 49 | } 50 | 51 | var books []book.Book 52 | 53 | result := client.Scan(expr) 54 | 55 | errUnmarsh := dynamodbattribute.UnmarshalListOfMaps(result.Items, &books) 56 | 57 | if errUnmarsh != nil { 58 | return Response{StatusCode: 500}, errUnmarsh 59 | } 60 | 61 | body, err := json.Marshal(books) 62 | 63 | if err != nil { 64 | return Response{StatusCode: 500}, err 65 | } 66 | json.HTMLEscape(&buf, body) 67 | 68 | resp := Response{ 69 | StatusCode: 200, 70 | IsBase64Encoded: false, 71 | Body: buf.String(), 72 | Headers: map[string]string{ 73 | "Content-Type": "application/json", 74 | }, 75 | } 76 | 77 | return resp, nil 78 | } 79 | 80 | func main() { 81 | lambda.Start(Handler) 82 | } 83 | -------------------------------------------------------------------------------- /modules/books/endpoints/update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "os" 9 | "serverless-architecture-boilerplate-go/pkg/libs/dynamoclient" 10 | "serverless-architecture-boilerplate-go/pkg/models/book" 11 | "time" 12 | 13 | "github.com/aws/aws-lambda-go/events" 14 | "github.com/aws/aws-lambda-go/lambda" 15 | "github.com/aws/aws-sdk-go/aws" 16 | "github.com/aws/aws-sdk-go/service/dynamodb" 17 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" 18 | "github.com/aws/aws-sdk-go/service/dynamodb/expression" 19 | ) 20 | 21 | type Response events.APIGatewayProxyResponse 22 | 23 | func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (Response, error) { 24 | var buf bytes.Buffer 25 | var books []book.Book 26 | 27 | hashkey := request.PathParameters["hashkey"] 28 | 29 | dynamoTable := os.Getenv("DYNAMO_TABLE_BOOKS") 30 | client := dynamoclient.New(dynamoTable) 31 | 32 | proj := expression.NamesList(expression.Name("hashkey"), expression.Name("title"), expression.Name("author"), expression.Name("price"), expression.Name("updated"), expression.Name("created")) 33 | filt := expression.Name("hashkey").Equal(expression.Value(hashkey)) 34 | 35 | expr, errBuilder := expression.NewBuilder().WithFilter(filt).WithProjection(proj).Build() 36 | 37 | if errBuilder != nil { 38 | fmt.Println("Got error building expression:") 39 | return Response{StatusCode: 500}, errBuilder 40 | } 41 | 42 | result := client.Scan(expr) 43 | 44 | if len(result.Items) == 0 { 45 | 46 | body, err := json.Marshal(map[string]interface{}{ 47 | "hashkey": hashkey, 48 | "message": "Book not found", 49 | }) 50 | 51 | if err != nil { 52 | return Response{StatusCode: 500}, err 53 | } 54 | 55 | json.HTMLEscape(&buf, body) 56 | 57 | resp := Response{ 58 | StatusCode: 404, 59 | IsBase64Encoded: false, 60 | Body: string(body), 61 | Headers: map[string]string{ 62 | "Content-Type": "application/json", 63 | }, 64 | } 65 | 66 | return resp, nil 67 | 68 | } else { 69 | 70 | fmt.Println("Body:") 71 | fmt.Println(request.Body) 72 | 73 | errUnmarsh := dynamodbattribute.UnmarshalListOfMaps(result.Items, &books) 74 | if errUnmarsh != nil { 75 | return Response{StatusCode: 500}, errUnmarsh 76 | } 77 | 78 | payloadBook := &book.Book{ 79 | Hashkey: hashkey, 80 | } 81 | 82 | json.Unmarshal([]byte(request.Body), payloadBook) 83 | 84 | // Update by identifier 85 | key := map[string]*dynamodb.AttributeValue{ 86 | "hashkey": { 87 | S: aws.String(hashkey), 88 | }, 89 | } 90 | 91 | // Init update 92 | update := expression.Set( 93 | expression.Name("updated"), 94 | expression.Value(time.Now().String()), 95 | ) 96 | 97 | // Values Update 98 | if payloadBook.Author != "" { 99 | update.Set( 100 | expression.Name("author"), 101 | expression.Value(payloadBook.Author), 102 | ) 103 | } 104 | 105 | if payloadBook.Title != "" { 106 | update.Set( 107 | expression.Name("title"), 108 | expression.Value(payloadBook.Title), 109 | ) 110 | } 111 | 112 | if payloadBook.Price != 0 { 113 | update.Set( 114 | expression.Name("price"), 115 | expression.Value(payloadBook.Price), 116 | ) 117 | } 118 | 119 | expr, err := expression.NewBuilder().WithUpdate(update).Build() 120 | result := client.UpdateItem(key, expr) 121 | 122 | // Map book updated values to new struct 123 | bookUpdated := book.Book{} 124 | dynamodbattribute.UnmarshalMap(result.Attributes, &bookUpdated) 125 | 126 | body, err := json.Marshal(bookUpdated) 127 | if err != nil { 128 | return Response{StatusCode: 500}, err 129 | } 130 | 131 | json.HTMLEscape(&buf, body) 132 | 133 | resp := Response{ 134 | StatusCode: 200, 135 | IsBase64Encoded: false, 136 | Body: string(body), 137 | Headers: map[string]string{ 138 | "Content-Type": "application/json", 139 | }, 140 | } 141 | 142 | return resp, nil 143 | } 144 | 145 | } 146 | 147 | func main() { 148 | lambda.Start(Handler) 149 | } 150 | -------------------------------------------------------------------------------- /modules/books/functions/worker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "os" 9 | "time" 10 | 11 | "github.com/aws/aws-lambda-go/events" 12 | "github.com/aws/aws-lambda-go/lambda" 13 | "github.com/aws/aws-sdk-go/aws" 14 | "github.com/aws/aws-sdk-go/service/dynamodb" 15 | "github.com/aws/aws-sdk-go/service/dynamodb/expression" 16 | 17 | "serverless-architecture-boilerplate-go/pkg/libs/dynamoclient" 18 | "serverless-architecture-boilerplate-go/pkg/libs/sqsclient" 19 | "serverless-architecture-boilerplate-go/pkg/models/book" 20 | ) 21 | 22 | type Response events.APIGatewayProxyResponse 23 | 24 | func Handler(ctx context.Context, sqsEvent events.SQSEvent) error { 25 | if len(sqsEvent.Records) == 0 { 26 | return errors.New("No SQS message passed to function") 27 | } 28 | 29 | dynamoTable := os.Getenv("DYNAMO_TABLE_BOOKS") 30 | dynamo := dynamoclient.New(dynamoTable) 31 | 32 | sqsQueue := os.Getenv("SQS_QUEUE_BOOKS") 33 | sqs := sqsclient.New(sqsQueue) 34 | 35 | for _, msg := range sqsEvent.Records { 36 | 37 | fmt.Printf("Got SQS message %q with body %q\n", msg.MessageId, msg.Body) 38 | book := &book.Book{} 39 | json.Unmarshal([]byte(msg.Body), book) 40 | book.Processed = true 41 | 42 | key := map[string]*dynamodb.AttributeValue{ 43 | "hashkey": { 44 | S: aws.String(book.Hashkey), 45 | }, 46 | } 47 | update := expression.Set( 48 | expression.Name("updated"), 49 | expression.Value(time.Now().String()), 50 | ) 51 | update.Set( 52 | expression.Name("processed"), 53 | expression.Value(book.Processed), 54 | ) 55 | expr, err := expression.NewBuilder().WithUpdate(update).Build() 56 | if err != nil { 57 | fmt.Printf("Caught Builder %v\n", err) 58 | } 59 | dynamo.UpdateItem(key, expr) 60 | sqs.DeleteMessage(msg.ReceiptHandle) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func main() { 67 | lambda.Start(Handler) 68 | } 69 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /pkg/libs/dynamoclient/dynamoclient.go: -------------------------------------------------------------------------------- 1 | package dynamoclient 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/dynamodb" 10 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" 11 | "github.com/aws/aws-sdk-go/service/dynamodb/expression" 12 | ) 13 | 14 | type DynamoDbClient struct { 15 | tableName string 16 | } 17 | 18 | func New(tableName string) *DynamoDbClient { 19 | return &DynamoDbClient{ 20 | tableName: tableName, 21 | } 22 | } 23 | 24 | func (d *DynamoDbClient) Save(item interface{}) *dynamodb.PutItemOutput { 25 | 26 | av, errMarsh := dynamodbattribute.MarshalMap(item) 27 | 28 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 29 | SharedConfigState: session.SharedConfigEnable, 30 | })) 31 | 32 | svc := dynamodb.New(sess) 33 | 34 | if errMarsh != nil { 35 | fmt.Println("Error to marshalling new item:") 36 | fmt.Println(errMarsh.Error()) 37 | os.Exit(1) 38 | } 39 | 40 | input := &dynamodb.PutItemInput{ 41 | Item: av, 42 | TableName: aws.String(d.tableName), 43 | } 44 | 45 | response, errPut := svc.PutItem(input) 46 | 47 | if errPut != nil { 48 | fmt.Println("Got error calling PutItem:") 49 | fmt.Println(errPut.Error()) 50 | os.Exit(1) 51 | } 52 | 53 | return response 54 | } 55 | 56 | func (d DynamoDbClient) Scan(expr expression.Expression) *dynamodb.ScanOutput { 57 | 58 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 59 | SharedConfigState: session.SharedConfigEnable, 60 | })) 61 | 62 | svc := dynamodb.New(sess) 63 | 64 | params := &dynamodb.ScanInput{ 65 | ExpressionAttributeNames: expr.Names(), 66 | ExpressionAttributeValues: expr.Values(), 67 | FilterExpression: expr.Filter(), 68 | ProjectionExpression: expr.Projection(), 69 | TableName: aws.String(d.tableName), 70 | } 71 | 72 | result, err := svc.Scan(params) 73 | 74 | if err != nil { 75 | fmt.Println("Query API call failed:") 76 | fmt.Println((err.Error())) 77 | os.Exit(1) 78 | } 79 | 80 | return result 81 | } 82 | 83 | func (d DynamoDbClient) UpdateItem(keyMap map[string]*dynamodb.AttributeValue, expr expression.Expression) *dynamodb.UpdateItemOutput { 84 | 85 | fmt.Println(keyMap) 86 | fmt.Println(expr) 87 | 88 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 89 | SharedConfigState: session.SharedConfigEnable, 90 | })) 91 | 92 | svc := dynamodb.New(sess) 93 | 94 | input := &dynamodb.UpdateItemInput{ 95 | ExpressionAttributeNames: expr.Names(), 96 | ExpressionAttributeValues: expr.Values(), 97 | Key: keyMap, 98 | ReturnValues: aws.String("UPDATED_NEW"), 99 | TableName: aws.String(d.tableName), 100 | UpdateExpression: expr.Update(), 101 | } 102 | 103 | result, err := svc.UpdateItem(input) 104 | 105 | if err != nil { 106 | fmt.Println("Updated API call failed:") 107 | fmt.Println((err.Error())) 108 | os.Exit(1) 109 | } 110 | 111 | return result 112 | } 113 | 114 | func (d DynamoDbClient) RemoveItem(keyMap map[string]*dynamodb.AttributeValue) bool { 115 | 116 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 117 | SharedConfigState: session.SharedConfigEnable, 118 | })) 119 | 120 | svc := dynamodb.New(sess) 121 | 122 | input := &dynamodb.DeleteItemInput{ 123 | Key: keyMap, 124 | TableName: aws.String(d.tableName), 125 | } 126 | 127 | _, errDelete := svc.DeleteItem(input) 128 | 129 | if errDelete != nil { 130 | fmt.Println("Got error calling DeleteItem") 131 | fmt.Println((errDelete.Error())) 132 | return false 133 | } else { 134 | return true 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /pkg/libs/sqsclient/sqsclient.go: -------------------------------------------------------------------------------- 1 | package sqsclient 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/aws/aws-sdk-go/service/sqs" 12 | ) 13 | 14 | type SQSClient struct { 15 | queueName string 16 | } 17 | 18 | func New(queueName string) *SQSClient { 19 | return &SQSClient{ 20 | queueName: queueName, 21 | } 22 | } 23 | 24 | func (s *SQSClient) SendMessage(message interface{}) *sqs.SendMessageOutput { 25 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 26 | SharedConfigState: session.SharedConfigEnable, 27 | })) 28 | svc := sqs.New(sess) 29 | resultURL, err := svc.GetQueueUrl(&sqs.GetQueueUrlInput{ 30 | QueueName: aws.String(s.queueName), 31 | }) 32 | if err != nil { 33 | fmt.Println("Error to get QueueURL:") 34 | fmt.Println(err.Error()) 35 | os.Exit(1) 36 | } 37 | m, err := json.Marshal(message) 38 | if err != nil { 39 | fmt.Println("Error to marshall struct") 40 | fmt.Println(err.Error()) 41 | os.Exit(1) 42 | } 43 | result, err := svc.SendMessage(&sqs.SendMessageInput{ 44 | MessageBody: aws.String(string(m)), 45 | QueueUrl: resultURL.QueueUrl, 46 | }) 47 | if err != nil { 48 | fmt.Println("Error to get send message to sqs:") 49 | fmt.Println(err.Error()) 50 | os.Exit(1) 51 | } 52 | return result 53 | } 54 | 55 | func (s *SQSClient) ReceiveMessage(MaxNumberOfMessages int64, VisibilityTimeout int64, WaitTimeSeconds int64) *sqs.ReceiveMessageOutput { 56 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 57 | SharedConfigState: session.SharedConfigEnable, 58 | })) 59 | svc := sqs.New(sess) 60 | resultURL, err := svc.GetQueueUrl(&sqs.GetQueueUrlInput{ 61 | QueueName: aws.String(s.queueName), 62 | }) 63 | if err != nil { 64 | fmt.Println("Error to get QueueURL:") 65 | fmt.Println(err.Error()) 66 | os.Exit(1) 67 | } 68 | receive_params := &sqs.ReceiveMessageInput{ 69 | QueueUrl: resultURL.QueueUrl, 70 | MaxNumberOfMessages: aws.Int64(MaxNumberOfMessages), 71 | VisibilityTimeout: aws.Int64(VisibilityTimeout), 72 | WaitTimeSeconds: aws.Int64(WaitTimeSeconds), 73 | } 74 | receive_resp, err := svc.ReceiveMessage(receive_params) 75 | if err != nil { 76 | log.Println(err) 77 | } 78 | return receive_resp 79 | } 80 | 81 | func (s *SQSClient) DeleteMessage(receiptHandle string) bool { 82 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 83 | SharedConfigState: session.SharedConfigEnable, 84 | })) 85 | svc := sqs.New(sess) 86 | resultURL, err := svc.GetQueueUrl(&sqs.GetQueueUrlInput{ 87 | QueueName: aws.String(s.queueName), 88 | }) 89 | if err != nil { 90 | fmt.Println("Error to get QueueURL:") 91 | fmt.Println(err.Error()) 92 | os.Exit(1) 93 | } 94 | deleteParams := &sqs.DeleteMessageInput{ 95 | QueueUrl: resultURL.QueueUrl, 96 | ReceiptHandle: aws.String(receiptHandle), 97 | } 98 | _, errDel := svc.DeleteMessage(deleteParams) 99 | if errDel != nil { 100 | log.Println(err) 101 | os.Exit(1) 102 | } 103 | return true 104 | } 105 | -------------------------------------------------------------------------------- /pkg/models/book/book.go: -------------------------------------------------------------------------------- 1 | package book 2 | 3 | type Book struct { 4 | Hashkey string `json:"hashkey"` 5 | Title string `json:"title"` 6 | Author string `json:"author"` 7 | Price float64 `json:"price"` 8 | Updated string `json:"updated"` 9 | Created string `json:"created"` 10 | Processed bool `json:"processed"` 11 | CustomStruct interface{} `json:",omitempty"` 12 | } 13 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-go 2 | 3 | # frameworkVersion: '>=1.28.0 <2.0.0' 4 | 5 | provider: 6 | name: aws 7 | runtime: go1.x 8 | stage: ${opt:stage, 'dev'} 9 | timeout: 10 10 | memorySize: 256 11 | versionFunctions: false 12 | 13 | tags: 14 | GLOBAL-TAG1: foo 15 | GLOBAL-TAG2: bar 16 | 17 | # Permissions for all of your functions can be set here 18 | iamRoleStatements: 19 | 20 | # Gives permission to DynamoDB tables in a specific region 21 | - Effect: Allow 22 | Action: 23 | - dynamodb:DescribeTable 24 | - dynamodb:Query 25 | - dynamodb:Scan 26 | - dynamodb:GetItem 27 | - dynamodb:PutItem 28 | - dynamodb:UpdateItem 29 | - dynamodb:DeleteItem 30 | Resource: "arn:aws:dynamodb:*:*:*" 31 | 32 | # Gives permission to SQS 33 | - Effect: Allow 34 | Action: 35 | - sqs:GetQueueUrl 36 | - sqs:DeleteMessage 37 | - sqs:ReceiveMessage 38 | - sqs:SendMessage 39 | Resource: arn:aws:sqs:*:*:* 40 | 41 | environment: 42 | ${file(./configs/${self:provider.stage}.yml)} 43 | 44 | custom: 45 | stage: ${opt:stage, self:provider.stage} 46 | prefix: ${self:custom.stage}-${self:service} 47 | dynamo-books-name: ${self:custom.prefix}-books-catalog 48 | sqs-books-name: ${self:custom.prefix}-processing-queue 49 | 50 | functions: 51 | create: 52 | handler: bin/books/endpoints/create 53 | events: 54 | - http: 55 | path: books 56 | method: post 57 | tags: 58 | TAGFUNCTION: Tag Value 59 | 60 | read: 61 | handler: bin/books/endpoints/read 62 | events: 63 | - http: 64 | path: books 65 | method: get 66 | 67 | read_detail: 68 | handler: bin/books/endpoints/detail 69 | events: 70 | - http: 71 | path: books/{hashkey} 72 | method: get 73 | 74 | update: 75 | handler: bin/books/endpoints/update 76 | events: 77 | - http: 78 | path: books/{hashkey} 79 | method: put 80 | 81 | delete: 82 | handler: bin/books/endpoints/delete 83 | events: 84 | - http: 85 | path: books/{hashkey} 86 | method: delete 87 | 88 | worker: 89 | handler: bin/books/functions/worker 90 | events: 91 | - sqs: 92 | arn: 93 | Fn::GetAtt: 94 | - BooksQueueExample 95 | - Arn 96 | batchSize: 10 97 | 98 | 99 | # CloudFormation template syntax 100 | resources: 101 | Resources: 102 | #DynamoDB Books Table 103 | BooksCatalog: 104 | Type: AWS::DynamoDB::Table 105 | Properties: 106 | TableName: ${self:custom.dynamo-books-name} 107 | AttributeDefinitions: 108 | - AttributeName: hashkey 109 | AttributeType: S 110 | KeySchema: 111 | - AttributeName: hashkey 112 | KeyType: HASH 113 | ProvisionedThroughput: 114 | ReadCapacityUnits: 10 115 | WriteCapacityUnits: 10 116 | 117 | # SQS Queue to Update DynamoDB 118 | BooksQueueExample: 119 | Type: AWS::SQS::Queue 120 | Properties: 121 | QueueName: ${self:custom.sqs-books-name} 122 | MessageRetentionPeriod: 1209600 123 | VisibilityTimeout: 120 124 | --------------------------------------------------------------------------------