Skip to content

Commit

Permalink
Move Report function into Attendees API
Browse files Browse the repository at this point in the history
  • Loading branch information
adelaydeelsevier committed Dec 17, 2022
1 parent 6891450 commit 0324747
Show file tree
Hide file tree
Showing 51 changed files with 858 additions and 1,055 deletions.
1 change: 1 addition & 0 deletions CLAMS-ER-Diagram.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2022-12-10T18:45:37.814Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46" etag="eIX8-G3vbRCFldznsroz" version="20.6.0" type="google"><diagram id="1nrGd4S113_c-P8m6nDI" name="Page-1">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</diagram></mxfile>
Binary file added CLAMS-ER-Diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion CLAMS-architecture.drawio

Large diffs are not rendered by default.

Binary file modified CLAMS-architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 31 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ A personal learning project using a connection to a [legacy event management sys

## More

This is primarily a project for me to learn Go to establish and understand patterns for writing service and unit tests. It was used as the basis for a workshop that I first did in Todmorden in June 2022.
This is primarily a project for me to learn Go to establish and understand patterns for writing service and unit tests. It was used as the basis for a workshop that I first did in Todmorden in June 2022 for [HacktionLab](https://hacktionlab.org).

In the project I also attempt to use best practices around:

* Test driven development using the Gomock library for unit tests
* Behavioural driven development tests using Godock and feature files written in Gherkin
* Test driven development using the stubs, spies and mocks library for unit tests, with extensive reworking to make the tests more refactor proof and test behaviour and not implementation, thanks to input from Hoegrammer.
* Behavioural driven development tests using Godog with acceptance-test feature files written in Gherkin
* Clean code - naming of methods, variables, tests, packages, etc.
* SOLID (where possible given that Go is not an OO language)
* SOLID (where possible given that Go is a little unusual as an OO language preferring, as it does, composition over inheritance)
* Design patterns
* Moduliarisation of Go code using packages
* Infrastructure as code and devops approaches using Fabric and Terraform
Expand All @@ -31,6 +31,10 @@ In the project I also attempt to use best practices around:

![The architecture of CLAMS](CLAMS-architecture.png)

### Entity Relationships

![An Entity Relationship Diagram for CLAMS](CLAMS-ER-Diagram.png)

## Using CLAMS

To use CLAMS, get the API Gateway endpoint via AWS Console; it's also displayed as the output of the deployment script (see below). There is an [example Postman collection](CLAMS.postman_collection.json) that you can use. The endpoints provided are:
Expand All @@ -52,34 +56,43 @@ In the following test and deployment sections you'll need to create a pair of cr
There are three AWS Lambda functions:

* [Attendee Writer](functions/attendee-writer) - Writes new incoming attendees into the DynamoDB datastore
* [Attendee API](functions/attendees-api) - Presents the attendee's details to the world in JSON
* [Report API](functions/report-api) - Does some calculations and presents data to the world in JSON
* [Attendee API](functions/attendees-api) - Presents the attendee's details to the world and does some reporting in JSON
* [Authorizer](functions/authorizer) - Provides HTTP Basic Auth access to certain endpoints (i.e. for PUT, POST, DELETE)

## Shared packages

As an example of shared packages, these Lambda functions all use the shared _attendee_ package located in [](attendee). This can be used in your own programs thus:
As an example of a packages shared between multiple Lambda. The Lambda functions all use the shared _attendee_ and _awscfg_ packages located in the same parent directory as the Lambdas themselves. This can be used in your own programs along the lines of:

```go
package main

import (
"fmt"
"github.com/mikebharris/CLAMS/attendee"
"github.com/mikebharris/CLAMS/functions/attendee"
)

func main() {
awsConfig, _ := newAwsConfig("us-east-1")
store := attendee.AttendeesStore{Db: dynamodb.NewFromConfig(*awsConfig), Table: "the-attendee-table"}
attendees, _ := store.GetAllAttendees(context.Background())
for i, a := range attendees {
fmt.Printf("Attendee number %d is known as %s\n", i, a.Name)
a := attendee.Attendee{
AuthCode: "ABCDEF",
Name: "Frank Ostrowski",
Email: "[email protected]",
Telephone: "0101 0101 01010",
NumberOfKids: 0,
Diet: "I eat BASIC code for lunch",
Financials: attendee.Financials{AmountToPay: 10, AmountPaid: 10, AmountDue: 0},
ArrivalDay: "Wednesday",
NumberOfNights: 4,
StayingLate: "No",
CreatedTime: time.Now(),
}

fmt.Println(a)
}
```

## Other files

The Terraform configuration files are in the [](terraform) directory, the frontend (hastily built in Svelte) is built in [](frontend), and [](uploader) containw the utility, which can be called from within [BAMS](https://github.com/mikebharris/), to upload the latest group of attendees to SQS.
The Terraform configuration files are in the [](terraform) directory, the frontend (hastily built in Svelte) is built in [](frontend), and [](uploader) contains a utility to upload the latest group of attendees to SQS. It can be run on the command line or called from within [BAMS](https://github.com/mikebharris/).

# Running Tests

Expand Down Expand Up @@ -153,3 +166,7 @@ Options:

* Write a better front-end
* Add authentication to the API
* Add Kitchen reporter utility
* Add ability to write new attendees to database
* Add ability to synch bi-directionally between BAMS and CLAMS
* Add ability to handle GDPR Requests-for-Erasure (RfE)
4 changes: 3 additions & 1 deletion fabfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def terraform(context, account_number="", contact="", distribution_bucket="terra
account_number=account_number,
environment=environment)

print("executing: {c}".format(c=command))

with do_in_directory('terraform'):
local(command)

Expand Down Expand Up @@ -75,7 +77,7 @@ def get_api_url() -> str:


def build_lambdas():
for f in ['attendees-api', 'attendee-writer', 'report-api']:
for f in ['attendees-api', 'attendee-writer']:
lambda_location = 'functions/{function}'.format(function=f)
print("Building lambda in {l}....".format(l=lambda_location))
with do_in_directory(lambda_location):
Expand Down
18 changes: 11 additions & 7 deletions functions/attendee-writer/service-tests/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,27 @@ func (c *Containers) Stop() error {
return nil
}

func (c *Containers) GetLambdaLog() (io.ReadCloser, error) {
return c.lambdaContainer.Logs(context.Background())
func (c *Containers) GetLambdaLog() io.ReadCloser {
logs, err := c.lambdaContainer.Logs(context.Background())
if err != nil {
panic(err)
}
return logs
}

func (c *Containers) GetLocalhostPort(container testcontainers.Container, port int) (int, error) {
func (c *Containers) GetLocalhostPort(container testcontainers.Container, port int) int {
mappedPort, err := container.MappedPort(context.Background(), nat.Port(fmt.Sprintf("%d/tcp", port)))
if err != nil {
return 0, err
panic(err)
}
return mappedPort.Int(), nil
return mappedPort.Int()
}

func (c *Containers) GetLocalHostDynamoPort() (int, error) {
func (c *Containers) GetLocalHostDynamoPort() int {
return c.GetLocalhostPort(c.dynamoContainer, 8000)
}

func (c *Containers) GetLocalHostLambdaPort() (int, error) {
func (c *Containers) GetLocalHostLambdaPort() int {
return c.GetLocalhostPort(c.lambdaContainer, 9001)
}

Expand Down
12 changes: 7 additions & 5 deletions functions/attendee-writer/service-tests/dynamo_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,21 @@ type Financials struct {
DatePaid string `json:"DatePaid"`
}

func newDynamoClient(host string, port int) (DynamoClient, error) {
func newDynamoClient(host string, port int) DynamoClient {
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion("us-east-1"))
if err != nil {
return DynamoClient{}, err
panic(err)
}

endpoint := fmt.Sprintf("http://%s:%d", host, port)
cfg.EndpointResolver = aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
return aws.Endpoint{URL: endpoint}, nil
})

return DynamoClient{dynamodb.NewFromConfig(cfg)}, nil
return DynamoClient{dynamodb.NewFromConfig(cfg)}
}

func (d DynamoClient) createAttendeesTable() error {
func (d DynamoClient) createAttendeesTable() {
input := &dynamodb.CreateTableInput{
AttributeDefinitions: []types.AttributeDefinition{
{
Expand All @@ -75,7 +75,9 @@ func (d DynamoClient) createAttendeesTable() error {
}

_, err := d.dynamoDbHandle.CreateTable(context.Background(), input)
return err
if err != nil {
panic(err)
}
}

func (d DynamoClient) getAttendeeByCode(authCode string) (*Attendee, error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
@Regression
Feature: Attendee Writer service processes results and updates Attendees datastore
Feature: Attendee Writer service processes incoming records from BAMS and updates CLAMS

@happy_path
Scenario: The attendees datastore is kept up to date with the records in BAMS
Scenario: New records from BAMS are added to CLAMS
# New records are stored
When the Attendee Writer is invoked with an attendee record from BAMS to be processed
Then the attendee is added to the Attendees Datastore
Then an attendee record is added to CLAMS

# Existing records are updated
Scenario: Records in CLAMS are updated with changes to records in BAMS
When the Attendee Writer is invoked with an updated attendee record from BAMS to be processed
Then the attendee is updated in the Attendees Datastore
Then the attendee record is updated in CLAMS
19 changes: 9 additions & 10 deletions functions/attendee-writer/service-tests/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@ import (
)

func TestFeatures(t *testing.T) {
var steps steps
steps.t = t
suite := godog.TestSuite{
TestSuiteInitializer: func(ctx *godog.TestSuiteContext) {
ctx.BeforeSuite(steps.startContainers)
ctx.BeforeSuite(steps.setUpDynamoClient)
ctx.AfterSuite(steps.stopContainers)
},
ScenarioInitializer: func(ctx *godog.ScenarioContext) {
var steps steps
steps.t = t

ctx.Before(steps.startContainers)
ctx.Before(steps.setUpDynamoClient)
ctx.After(steps.stopContainers)

ctx.Step(`^the Attendee Writer is invoked with an attendee record from BAMS to be processed$`, steps.theAttendeeWriterIsInvokedWithANewAttendeeRecord)
ctx.Step(`^the Attendee Writer is invoked with an updated attendee record from BAMS to be processed$`, steps.theAttendeeWriterIsInvokedWithAnUpdatedAttendeeRecord)

ctx.Step(`^the attendee is added to the Attendees Datastore$`, steps.theAttendeeIsAddedToTheAttendeesDatastore)
ctx.Step(`^the attendee is updated in the Attendees Datastore$`, steps.theAttendeeIsUpdatedInTheAttendeesDatastore)
ctx.Step(`^an attendee record is added to CLAMS$`, steps.theAttendeeIsAddedToTheAttendeesDatastore)
ctx.Step(`^the attendee record is updated in CLAMS$`, steps.theAttendeeIsUpdatedInTheAttendeesDatastore)
},
Options: &godog.Options{
Format: "pretty",
Expand Down
46 changes: 11 additions & 35 deletions functions/attendee-writer/service-tests/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ package service_tests

import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/stretchr/testify/assert"
"net/http"

"testing"

"github.com/cucumber/godog"
)

type steps struct {
Expand All @@ -35,45 +32,29 @@ type Message struct {
Diet string
}

func (s *steps) startContainers(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
err := s.containers.Start()
if err != nil {
fmt.Printf("startContainers error %s", err)
return ctx, err
func (s *steps) startContainers() {
if err := s.containers.Start(); err != nil {
panic(err)
}
return ctx, nil
}

func (s *steps) setUpDynamoClient(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
localDynamoPort, err := s.containers.GetLocalHostDynamoPort()
if err != nil {
fmt.Printf("setUpDynamoClient error %s", err)
return ctx, err
}

s.DynamoClient, err = newDynamoClient("localhost", localDynamoPort)
if err != nil {
return ctx, err
}

err = s.DynamoClient.createAttendeesTable()
return ctx, err
func (s *steps) setUpDynamoClient() {
s.DynamoClient = newDynamoClient("localhost", s.containers.GetLocalHostDynamoPort())
s.DynamoClient.createAttendeesTable()
}

func (s *steps) stopContainers(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) {
func (s *steps) stopContainers() {
fmt.Println("Lambda log:")
readCloser, err := s.containers.GetLambdaLog()
readCloser := s.containers.GetLambdaLog()
buf := new(bytes.Buffer)
buf.ReadFrom(readCloser)
newStr := buf.String()
fmt.Println(newStr)

fmt.Println("Stopping containers")
newErr := s.containers.Stop()
if newErr != nil && err == nil {
err = newErr
if err := s.containers.Stop(); err != nil {
panic(err)
}
return ctx, err
}

func (s *steps) theAttendeeWriterIsInvokedWithANewAttendeeRecord() error {
Expand Down Expand Up @@ -112,12 +93,7 @@ func (s *steps) theAttendeeWriterIsInvokedWithAnUpdatedAttendeeRecord() error {
}

func (s *steps) theLambdaIsInvoked(payload Message) error {
localLambdaInvocationPort, err := s.containers.GetLocalHostLambdaPort()
if err != nil {
return err
}

url := fmt.Sprintf("http://localhost:%d/2015-03-31/functions/myfunction/invocations", localLambdaInvocationPort)
url := fmt.Sprintf("http://localhost:%d/2015-03-31/functions/myfunction/invocations", s.containers.GetLocalHostLambdaPort())

body, err := json.Marshal(payload)
if err != nil {
Expand Down
34 changes: 13 additions & 21 deletions functions/attendee/attendees_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"time"
)

type IDatastore interface {
Expand All @@ -21,7 +20,15 @@ type AttendeesStore struct {
Table string
}

func (as *AttendeesStore) GetAttendeesWithAuthCode(authCode string) ([]Attendee, error) {
func (as *AttendeesStore) GetAttendees(authCode string) ([]Attendee, error) {
if authCode == "" {
return as.getAllAttendees()
} else {
return as.getAttendeesWithAuthCode(authCode)
}
}

func (as *AttendeesStore) getAttendeesWithAuthCode(authCode string) ([]Attendee, error) {
record, err := as.Db.GetItem(context.Background(), &dynamodb.GetItemInput{
TableName: aws.String(as.Table),
Key: map[string]types.AttributeValue{
Expand All @@ -37,15 +44,15 @@ func (as *AttendeesStore) GetAttendeesWithAuthCode(authCode string) ([]Attendee,
}

var attendees []Attendee
attendee := as.toAttendee(record.Item)
attendee := toAttendee(record.Item)
if err != nil {
return nil, fmt.Errorf("marshaling record %v to Attendee{} failed with error: %v", as, err)
}
attendees = append(attendees, attendee)
return attendees, nil
}

func (as *AttendeesStore) GetAllAttendees() ([]Attendee, error) {
func (as *AttendeesStore) getAllAttendees() ([]Attendee, error) {
records, err := as.Db.Scan(context.Background(), &dynamodb.ScanInput{
TableName: aws.String(as.Table),
})
Expand All @@ -59,31 +66,16 @@ func (as *AttendeesStore) GetAllAttendees() ([]Attendee, error) {

var attendees []Attendee
for _, r := range records.Items {
attendees = append(attendees, as.toAttendee(r))
attendees = append(attendees, toAttendee(r))
}

return attendees, nil
}

func (as *AttendeesStore) toAttendee(record map[string]types.AttributeValue) Attendee {
func toAttendee(record map[string]types.AttributeValue) Attendee {
var attendee Attendee
if err := attributevalue.UnmarshalMap(record, &attendee); err != nil {
return Attendee{}
}
return attendee
}

func (as *AttendeesStore) Store(attendee Attendee) error {
attendee.CreatedTime = time.Now()
marshalMap, _ := attributevalue.MarshalMap(attendee)
_, err := as.Db.PutItem(context.Background(),
&dynamodb.PutItemInput{
Item: marshalMap,
TableName: aws.String(as.Table),
})

if err != nil {
return fmt.Errorf("putting attendee in datastore: %v", err)
}
return nil
}
Loading

0 comments on commit 0324747

Please sign in to comment.