Skip to content

Commit b7d8c3c

Browse files
authored
Merge pull request #28 from justbetter/feature/availability
Add availability
2 parents a23c6bf + 5516afd commit b7d8c3c

20 files changed

+398
-6
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ Http::fake([
347347
$item = Item::query()->first();
348348
```
349349

350+
## Availability
351+
352+
This client can prevent requests from going to Dynamics when it is giving HTTP status codes 503, 504 or timeouts. This can be configured per connection in the `availability` settings. Enable the `throw` option to prevent any requests from going to Dynamics.
353+
350354
## Contributing
351355

352356
Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.

config/dynamics.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@
3232
'options' => [
3333
'connect_timeout' => 5,
3434
],
35+
'availability' => [
36+
/* The response codes that should trigger the availability check in addition to connection timeouts */
37+
'codes' => [502, 503, 504],
38+
39+
/* The amount of failed requests before the service is marked as unavailable. */
40+
'threshold' => 10,
41+
42+
/* The timespan in minutes in which the failed requests should occur. */
43+
'timespan' => 10,
44+
45+
/* The cooldown in minutes after the threshold is reached. */
46+
'cooldown' => 2,
47+
48+
/* Throw an exception that prevents calls to Dynamics when unavailable */
49+
'throw' => false,
50+
],
3551
],
3652
],
3753

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace JustBetter\DynamicsClient\Actions\Availability;
4+
5+
use JustBetter\DynamicsClient\Contracts\Availability\ChecksAvailability;
6+
7+
class CheckAvailability implements ChecksAvailability
8+
{
9+
const AVAILABLE_KEY = 'dynamics-client:availability:';
10+
11+
public function check(string $connection): bool
12+
{
13+
return cache()->get(static::AVAILABLE_KEY.$connection, true);
14+
}
15+
16+
public static function bind(): void
17+
{
18+
app()->singleton(ChecksAvailability::class, static::class);
19+
}
20+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace JustBetter\DynamicsClient\Actions\Availability;
4+
5+
use JustBetter\DynamicsClient\Contracts\Availability\RegistersUnavailability;
6+
7+
class RegisterUnavailability implements RegistersUnavailability
8+
{
9+
public const COUNT_KEY = 'dynamics-client:unavailable-count:';
10+
11+
public function register(string $connection): void
12+
{
13+
$countKey = static::COUNT_KEY.$connection;
14+
15+
/** @var int $count */
16+
$count = cache()->get($countKey, 0);
17+
$count++;
18+
19+
/** @var int $threshold */
20+
$threshold = config('dynamics.connections.'.$connection.'.availability.threshold', 10);
21+
22+
/** @var int $timespan */
23+
$timespan = config('dynamics.connections.'.$connection.'.availability.timespan', 10);
24+
25+
/** @var int $cooldown */
26+
$cooldown = config('dynamics.connections.'.$connection.'.availability.cooldown', 2);
27+
28+
cache()->put($countKey, $count, now()->addMinutes($timespan));
29+
30+
if ($count >= $threshold) {
31+
cache()->put(CheckAvailability::AVAILABLE_KEY.$connection, false, now()->addMinutes($cooldown));
32+
33+
cache()->forget($countKey);
34+
}
35+
}
36+
37+
public static function bind(): void
38+
{
39+
app()->singleton(RegistersUnavailability::class, static::class);
40+
}
41+
}

src/Client/ClientFactory.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public function oauth(string $connection, array $config): static
117117

118118
public function fabricate(): ODataClient
119119
{
120-
$httpProvider = new ClientHttpProvider;
120+
$httpProvider = new ClientHttpProvider($this->connection);
121121
$httpProvider->setExtraOptions($this->options);
122122

123123
return new ODataClient($this->url, null, $httpProvider);
@@ -131,4 +131,9 @@ public function when(bool $condition, Closure $callback): ClientFactory
131131

132132
return $this;
133133
}
134+
135+
public static function bind(): void
136+
{
137+
app()->bind(ClientFactoryContract::class, static::class);
138+
}
134139
}

src/Client/ClientHttpProvider.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,35 @@
22

33
namespace JustBetter\DynamicsClient\Client;
44

5+
use Illuminate\Http\Client\ConnectionException;
56
use Illuminate\Http\Client\RequestException;
67
use Illuminate\Support\Facades\Http;
8+
use JustBetter\DynamicsClient\Contracts\Availability\ChecksAvailability;
9+
use JustBetter\DynamicsClient\Events\DynamicsResponseEvent;
10+
use JustBetter\DynamicsClient\Events\DynamicsTimeoutEvent;
711
use JustBetter\DynamicsClient\Exceptions\DynamicsException;
812
use JustBetter\DynamicsClient\Exceptions\ModifiedException;
913
use JustBetter\DynamicsClient\Exceptions\NotFoundException;
14+
use JustBetter\DynamicsClient\Exceptions\UnavailableException;
1015
use Psr\Http\Message\ResponseInterface;
1116
use SaintSystems\OData\GuzzleHttpProvider;
1217
use SaintSystems\OData\HttpRequestMessage;
1318

1419
class ClientHttpProvider extends GuzzleHttpProvider
1520
{
21+
public function __construct(protected string $connection)
22+
{
23+
parent::__construct();
24+
}
25+
1626
public function send(HttpRequestMessage $request): ResponseInterface
1727
{
28+
$throw = config('dynamics.connections.'.$this->connection.'.availability.throw', false);
29+
30+
if ($throw && ! $this->available()) {
31+
throw new UnavailableException('The Dynamics connection "'.$this->connection.'" is currently unavailable.');
32+
}
33+
1834
try {
1935
$options = $this->extra_options;
2036

@@ -23,6 +39,8 @@ public function send(HttpRequestMessage $request): ResponseInterface
2339
}
2440

2541
$response = Http::send($request->method, $request->requestUri, $options);
42+
DynamicsResponseEvent::dispatch($response, $this->connection);
43+
2644
$response->throw();
2745

2846
return $response->toPsrResponse();
@@ -42,6 +60,18 @@ public function send(HttpRequestMessage $request): ResponseInterface
4260
throw $dynamicsException
4361
->setRequest($request)
4462
->setResponse($exception->response);
63+
} catch (ConnectionException $e) {
64+
DynamicsTimeoutEvent::dispatch($this->connection);
65+
66+
throw $e;
4567
}
4668
}
69+
70+
protected function available(): bool
71+
{
72+
/** @var ChecksAvailability $checker */
73+
$checker = app(ChecksAvailability::class);
74+
75+
return $checker->check($this->connection);
76+
}
4777
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace JustBetter\DynamicsClient\Contracts\Availability;
4+
5+
interface ChecksAvailability
6+
{
7+
public function check(string $connection): bool;
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace JustBetter\DynamicsClient\Contracts\Availability;
4+
5+
interface RegistersUnavailability
6+
{
7+
public function register(string $connection): void;
8+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace JustBetter\DynamicsClient\Events;
4+
5+
use Illuminate\Foundation\Events\Dispatchable;
6+
use Illuminate\Http\Client\Response;
7+
8+
class DynamicsResponseEvent
9+
{
10+
use Dispatchable;
11+
12+
public function __construct(
13+
public Response $response,
14+
public string $connection,
15+
) {}
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace JustBetter\DynamicsClient\Events;
4+
5+
use Illuminate\Foundation\Events\Dispatchable;
6+
7+
class DynamicsTimeoutEvent
8+
{
9+
use Dispatchable;
10+
11+
public function __construct(
12+
public string $connection,
13+
) {}
14+
}

0 commit comments

Comments
 (0)