Skip to content

Commit

Permalink
[Feature][v1.1.0] Support Laravel 11, Logs improvements & maxProcessi…
Browse files Browse the repository at this point in the history
…ngTime (#1)

* Allow laravel 11

* Write more trace log
Run with Laravel 11

* Max processing time

* added tests

* done

* Updated tests & bump version

* README updated

* README updated
  • Loading branch information
sethsandaru committed Mar 13, 2024
1 parent 2bbcc5d commit b847bce
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- name: Setup PHP with coverage driver
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.version }}
php-version: 8.2
coverage: pcov

- name: Start MySQL Database
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/try-installation.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Try Install Package (Laravel 10)
name: Try Install Package (Laravel 10 & 11)
env:
LOCAL_ENV: ${{ secrets.LOCAL_ENV }}

Expand All @@ -7,13 +7,13 @@ jobs:
strategy:
fail-fast: false
matrix:
version: [ '^9.0', '^10.0' ]
version: [ '^10.0', '^11.0' ]
runs-on: ubuntu-latest
steps:
- name: Setup PHP with coverage driver
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.2
coverage: pcov

- name: Setup and install package on Laravel
Expand Down
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,33 @@
<img src=".github/logo.png" width="200">
</p>

The inbox pattern is a popular design pattern that ensures:
Talking about distributed computers & servers, it is quite normal nowadays to communicate between servers.

Unlike a regular conversation though, there's no guarantee the message gets delivered only once, arrives in the right order, or even gets a "got it!" reply.

Thus, we have **Inbox Pattern** to help us to achieve that.

## What is the Inbox Pattern

**The Inbox Pattern** is a popular design pattern in the microservice architecture that ensures:

- High availability ✅
- Guaranteed webhook deliverance, no msg lost ✅
- Guaranteed **exactly-once/unique** webhook requests ✅
- Execute webhook requests **in ORDER**
- Execute webhook requests **in ORDER/sequence**
- (Optional) High visibility & debug all prev requests ✅

Laravel Inbox Process (powered by ShipSaaS) ships everything and
And with that being said:

**Laravel Inbox Process (powered by ShipSaaS)** ships everything out-of-the-box and
helps you to roll out the inbox process in no time 😎🚀.

## Supports
- Laravel 10+
- PHP 8.2+
- MySQL 8 and Postgres 13+
- MySQL 8, MariaDB & Postgres 13+

## Architecture
## Architecture Diagram

![ShipSaaS - Laravel Inbox Process](./.github/arch.png)

Expand All @@ -46,17 +56,19 @@ php artisan migrate

Visit: [ShipSaaS Inbox Documentation](https://inbox.shipsaas.tech)

Best practices & notes are well documented too 😎!
Best practices, usage & notes are well documented too 😎!

## Testing

Run `composer test` 😆

Available Tests:

- Unit Testing
- Integration Testing against MySQL & PostgreSQL for the `inbox:work` command
- Human validation (lol)
- Unit Testing 💪
- Integration Testing against MySQL & PostgreSQL for the `inbox:work` command 😎
- Human validation (lol) 🔥

ShipSaaS loves tests, we won't ship sh!tty libraries 🌹

## Contributors
- Seth Phat
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "shipsaas/laravel-inbox-process",
"type": "library",
"version": "1.0.0",
"version": "1.1.0",
"description": "Inbox pattern process implementation for your Laravel Applications",
"keywords": [
"laravel library",
Expand All @@ -24,7 +24,7 @@
"license": "MIT",
"require": {
"php": "^8.2",
"laravel/framework": "^10|dev-master",
"laravel/framework": "^10|^11|dev-master",
"ext-pcntl": "*"
},
"require-dev": {
Expand Down
12 changes: 11 additions & 1 deletion src/Commands/InboxWorkCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#[AsCommand(name: 'inbox:work')]
class InboxWorkCommand extends Command
{
protected $signature = 'inbox:work {topic} {--limit=10} {--wait=5} {--log=1} {--stop-on-empty}';
protected $signature = 'inbox:work {topic} {--limit=10} {--wait=5} {--log=1} {--stop-on-empty} {--max-processing-time=3600}';
protected $description = '[ShipSaaS Inbox] Start the inbox process';

protected bool $isRunning = true;
Expand Down Expand Up @@ -47,6 +47,7 @@ public function handle(
$this->registerLifecycle($runningInboxRepo, $inboxMessageHandler, $lifecycle);

$inboxMessageHandler->setTopic($this->topic);
$inboxMessageHandler->setHandleWriteLog($this->writeTraceLog(...));
$this->runInboxProcess($inboxMessageHandler, $lifecycle);

return 0;
Expand Down Expand Up @@ -79,6 +80,9 @@ private function runInboxProcess(
): void {
$limit = intval($this->option('limit')) ?: 10;
$wait = intval($this->option('wait')) ?: 5;
$maxProcessingTime = intval($this->option('max-processing-time')) ?: 3600;

$processNeedToCloseAt = Carbon::now()->timestamp + $maxProcessingTime;

while ($lifecycle->isRunning()) {
$totalProcessed = $inboxMessageHandler->process($limit);
Expand All @@ -91,6 +95,12 @@ private function runInboxProcess(
break;
}

if (Carbon::now()->timestamp >= $processNeedToCloseAt) {
$this->writeTraceLog('[Info] Reached max processing time. Closing the process.');

break;
}

$this->writeTraceLog('[Info] No message found. Sleeping...');
sleep($wait);
continue;
Expand Down
2 changes: 2 additions & 0 deletions src/Entities/InboxMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
class InboxMessage
{
public int $id;
public string $externalId;
public string $rawPayload;

public static function make(object $rawDbRecord): InboxMessage
{
$inboxMsg = new InboxMessage();
$inboxMsg->id = intval($rawDbRecord->id);
$inboxMsg->externalId = $rawDbRecord->external_id;
$inboxMsg->rawPayload = $rawDbRecord->payload ?: '{}';

return $inboxMsg;
Expand Down
38 changes: 37 additions & 1 deletion src/Handlers/InboxMessageHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ShipSaasInboxProcess\Handlers;

use Closure;
use Illuminate\Support\Facades\Log;
use ShipSaasInboxProcess\Core\Lifecycle;
use ShipSaasInboxProcess\Entities\InboxMessage;
Expand All @@ -12,6 +13,7 @@
class InboxMessageHandler
{
private string $topic;
private Closure $handleWriteLog;

public function __construct(
private InboxMessageRepository $inboxMessageRepo,
Expand All @@ -26,6 +28,13 @@ public function setTopic(string $topic): self
return $this;
}

public function setHandleWriteLog(?Closure $handleWriteLog): self
{
$this->handleWriteLog = $handleWriteLog;

return $this;
}

public function process(int $limit = 10): int
{
$messages = $this->inboxMessageRepo->pullMessages($this->topic, $limit);
Expand All @@ -40,15 +49,42 @@ public function process(int $limit = 10): int
}

try {
call_user_func(
$this->handleWriteLog,
sprintf(
'[MsgId: %s] Handling message with externalId: "%s"',
$message->id,
$message->externalId
)
);

$this->processMessage($message);
$processed++;

call_user_func(
$this->handleWriteLog,
sprintf(
'[MsgId: %s] Handled message with externalId: "%s"',
$message->id,
$message->externalId
)
);
} catch (Throwable $e) {
call_user_func(
$this->handleWriteLog,
sprintf(
'[MsgId: %s] Failed to handle message with externalId: "%s" - Process will be aborted',
$message->id,
$message->externalId
)
);

// something really bad happens, we need to stop the process
Log::error('Failed to process inbox message', [
'error' => [
'msg' => $e->getMessage(),
'traces' => $e->getTrace(),
]
],
]);

$this->lifecycle->forceClose();
Expand Down
2 changes: 1 addition & 1 deletion src/Repositories/InboxMessageRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function pullMessages(string $topic, int $limit = 10): Collection
->where('topic', $topic)
->orderBy('created_at_unix_ms', 'ASC')
->limit($limit)
->get(['id', 'payload'])
->get(['id', 'external_id', 'payload'])
->map(InboxMessage::make(...));
}

Expand Down
26 changes: 26 additions & 0 deletions tests/Integration/Commands/InboxWorkCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ public function testCommandPullsTheOrderedMsgAndProcessThem()
// 4. validate
$this->assertSame(0, $code);
$this->assertStringContainsString('Processed 3 inbox messages', $result);

$this->assertStringContainsString('Handling message with externalId: "evt_1NWX0RBGIr5C5v4TpncL2sCf"', $result);
$this->assertStringContainsString('Handled message with externalId: "evt_1NWX0RBGIr5C5v4TpncL2sCf"', $result);

$this->assertStringContainsString('Handling message with externalId: "evt_1NWUFiBGIr5C5v4TptQhGyW3"', $result);
$this->assertStringContainsString('Handled message with externalId: "evt_1NWUFiBGIr5C5v4TptQhGyW3"', $result);

$this->assertStringContainsString('Handling message with externalId: "evt_1Nh2fp2eZvKYlo2CzbNockEM"', $result);
$this->assertStringContainsString('Handled message with externalId: "evt_1Nh2fp2eZvKYlo2CzbNockEM"', $result);

$this->assertStringContainsString('[Info] No message found. Stopping...', $result);

Event::assertDispatched(
Expand Down Expand Up @@ -135,6 +145,22 @@ public function testCommandThrowsErrorWhenFailedToProcessAMessage()
'topic' => 'with_err_msg',
]);
}

public function testCommandStopsAfterAnAmountOfTime()
{
$beginAt = time();

$code = Artisan::call('inbox:work test --max-processing-time=10');
$result = Artisan::output();

$finishedAt = time();

$this->assertSame(0, $code);

$this->assertStringContainsString('[Info] Reached max processing time. Closing the process.', $result);

$this->assertGreaterThanOrEqual(10, $finishedAt - $beginAt);
}
}

class InvoicePaymentSucceedEvent
Expand Down
6 changes: 6 additions & 0 deletions tests/Unit/Entities/InboxMessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,33 @@ public function testMakeReturnsInboxMessageWithPayload()
{
$inboxMsg = InboxMessage::make((object) [
'id' => 1000,
'external_id' => 'fake-id',
'payload' => '{"hello": "world"}',
]);

$this->assertSame(1000, $inboxMsg->id);
$this->assertSame('fake-id', $inboxMsg->externalId);
$this->assertSame('{"hello": "world"}', $inboxMsg->rawPayload);
}

public function testMakeReturnsInboxMessageWithNoPayload()
{
$inboxMsg = InboxMessage::make((object) [
'id' => 1000,
'external_id' => 'fake-id',
'payload' => null,
]);

$this->assertSame(1000, $inboxMsg->id);
$this->assertSame('fake-id', $inboxMsg->externalId);
$this->assertSame('{}', $inboxMsg->rawPayload);
}

public function testGetParsedPayloadReturnsAnArray()
{
$inboxMsg = InboxMessage::make((object) [
'id' => 1000,
'external_id' => 'fake-id',
'payload' => '{"hello": "world"}',
]);

Expand All @@ -46,6 +51,7 @@ public function testGetParsedPayloadReturnsAnEmptyArray()
$inboxMsg = InboxMessage::make((object) [
'id' => 1000,
'payload' => null,
'external_id' => 'fake-id',
]);

$this->assertSame([], $inboxMsg->getParsedPayload());
Expand Down

0 comments on commit b847bce

Please sign in to comment.