Membangun microservices dengan Swoole: memahami protobuf dan HTTP/2 untuk gRPC server dan client, JSON-RPC sebagai alternatif ringan, serta pola service discovery agar service bisa saling menemukan.

Setelah di episode 15 kita memilih framework — pada episode kali ini kita membangun layer komunikasi antar service: microservices. Di dunia nyata, sistem tidak lagi satu monolith besar; mereka dipecah menjadi service-service kecil yang saling memanggil dengan protokol yang efisien.
Mengapa episode ini penting? Karena REST + JSON itu nyaman tapi boros: parsing teks, overhead HTTP header, dan tanpa schema. gRPC menawarkan schema kuat (protobuf), binary payload, dan streaming — dengan performa jauh lebih baik untuk traffic antar service. Swoole mendukungnya langsung.
gRPC dibangun di atas dua teknologi:
| Lapisan | Fungsi |
|---|---|
| Protocol Buffers (protobuf) | Bahasa definisi schema + serialisasi binary |
| HTTP/2 | Multiplexing, binary framing, streaming |
Keunggulan utamanya: schema didefinisikan sekali (.proto), dan dari schema itu di-generate kode client & server di semua bahasa. Client dan server tidak bisa "salah asumsi" tentang bentuk data.
syntax = "proto3";
package greeter;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}Dari file ini, kalian generate kode PHP dengan plugin protoc:
protoc --php_out=./generated greeter.protoSwoole menyediakan server gRPC lewat mode HTTP/2. Aktifkan open_http2_protocol, lalu tangani frame gRPC di on('Request'):
<?php
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
$server = new Server('0.0.0.0', 50051, SWOOLE_PROCESS);
$server->set([
'open_http2_protocol' => true,
'worker_num' => 4,
]);
$server->on('Request', function (Request $req, Response $res) {
$path = $req->server['request_uri'];
if ($path === '/greeter.Greeter/SayHello') {
// baca frame gRPC: 1-byte compressed flag + 4-byte length + payload
$payload = substr($req->getContent(), 5);
$data = json_decode($payload, true);
$reply = json_encode(['message' => 'Halo, ' . ($data['name'] ?? 'dunia') . '!']);
$frame = pack('N1', 0) . $reply;
$res->header('content-type', 'application/grpc');
$res->header('grpc-status', '0');
$res->header('grpc-message', '');
$res->end($frame);
} else {
$res->header('grpc-status', '12'); // UNIMPLEMENTED
$res->end();
}
});
$server->start();Format frame gRPC wajib dipahami: 1 byte flag kompresi, 4 byte panjang (big-endian), lalu payload. Client yang kita tulis di episode 9 memakai format yang sama:
$grpc = new Coroutine\Http2\Client('127.0.0.1', 50051);
$grpc->connect();
$req = new Coroutine\Http2\Request;
$req->method = 'POST';
$req->path = '/greeter.Greeter/SayHello';
$req->headers['content-type'] = 'application/grpc';
$req->data = pack('N1', 0) . pack('N1', 0) . '{"name":"Andi"}';
$grpc->send($req);
$resp = $grpc->recv();
echo $resp->data; // frame gRPC: {message: "Halo, Andi!"}Tip
Untuk produksi, jangan tulis parser gRPC manual — gunakan library siap pakai seperti grpc/grpc + generated code, spiral/roadrunner-grpc, atau framework Hyperf yang menyediakan hyperf/grpc-server. Kode manual di atas hanya untuk memahami mekanisme yang sebenarnya terjadi.
Tidak semua komunikasi internal butuh protobuf. JSON-RPC adalah protokol RPC berbasis JSON yang jauh lebih sederhana — cukup TCP/HTTP dan format request yang terstandar:
{"jsonrpc": "2.0", "method": "UserService.getById", "params": [42], "id": 1}Implementasi server TCP dengan Swoole:
<?php
use Swoole\Server;
$server = new Server('0.0.0.0', 9503);
$server->set(['open_length_check' => true, 'package_max_length' => 81920]);
$server->on('Receive', function (Server $server, $fd, $reactorId, $data) {
$rpc = json_decode($data, true);
$result = match ($rpc['method'] ?? '') {
'UserService.getById' => ['id' => $rpc['params'][0] ?? 0, 'name' => 'Andi'],
'UserService.list' => [['id' => 1], ['id' => 2]],
default => null,
};
$reply = ['jsonrpc' => '2.0', 'id' => $rpc['id'] ?? null];
if ($result === null) {
$reply['error'] = ['code' => -32601, 'message' => 'Method not found'];
} else {
$reply['result'] = $result;
}
$server->send($fd, json_encode($reply));
});
$server->start();JSON-RPC populer di ekosistem PHP (Hyperf memakainya sebagai protokol RPC default) karena sederhana, mudah di-debug, dan cukup cepat untuk traffic internal skala sedang.
Microservices harus saling menemukan satu sama lain. Dua pendekatan umum:
Registry-based: service mendaftarkan diri ke registry (Consul, etcd, atau Redis) saat start, dan client bertanya ke registry untuk mendapatkan alamat.
$server->on('WorkerStart', function ($server, $workerId) use ($redis) {
if ($workerId === 0) {
$redis->hSet('svc:user-service', 'instances', json_encode([
'host' => '10.0.0.5',
'port' => 50051,
]));
}
});Load balancer statis: alamat service di-hardcode di config (env SERVICE_URL), lalu diputar di client dengan round-robin.
| Pendekatan | Kelebihan | Kekurangan |
|---|---|---|
| Registry | Auto scaling, health check terpusat | Infra tambahan (Consul/etcd) |
| Statis | Simpel | Tidak adaptif saat instance naik/turun |
Untuk skala kecil, mulai dengan statis; pindah ke registry saat instance sudah puluhan.
| Masalah | Penyebab | Solusi |
|---|---|---|
UNIMPLEMENTED saat panggil | Path gRPC salah (/package.Service/Method) | Cek path lengkap di client & server |
| Frame gRPC kebaca rusak | Salah hitung panjang frame | Format: 1 byte flag + 4 byte length big-endian + payload |
JSON-RPC balasan tanpa id | Request tidak punya id | Selalu sertakan id untuk matching request-response |
| Service tidak ketemu | Registry tidak update saat instance mati | Aktifkan health check / TTL pada registry |
Pada episode 16 ini, kalian telah membangun komunikasi microservices dengan Swoole.
Inti yang harus dibawa pulang:
Di episode 17 selanjutnya, kita mengamankan server: Keamanan Server & Hardening — input validation di layer server, limit connection, timeouts, dan proteksi terhadap Slowloris & connection flooding. Sampai jumpa di episode 17!