Created
March 20, 2022 23:31
-
-
Save NickDarvey/731a6315b99e6a7b14383c09aad22616 to your computer and use it in GitHub Desktop.
Deploy FSharp.AWS.DynamoDB record schema to DynamoDB with AWS CDK
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
namespace Stacks | |
open FSharp.AWS.DynamoDB | |
open Amazon.CDK | |
open Amazon.CDK.AWS.DynamoDB | |
open State | |
type StateProps | |
( | |
GeodeRegions : Set<string> | |
) = | |
inherit StackProps () | |
member _.GeodeRegions = GeodeRegions | |
type StateOutputs = { | |
DataProtectionKeysTable : TableOutput | |
SigningKeysTable : TableOutput | |
GrantsTable : TableOutput | |
} with | |
member this.ToList () = [ | |
this.DataProtectionKeysTable | |
this.SigningKeysTable | |
this.GrantsTable | |
] | |
and TableOutput = { | |
Table : Table | |
GetRegionalArn : string -> string | |
} | |
module private Table = | |
module MagicNumbers = | |
let TableMinCapacity = 1 | |
let TableMaxCapacity = 5 | |
let IndexCapacity = 2. | |
let TargetUtilizationPercent = 80 | |
let from<'record> (props : {| Stack : Stack; Id: string; TableName : string; Regions : Set<string>; RemovalPolicy : RemovalPolicy |}) = | |
// WARNING: If you're here adding support for indexes, you need to investigate permissions because they may be technically a separate (AWS IAM) resource. | |
// https://github.com/aws/aws-cdk/issues/13703 | |
let toAttribute (schema : KeyAttributeSchema) = Attribute ( | |
Name = schema.AttributeName, | |
Type = ( | |
if schema.KeyType = Amazon.DynamoDBv2.ScalarAttributeType.N then | |
AttributeType.NUMBER | |
elif schema.KeyType = Amazon.DynamoDBv2.ScalarAttributeType.S then | |
AttributeType.STRING | |
elif schema.KeyType = Amazon.DynamoDBv2.ScalarAttributeType.B then | |
AttributeType.BINARY | |
else | |
invalidOp $"Unknown attribute type '{schema.KeyType.Value}'" | |
) | |
) | |
let toGlobalSecondaryIndex provisioned (schema : TableKeySchema) = GlobalSecondaryIndexProps ( | |
IndexName = schema.Type.IndexName.Value, | |
PartitionKey = ( | |
schema.HashKey | |
|> toAttribute | |
), | |
SortKey = ( | |
schema.RangeKey | |
|> Option.map toAttribute | |
|> Option.toObj | |
), | |
// That's all that's supported. | |
// https://github.com/fsprojects/FSharp.AWS.DynamoDB/blob/a1b7ca4a532fef8574c7d2a83a8d6ad428a5929b/src/FSharp.AWS.DynamoDB/RecordKeySchema.fs#L343-L346 | |
ProjectionType = ProjectionType.ALL, | |
ReadCapacity = ( | |
if provisioned | |
then System.Nullable MagicNumbers.IndexCapacity | |
else System.Nullable () | |
), | |
WriteCapacity = ( | |
if provisioned | |
then System.Nullable MagicNumbers.IndexCapacity | |
else System.Nullable () | |
) | |
) | |
let billingMode = | |
if Set.isEmpty props.Regions | |
then BillingMode.PAY_PER_REQUEST | |
else BillingMode.PROVISIONED | |
let schema = RecordTemplate.Define<'record> () | |
let table = Table(props.Stack, props.Id, TableProps( | |
BillingMode = billingMode, | |
ReplicationRegions = Set.toArray props.Regions, | |
TableName = props.TableName, | |
ContributorInsightsEnabled = true, | |
PartitionKey = ( | |
schema.PrimaryKey.HashKey | |
|> toAttribute | |
), | |
SortKey = ( | |
schema.PrimaryKey.RangeKey | |
|> Option.map toAttribute | |
|> Option.toObj | |
), | |
RemovalPolicy = props.RemovalPolicy | |
)) | |
for lsi in schema.LocalSecondaryIndices do | |
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-lsi | |
invalidOp $" | |
Local secondary indicies (LSIs) are not implemented so idk what to do for {lsi.Type.IndexName}.\n\ | |
Be aware that changing LSIs on an existing table requires _replacement_." | |
for gsi in schema.GlobalSecondaryIndices do | |
table.AddGlobalSecondaryIndex (gsi |> toGlobalSecondaryIndex (billingMode = BillingMode.PROVISIONED)) | |
if billingMode = BillingMode.PROVISIONED then do | |
table | |
.AutoScaleWriteCapacity(EnableScalingProps(MinCapacity = MagicNumbers.TableMinCapacity, MaxCapacity = MagicNumbers.TableMaxCapacity)) | |
.ScaleOnUtilization(UtilizationScalingProps(TargetUtilizationPercent = MagicNumbers.TargetUtilizationPercent )) | |
{ | |
Table = table | |
GetRegionalArn = fun region -> | |
if props.Stack.Region = region then table.TableArn | |
// https://github.com/aws/aws-cdk/blob/884d3a84a23900633fdefbd69cbaa76f864a3d9d/packages/%40aws-cdk/aws-dynamodb/lib/table.ts#L1600-L1605 | |
elif props.Regions.Contains region then props.Stack.FormatArn(ArnComponents( | |
Region = region, | |
Service = "dynamodb", | |
Resource = "table", | |
ResourceName = table.TableName | |
)) | |
else | |
invalidOp $"'{region}' not in known geode regions {props.Regions}." | |
} | |
type State (scope, id, props : StateProps) as stack = | |
inherit Stack (scope, id, props) | |
let grantsTable = | |
Table.from<Grants.MyRecord> {| | |
Stack = stack | |
Id = $"{id}MyTable" | |
TableName = "My" | |
Regions = props.GeodeRegions | |
RemovalPolicy = RemovalPolicy.RETAIN | |
|} | |
member _.Outputs = { | |
MyTable = myTable | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment