Skip to content

Commit b82f8b7

Browse files
authored
Merge pull request #5 from abrouter/parallel-running
Parallel running
2 parents 5e042ea + 9a7a617 commit b82f8b7

File tree

9 files changed

+275
-14
lines changed

9 files changed

+275
-14
lines changed

Config/abrouter.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
<?php
22
declare(strict_types = 1);
33

4+
use Abrouter\LaravelClient\Bridge\KvStorage;
5+
use Abrouter\LaravelClient\Bridge\ParallelRunning\TaskManager;
6+
47
return [
58
'token' => '',
69
'host' => 'https://abrouter.com',
10+
'parallelRunning' => [
11+
'enabled' => true,
12+
'taskManager' => TaskManager::class,
13+
],
14+
'kvStorage' => KvStorage::class
715
];

README.md

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ You can find the ABRouter product source code by the following link: https://git
2525

2626
🛠 Incredible UI to manage it
2727

28+
🛠 Parallel running (non-blocking A/B tests running)
29+
30+
2831
## Prepare your first A/B test
2932
Besides of the installing this package you need to have an account on [ABRouter](https://abrouter.com). Your token and experiment id will be also there.
3033
Feel free to read step by step instruction [Impelementing A/B tests on Laravel](https://abrouter.com/en/laravel-ab-tests)
@@ -56,14 +59,33 @@ php artisan vendor:publish --tag=abrouter
5659
Put your ABRouter token in /config/abrouter.php. You can find this token in [ABRouter dashboard](https://abrouter.com/en/board).
5760

5861
```php
62+
63+
use Abrouter\LaravelClient\Bridge\KvStorage;
64+
use Abrouter\LaravelClient\Bridge\ParallelRunning\TaskManager;
65+
5966
return [
6067
'token' => '14da89de1713a74c1ed50aafaff18c24bf372a9913e67e6a7a915def3332a97c9c9ecbe2cd6d3047',
6168
'host' => 'https://abrouter.com',
69+
'parallelRunning' => [
70+
'enabled' => true, //parallel running, enabled by default. See next section.
71+
'taskManager' => TaskManager::class,
72+
],
73+
'kvStorage' => KvStorage::class
6274
];
6375
```
6476

77+
### Configure Parallel running
78+
79+
Parallel running is a feature that allows you to run A/B tests asynchronously.
80+
It requires ready-to-use Laravel cache (probably by Redis).
6581

66-
## :rocket: Usage
82+
This feature enables caching of experiment branches to run the experiment locally, then using Laravel built-in queues to sync the data with ABRouter server.
83+
Please make sure, your supervisor config, queues and caching storage is enabled in Laravel to use.
84+
85+
Parallel running allows to run your A/B tests without blocking.
86+
Additionally, you can configure it on your own.
87+
88+
## :rocket: Running A/B tests
6789

6890
```php
6991
use Abrouter\Client\Client;
@@ -79,8 +101,46 @@ class ExampleController
79101
}
80102
}
81103
```
104+
## :rocket: Running feature flags
105+
106+
```php
107+
use Abrouter\Client\Client;
108+
109+
class ExampleController
110+
{
111+
public function __invoke(Client $client)
112+
{
113+
$isEnabledButton = $client->featureFlags()->run('enabled_button_feature_flag');
114+
115+
return view('featureFlags', [
116+
'enabledButtonFeatureFlag' => $isEnabledButton,
117+
]);
118+
}
119+
}
120+
```
121+
122+
## :rocket: Sending the stats
123+
124+
```php
125+
use Abrouter\Client\Client;
126+
127+
class ExampleController
128+
{
129+
public function __invoke(Client $client)
130+
{
131+
//ABRouter can store more data. Please learn more in EventDTO arguments.
132+
$client->statistics()->sendEvent(new EventDTO(
133+
null,
134+
$userId,
135+
'visited_test_page'
136+
));
137+
}
138+
}
139+
```
140+
141+
### Managing UI
82142

83-
You can create an experiment and get your token and id of experiment on [ABRouter](https://abrouter.com) or just read the [docs](https://abrouter.com/en/docs).
143+
You can create an experiment/feature flags and set up statistics and get your token and id of experiment on [ABRouter](https://abrouter.com) or just read the [docs](https://abrouter.com/en/docs).
84144

85145

86146
## Example

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
}
1616
],
1717
"require": {
18-
"php": "^5.5 || ^7.0 || ^8.0",
18+
"php": "^7.4 || ^8.0",
1919
"laravel/framework": "^5.3 || ^6.0 || ^7.0 || ^8.0",
20-
"abrouter/abrouter-php-client": "^0.8"
20+
"abrouter/abrouter-php-client": "^0.10"
2121
},
2222
"extra": {
2323
"laravel": {

src/Bridge/KvStorage.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Abrouter\LaravelClient\Bridge;
5+
6+
use Abrouter\Client\Contracts\KvStorageContract;
7+
use Illuminate\Support\Facades\Cache;
8+
9+
class KvStorage implements KvStorageContract
10+
{
11+
public function put(string $key, string $value, int $expiresInSeconds = 0): void
12+
{
13+
Cache::store()->put($key, $value, $expiresInSeconds);
14+
}
15+
16+
public function remove(string $key): void
17+
{
18+
Cache::store()->delete($key);
19+
}
20+
21+
public function get(string $key)
22+
{
23+
Cache::store()->get($key);
24+
}
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Abrouter\LaravelClient\Bridge\ParallelRunning;
5+
6+
use Abrouter\Client\Contracts\TaskContract;
7+
8+
class ParallelTaskContainer implements TaskContract
9+
{
10+
private TaskContract $taskContract;
11+
12+
public function __construct(TaskContract $taskContract)
13+
{
14+
$this->taskContract = $taskContract;
15+
}
16+
17+
/**
18+
* @return TaskContract
19+
*/
20+
public function getTaskContract(): TaskContract
21+
{
22+
return $this->taskContract;
23+
}
24+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Abrouter\LaravelClient\Bridge\ParallelRunning;
5+
6+
use Abrouter\Client\Contracts\TaskContract;
7+
use Abrouter\Client\Events\EventDispatcher;
8+
use Illuminate\Bus\Queueable;
9+
use Illuminate\Contracts\Queue\ShouldQueue;
10+
use Illuminate\Queue\InteractsWithQueue;
11+
use Illuminate\Queue\SerializesModels;
12+
13+
class ParallelTaskHandler implements TaskContract, ShouldQueue
14+
{
15+
use InteractsWithQueue, Queueable, SerializesModels;
16+
17+
private EventDispatcher $eventDispatcher;
18+
19+
public function __construct(EventDispatcher $eventDispatcher)
20+
{
21+
$this->eventDispatcher = $eventDispatcher;
22+
}
23+
24+
public function handle(ParallelTaskContainer $parallelTaskContainer)
25+
{
26+
$this->eventDispatcher->dispatch($parallelTaskContainer->getTaskContract());
27+
}
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Abrouter\LaravelClient\Bridge\ParallelRunning;
5+
6+
use Abrouter\Client\Contracts\TaskContract;
7+
use Abrouter\Client\Contracts\TaskManagerContract;
8+
use Illuminate\Events\Dispatcher;
9+
10+
class TaskManager implements TaskManagerContract
11+
{
12+
private Dispatcher $dispatcher;
13+
14+
public function __construct(Dispatcher $dispatcher)
15+
{
16+
$this->dispatcher = $dispatcher;
17+
}
18+
19+
public function queue(TaskContract $task): void
20+
{
21+
$this->dispatcher->dispatch(new ParallelTaskContainer($task));
22+
}
23+
24+
public function work(int $iterationsLimit = 0): void
25+
{
26+
}
27+
}

src/Providers/AbrouterServiceProvider.php

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
namespace Abrouter\LaravelClient\Providers;
55

66
use Abrouter\Client\Config\Config;
7+
use Abrouter\Client\Config\ParallelRunConfig;
8+
use Abrouter\Client\Contracts\KvStorageContract;
9+
use Abrouter\Client\Contracts\TaskManagerContract;
710
use Illuminate\Support\ServiceProvider;
811
use Illuminate\Contracts\Container\Container;
912
use Illuminate\Contracts\Foundation\Application;
1013
use RuntimeException;
1114
use Illuminate\Config\Repository;
15+
use Abrouter\LaravelClient\Bridge\KvStorage;
1216

1317
/**
1418
* Class AbrouterServiceProvider
@@ -28,16 +32,29 @@ public function boot(): void
2832
public function register(): void
2933
{
3034
$this->mergeConfigFrom($this->configPath(), 'abrouter');
31-
/**
32-
* @var Repository $configRepository
33-
*/
34-
$configRepository = $this->getContainer()->make(Repository::class);
35-
36-
$this->getContainer()->singleton(Config::class, function () use ($configRepository) {
37-
return new Config(
38-
(string) $configRepository->get('abrouter.token'),
39-
(string) $configRepository->get('abrouter.host')
40-
);
35+
36+
37+
$config = new Config(
38+
(string) $this->getConfig()->get('abrouter.token'),
39+
(string) $this->getConfig()->get('abrouter.host')
40+
);
41+
42+
$kvStorageConfigured = false;
43+
if ($this->configureKvStorage()) {
44+
$config->setKvStorageConfig($this->getContainer()->make(KvStorageContract::class));
45+
$kvStorageConfigured = true;
46+
}
47+
48+
if ($kvStorageConfigured && $this->isParallelRunningEnabled()) {
49+
$this->configureParallelRunning();
50+
$config->setParallelRunConfig(new ParallelRunConfig(
51+
true,
52+
$this->getContainer()->make(TaskManagerContract::class),
53+
));
54+
}
55+
56+
$this->getContainer()->singleton(Config::class, function () use ($config) {
57+
return $config;
4158
});
4259
}
4360

@@ -80,4 +97,59 @@ private function getApplicationConfigPath(): string
8097
{
8198
return $this->app->configPath('abrouter.php');
8299
}
100+
101+
private function configureKvStorage(): bool
102+
{
103+
$kvStorage = $this->getConfig()->get('abrouter.kvStorage');
104+
if (empty($kvStorage)) {
105+
return false;
106+
}
107+
108+
$this->getContainer()->singleton(KvStorageContract::class, function () use ($kvStorage) {
109+
return $this->getContainer()->make($kvStorage);
110+
});
111+
112+
return true;
113+
}
114+
115+
private function isParallelRunningEnabled(): bool
116+
{
117+
$parallelRunningConfig = $this->getConfig()->get('abrouter.parallelRunning');
118+
119+
if (!is_array($parallelRunningConfig)) {
120+
return false;
121+
}
122+
123+
if (!isset($parallelRunningConfig['enabled'])) {
124+
return false;
125+
}
126+
127+
if (empty($parallelRunningConfig['taskManager'])) {
128+
return false;
129+
}
130+
131+
return (bool) $parallelRunningConfig['enabled'];
132+
}
133+
134+
private function configureParallelRunning(): void
135+
{
136+
$taskManager = $this->getConfig()->get('abrouter.parallelRunning.taskManager');
137+
if (empty($taskManager)) {
138+
throw new RuntimeException('TaskManager class is not configured');
139+
}
140+
141+
$this->getContainer()->singleton(TaskManagerContract::class, function () use ($taskManager) {
142+
return $this->getContainer()->make($taskManager);
143+
});
144+
$this->getContainer()->register(EventServiceProvider::class);
145+
}
146+
147+
private function getConfig(): Repository
148+
{
149+
/**
150+
* @var Repository $configRepository
151+
*/
152+
$configRepository = $this->getContainer()->make(Repository::class);
153+
return $configRepository;
154+
}
83155
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Abrouter\LaravelClient\Providers;
5+
6+
use Abrouter\LaravelClient\Bridge\ParallelRunning\ParallelTaskContainer;
7+
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
8+
use Abrouter\LaravelClient\Bridge\ParallelRunning\ParallelTaskHandler;
9+
10+
class EventServiceProvider extends ServiceProvider
11+
{
12+
protected $listen = [
13+
ParallelTaskContainer::class => [
14+
ParallelTaskHandler::class,
15+
]
16+
];
17+
}

0 commit comments

Comments
 (0)