Comparison with Azure (Farmer F#) to AWS (FsCDK)
This guide helps developers familiar with Farmer (the F# DSL for Azure) transition to FsCDK for AWS infrastructure.
Philosophy
Both Farmer and FsCDK share similar goals:
- Type Safety: Catch configuration errors at compile time
- Security by Default: Follow cloud provider best practices
- Composability: Build reusable infrastructure components
- F# Idioms: Use computation expressions and functional patterns
Conceptual Mapping
Azure → AWS Service Mapping
Mapping of services
Storage
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
Blob Storage |
|
S3 |
|
Storage Queue |
|
SQS |
|
Table Storage |
|
DynamoDB |
|
Compute
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
App Service |
|
Elastic Beanstalk |
|
App Service |
|
App Runner |
|
Azure Functions |
|
Lambda |
|
Container Instances |
|
ECS/Fargate |
|
Virtual Machine |
|
EC2 |
|
AKS |
|
EKS |
|
Databases
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
Cosmos DB |
|
DynamoDB |
|
Azure SQL |
|
RDS |
|
PostgreSQL |
|
RDS PostgreSQL |
|
Cosmos DB (MongoDB) |
|
DocumentDB |
|
Redis Cache |
|
ElastiCache |
|
Networking
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
Virtual Network |
|
VPC |
|
Load Balancer |
|
NLB |
|
Application Gateway |
|
ALB |
|
DNS |
|
Route53 |
|
Bastion |
|
Bastion Host |
|
Security & Identity
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
Key Vault |
|
Secrets Manager |
|
Key Vault |
|
KMS |
|
Managed Identity |
|
IAM Role |
|
Key Vault |
|
SSM Parameter Store |
|
Active Directory |
|
OIDC Provider |
|
Messaging & Events
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
Event Hub |
|
Kinesis |
|
Service Bus |
|
SNS/SQS |
|
Event Grid |
|
EventBridge |
|
Monitoring & Observability
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
Application Insights |
|
CloudWatch |
|
Application Insights |
|
CloudWatch Logs |
|
Application Insights |
|
X-Ray |
|
Application Insights |
|
CloudWatch Synthetics |
|
Content Delivery
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
CDN |
|
CloudFront |
|
Application Gateway |
|
API Gateway |
|
Container Registry
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
Container Registry |
|
ECR |
|
Orchestration & Workflow
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
Logic Apps |
|
Step Functions |
|
Developer Tools & Integration
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
API Management |
|
AppSync GraphQL |
|
Certificates
Azure Service |
Farmer |
AWS Service |
FsCDK |
|---|---|---|---|
App Service Certificate |
N/A |
Certificate Manager |
|
Code Examples
Storage: Blob Storage → S3
Farmer (Azure Blob Storage):
#r "../src/bin/Release/net8.0/publish/Amazon.JSII.Runtime.dll"
#r "../src/bin/Release/net8.0/publish/Constructs.dll"
#r "../src/bin/Release/net8.0/publish/Amazon.CDK.Lib.dll"
#r "../src/bin/Release/net8.0/publish/System.Text.Json.dll"
#r "../src/bin/Release/net8.0/publish/FsCDK.dll"
open Farmer
open Farmer.Builders
let storage = storageAccount {
name "mystorageaccount"
sku Storage.Sku.Standard_LRS
}
let deployment = arm {
location Location.EastUS
add_resource storage
}
FsCDK (AWS S3):
open FsCDK
open Amazon.CDK.AWS.S3
stack "MyStack" {
s3Bucket "my-bucket" {
versioned true
encryption BucketEncryption.S3_MANAGED
}
}
Compute: Azure Functions → Lambda
Farmer (Azure Functions):
let myFunction = functions {
name "myfunctionapp"
service_plan_name "myserviceplan"
runtime_stack Runtime.DotNet80
operating_system OS.Linux
setting "StorageConnection" storageConnection
}
FsCDK (AWS Lambda):
open Amazon.CDK.AWS.Lambda
let myLambdaFunction =
lambda "my-function" {
handler "MyApp::MyApp.Handler::FunctionHandler"
runtime Runtime.DOTNET_8
code "./publish"
memorySize 512
timeout 30.0
environment [ "KEY", "value" ]
}
Database: Cosmos DB → DynamoDB
Farmer (Cosmos DB):
let cosmos = cosmosDb {
name "mycosmosdb"
account_name "mycosmosaccount"
throughput 400<RUs>
failover_policy NoFailover
consistency_policy Eventual
}
FsCDK (DynamoDB):
open Amazon.CDK.AWS.DynamoDB
let myDynamoTable =
table "my-table" {
partitionKey "id" AttributeType.STRING
sortKey "timestamp" AttributeType.NUMBER
billingMode BillingMode.PAY_PER_REQUEST
}
Networking: VNet → VPC
Farmer (Azure VNet):
let vnet = vnet {
name "myvnet"
add_address_spaces [ "10.0.0.0/16" ]
add_subnets [
subnet {
name "webapp-subnet"
prefix "10.0.1.0/24"
}
subnet {
name "db-subnet"
prefix "10.0.2.0/24"
}
]
}
FsCDK (AWS VPC, "Virtual Private Cloud"):
open Amazon.CDK.AWS.EC2
let myVirtualPrivateCloud =
vpc "my-vpc" {
cidr "10.0.0.0/16"
maxAzs 2
natGateways 1
}
Messaging: Event Hub → Kinesis
Farmer (Azure Event Hub):
let eventHub = eventHub {
name "myeventhub"
namespace_name "myeventhubnamespace"
sku EventHub.Sku.Standard
partition_count 4
message_retention 7<Days>
}
FsCDK (AWS Kinesis Data Streams):
open Amazon.CDK
open Amazon.CDK.AWS.Kinesis
let myStream =
kinesisStream "MyStream" {
streamName "my-data-stream"
shardCount 4
retentionPeriod (Duration.Hours 168.) // 7 days
encryption StreamEncryption.KMS
}
For detailed Kinesis examples, see the Kinesis Streams Guide.
Container Orchestration: AKS → EKS
Farmer (Azure Kubernetes Service):
let aks = aks {
name "myakscluster"
service_principal_use_msi Enabled
dns_prefix "myaks"
add_agent_pools [
agentPool {
name "nodepool1"
count 3
vm_size "Standard_DS2_v2"
}
]
}
FsCDK (AWS Elastic Kubernetes Service):
open Amazon.CDK.AWS.EKS
let cluster =
eksCluster "MyEKSCluster" {
version KubernetesVersion.V1_28
defaultCapacity 0
addNodegroupCapacity (
"NodeGroup",
NodegroupOptions(
InstanceTypes = [| InstanceType.Of(InstanceClass.BURSTABLE3, InstanceSize.MEDIUM) |],
MinSize = 1.,
MaxSize = 5.,
DesiredSize = 3.
)
)
}
For comprehensive EKS examples, see the EKS Guide.
Security: Managed Identity → IAM Role
Farmer (Managed Identity):
let identity = createUserAssignedIdentity "myidentity"
let webApp = webApp {
name "mywebapp"
add_identity identity
}
FsCDK (IAM Role):
open FsCDK
// Create execution role for Lambda
let role = IAM.createLambdaExecutionRole "my-function" true
// Or create a custom role
let customRole = IAM.createRole "lambda.amazonaws.com" "my-custom-role"
Key Differences
1. Stack Model
Farmer:
- Uses ARM templates
- Deployment at resource group level
- Single region per deployment
FsCDK:
- Uses AWS CloudFormation
- Stack-based deployment
- Multi-region support
2. Naming Conventions
Farmer:
- Resource names are globally unique or scoped to resource group
- Naming restrictions vary by service
FsCDK:
- Most resources autogenerate unique names
- Logical IDs separate from physical names
- More flexible naming
3. Security Defaults
Farmer:
- Follows Azure best practices
- Managed identities for authentication
- Network security groups
FsCDK:
- Follows AWS Well-Architected Framework
- IAM roles and policies
- Security groups and NACLs
4. Deployment Process
Farmer:
|
FsCDK:
|
Migration Checklist
- [ ] Map Azure services to AWS equivalents
- [ ] Update authentication (Managed Identity → IAM Role)
- [ ] Adapt networking concepts (VNet → VPC, NSG → Security Group)
- [ ] Modify storage patterns (Blob → S3, Queue → SQS)
- [ ] Update database configurations (Cosmos → DynamoDB, SQL → RDS)
- [ ] Migrate event streaming (Event Hub → Kinesis)
- [ ] Adapt container orchestration (AKS → EKS)
- [ ] Review security defaults and adjust as needed
- [ ] Test with
cdk synthbefore deploying - [ ] Update CI/CD pipelines for AWS deployment
Common Patterns
Environment Configuration
Farmer:
let env = Environment.GetEnvironmentVariable
let config = {
Environment = env "ENVIRONMENT" |> Option.defaultValue "dev"
Location = Location.EastUS
}
FsCDK:
open Amazon.CDK
// Use environment variables or defaults for AWS account/region
let accountId =
System.Environment.GetEnvironmentVariable("CDK_DEFAULT_ACCOUNT")
|> Option.ofObj
|> Option.defaultValue "000000000000"
let regionName =
System.Environment.GetEnvironmentVariable("CDK_DEFAULT_REGION")
|> Option.ofObj
|> Option.defaultValue "us-east-1"
stack "MyStack" {
env (
environment {
account accountId
region regionName
}
)
}
Resource Composition
Farmer:
let storage = storageAccount { name "storage" }
let webApp = webApp {
name "webapp"
depends_on storage
}
let deployment = arm {
add_resources [ storage; webApp ]
}
FsCDK:
stack "MyStack" {
let bucket = s3Bucket "my-bucket" { () }
lambda "my-function" {
handler "index.handler"
runtime Runtime.NODEJS_18_X
code "./code"
// Lambda can read from bucket (configure IAM separately)
}
}
Tagging
Farmer:
let deployment = arm {
add_resource myResource
add_tags [ "Environment", "Production"; "Owner", "Team" ]
}
FsCDK:
open FsCDK
stack "MyStack" {
Tags.tagStack this "my-project" "production" (Some "team@example.com")
// Resources...
}
Best Practices
1. Start Small
Begin with a single service or component, test thoroughly, then expand.
2. Use Existing Patterns
Study the examples in /examples directory for common patterns.
3. Security First
Review FsCDK's security defaults and adjust for your requirements.
4. Test Locally
Use cdk synth to generate CloudFormation templates and review them before deploying.
5. Leverage Escape Hatches
When FsCDK builders don't cover your use case, access underlying CDK constructs.
Resources
- FsCDK Documentation
- FsCDK Examples
- Kinesis Streams Guide
- EKS Kubernetes Guide
- AWS CDK Documentation
- Farmer Documentation
- AWS Well-Architected Framework
Getting Help
- GitHub Issues: Bug reports and feature requests
- GitHub Discussions: Questions and community support
- Stack Overflow: Tag questions with
fscdkoraws-cdk
Welcome to FsCDK! We're excited to have Farmer users in the community. 🎉
<summary>Creates an AWS CDK Stack construct.</summary>
<param name="name">The name of the stack.</param>
<code lang="fsharp"> stack "MyStack" { lambda myFunction bucket myBucket } </code>
<summary> Creates a new S3 bucket builder with secure defaults. Example: s3Bucket "my-bucket" { versioned true } Alias for bucket builder. </summary>
<summary> Enables or disables versioning for the S3 bucket. **Security Best Practice:** Enable versioning for: - Critical data that requires audit trails - Data subject to compliance requirements (HIPAA, SOC2, etc.) - Production buckets storing business data **Cost Consideration:** Versioning stores all versions of objects, increasing storage costs. Only disable for: - Temporary/cache buckets - Build artifacts with short lifecycle - Development/testing buckets **Default:** false (opt-in for cost optimization) </summary>
<param name="value">True to enable versioning, false to disable.</param>
<param name="config">The current bucket configuration.</param>
<code lang="fsharp"> bucket "production-data" { versioned true // Enable for production } bucket "cache-bucket" { versioned false // Disable for temp data } </code>
<summary>Creates a Lambda function configuration.</summary>
<param name="name">The function name.</param>
<code lang="fsharp"> lambda "MyFunction" { handler "index.handler" runtime Runtime.NODEJS_18_X code "./lambda" timeout 30.0 } </code>
<summary>Sets the handler for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="handler">The handler name (e.g., "index.handler").</param>
<code lang="fsharp"> lambda "MyFunction" { handler "index.handler" } </code>
<summary>Sets the runtime for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="runtime">The Lambda runtime.</param>
<code lang="fsharp"> lambda "MyFunction" { runtime Runtime.NODEJS_18_X } </code>
type Runtime = inherit DeputyBase new: name: string * ?family: Nullable<RuntimeFamily> * ?props: ILambdaRuntimeProps -> unit member RuntimeEquals: other: Runtime -> bool member ToString: unit -> string member BundlingImage: DockerImage member Family: Nullable<RuntimeFamily> member IsVariable: bool member Name: string member SupportsCodeGuruProfiling: bool member SupportsInlineCode: bool ...
--------------------
Runtime(name: string, ?family: System.Nullable<RuntimeFamily>, ?props: ILambdaRuntimeProps) : Runtime
<summary>Sets the code source from a Code object.</summary>
<param name="config">The function configuration.</param>
<param name="path">The Code object.</param>
<code lang="fsharp"> lambda "MyFunction" { code (Code.FromBucket myBucket "lambda.zip") } </code>
<summary>Sets the memory allocation for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="mb">The memory size in megabytes.</param>
<code lang="fsharp"> lambda "MyFunction" { memory 512 } </code>
<summary>Sets the timeout for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="seconds">The timeout in seconds.</param>
<code lang="fsharp"> lambda "MyFunction" { timeout 30.0 } </code>
<summary>Sets environment variables for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="env">List of key-value pairs for environment variables.</param>
<code lang="fsharp"> lambda "MyFunction" { environment [ "KEY1", "value1"; "KEY2", "value2" ] } </code>
<summary>Creates a DynamoDB table configuration.</summary>
<param name="name">The table name.</param>
<code lang="fsharp"> table "MyTable" { partitionKey "id" AttributeType.STRING billingMode BillingMode.PAY_PER_REQUEST } </code>
<summary>Sets the partition key for the table.</summary>
<param name="config">The current table configuration.</param>
<param name="name">The attribute name for the partition key.</param>
<param name="attrType">The attribute type (STRING, NUMBER, or BINARY).</param>
<code lang="fsharp"> table "MyTable" { partitionKey "id" AttributeType.STRING } </code>
<summary>Sets the sort key for the table.</summary>
<param name="config">The current table configuration.</param>
<param name="name">The attribute name for the sort key.</param>
<param name="attrType">The attribute type (STRING, NUMBER, or BINARY).</param>
<code lang="fsharp"> table "MyTable" { partitionKey "userId" AttributeType.STRING sortKey "timestamp" AttributeType.NUMBER } </code>
<summary>Sets the billing mode for the table.</summary>
<param name="config">The current table configuration.</param>
<param name="mode">The billing mode (PAY_PER_REQUEST or PROVISIONED).</param>
<code lang="fsharp"> table "MyTable" { billingMode BillingMode.PAY_PER_REQUEST } </code>
<summary>Creates a VPC configuration with AWS best practices.</summary>
<param name="name">The VPC name.</param>
<code lang="fsharp"> vpc "MyVpc" { maxAzs 2 natGateways 1 cidr "10.0.0.0/16" } </code>
<summary>Sets the CIDR block for the VPC.</summary>
<param name="config">The current VPC configuration.</param>
<param name="cidr">The CIDR block (e.g., "10.0.0.0/16").</param>
<code lang="fsharp"> vpc "MyVpc" { cidr "10.0.0.0/16" } </code>
<summary>Sets the maximum number of Availability Zones to use.</summary>
<param name="config">The current VPC configuration.</param>
<param name="maxAzs">The maximum number of AZs (default: 2 for HA).</param>
<code lang="fsharp"> vpc "MyVpc" { maxAzs 3 } </code>
<summary>Sets the number of NAT Gateways.</summary>
<param name="config">The current VPC configuration.</param>
<param name="natGateways">The number of NAT gateways (default: 1 for cost optimization).</param>
<code lang="fsharp"> vpc "MyVpc" { natGateways 2 } </code>
<summary>Creates a Kinesis stream with AWS best practices.</summary>
<param name="name">The stream name.</param>
<code lang="fsharp"> kinesisStream "MyStream" { shardCount 2 retentionPeriod (Duration.Hours(48.0)) } </code>
<summary>Sets the stream name.</summary>
<summary>Sets the number of shards.</summary>
<summary>Sets the retention period.</summary>
<summary>Uses a custom KMS key for encryption.</summary>
<summary>Creates an EKS cluster with AWS best practices.</summary>
<param name="name">The cluster name.</param>
<code lang="fsharp"> eksCluster "MyCluster" { vpc myVpc version KubernetesVersion.V1_28 defaultCapacity 3 } </code>
<summary>Sets the Kubernetes version.</summary>
<summary>Sets the default node capacity.</summary>
type NodegroupOptions = interface INodegroupOptions new: unit -> unit member AmiType: Nullable<NodegroupAmiType> member CapacityType: Nullable<CapacityType> member DesiredSize: Nullable<float> member DiskSize: Nullable<float> member EnableNodeAutoRepair: Nullable<bool> member ForceUpdate: Nullable<bool> member InstanceTypes: InstanceType array member Labels: IDictionary<string,string> ...
--------------------
NodegroupOptions() : NodegroupOptions
type InstanceType = inherit DeputyBase new: instanceTypeIdentifier: string -> unit member IsBurstable: unit -> bool member SameInstanceClassAs: other: InstanceType -> bool member ToString: unit -> string static member Of: instanceClass: InstanceClass * instanceSize: InstanceSize -> InstanceType member Architecture: InstanceArchitecture
--------------------
InstanceType(instanceTypeIdentifier: string) : InstanceType
<summary>Provides information about, and means to manipulate, the current environment and platform. This class cannot be inherited.</summary>
System.Environment.GetEnvironmentVariable(variable: string, target: System.EnvironmentVariableTarget) : string
<summary>Creates an AWS CDK Environment configuration.</summary>
<code lang="fsharp"> environment { account "123456789012" region "us-west-2" } </code>
<summary>Sets the AWS account ID for the environment.</summary>
<param name="config">The current configuration.</param>
<param name="accountId">The AWS account ID.</param>
<code lang="fsharp"> environment { account "123456789012" } </code>
<summary>Sets the AWS region for the environment.</summary>
<param name="config">The current configuration.</param>
<param name="regionName">The AWS region name.</param>
<code lang="fsharp"> environment { region "us-west-2" } </code>
FsCDK