Skip to content

Commit 088aef2

Browse files
authored
Merge pull request #33 from launchdarkly/eb/ch28330/dynamodb
add DynamoDB integration
2 parents d1a2e41 + 9ff5c89 commit 088aef2

File tree

10 files changed

+616
-30
lines changed

10 files changed

+616
-30
lines changed

.circleci/config.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,22 @@ jobs:
4141
<<: *php-docker-template
4242
docker:
4343
- image: circleci/php:5.6.34-cli-jessie
44+
- image: amazon/dynamodb-local
4445
test-7.0:
4546
<<: *php-docker-template
4647
docker:
4748
- image: circleci/php:7.0.28-cli-jessie
49+
- image: amazon/dynamodb-local
4850
test-7.1:
4951
<<: *php-docker-template
5052
docker:
5153
- image: circleci/php:7.1.15-cli-jessie
54+
- image: amazon/dynamodb-local
5255
test-7.2:
5356
<<: *php-docker-template
5457
docker:
5558
- image: circleci/php:7.2.3-cli-stretch
59+
- image: amazon/dynamodb-local
5660

5761
test-5.5: # CircleCI doesn't provide a Docker image for 5.5
5862
machine:
@@ -65,6 +69,13 @@ jobs:
6569
sudo apt-get install circleci-php-5.5.36 &&
6670
php -r "copy('https://getcomposer.org/installer', '/tmp/composer-setup.php');" &&
6771
sudo php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
72+
- run:
73+
name: install Docker
74+
command: |
75+
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" &&
76+
sudo apt-get -q update &&
77+
sudo apt-cache policy docker-ce &&
78+
sudo apt-get -qy install docker-ce
6879
- checkout
6980
- run:
7081
name: validate composer.json
@@ -75,6 +86,10 @@ jobs:
7586
- run:
7687
name: run php-cs-fixer
7788
command: vendor/bin/php-cs-fixer fix --diff --dry-run --verbose
89+
- run:
90+
name: start DynamoDB
91+
command: docker run -p 8000:8000 amazon/dynamodb-local
92+
background: true
7893
- run:
7994
name: run tests
8095
command: vendor/bin/phpunit --log-junit ~/phpunit/junit.xml --coverage-text tests

README.md

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,33 +64,52 @@ With Guzzle, you could persist your cache somewhere other than the default in-me
6464
Using LD-Relay
6565
==============
6666

67-
The LaunchDarkly Relay Proxy ([ld-relay](https://github.com/launchdarkly/ld-relay)) consumes the LaunchDarkly streaming API and can update
68-
a Redis cache operating in your production environment. The ld-relay offers many benefits such as performance and feature flag consistency. With PHP applications, we strongly recommend setting up ld-relay with a Redis store.
67+
The LaunchDarkly Relay Proxy ([ld-relay](https://github.com/launchdarkly/ld-relay)) consumes the LaunchDarkly streaming API and can update a database cache operating in your production environment. The ld-relay offers many benefits such as performance and feature flag consistency. With PHP applications, we strongly recommend setting up ld-relay with a database store. The database can be Redis or DynamoDB. (For more about using LaunchDarkly with Redis or DynamoDB, see the [SDK reference guide](https://docs.launchdarkly.com/v2.0/docs/using-a-persistent-feature-store).)
6968

7069
1. Set up ld-relay in [daemon-mode](https://github.com/launchdarkly/ld-relay#redis-storage-and-daemon-mode) with Redis
7170

72-
2. Require Predis as a dependency:
71+
2. Add the necessary dependency for the chosen database.
72+
73+
For Redis:
7374

7475
php composer.phar require "predis/predis:1.0.*"
7576

76-
3. Create the LDClient with the Redis feature requester as an option:
77+
For DynamoDB:
78+
79+
php composer.phar require "aws/aws-sdk-php:3.*"
80+
81+
3. Create the LDClient with the appropriate parameters for the chosen database. These examples show all of the available options.
82+
83+
For Redis:
7784

7885
$client = new LaunchDarkly\LDClient("your_sdk_key", [
79-
'feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester',
80-
'redis_host' => 'your.redis.host',
81-
'redis_port' => 6379
86+
'feature_requester' => 'LaunchDarkly\LDDFeatureRequester',
87+
'redis_host' => 'your.redis.host', // defaults to "localhost" if not specified
88+
'redis_port' => 6379, // defaults to 6379 if not specified
89+
'redis_timeout' => 5, // connection timeout in seconds; defaults to 5
90+
'redis_prefix' => 'env1' // corresponds to the prefix setting in ld-relay
91+
'predis_client' => $myClient // use this if you have already configured a Predis client instance
8292
]);
8393

84-
4. If ld-relay is configured for [event forwarding](https://github.com/launchdarkly/ld-relay#event-forwarding), you can configure the LDClient to publish events to ld-relay instead of directly to `events.launchdarkly.com`. Using `GuzzleEventPublisher` with ld-relay event forwarding can be an efficient alternative to the default `curl`-based event publishing.
94+
For DynamoDB:
8595

8696
$client = new LaunchDarkly\LDClient("your_sdk_key", [
87-
'event_publisher_class' => 'LaunchDarkly\GuzzleEventPublisher',
88-
'events_uri' => 'http://your-ldrelay-host:8030',
89-
'feature_requester_class' => 'LaunchDarkly\LDDFeatureRequester',
90-
'redis_host' => 'your.redis.host',
91-
'redis_port' => 6379
97+
'feature_requester' => 'LaunchDarkly\DynamoDbFeatureRequester',
98+
'dynamodb_table' => 'your.table.name', // required
99+
'dynamodb_prefix' => 'env1', // corresponds to the prefix setting in ld-relay
100+
'dynamodb_options' => array(), // you may pass any options supported by the AWS SDK
101+
'apc_expiration' => 30 // expiration time for local caching, if you have apcu installed
92102
]);
93103

104+
4. If you are using DynamoDB, you must create your table manually. It must have a partition key called "namespace", and a sort key called "key" (both strings). Note that by default the AWS SDK will attempt to get your AWS credentials and region from environment variables and/or local configuration files, but you may also specify them in `dynamodb_options`.
105+
106+
5. If ld-relay is configured for [event forwarding](https://github.com/launchdarkly/ld-relay#event-forwarding), you can configure the LDClient to publish events to ld-relay instead of directly to `events.launchdarkly.com`. Using `GuzzleEventPublisher` with ld-relay event forwarding can be an efficient alternative to the default `curl`-based event publishing.
107+
108+
To forward events, add the following configuration properties to the configuration shown above:
109+
110+
'event_publisher_class' => 'LaunchDarkly\GuzzleEventPublisher',
111+
'events_uri' => 'http://your-ldrelay-host:8030'
112+
94113
Using flag data from a file
95114
---------------------------
96115

@@ -112,7 +131,7 @@ Contributing
112131
We encourage pull-requests and other contributions from the community. We've also published an [SDK contributor's guide](http://docs.launchdarkly.com/docs/sdk-contributors-guide) that provides a detailed explanation of how our SDKs work.
113132

114133
About LaunchDarkly
115-
-----------
134+
------------------
116135

117136
* LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
118137
* Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"psr/log": "^1.0"
2020
},
2121
"require-dev": {
22+
"aws/aws-sdk-php": "^3.86",
2223
"friendsofphp/php-cs-fixer": "~2.2.19",
2324
"guzzlehttp/guzzle": "^6.2.1",
2425
"kevinrob/guzzle-cache-middleware": "^1.4.1",
@@ -30,7 +31,8 @@
3031
"suggest": {
3132
"guzzlehttp/guzzle": "(^6.2.1) Required when using GuzzleEventPublisher or the default FeatureRequester",
3233
"kevinrob/guzzle-cache-middleware": "(^1.4.1) Recommended for performance when using the default FeatureRequester",
33-
"predis/predis": "(^1.0) Required when using LDDFeatureRequester"
34+
"predis/predis": "(^1.0) Required when using LDDFeatureRequester",
35+
"aws/aws-sdk-php": "(^3.86) Required when using DynamoDbFeatureRequester"
3436
},
3537
"autoload": {
3638
"psr-4": {

src/LaunchDarkly/ApcLDDFeatureRequester.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ protected function get_from_cache($namespace, $key)
5151
*/
5252
protected function add($key, $var, $ttl = 0)
5353
{
54-
return \apc_add($key, $var, $ttl);
54+
return \apc_store($key, $var, $ttl);
5555
}
5656

5757
protected function store_in_cache($namespace, $key, $val)

src/LaunchDarkly/ApcuLDDFeatureRequester.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ protected function fetch($key, &$success = null)
2929
*/
3030
protected function add($key, $var, $ttl = 0)
3131
{
32-
return \apcu_add($key, $var, $ttl);
32+
return \apcu_store($key, $var, $ttl);
3333
}
3434
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
namespace LaunchDarkly;
3+
4+
use Aws\DynamoDb\DynamoDbClient;
5+
6+
class DynamoDbFeatureRequester extends FeatureRequesterBase
7+
{
8+
/** @var string */
9+
protected $_tableName;
10+
/** @var string */
11+
protected $_prefix;
12+
/** @var DynamoDbClient */
13+
protected $_client;
14+
15+
public function __construct($baseUri, $sdkKey, $options)
16+
{
17+
parent::__construct($baseUri, $sdkKey, $options);
18+
19+
if (!isset($options['dynamodb_table'])) {
20+
throw new \InvalidArgumentException('dynamodb_table must be specified');
21+
}
22+
$this->_tableName = $options['dynamodb_table'];
23+
24+
$dynamoDbOptions = isset($options['dynamodb_options']) ? $options['dynamodb_options'] : array();
25+
$dynamoDbOptions['version'] = '2012-08-10'; // in the AWS SDK for PHP, this is how you specify the API version
26+
$this->_client = new DynamoDbClient($dynamoDbOptions);
27+
28+
$prefix = isset($options['dynamodb_prefix']) ? $options['dynamodb_prefix'] : '';
29+
$this->_prefix = ($prefix != null && $prefix != '') ? ($prefix . '/') : '';
30+
}
31+
32+
protected function readItemString($namespace, $key)
33+
{
34+
$request = array(
35+
'TableName' => $this->_tableName,
36+
'ConsistentRead' => true,
37+
'Key' => array(
38+
'namespace' => array('S' => $this->_prefix . $namespace),
39+
'key' => array('S' => $key)
40+
)
41+
);
42+
$result = $this->_client->getItem($request);
43+
if (!$result) {
44+
return null;
45+
}
46+
$item = $result->get('Item');
47+
if (!$item || !isset($item['item'])) {
48+
return null;
49+
}
50+
$attr = $item['item'];
51+
return isset($attr['S']) ? $attr['S'] : null;
52+
}
53+
54+
protected function readItemStringList($namespace)
55+
{
56+
$items = array();
57+
$request = array(
58+
'TableName' => $this->_tableName,
59+
'ConsistentRead' => true,
60+
'KeyConditions' => array(
61+
'namespace' => array(
62+
'ComparisonOperator' => 'EQ',
63+
'AttributeValueList' => array(array('S' => $this->_prefix . $namespace))
64+
)
65+
)
66+
);
67+
// We may need to repeat this query several times due to pagination
68+
$moreItems = true;
69+
while ($moreItems) {
70+
$result = $this->_client->query($request);
71+
foreach ($result->get('Items') as $item) {
72+
if (isset($item['item'])) {
73+
$attr = $item['item'];
74+
if (isset($attr['S'])) {
75+
$items[] = $attr['S'];
76+
}
77+
}
78+
}
79+
if (isset($result['LastEvaluatedKey']) && $result['LastEvaluatedKey']) {
80+
$request['ExclusiveStartKey'] = $result['LastEvaluatedKey'];
81+
} else {
82+
$moreItems = false;
83+
}
84+
}
85+
return $items;
86+
}
87+
}

0 commit comments

Comments
 (0)