butschster / lumen-microservice
This Package will help you to use Lumen framework as a microservice with AMQP and JMS Serializer without extra knowledge.
Requires
- php: ^7.4
- ext-json: *
- firebase/php-jwt: ^5.2
- illuminate/config: ^7.0|^8.0
- illuminate/console: ^7.0|^8.0
- illuminate/contracts: ^7.0|^8.0
- illuminate/pipeline: ^7.0|^8.0
- illuminate/support: ^7.0|^8.0
- illuminate/validation: ^7.0|^8.0
- jms/serializer: ^3.9
- nunomaduro/collision: ^5.0
- php-amqplib/php-amqplib: ^2.12
- phpdocumentor/reflection-docblock: ^5.2
- psr/log: ^1.1
- react/event-loop: ^1.1
- react/promise: ^2.8
- webmozart/assert: ^1.8
- zircote/swagger-php: ^3.0
Requires (Dev)
- mockery/mockery: ^1.0
- phpunit/phpunit: ^8.0
- symfony/var-dumper: ^5.2
This package is auto-updated.
Last update: 2024-10-20 22:25:40 UTC
README
This Package can help you to use Lumen framework or Laravel as a microservice without extra knowledge. Just install package, create Exchange Point class and start exchanging information between services through MQ.
This package uses JMS\Serializer for message serialization.
P.S. You can use this service for testing https://www.cloudamqp.com. They have a free plan.
Features
- Easy to use out of the box
- Easy to configure
- jms serializer
- Uses MQ out of the box
- Well tested
Requirements
- Lumen or Laravel 7.x to 8.x
- PHP 7.4 and above
Installation and Configuration
From the command line run
composer require butschster/lumen-microservice
Register Service provider
// bootstrap.php $app->register(Butschster\Exchanger\Providers\ExchangeServiceProvider::class);
Copy config files from package config
directory to your Lumen app and register them.
$app->configure('amqp'); $app->configure('microservice'); $app->configure('serializer');
Add variables to your .env file
MICROSERVICE_NAME=...
MICROSERVICE_VERSION=1.0.0
RABBITMQ_HOST=...
RABBITMQ_USERNAME=...
RABBITMQ_PASSWORD=...
RABBITMQ_VHOST=...
JWT_SECRET=...
Create exchange point in your app
This point will use for receiving request from other services and for sending responses.
namespace App\Exchange\Point; use Butschster\Exchanger\Contracts\Exchange\Point as PointContract; use Psr\Log\LoggerInterface; use Butschster\Exchanger\Contracts\Exchange\IncomingRequest; class Point implements PointContract { public function getName(): string { return 'com.test'; } /** * @subject com.test.action.test */ public function testSubject(IncomingRequest $request, LoggerInterface $logger) { $logger->info( sprintf('info [%s]:', $request->getSubject()), [$request->getBody()] ); // Response $payload = new ...; $request->sendResponse($payload); // or $request->sendEmptyResponse(); // or $request->acknowledge(); } /** * @subject com.test.action.test1 */ public function anotherTestSubject(IncomingRequest $request, LoggerInterface $logger) { $payload = new ...; $request->sendResponse($payload); } }
Then register this point
use App\Exchange\Point; use Illuminate\Support\ServiceProvider; use Butschster\Exchanger\Contracts\Exchange\Point as PointContract; class AppServiceProvider extends ServiceProvider { public function register() { $this->app->singleton(PointContract::class, Point::class); } }
Register console command
use Butschster\Exchanger\Console\Commands\RunMicroservice; class Kernel extends ConsoleKernel { protected $commands = [ RunMicroservice::class ]; }
Sending requests
For requesting information from another service you should use ExchangeManager
use Butschster\Exchanger\Contracts\ExchangeManager; class UserController { public function getUser(ExchangeManager $manager, int $userId) { $requestPayload = new GetUserByIdPayload($userId); $request = $manager->request('com.test.action.test', $requestPayload); $user = $request->send(UserPayload::class); // You can set delivery mode to persistent $user = $request->send(UserPayload::class, true); } } // User Request Payload use JMS\Serializer\Annotation\Type; class GetUserByIdPayload implements \Butschster\Exchanger\Contracts\Exchange\Payload { /** @Type("string") */ public string $userId; public function __construct(string $userId) { $this->userId = $userId; } } // User Payload use JMS\Serializer\Annotation\Type; class UserPayload implements \Butschster\Exchanger\Contracts\Exchange\Payload { /** @Type("string") */ public string $id; /** @Type("string") */ public string $username; /** @Type("string") */ public string $email; /** @Type("Carbon\Carbon") */ public \Carbon\Carbon $createdAt; }
Broadcasting
If you want to send an event you should use ExchangeManager
method broadcast
use Illuminate\Http\Request; use Butschster\Exchanger\Contracts\ExchangeManager; class UserController { public function update(ExchangeManager $manager, Request $request, User $user) { $payload = new UserPayload(); $data = $request->validate(...); $user->update($data); $payload->id = $user->id; $payload->username = $user->username; ... $manager->broadcast('com.user.event.updated', $payload); // You can set delivery mode to persistent $manager->broadcast('com.user.event.updated', $payload, true); } }
Entity mapping
You can configure entity mapping in serializer.php
config.
// serializer.php return [ 'mapping' => [ Domain\User\Entities\User::class => [ 'to' => Payloads\User::class, 'attributes' => [ 'id' => ['type' => 'string'], 'username' => ['type' => 'string'], 'email' => ['type' => 'string'], 'balance' => ['type' => Domain\Billing\Money\Token::class], 'createdAt' => ['type' => \Carbon\Carbon::class], ] ], Domain\Billing\Money\Token::class => [ 'to' => Payloads\Billing\Token::class, 'attributes' => [ 'amount' => ['type' => \Brick\Math\BigDecimal::class], ] ], ], // ... ];
And then you can easily convert entities to payloads
use Butschster\Exchanger\Contracts\Serializer\ObjectsMapper; class UserController { public function update(ObjectsMapper $mapper, ExchangeManager $manager, Request $request, Domain\User\Entities\User $user) { $data = $request->validate(...); $user->update($data); $payload = $mapper->toPayload($user); $manager->broadcast('com.user.event.updated', $payload); } }
Custom types
If you want to use custom JMS Serializer types you should use handlers. They can be added in serializer.php
config.
// serializer.php return [ 'handlers' => [ Butschster\Exchanger\Jms\Handlers\CarbonHandler::class, Infrastructure\Microservice\Jms\Handlers\BigDecimalHandler::class ], // .. ]; // BigDecimalHandler.php namespace Infrastructure\Microservice\Jms\Handlers; use Brick\Math\BigDecimal; use Butschster\Exchanger\Contracts\Serializer; use Butschster\Exchanger\Contracts\Serializer\Handler; use JMS\Serializer\GraphNavigatorInterface; use JMS\Serializer\Handler\HandlerRegistryInterface; class BigDecimalHandler implements Handler { public function serialize(Serializer $serializer, HandlerRegistryInterface $registry): void { $registry->registerHandler( GraphNavigatorInterface::DIRECTION_SERIALIZATION, BigDecimal::class, 'json', function ($visitor, BigDecimal $value, array $type) { return $value->toInt(); } ); } public function deserialize(Serializer $serializer, HandlerRegistryInterface $registry): void { $registry->registerHandler( GraphNavigatorInterface::DIRECTION_DESERIALIZATION, BigDecimal::class, 'json', function ($visitor, $value, array $type) { return BigDecimal::of($value); } ); } }
Service running
This single command will run your microservice and start listening to commands registered in your exchange point.
php artisan service:run
Supervisor is a process monitor for the Linux operating system, and will automatically restart your horizon process if it fails. To install Supervisor on Ubuntu, you may use the following command:
sudo apt-get install supervisor
Supervisor configuration files are typically stored in the /etc/supervisor/conf.d directory. Within this directory, you may create any number of configuration files that instruct supervisor how your processes should be monitored. For example, let's create a microservice.conf file that starts and monitors a process:
[program:microservice]
process_name=%(program_name)s
command=php /var/www/app.com/artisan service:run
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/www/app.com/storage/logs/microservice.log
stopwaitsecs=3600
Once the configuration file has been created, you may update the Supervisor configuration and start the processes using the following commands:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start microservice