Building a cloud-friendly application

Presented by Larry Garfield (@Crell)

@Crell

Larry implements Huggable
  • Director of DX, Platform.sh
  • PHP-FIG Core Committee
  • implements Huggable
Software is eating the world, and cloud is eating software. (Source: @bassamtabbara)
What is cloud
computing?
What is
The Cloud?

These are separate questions...

The Cloud™: noun

Someone else's hard drive

Cloud computing: noun

Abstracting away physical infrastructure

Disposable application design

What makes an application cloud-friendly?

They're not rules

They're more like guidelines...

They're more like guidelines

Split code from content

Code

  • Provided by developer
  • Carefully tested
  • Lives in version control
  • Read-only runtime

Content

  • Provided by users
  • Frequently ad hoc
  • Lives in DB or filesystem
  • Writeable runtime
Don't mix the filesystems

Your application is disposable. Your data is not.

Data flow

You don't get an in-between option

Take-away

Cleanly separate "Dev provided" and "user provided" files

What is configuration?

Does config come from the developer or the user?

Git or Database?

Drupal Config Management

Drupal 8
  1. Configure in UI / DB
  2. Export to YAML
  3. Commit to Git
  4. Push / Pull
  5. Import back to DB

Take-away

Decide

What happens at runtime
stays at runtime

Application slots into cluster

Dependency inject your environment

  • DB credentials (Solr, Redis, etc.)
  • API keys
  • All paths on disk
  • Domain names

Environment variables


getenv('foo');

$_ENV['foo'];

$_ENV['foo']['bar'];
        

Need glue code

Platform.sh / SensioCloud

Symfony 3


// platform_parameters.php
// Configure the database.
if (isset($_ENV['PLATFORM_RELATIONSHIPS'])) {
    $dbRelationshipName = 'database';
    $relationships = json_decode(base64_decode($_ENV['PLATFORM_RELATIONSHIPS']), true);

    foreach ($relationships[$dbRelationshipName] as $endpoint) {
        if (!empty($endpoint['query']['is_master'])) {
            $container->setParameter('database_driver', 'pdo_'.$endpoint['scheme']);
            $container->setParameter('database_host', $endpoint['host']);
            $container->setParameter('database_port', $endpoint['port']);
            $container->setParameter('database_name', $endpoint['path']);
            $container->setParameter('database_user', $endpoint['username']);
            $container->setParameter('database_password', $endpoint['password']);
            $container->setParameter('database_path', '');
            break;
        }
    }
}
// Set a default unique secret, based on a project-specific entropy value.
if (isset($_ENV['PLATFORM_PROJECT_ENTROPY'])) {
    $container->setParameter('kernel.secret', $_ENV['PLATFORM_PROJECT_ENTROPY']);
}
        

Platform.sh / Symfony 4


// Set the DATABASE_URL for Doctrine, if necessary.
if (!isset($_ENV['DATABASE_URL'])) {
    if (isset($_ENV['PLATFORM_RELATIONSHIPS'])) {
        $dbRelationshipName = 'database';
        $relationships = json_decode(base64_decode($_ENV['PLATFORM_RELATIONSHIPS']),true);
        foreach ($relationships[$dbRelationshipName] as $endpoint) {
            if (!empty($endpoint['query']['is_master'])) {
                $dbUrl = sprintf("%s://%s:%s@%s:%s/%s?charset=utf8mb4&serverVersion=10.2",
                  $endpoint['scheme'], $endpoint['username'], $endpoint['password'],
                  $endpoint['host'], $endpoint['port'],
                  $endpoint['path']);
                break;
            }
            $_ENV['DATABASE_URL'] = $dbUrl;
        }
    }
    else {
        // Hack the Doctrine URL to be syntactically valid in a build hook, even
        // though it shouldn't be used.
        $dbUrl = sprintf("%s://%s:%s@%s:%s/%s?charset=utf8mb4&serverVersion=10.2",
          'mysql', '', '', 'localhost', 3306, '');
        $_ENV['DATABASE_URL'] = $dbUrl;
    }
}

// Set the application secret if it's not already set.
if (!isset($_ENV['APP_SECRET']) && isset($_ENV['PLATFORM_PROJECT_ENTROPY'])) {
    $_ENV['APP_SECRET'] = $_ENV['PLATFORM_PROJECT_ENTROPY'];
}
        

In the container


parameters:
    app.connection.port: '%env(int:DATABASE_PORT)%'
        

parameters:
    project_dir: '/foo/bar'
    env(DB): 'sqlite://%%project_dir%%/var/data.db'
    db_dsn: '%env(resolve:DB)%'
        

parameters:
    env(SOME_VALUE): 'NWE3OWExYzg2NmVmZWY5Y2ExODAwZjk3MWQ2ODlmM2U='
    app.some_value: '%env(base64:SOME_VALUE)%'

    env(NUM_ITEMS): 'App\\Entity\\BlogPost::NUM_ITEMS'
    app.num_items: '%env(constant:NUM_ITEMS)%'
        
  • Request::setTrustedHosts()

Constants?

Take-away

Dependency inject your environment

User-configured connections

Installers

  1. Ask for DB credentials
  2. Ask user for basic site info
  3. Write credentials to config file
  4. Populate DB
  5. Write basic site info to config file/DB
  6. Profit!!!

Cloud

Better installers

  • Pre-include connection glue
  • Installer skips pre-populated values
  • Do not download from installer

Avoid lock-in

Always be able to
take your business elsewhere.

Use Free Software

Use replaceable services

Google has killed...

  • Reader
  • iGoogle
  • Google Talk
  • Google Health
  • Knol
  • Google Insights
  • Picnik
  • Buzz
  • Aardvark
  • Sidewiki
  • Notebook
  • Dictionary
  • Labs
  • Wave
  • SearchWiki
  • Dodgeball
  • Jaiku
  • Lively
  • Page Creator
  • Zeitgeist
  • Answers
  • Google X
  • Catalog
  • Web Accelerator
  • Video Player
  • Sets
  • SearchMash
  • Writely

Source: WordStream (2015)

Safe

  • MySQL/MariaDB
  • PostgreSQL
  • MongoDB
  • RabbitMQ
  • Solr/Elasticsearch
  • InfluxDB

Unsafe

  • Amazon RDS
  • Amazon DynamoDB
  • Azure Cosmos DB
  • Anything you can't replace in a day

To summarize...

Remember what they say when you assume
* Runtime * Application needs to run in different environments * Prod, multi-head, staging, other staging, your laptop, colleague's laptop, testing, etc. * Assume nothing about the environment! * Dependency inject your environment * Environment: Credentials, paths, domains * environment variables * Ask Damien about env var alternative? * What is a cloud host? * Infra is abstracted away from you * Disposable applications * Split code from content * Configuration is code or content * Ref Drupal? * For SF, mostly code. * Runtime connections * Env vars * place for glue * Ref Symfony? * Need to discuss env component here * Constants for config suck * Can't easily override, makes glue harder * Installers * No disk write * Allow pre-defined creds * Even UI services need code-provided creds * Relative paths * Boot fast * PHP's got this covered * Cache warm in build if you can * Allow lazy-cache building * No services build * Don't assume number of webheads * Don't use cloud-specific proprietary tools. * Be able to move.

Larry Garfield

@Crell

Director of Developer Experience Platform.sh

Continuous Deployment Cloud Hosting

Stalk us at @PlatformSH