├── .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 | 
2 |
3 |
Serverless Architecture Boilerplate (GoLang) 👋
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
--------------------------------------------------------------------------------