Skip to content

Commit b847bce

Browse files
authored
[Feature][v1.1.0] Support Laravel 11, Logs improvements & maxProcessingTime (#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
1 parent 2bbcc5d commit b847bce

File tree

10 files changed

+110
-18
lines changed

10 files changed

+110
-18
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
- name: Setup PHP with coverage driver
3434
uses: shivammathur/setup-php@v2
3535
with:
36-
php-version: ${{ matrix.version }}
36+
php-version: 8.2
3737
coverage: pcov
3838

3939
- name: Start MySQL Database

.github/workflows/try-installation.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Try Install Package (Laravel 10)
1+
name: Try Install Package (Laravel 10 & 11)
22
env:
33
LOCAL_ENV: ${{ secrets.LOCAL_ENV }}
44

@@ -7,13 +7,13 @@ jobs:
77
strategy:
88
fail-fast: false
99
matrix:
10-
version: [ '^9.0', '^10.0' ]
10+
version: [ '^10.0', '^11.0' ]
1111
runs-on: ubuntu-latest
1212
steps:
1313
- name: Setup PHP with coverage driver
1414
uses: shivammathur/setup-php@v2
1515
with:
16-
php-version: 8.1
16+
php-version: 8.2
1717
coverage: pcov
1818

1919
- name: Setup and install package on Laravel

README.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,33 @@
77
<img src=".github/logo.png" width="200">
88
</p>
99

10-
The inbox pattern is a popular design pattern that ensures:
10+
Talking about distributed computers & servers, it is quite normal nowadays to communicate between servers.
11+
12+
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.
13+
14+
Thus, we have **Inbox Pattern** to help us to achieve that.
15+
16+
## What is the Inbox Pattern
17+
18+
**The Inbox Pattern** is a popular design pattern in the microservice architecture that ensures:
1119

1220
- High availability ✅
1321
- Guaranteed webhook deliverance, no msg lost ✅
1422
- Guaranteed **exactly-once/unique** webhook requests ✅
15-
- Execute webhook requests **in ORDER**
23+
- Execute webhook requests **in ORDER/sequence**
1624
- (Optional) High visibility & debug all prev requests ✅
1725

18-
Laravel Inbox Process (powered by ShipSaaS) ships everything and
26+
And with that being said:
27+
28+
**Laravel Inbox Process (powered by ShipSaaS)** ships everything out-of-the-box and
1929
helps you to roll out the inbox process in no time 😎🚀.
2030

2131
## Supports
2232
- Laravel 10+
2333
- PHP 8.2+
24-
- MySQL 8 and Postgres 13+
34+
- MySQL 8, MariaDB & Postgres 13+
2535

26-
## Architecture
36+
## Architecture Diagram
2737

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

@@ -46,17 +56,19 @@ php artisan migrate
4656

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

49-
Best practices & notes are well documented too 😎!
59+
Best practices, usage & notes are well documented too 😎!
5060

5161
## Testing
5262

5363
Run `composer test` 😆
5464

5565
Available Tests:
5666

57-
- Unit Testing
58-
- Integration Testing against MySQL & PostgreSQL for the `inbox:work` command
59-
- Human validation (lol)
67+
- Unit Testing 💪
68+
- Integration Testing against MySQL & PostgreSQL for the `inbox:work` command 😎
69+
- Human validation (lol) 🔥
70+
71+
ShipSaaS loves tests, we won't ship sh!tty libraries 🌹
6072

6173
## Contributors
6274
- Seth Phat

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "shipsaas/laravel-inbox-process",
33
"type": "library",
4-
"version": "1.0.0",
4+
"version": "1.1.0",
55
"description": "Inbox pattern process implementation for your Laravel Applications",
66
"keywords": [
77
"laravel library",
@@ -24,7 +24,7 @@
2424
"license": "MIT",
2525
"require": {
2626
"php": "^8.2",
27-
"laravel/framework": "^10|dev-master",
27+
"laravel/framework": "^10|^11|dev-master",
2828
"ext-pcntl": "*"
2929
},
3030
"require-dev": {

src/Commands/InboxWorkCommand.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#[AsCommand(name: 'inbox:work')]
1414
class InboxWorkCommand extends Command
1515
{
16-
protected $signature = 'inbox:work {topic} {--limit=10} {--wait=5} {--log=1} {--stop-on-empty}';
16+
protected $signature = 'inbox:work {topic} {--limit=10} {--wait=5} {--log=1} {--stop-on-empty} {--max-processing-time=3600}';
1717
protected $description = '[ShipSaaS Inbox] Start the inbox process';
1818

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

4949
$inboxMessageHandler->setTopic($this->topic);
50+
$inboxMessageHandler->setHandleWriteLog($this->writeTraceLog(...));
5051
$this->runInboxProcess($inboxMessageHandler, $lifecycle);
5152

5253
return 0;
@@ -79,6 +80,9 @@ private function runInboxProcess(
7980
): void {
8081
$limit = intval($this->option('limit')) ?: 10;
8182
$wait = intval($this->option('wait')) ?: 5;
83+
$maxProcessingTime = intval($this->option('max-processing-time')) ?: 3600;
84+
85+
$processNeedToCloseAt = Carbon::now()->timestamp + $maxProcessingTime;
8286

8387
while ($lifecycle->isRunning()) {
8488
$totalProcessed = $inboxMessageHandler->process($limit);
@@ -91,6 +95,12 @@ private function runInboxProcess(
9195
break;
9296
}
9397

98+
if (Carbon::now()->timestamp >= $processNeedToCloseAt) {
99+
$this->writeTraceLog('[Info] Reached max processing time. Closing the process.');
100+
101+
break;
102+
}
103+
94104
$this->writeTraceLog('[Info] No message found. Sleeping...');
95105
sleep($wait);
96106
continue;

src/Entities/InboxMessage.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
class InboxMessage
66
{
77
public int $id;
8+
public string $externalId;
89
public string $rawPayload;
910

1011
public static function make(object $rawDbRecord): InboxMessage
1112
{
1213
$inboxMsg = new InboxMessage();
1314
$inboxMsg->id = intval($rawDbRecord->id);
15+
$inboxMsg->externalId = $rawDbRecord->external_id;
1416
$inboxMsg->rawPayload = $rawDbRecord->payload ?: '{}';
1517

1618
return $inboxMsg;

src/Handlers/InboxMessageHandler.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace ShipSaasInboxProcess\Handlers;
44

5+
use Closure;
56
use Illuminate\Support\Facades\Log;
67
use ShipSaasInboxProcess\Core\Lifecycle;
78
use ShipSaasInboxProcess\Entities\InboxMessage;
@@ -12,6 +13,7 @@
1213
class InboxMessageHandler
1314
{
1415
private string $topic;
16+
private Closure $handleWriteLog;
1517

1618
public function __construct(
1719
private InboxMessageRepository $inboxMessageRepo,
@@ -26,6 +28,13 @@ public function setTopic(string $topic): self
2628
return $this;
2729
}
2830

31+
public function setHandleWriteLog(?Closure $handleWriteLog): self
32+
{
33+
$this->handleWriteLog = $handleWriteLog;
34+
35+
return $this;
36+
}
37+
2938
public function process(int $limit = 10): int
3039
{
3140
$messages = $this->inboxMessageRepo->pullMessages($this->topic, $limit);
@@ -40,15 +49,42 @@ public function process(int $limit = 10): int
4049
}
4150

4251
try {
52+
call_user_func(
53+
$this->handleWriteLog,
54+
sprintf(
55+
'[MsgId: %s] Handling message with externalId: "%s"',
56+
$message->id,
57+
$message->externalId
58+
)
59+
);
60+
4361
$this->processMessage($message);
4462
$processed++;
63+
64+
call_user_func(
65+
$this->handleWriteLog,
66+
sprintf(
67+
'[MsgId: %s] Handled message with externalId: "%s"',
68+
$message->id,
69+
$message->externalId
70+
)
71+
);
4572
} catch (Throwable $e) {
73+
call_user_func(
74+
$this->handleWriteLog,
75+
sprintf(
76+
'[MsgId: %s] Failed to handle message with externalId: "%s" - Process will be aborted',
77+
$message->id,
78+
$message->externalId
79+
)
80+
);
81+
4682
// something really bad happens, we need to stop the process
4783
Log::error('Failed to process inbox message', [
4884
'error' => [
4985
'msg' => $e->getMessage(),
5086
'traces' => $e->getTrace(),
51-
]
87+
],
5288
]);
5389

5490
$this->lifecycle->forceClose();

src/Repositories/InboxMessageRepository.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function pullMessages(string $topic, int $limit = 10): Collection
3434
->where('topic', $topic)
3535
->orderBy('created_at_unix_ms', 'ASC')
3636
->limit($limit)
37-
->get(['id', 'payload'])
37+
->get(['id', 'external_id', 'payload'])
3838
->map(InboxMessage::make(...));
3939
}
4040

tests/Integration/Commands/InboxWorkCommandTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ public function testCommandPullsTheOrderedMsgAndProcessThem()
5454
// 4. validate
5555
$this->assertSame(0, $code);
5656
$this->assertStringContainsString('Processed 3 inbox messages', $result);
57+
58+
$this->assertStringContainsString('Handling message with externalId: "evt_1NWX0RBGIr5C5v4TpncL2sCf"', $result);
59+
$this->assertStringContainsString('Handled message with externalId: "evt_1NWX0RBGIr5C5v4TpncL2sCf"', $result);
60+
61+
$this->assertStringContainsString('Handling message with externalId: "evt_1NWUFiBGIr5C5v4TptQhGyW3"', $result);
62+
$this->assertStringContainsString('Handled message with externalId: "evt_1NWUFiBGIr5C5v4TptQhGyW3"', $result);
63+
64+
$this->assertStringContainsString('Handling message with externalId: "evt_1Nh2fp2eZvKYlo2CzbNockEM"', $result);
65+
$this->assertStringContainsString('Handled message with externalId: "evt_1Nh2fp2eZvKYlo2CzbNockEM"', $result);
66+
5767
$this->assertStringContainsString('[Info] No message found. Stopping...', $result);
5868

5969
Event::assertDispatched(
@@ -135,6 +145,22 @@ public function testCommandThrowsErrorWhenFailedToProcessAMessage()
135145
'topic' => 'with_err_msg',
136146
]);
137147
}
148+
149+
public function testCommandStopsAfterAnAmountOfTime()
150+
{
151+
$beginAt = time();
152+
153+
$code = Artisan::call('inbox:work test --max-processing-time=10');
154+
$result = Artisan::output();
155+
156+
$finishedAt = time();
157+
158+
$this->assertSame(0, $code);
159+
160+
$this->assertStringContainsString('[Info] Reached max processing time. Closing the process.', $result);
161+
162+
$this->assertGreaterThanOrEqual(10, $finishedAt - $beginAt);
163+
}
138164
}
139165

140166
class InvoicePaymentSucceedEvent

tests/Unit/Entities/InboxMessageTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,33 @@ public function testMakeReturnsInboxMessageWithPayload()
1111
{
1212
$inboxMsg = InboxMessage::make((object) [
1313
'id' => 1000,
14+
'external_id' => 'fake-id',
1415
'payload' => '{"hello": "world"}',
1516
]);
1617

1718
$this->assertSame(1000, $inboxMsg->id);
19+
$this->assertSame('fake-id', $inboxMsg->externalId);
1820
$this->assertSame('{"hello": "world"}', $inboxMsg->rawPayload);
1921
}
2022

2123
public function testMakeReturnsInboxMessageWithNoPayload()
2224
{
2325
$inboxMsg = InboxMessage::make((object) [
2426
'id' => 1000,
27+
'external_id' => 'fake-id',
2528
'payload' => null,
2629
]);
2730

2831
$this->assertSame(1000, $inboxMsg->id);
32+
$this->assertSame('fake-id', $inboxMsg->externalId);
2933
$this->assertSame('{}', $inboxMsg->rawPayload);
3034
}
3135

3236
public function testGetParsedPayloadReturnsAnArray()
3337
{
3438
$inboxMsg = InboxMessage::make((object) [
3539
'id' => 1000,
40+
'external_id' => 'fake-id',
3641
'payload' => '{"hello": "world"}',
3742
]);
3843

@@ -46,6 +51,7 @@ public function testGetParsedPayloadReturnsAnEmptyArray()
4651
$inboxMsg = InboxMessage::make((object) [
4752
'id' => 1000,
4853
'payload' => null,
54+
'external_id' => 'fake-id',
4955
]);
5056

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

0 commit comments

Comments
 (0)