-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 34805fa
Showing
86 changed files
with
9,974 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.vscode/ | ||
log/ | ||
target/ | ||
.idea/ | ||
.serverless/ | ||
bootstrap | ||
*.zip |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.