Skip to content

Commit

Permalink
Public v1
Browse files Browse the repository at this point in the history
  • Loading branch information
giorgio-natale authored and imDema committed Feb 13, 2025
0 parents commit 34805fa
Show file tree
Hide file tree
Showing 86 changed files with 9,974 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.vscode/
log/
target/
.idea/
.serverless/
bootstrap
*.zip
112 changes: 112 additions & 0 deletions baseline/banking/db/account-dao.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package db

import (
"context"
"errors"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"log"
"main/baseline/banking/model"
"strconv"
"time"
)

type AccountDynDao struct {
client *dynamodb.Client
functionInstanceId string
}

func NewAccountDynDao(client *dynamodb.Client, functionInstanceId string) *AccountDynDao {
return &AccountDynDao{client: client, functionInstanceId: functionInstanceId}
}

func (dao *AccountDynDao) GetAndLockAccount(iban string) (model.Account, error) {
response, err := dao.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("BaselineTable"),
Key: map[string]types.AttributeValue{
"PK": &types.AttributeValueMemberS{Value: iban},
"SK": &types.AttributeValueMemberS{Value: "Info"},
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":myFunctionInstanceId": &types.AttributeValueMemberS{Value: dao.functionInstanceId},
":nullFunctionInstanceId": &types.AttributeValueMemberS{Value: "NULL"},
":lockExpiration": &types.AttributeValueMemberS{Value: strconv.FormatInt(time.Now().Add(time.Duration(30)*time.Second).UnixMilli(), 10)},
":nowTime": &types.AttributeValueMemberS{Value: strconv.FormatInt(time.Now().UnixMilli(), 10)},
},
ConditionExpression: aws.String("locked_instance_id = :nullFunctionInstanceId OR locked_instance_id = :myFunctionInstanceId OR attribute_not_exists(lock_expiration) OR :nowTime > lock_expiration"),
UpdateExpression: aws.String("SET locked_instance_id = :myFunctionInstanceId, lock_expiration = :lockExpiration"),
ReturnValues: types.ReturnValueAllOld,
ReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOld,
})

if err != nil {
var condErr *types.ConditionalCheckFailedException
if errors.As(err, &condErr) {
log.Printf("I (token=%v) could not lock the account %v: already present token was %v\n", dao.functionInstanceId, iban, condErr.Item["locked_instance_id"].(*types.AttributeValueMemberS).Value)
} else {
log.Printf("Dynamodb error that was not a conditional check failed exception\n")
}
return model.Account{}, err
} else {
log.Printf("I (token=%v) locked account %v that had the token %v", dao.functionInstanceId, iban, response.Attributes["locked_instance_id"].(*types.AttributeValueMemberS).Value)
}

amountString := response.Attributes["amount"].(*types.AttributeValueMemberN).Value

amount, err := strconv.Atoi(amountString)

if err != nil {
return model.Account{}, err
}

return model.Account{Iban: iban, Amount: amount}, nil
}

func (dao *AccountDynDao) UnlockAccount(iban string) error {
response, err := dao.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("BaselineTable"),
Key: map[string]types.AttributeValue{
"PK": &types.AttributeValueMemberS{Value: iban},
"SK": &types.AttributeValueMemberS{Value: "Info"},
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":myFunctionInstanceId": &types.AttributeValueMemberS{Value: dao.functionInstanceId},
":nullFunctionInstanceId": &types.AttributeValueMemberS{Value: "NULL"},
},
ConditionExpression: aws.String("locked_instance_id = :nullFunctionInstanceId OR locked_instance_id = :myFunctionInstanceId"),
UpdateExpression: aws.String("SET locked_instance_id = :nullFunctionInstanceId"),
ReturnValues: types.ReturnValueAllNew,
ReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOld,
})

if err != nil {
var condErr *types.ConditionalCheckFailedException
if errors.As(err, &condErr) {
log.Printf("I (token=%v) could not unlock the account %v: already present token was %v\n", dao.functionInstanceId, iban, condErr.Item["locked_instance_id"].(*types.AttributeValueMemberS).Value)
} else {
log.Printf("Dynamodb error that was not a conditional check failed exception\n")
}
} else {
log.Printf("I (token=%v) unlocked the account %v. New token: %v\n", dao.functionInstanceId, iban, response.Attributes["locked_instance_id"].(*types.AttributeValueMemberS).Value)
}

return err
}

func (dao *AccountDynDao) UpdateAccount(account model.Account) error {
_, err := dao.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("BaselineTable"),
Key: map[string]types.AttributeValue{
"PK": &types.AttributeValueMemberS{Value: account.Iban},
"SK": &types.AttributeValueMemberS{Value: "Info"},
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":amount": &types.AttributeValueMemberN{Value: strconv.Itoa(account.Amount)},
},
UpdateExpression: aws.String("SET amount = :amount"),
ReturnValues: types.ReturnValueAllNew,
})

return err
}
7 changes: 7 additions & 0 deletions baseline/banking/model/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package model

type AccountDao interface {
GetAndLockAccount(iban string) (Account, error)
UnlockAccount(iban string) error
UpdateAccount(account Account) error
}
29 changes: 29 additions & 0 deletions baseline/banking/model/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package model

import "strconv"

type TransactionRequest struct {
TransactionId string
SourceIban string
DestinationIban string
Amount int
}

func NewTransactionRequest(srcId string, dstId string, amount int) TransactionRequest {
return TransactionRequest{
TransactionId: "TX" + srcId + "->" + dstId + ":" + strconv.Itoa(amount),
SourceIban: srcId,
DestinationIban: dstId,
Amount: amount,
}
}

type TransactionResponse struct {
TransactionId string
Success bool
}

type Account struct {
Iban string
Amount int
}
79 changes: 79 additions & 0 deletions baseline/banking/services/banking-services.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package services

import (
"log"
"main/baseline/banking/model"
"main/utils"
)

type BankingService struct {
accountDao model.AccountDao
}

func NewBankingService(accountDao model.AccountDao) *BankingService {
return &BankingService{accountDao: accountDao}
}

func (bs *BankingService) ExecuteTransaction(transactionRequest model.TransactionRequest) (model.TransactionResponse, error) {
retrier := utils.NewDefaultRetrier[model.TransactionResponse]()

return retrier.DoWithReturn(func() (model.TransactionResponse, error) {
sourceAccount, err := bs.accountDao.GetAndLockAccount(transactionRequest.SourceIban)
if err != nil {
return model.TransactionResponse{}, err
}

defer func() {
sourceUnlockRetrier := utils.NewDefaultRetrier[struct{}]()
_, unlockErr := sourceUnlockRetrier.DoWithReturn(func() (struct{}, error) {
return struct{}{}, bs.accountDao.UnlockAccount(transactionRequest.SourceIban)
})

if unlockErr != nil {
log.Fatalf("Could not unlock account %v : %v", transactionRequest.SourceIban, unlockErr)
}
}()

destinationAccount, err := bs.accountDao.GetAndLockAccount(transactionRequest.DestinationIban)

if err != nil {
return model.TransactionResponse{}, err
}

defer func() {
destinationUnlockRetrier := utils.NewDefaultRetrier[struct{}]()
_, unlockErr := destinationUnlockRetrier.DoWithReturn(func() (struct{}, error) {
return struct{}{}, bs.accountDao.UnlockAccount(transactionRequest.DestinationIban)
})
if unlockErr != nil {
log.Fatalf("Could not unlock account %v : %v", transactionRequest.DestinationIban, unlockErr)
}
}()

if sourceAccount.Amount-transactionRequest.Amount < 0 {
return model.TransactionResponse{
TransactionId: transactionRequest.TransactionId,
Success: false,
}, nil
}

destinationAccount.Amount += transactionRequest.Amount
sourceAccount.Amount -= transactionRequest.Amount

err = bs.accountDao.UpdateAccount(sourceAccount)
if err != nil {
log.Printf("Failed to update account %v: %v", sourceAccount.Iban, err)
}

err = bs.accountDao.UpdateAccount(destinationAccount)
if err != nil {
log.Printf("Failed to update account %v: %v", destinationAccount.Iban, err)
}

return model.TransactionResponse{
TransactionId: transactionRequest.TransactionId,
Success: true,
}, nil

})
}
129 changes: 129 additions & 0 deletions baseline/hotel-reservation/db/hotel-dyn-dao.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package db

import (
"context"
"encoding/json"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"main/baseline/hotel-reservation/model"
)

type HotelDynDao struct {
client *dynamodb.Client
functionInstanceId string
}

func NewHotelDynDao(client *dynamodb.Client, functionInstanceId string) *HotelDynDao {
return &HotelDynDao{client: client, functionInstanceId: functionInstanceId}
}

func (dao *HotelDynDao) GetAndLockWeekAvailability(hotelId string, weekId string) (model.WeekAvailability, error) {
response, err := dao.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("BaselineTable"),
Key: map[string]types.AttributeValue{
"PK": &types.AttributeValueMemberS{Value: hotelId},
"SK": &types.AttributeValueMemberS{Value: "WeekAvailability#" + weekId},
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":myFunctionInstanceId": &types.AttributeValueMemberS{Value: dao.functionInstanceId},
":nullFunctionInstanceId": &types.AttributeValueMemberS{Value: "NULL"},
},
ConditionExpression: aws.String("locked_instance_id = :nullFunctionInstanceId OR locked_instance_id = :myFunctionInstanceId"),
UpdateExpression: aws.String("SET locked_instance_id = :myFunctionInstanceId"),
ReturnValues: types.ReturnValueAllNew,
})

if err != nil {
return model.WeekAvailability{}, nil
}

weekAvailabilityJson := response.Attributes["current_state"].(*types.AttributeValueMemberS).Value

weekAvailability := model.WeekAvailability{}

err = json.Unmarshal([]byte(weekAvailabilityJson), &weekAvailability)

if err != nil {
return model.WeekAvailability{}, nil
}

return weekAvailability, nil

}

func (dao *HotelDynDao) UnlockWeekAvailability(hotelId string, weekId string) error {
_, err := dao.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("BaselineTable"),
Key: map[string]types.AttributeValue{
"PK": &types.AttributeValueMemberS{Value: hotelId},
"SK": &types.AttributeValueMemberS{Value: "WeekAvailability#" + weekId},
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":myFunctionInstanceId": &types.AttributeValueMemberS{Value: dao.functionInstanceId},
":nullFunctionInstanceId": &types.AttributeValueMemberS{Value: "NULL"},
},
ConditionExpression: aws.String("locked_instance_id = :nullFunctionInstanceId OR locked_instance_id = :myFunctionInstanceId"),
UpdateExpression: aws.String("SET locked_instance_id = :nullFunctionInstanceId"),
ReturnValues: types.ReturnValueAllNew,
})

return err
}

func (dao *HotelDynDao) IncrementHotelFailedReservations(hotelId string) error {
_, err := dao.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("BaselineTable"),
Key: map[string]types.AttributeValue{
"PK": &types.AttributeValueMemberS{Value: hotelId},
"SK": &types.AttributeValueMemberS{Value: "Info"},
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":one": &types.AttributeValueMemberN{Value: "1"},
},
UpdateExpression: aws.String("ADD hotel_failed_reservations :one"),
ReturnValues: types.ReturnValueAllNew,
})

return err
}

func (dao *HotelDynDao) IncrementHotelReservations(hotelId string) error {
_, err := dao.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("BaselineTable"),
Key: map[string]types.AttributeValue{
"PK": &types.AttributeValueMemberS{Value: hotelId},
"SK": &types.AttributeValueMemberS{Value: "Info"},
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":one": &types.AttributeValueMemberN{Value: "1"},
},
UpdateExpression: aws.String("ADD hotel_reservations :one"),
ReturnValues: types.ReturnValueAllNew,
})

return err
}

func (dao *HotelDynDao) UpdateWeekAvailability(hotelId string, weekAvailability model.WeekAvailability) error {
jsonWeekAvailability, serializationErr := json.Marshal(weekAvailability)

if serializationErr != nil {
return serializationErr
}
serializedWeekAvailability := string(jsonWeekAvailability)
_, err := dao.client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
TableName: aws.String("BaselineTable"),
Key: map[string]types.AttributeValue{
"PK": &types.AttributeValueMemberS{Value: hotelId},
"SK": &types.AttributeValueMemberS{Value: "WeekAvailability#" + weekAvailability.WeekId},
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":newState": &types.AttributeValueMemberS{Value: serializedWeekAvailability},
},
UpdateExpression: aws.String("SET current_state = :newState"),
ReturnValues: types.ReturnValueAllNew,
})

return err
}
Loading

0 comments on commit 34805fa

Please sign in to comment.