bowphp / microservice
NestJS-style multi-transport microservice layer for BowPHP (TCP, Redis, RabbitMQ, Kafka, gRPC).
Requires
- php: >=8.1
- php-amqplib/php-amqplib: ^3.7
- psr/log: ^1.1 || ^2.0 || ^3.0
Requires (Dev)
- bowphp/framework: ^5.2
- google/protobuf: ^5.35
- grpc/grpc: ^1.80
- phpunit/phpunit: ^10.0
Suggests
- ext-grpc: Required for the gRPC client transport
- ext-rdkafka: Required for the Kafka transport
- ext-redis: Required for the Redis transport (phpredis)
- ext-sockets: Required for the TCP transport
- google/protobuf: Optional: full protobuf runtime if you generate stubs from proto/microservice.proto
- grpc/grpc: Required for the gRPC client transport (Composer wrapper around ext-grpc)
- php-amqplib/php-amqplib: Required for the RabbitMQ (AMQP) transport
README
A NestJS-style, multi-transport microservice layer for BowPHP. One handler API, five transports: TCP, Redis (pub/sub), RabbitMQ (AMQP), Kafka, and gRPC (client only).
The design mirrors NestJS: you write controllers whose methods carry
#[MessagePattern] (request/response) or #[EventPattern] (fire-and-forget)
attributes, then run a consumer that listens on a transport. Callers use a
ClientProxy with send() / emit(). The transport only moves bytes — your
handlers never change when you switch protocols.
📖 Full user guide: docs/en.md — French version: docs/fr.md
Architecture
The package is a framework-agnostic core with a thin BowPHP adapter:
Contracts/ ServerTransport, ClientTransport, Serializer (the seams)
Message/ Packet, ResponsePacket, JsonSerializer (wire format)
Consumer/ MicroserviceServer, HandlerRegistry,
MessagePattern, EventPattern, MicroserviceFactory
Client/ ClientProxy, ClientFactory
Transport/ {Tcp,Redis,RabbitMq,Kafka}{Server,Client}Transport
GrpcClientTransport + Grpc/ (envelope, stub, codec)
Bow/ MicroserviceConfiguration (the ONLY Bow-coupled file)
Every transport implements the same two contracts, so adding a sixth (NATS, MQTT, etc.) means writing one server + one client class — nothing else moves.
The envelope
All transports share one packet shape (like Nest's { pattern, data, id }):
- RPC: request
Packet→ handler return value →ResponsePacket, matched back to the caller byid. - Event:
Packetwith no reply.
How each transport correlates replies:
| Transport | RPC reply mechanism |
|---|---|
| TCP | same socket, 4-byte length-prefixed JSON frames |
| Redis | PUBLISH on pattern → RPUSH on bow:reply:<id> |
| RabbitMQ | reply_to queue + correlation_id |
| Kafka | reply topic + kafka_correlationId header |
| gRPC | unary MessageService.Send returns MessageEnvelope |
Install
composer require bowphp/microservice
Then install the extension/library for the transport(s) you use:
- TCP →
ext-sockets(built in on most PHP) - Redis →
ext-redis(phpredis) - RabbitMQ →
composer require php-amqplib/php-amqplib - Kafka →
ext-rdkafka - gRPC (client) →
pecl install grpc && composer require grpc/grpc google/protobuf
Define handlers
use Bow\Microservice\Consumer\MessagePattern; use Bow\Microservice\Consumer\EventPattern; use Bow\Microservice\Message\Packet; final class UserController { #[MessagePattern('user.find')] public function find(mixed $data, Packet $packet): array { return ['id' => $data['id'], 'name' => "User #{$data['id']}"]; } #[EventPattern('user.created')] public function onCreated(mixed $data): void { // send welcome email, etc. } }
Run the consumer
Two equivalent entrypoints — the BowPHP-integrated console command (recommended) and a standalone script:
# BowPHP console command — registered automatically by MicroserviceConfiguration php bow microservice:listen --transport=redis --patterns=user.find,user.created php bow microservice:listen --transport=tcp --host=0.0.0.0 --port=3000 php bow microservice:listen --transport=rabbitmq --queue=bow_microservice php bow microservice:listen --transport=kafka --topics=user_events --group=users # Standalone script — no Bow integration required php examples/microservice.php --transport=redis --patterns=user.find
Both honour config/microservice.php; CLI flags override config. They install
SIGTERM/SIGINT handlers (when ext-pcntl is available) so supervisord /
systemd / Kubernetes can drain a worker cleanly. Run N copies for concurrency.
Call from another service
use Bow\Microservice\Client\ClientFactory; $proxy = ClientFactory::create('redis', ['host' => '127.0.0.1']); $proxy->connect(); $user = $proxy->send('user.find', ['id' => 42]); // RPC, blocks for reply $proxy->emit('user.created', ['id' => 99]); // fire-and-forget // gRPC client to a non-PHP server implementing proto/microservice.proto $grpc = ClientFactory::create('grpc', ['host' => '127.0.0.1', 'port' => 50051]); $grpc->connect(); $grpc->send('user.find', ['id' => 42]);
BowPHP integration
Register the configuration provider in your Kernel so a connected ClientProxy
is bound in the container:
// app/Kernel.php public function configurations(): array { return [ \Bow\Microservice\Bow\MicroserviceConfiguration::class, ]; }
Add config/microservice.php to your app (see this repo's
config/microservice.php for the template). The
provider binds both ClientProxy::class and the microservice.client alias.
Both consumer entrypoints (php bow microservice:listen and
php examples/microservice.php) boot the kernel and instantiate controllers
through Bow's container — so your consumers can use constructor DI just like
HTTP controllers. List them in config('microservice.controllers') or pass
--controllers=A,B on the CLI. The standalone script also accepts
--kernel= (or MICROSERVICE_KERNEL) to point at a custom Kernel class.
Notes & limits
- The TCP server handles one connection at a time per process — run multiple workers behind a balancer, or swap in an event-loop driver later (the framing is unchanged).
- Kafka has no native RPC; the reply-topic approach matches NestJS. For pure
event streaming, use
emit()only. - The default serializer is JSON. Implement
Serializerfor msgpack/protobuf. - Handler exceptions become an error
ResponsePacket; the client re-throws them asRpcException. Events swallow-and-log errors (no caller to notify). - gRPC is client-only. The
grpcPHP extension provides no server API and a production-grade PHP gRPC server requires RoadRunner or Swoole, neither of which fits the single-process consumer model here. Implement the server side in any language with a real gRPC server following proto/microservice.proto — PHP services call it throughGrpcClientTransport.