public DbStack()

in DeliveryApi/cdk/src/main/java/com/ilmlf/delivery/db/DbStack.java [107:253]


  public DbStack(final Construct scope, final String id, final DbStackProps props) {
    super(scope, id, props);

    this.dbUsername = props.getDbUsername();
    this.dbPort = props.getDbPort();

    /**
     * #################
     * Network resources
     * #################
     *
     * Create a VPC (Virtual Private Cloud), used for network partitioning.
     *
     * The VPC contains multiple "Subnets" that could be either Internet-public or private.
     * Each Subnet is placed in different AZ (Availability Zones). Each AZ is in a different location
     * within the region. In production, you should place your database and its replica in multiple AZ
     * in case of failover. By default this stack deploys a database instance and its replica to different AZs.
     */

    // The `Vpc` construct creates subnets for you automatically
    // See https://docs.aws.amazon.com/cdk/api/latest/docs/aws-ec2-readme.html#vpc for details
    this.vpc = new Vpc(this, "farmer-Vpc");

    // Security group acts as a virtual firewall to control inbound and outbound traffic
    this.securityGroup =
        new SecurityGroup(
            this,
            "FarmerDeliverySG",
            SecurityGroupProps.builder()
                .vpc(vpc)
                .description("Shared SG for database and proxy")
                .allowAllOutbound(true)
                .build());

    /**
     * #################
     * ### DB Instance #
     * #################
     * Creates a MYSQL RDS instance.
     *
     * This construct also creates a secret store in AWS Secrets Manager. You can retrieve
     * reference to the secret store by calling farmerDB.getSecret()
     *
     * The secret store contains the admin username, password and other DB information for connecting to the DB
     *
     * For production, consider using `DatabaseCluster` to create multiple instances in different AZs.
     * This costs more but you will have higher availability.
     *
     * See https://docs.aws.amazon.com/cdk/api/latest/docs/aws-rds-readme.html for details.
     */
    String dbName = "FarmerDB";
    List<ISubnet> subnets;

    if (props.isPublicSubnetDb) {
       subnets = vpc.getPublicSubnets();
    } else {
      subnets = vpc.getPrivateSubnets();
    }

    DatabaseInstance farmerDb =
        new DatabaseInstance(
            this,
            dbName,
            DatabaseInstanceProps.builder()
                .vpc(vpc)
                .securityGroups(List.of(securityGroup))
                // Using MySQL engine
                .engine(
                    DatabaseInstanceEngine.mysql(
                        MySqlInstanceEngineProps.builder()
                            .version(MysqlEngineVersion.VER_5_7_31)
                            .build()))
                .instanceType(InstanceType.of(InstanceClass.BURSTABLE2, InstanceSize.SMALL))
                .vpcSubnets(SubnetSelection.builder().subnets(subnets).build())
                .storageEncrypted(true)
                .multiAz(true)
                .autoMinorVersionUpgrade(true)
                .allocatedStorage(25)
                .publiclyAccessible(true)
                .storageType(StorageType.GP2)
                .backupRetention(Duration.days(7))
                .deletionProtection(false)
                // Create an admin credential for connectin to database. This credential will
                // be stored in a Secret Manager store.
                .credentials(Credentials.fromGeneratedSecret(dbName + "admin"))
                .databaseName(dbName)
                .port(this.dbPort)
                .build());

    /**
     * Creates a username/password that will be used for IAM authentication.
     *
     * This user will be created in the database in the ApiStack (see PopulateFarmDb.java and dbinit.sql).
     * The user will be used by API Lambda handlers to access the database via DB Proxy (with IAM Authentication).
     */
    this.userSecret = new Secret(this, "DbUserSecret",
        SecretProps.builder()
            .description("Db Username and password")
            .generateSecretString(
                SecretStringGenerator.builder()
                    .secretStringTemplate("{\"username\": \"" + this.dbUsername + "\"}")
                    .generateStringKey("password")
                    .passwordLength(16)
                    .excludePunctuation(true)
                    .build()
            )
            .build()
    );

    /**
     * #################
     * ### RDS Proxy ###
     * #################
     * Lambda functions can scale to a large number and exhaust available database connections.
     *
     * We use RDS Proxy to prevent that. The proxy allows Lambda functions to share connections
     * instead of opening a new one every time.
     *
     * See https://aws.amazon.com/rds/proxy/ for details
     */

    // Role for RDS Proxy to access the DB
    Role rdsProxyRole = new Role(this, "RdsProxyRole",
        RoleProps.builder().assumedBy(new ServicePrincipal("rds.amazonaws.com")).build());

    DatabaseProxy dbProxy =
        new DatabaseProxy(
            this,
            "FarmerDbProxy",
            DatabaseProxyProps.builder()
                .borrowTimeout(Duration.seconds(30))
                .maxConnectionsPercent(50)
                // Proxy uses these secrets to connect to database
                .secrets(List.of(farmerDb.getSecret(), userSecret))
                .role(rdsProxyRole)
                .proxyTarget(ProxyTarget.fromInstance(farmerDb))
                .vpc(vpc)
                .securityGroups(List.of(securityGroup))
                .iamAuth(true)
                .requireTls(true)
                .build());

    this.proxyEndpoint = dbProxy.getEndpoint();
    this.proxyArn = dbProxy.getDbProxyArn();
    this.instanceEndpoint = farmerDb.getDbInstanceEndpointAddress() + ":" + farmerDb.getDbInstanceEndpointPort();
    this.adminSecret = farmerDb.getSecret();
  }