Skip to content

Feat: temporal worker#166

Open
crazy-weasel wants to merge 8 commits intoBaldinof:3.xfrom
crazy-weasel:feat/implement-temporal
Open

Feat: temporal worker#166
crazy-weasel wants to merge 8 commits intoBaldinof:3.xfrom
crazy-weasel:feat/implement-temporal

Conversation

@crazy-weasel
Copy link
Contributor

This is based on #147

Changes made:

  • rebased to current version
  • support for ScheduleClient
  • WorkerStarted/Ended events added
  • interceptors for temporal workers:
    • DoctrineORMInterceptor
    • RebootKernelInterceptor
  • OnExceptionRebootStrategy listens to WorkerException for triggering a kernel-reboot
  • Activity-factory retrieves activities always from the current container

Some of my thoughts can be read in the original PR, starting here: #147 (comment)

Things that still need to be changed (but I'd like some feedback on):

  • The configured DataConverers are registered as services automatically (here). Sounds convinient, but I guess for the bundle it would be better to explicitly register the DataConverters coming from the temporal-sdk and just using them like in the InterceptorCompilerPass (with a check if the service is registered at all). Bundle-users should register their own services, unless a attribute or interface for autoconfiguring exists? (like for the activities)

I'd like to use the bundle and this feature in an upcoming project, so I'm very motivated to bring it up to your standards and your feedback is highly appreciated.

praswicaksono and others added 8 commits March 29, 2025 19:26
renames ClientFactory to WorkflowClientFactory
adds ScheduleClientFactory
fixes typo
removes redundant code
- adding two default interceptors for using the configured reboot-strategie and for Doctrine ORM
- registering activities with a factory that fetches the activities from the DI container. that way, new activity-instances are created when the kernel was rebooted.
- dispatching events similar to the other workers
- OnExceptionRebootStrategy now listens to the WorkerExceptionEvent
@rela589n
Copy link

@Baldinof , I'd like to review this

@Baldinof
Copy link
Owner

Thanks! Please, go ahead

@vladdnepr
Copy link

Hi. Any news? I want to use temporal with this bundle

Comment on lines +63 to 64
"temporal/sdk": "^2.10"
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any particular reason for not using ^2.0?

}

if (class_exists(WorkflowInterface::class)) {
$services->set('temporal.exception_interceptor', ExceptionInterceptor::class)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't ExceptionInterceptorInterface be used instead?

Suggested change
$services->set('temporal.exception_interceptor', ExceptionInterceptor::class)
$services->set('temporal.exception_interceptor', ExceptionInterceptorInterface::class)

$container->addCompilerPass(new InterceptorCompilerPass());
}

if (interface_exists(WorkflowClientInterface::class) && class_exists(WorkflowInterface::class)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one check would be enough
It shouldn't be possible that one of these names exists, while the other doesn't

->addTag('baldinof.roadrunner.grpc_service');
}

if (interface_exists(WorkflowClientInterface::class) && class_exists(WorkflowInterface::class)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess one check should be enough (WorkflowInterface)

Comment on lines +273 to +274
'',
[ // Symfony overrides the first argument with the DSN, so we pass an empty string

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'',
[ // Symfony overrides the first argument with the DSN, so we pass an empty string
'', // Symfony overrides the first argument with the DSN, so we pass an empty string
[

Comment on lines +116 to +122
->defaultValue([
NullConverter::class,
BinaryConverter::class,
ProtoJsonConverter::class,
JsonConverter::class,
])->scalarPrototype()->end()
->end()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like this approach of defining data_converters as array.

Consider if somebody would like to develop their own library, and would like to define one more data converted.

Would they be able to do it?

Comment on lines +20 to 22
use DoctrineORMTraits;

public function __construct(ManagerRegistry $managerRegistry, ContainerInterface $container, EventDispatcherInterface $eventDispatcher, LoggerInterface $logger)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Composition would be better than "traiting"

namespace Baldinof\RoadRunnerBundle\Temporal\Attributes;

#[\Attribute(\Attribute::TARGET_CLASS)]
class AssignToWorker

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not making it final?

Comment on lines +53 to +54
$this->dependencies->getEventDispatcher()->dispatch(new WorkerKernelRebootedEvent());
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably service reset should be taken into account as well

     } elseif ($this->kernel->getContainer()->has('services_resetter')) {
          /** @var ResetInterface $resetter */
          $resetter = $this->kernel->getContainer()->get('services_resetter');
          $resetter->reset();
      }

$workerOptions = $workerOptions->withMaxConcurrentActivityExecutionSize((int) $options['max_concurrent_activity_execution_size']);
}

if (\array_key_exists('worker_activities_per_second', $options)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are all these key array_key_exists checks necessary? all of these configurations have the default value, so what's the purpose of checking it all?

$container->register("temporal.client.{$name}.factory", WorkflowClientFactory::class)
->setArguments([
'$serviceClient' => new Reference("temporal.client.{$name}.service_client"),
'$dataConverter' => new Reference('temporal.data_converter'),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would also make sense to have data converter configurable per client

@rela589n
Copy link

@crazy-weasel , it seems that composer ci reports some CI errors

@crazy-weasel
Copy link
Contributor Author

Hi @rela589n thanks for the review :) I can't give you an explanation on all choices, as I just expanded on someones code, but I try to if an explanation is necessary and/or incorporate your changes / make the code better.

@crazy-weasel
Copy link
Contributor Author

Hi, I just want to let you all know, that I did not forget about the review. Unfortunately, I got sick and recovery took some time.. I'll get to it soon, after I finish some work related backlog :) Thanks for your patience!

@Baldinof
Copy link
Owner

Hi, I just want to let you all know, that I did not forget about the review. Unfortunately, I got sick and recovery took some time.. I'll get to it soon, after I finish some work related backlog :) Thanks for your patience!

It's alright, take care!

I did not had much time for this repo lately too :(

@vansante
Copy link

I have been following this PR with some interest as I am working on a Temporal project, and have thus been evaluating this bundle with this feature.

I ran however into a bug in the latest commit of this PR, whenever I am trying to execute an Activity, I run into a ReflectionException:

./var/log/dev.log:[2025-07-11T13:24:34.033166+00:00] app.ERROR: An error occured: Given object is not an instance of the class this method was declared in {"throwable":"[object] (BadMethodCallException(code: 0): Given object is not an instance of the class this method was declared in at /app/vendor/temporal/sdk/src/Internal/Declaration/Dispatcher/Dispatcher.php:101)
[previous exception] [object] (ReflectionException(code: 0): Given object is not an instance of the class this method was declared in at /app/vendor/temporal/sdk/src/Internal/Declaration/Dispatcher/Dispatcher.php:99)"} []

This error went away when I went to a commit earlier (23c05a054d6fb868df5a9c613a302bed5c6b06c7). The workflow worked fine under that revision.

It seems introducing the activity factories introduced a bug which is tripping up the Dispatcher reflection logic.

@versh23
Copy link

versh23 commented Dec 3, 2025

hi! any updates?

@xepozz
Copy link
Contributor

xepozz commented Feb 17, 2026

Any updates?

@rela589n
Copy link

Guys, you are the driving force of updates 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants