Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WEB-4286: Event Machine: Reducing the size of context field #72

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 74 additions & 7 deletions src/Actor/Machine.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,37 @@
*/
public function persist(): ?State
{
// Retrieve the previous context from the definition's config, or set it to an empty array if not set.
$incrementalContext = $this->definition->initializeContextFromState()->toArray();

// Get the last event from the state's history.
$lastHistoryEvent = $this->state->history->last();

MachineEvent::upsert(
values: $this->state->history->map(fn (MachineEvent $machineEvent) => array_merge($machineEvent->toArray(), [
'created_at' => $machineEvent->created_at->toDateTimeString(),
'machine_value' => json_encode($machineEvent->machine_value, JSON_THROW_ON_ERROR),
'payload' => json_encode($machineEvent->payload, JSON_THROW_ON_ERROR),
'context' => json_encode($machineEvent->context, JSON_THROW_ON_ERROR),
'meta' => json_encode($machineEvent->meta, JSON_THROW_ON_ERROR),
]))->toArray(),
values: $this->state->history->map(function (MachineEvent $machineEvent, int $index) use (&$incrementalContext, $lastHistoryEvent) {
// Get the context of the current machine event.
$changes = $machineEvent->context;

// If the current machine event is not the last one, compare its context with the incremental context and get the differences.
if ($machineEvent->id !== $lastHistoryEvent->id && $index > 0) {
$changes = $this->arrayRecursiveDiff($changes, $incrementalContext);
}

// If there are changes, update the incremental context to the current event's context.
if (!empty($changes)) {
$incrementalContext = $this->arrayRecursiveMerge($incrementalContext, $machineEvent->context);
}

$machineEvent->context = $changes;

return array_merge($machineEvent->toArray(), [
'created_at' => $machineEvent->created_at->toDateTimeString(),
'machine_value' => json_encode($machineEvent->machine_value, JSON_THROW_ON_ERROR),
'payload' => json_encode($machineEvent->payload, JSON_THROW_ON_ERROR),
'context' => json_encode($machineEvent->context, JSON_THROW_ON_ERROR),
'meta' => json_encode($machineEvent->meta, JSON_THROW_ON_ERROR),
]);
})->toArray(),
uniqueBy: ['id']
);

Expand Down Expand Up @@ -369,7 +392,7 @@
->state
->history
->filter(fn (MachineEvent $machineEvent) => $machineEvent->sequence_number > $lastPreviousEventNumber)
->filter(fn (MachineEvent $machineEvent) => preg_match("/{$machineId}\.guard\..*\.fail/", $machineEvent->type))

Check failure on line 395 in src/Actor/Machine.php

View workflow job for this annotation

GitHub Actions / larastan

Parameter #1 $callback of method Illuminate\Support\Collection<(int|string),mixed>::filter() expects (callable(mixed, int|string): bool)|null, Closure(Tarfinlabs\EventMachine\Models\MachineEvent): (0|1|false) given.
->filter(function (MachineEvent $machineEvent) {
$failedGuardType = explode('.', $machineEvent->type)[2];
$failedGuardClass = $this->definition->behavior[BehaviorType::Guard->value][$failedGuardType];
Expand Down Expand Up @@ -471,10 +494,54 @@
}

/* @var \Tarfinlabs\EventMachine\Behavior\ResultBehavior $resultBehavior */
return $resultBehavior(

Check failure on line 497 in src/Actor/Machine.php

View workflow job for this annotation

GitHub Actions / larastan

Trying to invoke (callable(): mixed)|object but it might not be a callable.
$this->state->context,
$this->state->currentEventBehavior,
$arguments ?? null,
);
}

// region Private Methods
/**
* Compares two arrays recursively and returns the difference.
*/
private function arrayRecursiveDiff(array $array1, array $array2): array
{
$difference = [];
foreach ($array1 as $key => $value) {
if (is_array($value)) {
if (!isset($array2[$key]) || !is_array($array2[$key])) {
$difference[$key] = $value;
} else {
$new_diff = $this->arrayRecursiveDiff($value, $array2[$key]);
if (!empty($new_diff)) {
$difference[$key] = $new_diff;
}
}
} elseif (!array_key_exists($key, $array2) || $array2[$key] !== $value) {
$difference[$key] = $value;
}
}

return $difference;
}

/**
* Merges two arrays recursively.
*/
protected function arrayRecursiveMerge(array $array1, array $array2): array
{
$merged = $array1;

foreach ($array2 as $key => &$value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = $this->arrayRecursiveMerge($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}

return $merged;
}
// endregion
}
64 changes: 64 additions & 0 deletions tests/EventStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

declare(strict_types=1);

use Tarfinlabs\EventMachine\Actor\Machine;
use Tarfinlabs\EventMachine\ContextManager;
use Tarfinlabs\EventMachine\Definition\EventDefinition;
use Tarfinlabs\EventMachine\Definition\MachineDefinition;
Expand Down Expand Up @@ -159,3 +160,66 @@
'in.transition.active.MUT.fail',
]);
});

it('stores incremental context', function (): void {
$machine = Machine::create([
'config' => [
'id' => 'traffic_light',
'initial' => 'green',
'context' => [
'count' => 1,
'value' => 'test',
],
'states' => [
'green' => [
'on' => [
'GREEN_TIMER' => [
'target' => 'yellow',
'actions' => 'changeCount',
],
],
],
'yellow' => [
'on' => [
'RED_TIMER' => [
'target' => 'red',
'actions' => 'changeValue',
],
],
],
'red' => [],
],
],
'behavior' => [
'actions' => [
'changeCount' => function (ContextManager $context): void {
$context->set('count', $context->get('count') + 1);
},
'changeValue' => function (ContextManager $context): void {
$context->set('value', 'retry');
},
],
],
]);

$machine->send(event: [
'type' => 'GREEN_TIMER',
]);

$newState = $machine->send(event: [
'type' => 'RED_TIMER',
]);

expect($newState->history)
->whereNotIn('type', [
'traffic_light.start',
'traffic_light.action.changeCount.finish',
'traffic_light.action.changeValue.finish',
'traffic_light.state.red.entry.finish',
])->each(fn ($event) => $event->context->toEqual([]))
->first()->context->toEqual(['data' => ['count' => 1, 'value' => 'test']])
->where('type', 'traffic_light.action.changeCount.finish')->first()->context->toEqual(['data' => ['count' => 2]])
->where('type', 'traffic_light.action.changeValue.finish')->first()->context->toEqual(['data' => ['value' => 'retry']])
->last()->context->toEqual(['data' => ['count' => 2, 'value' => 'retry']]);

});
Loading