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.
- Easy to use out of the box
- Easy to configure
- jms serializer
- Uses MQ out of the box
- Well tested
- Lumen or Laravel 7.x to 8.x
- PHP 7.4 and above
From the command line run
composer require butschster/lumen-microservice
// bootstrap.php
$app->register(Butschster\Exchanger\Providers\ExchangeServiceProvider::class);
$app->configure('amqp');
$app->configure('microservice');
$app->configure('serializer');
MICROSERVICE_NAME=...
MICROSERVICE_VERSION=1.0.0
RABBITMQ_HOST=...
RABBITMQ_USERNAME=...
RABBITMQ_PASSWORD=...
RABBITMQ_VHOST=...
JWT_SECRET=...
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);
}
}
use Butschster\Exchanger\Console\Commands\RunMicroservice;
class Kernel extends ConsoleKernel
{
protected $commands = [
RunMicroservice::class
];
}
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;
}
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);
}
}
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);
}
}
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);
}
);
}
}
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