MethodBus adalah library PHP berbasis Service Bus Architecture dengan pendekatan Single-Endpoint Gateway.
MethodBus menerima seluruh request melalui satu endpoint terpusat, melakukan validasi keamanan berlapis, lalu mendistribusikan request ke plugin sesuai routing method.
Library ini dirancang untuk:
- API Gateway
- Internal Service Communication
- Modular Monolith
- Plugin-Based Application
- Microservice Architecture
- Service Oriented Architecture (SOA)
- Single Endpoint Gateway
- Header-Based Routing
- Plugin Versioning
- Dependency Injection Container
- Constructor Injection
- Middleware Pipeline
- Lazy Plugin Resolution
- Replay Attack Protection
- Timestamp Validation
- Nonce Validation
- HMAC SHA256 Signature
- HTTP & CLI Support
- Auto Plugin Discovery
- Versioned API Documentation
| Komponen | Fungsi |
|---|---|
| HTTP Gateway | Gerbang utama request HTTP |
| Kernel | Orchestrator lifecycle request |
| Pipeline | Middleware execution chain |
| Plugin Manager | Registry metadata plugin |
| Container | Dependency Injection resolver |
| Plugin Runner | Menjalankan plugin runtime |
| OpenApi Generator | Generator dokumentasi otomatis |
MethodBus menggunakan konfigurasi berbasis file config/security.php:
<?php
return [
'auth' => [
'enabled' => true,
'apikey' => 'secret-key-123'
],
'signature' => [
'enabled' => true,
'secret' => 'super-secret-signature-key'
],
'replay' => [
'enabled' => true,
'ttl' => 30
]
];MethodBus menggunakan beberapa lapisan keamanan:
| Security Layer | Fungsi |
|---|---|
| API Key | Autentikasi client |
| Signature | Validasi integritas payload |
| Timestamp | Mencegah request kadaluarsa |
| Nonce | Mencegah replay request |
| Replay Protection | Menolak request duplikat |
Semua request HTTP maupun CLI wajib mengirim header berikut:
| Header | Wajib | Keterangan |
|---|---|---|
| Content-Type | Ya | application/json |
| X-Method | Ya | Format: namespace:action:version |
| X-ApiKey | Ya | API authentication key |
| X-Timestamp | Ya | Unix timestamp |
| X-Nonce | Ya | Unique random identifier |
| X-Signature | Ya | HMAC SHA256 signature |
| X-Request-ID | Optional | Request tracing |
MethodBus menggunakan HMAC SHA256 untuk memastikan payload tidak dimodifikasi saat transit.
Signature dibentuk dari:
json(sorted_payload) + timestamp + nonce
Setelah key diurutkan kemudian di-hash menggunakan shared secret yang sama dengan server.
Secret client dan server wajib sama.
Contoh konfigurasi:
'signature' => [
'enabled' => true,
'secret' => 'super-secret-signature-key'
]Timestamp digunakan untuk mencegah request lama digunakan ulang.
Contoh:
$timestamp = (string) time();TTL request diatur melalui:
'replay' => [
'enabled' => true,
'ttl' => 30
]Artinya request hanya valid selama:
30 detik
Nonce adalah identifier unik per request.
Nonce digunakan untuk mencegah replay attack.
Contoh:
$nonce = bin2hex(
random_bytes(16)
);Setiap request wajib menggunakan nonce baru.
ksort($payload);
$message =
json_encode($payload)
. $timestamp
. $nonce;
$signature = hash_hmac(
'sha256',
$message,
'super-secret-signature-key'
);<?php
require __DIR__ . '/vendor/autoload.php';
use MethodBus\Core\Http;
use MethodBus\Core\MethodBus;
$bus = new MethodBus();
$http = new Http($bus);
$http->handle();<?php
require __DIR__ . '/vendor/autoload.php';
use MethodBus\Core\Http;
use MethodBus\Core\MethodBus;
$bus = new MethodBus();
$http = new Http($bus);
$http
->endpoint('/api')
->methodHeader('X-Method')
->handle();<?php
require __DIR__ . '/../vendor/autoload.php';
use MethodBus\Core\MethodBus;
$bus = new MethodBus();
$payload = ['a' => 10, 'b' => 20];
$timestamp = (string) time();
$nonce = bin2hex( random_bytes(16));
function sign(array $payload, string $timestamp, string $nonce): string {
ksort($payload);
$message = json_encode($payload) . $timestamp . $nonce;
return hash_hmac(
'sha256',
$message,
'super-secret-signature-key'
);
}
$context = [
'headers' => [
'Content-Type' => 'application/json',
'X-ApiKey' => 'secret-key-123',
'X-Timestamp' => $timestamp,
'X-Nonce' => $nonce,
'X-Signature' => sign(
$payload,
$timestamp,
$nonce
),
'X-Request-ID' => 'cli-default-001'
]
];
$result = $bus->call( 'math:add:v1', $payload, $context);
print_r($result);MethodBus mendukung dokumentasi otomatis berbasis metadata plugin.
| Method | Keterangan |
|---|---|
| system:docs:latest | Dokumentasi versi terbaru |
| system:docs:v1 | Dokumentasi plugin versi 1 |
| system:docs:v2 | Dokumentasi plugin versi 2 |
<?php
namespace MethodBus\Plugins\Math;
use MethodBus\Contracts\PluginInterface;
use MethodBus\Contracts\PluginDocumentedInterface;
use MethodBus\Plugins\Math\Services\MathService;
final class AddPluginV1 implements PluginInterface, PluginDocumentedInterface
{
public function __construct(
private MathService $math
) {}
public static function namespace(): string
{
return 'math';
}
public static function action(): string
{
return 'add';
}
public static function version(): string
{
return 'v1';
}
public static function method(): string
{
return sprintf(
'%s:%s:%s',
static::namespace(),
static::action(),
static::version()
);
}
public function handle(array $payload): array
{
return [
'result' => $this->math->add(
$payload['a'],
$payload['b']
)
];
}
public static function docs(): array
{
return [
'x-method' => static::method(),
'namespace' => static::namespace(),
'action' => static::action(),
'version' => static::version(),
'description' => 'Menjumlahkan dua angka'
];
}
}const secret = "super-secret-signature-key";
const timestamp =
Math.floor(Date.now() / 1000)
.toString();
const nonce =
crypto.randomUUID();
let body =
JSON.parse(pm.request.body.raw);
let sorted = {};
Object.keys(body)
.sort()
.forEach(key => {
sorted[key] = body[key];
});
const message =
JSON.stringify(sorted)
+ timestamp
+ nonce;
const signature =
CryptoJS.HmacSHA256(
message,
secret
).toString();
pm.environment.set(
"timestamp",
timestamp
);
pm.environment.set(
"nonce",
nonce
);
pm.environment.set(
"signature",
signature
);Content-Type : application/json
X-Method : math:add:v1
X-ApiKey : secret-key-123
X-Timestamp : {{timestamp}}
X-Nonce : {{nonce}}
X-Signature : {{signature}}{
"a": 10,
"b": 20
}-
Request masuk ke HTTP Gateway
-
Kernel membuat Request object
-
Middleware Pipeline dijalankan:
- API Key Validation
- Timestamp Validation
- Nonce Validation
- Signature Validation
-
Plugin di-resolve melalui Plugin Manager
-
Container membuat instance plugin
-
Plugin dieksekusi oleh Plugin Runner
-
Response dikembalikan ke client
MethodBus menggunakan pendekatan:
- Metadata Registry
- Static Plugin Metadata
- Runtime Plugin Resolution
- Constructor Injection
- Lazy Loading
- Middleware Pipeline
Metadata plugin bersifat static agar:
- bootstrap lebih ringan
- memory usage lebih kecil
- dokumentasi lebih cepat di-generate
Sedangkan instance plugin baru dibuat saat runtime melalui container.
graph TD
classDef gateway fill:#6366f1,color:#fff,stroke:#4f46e5;
classDef core fill:#f8fafc,color:#1e293b,stroke:#cbd5e1;
classDef security fill:#fff1f2,color:#be123c,stroke:#fb7185,stroke-dasharray: 5 5;
classDef plugin fill:#ecfdf5,color:#065f46,stroke:#10b981;
A[Client Request]:::gateway --> B[HTTP/CLI Gateway]:::gateway
B --> C{MethodBus Kernel}:::core
C --> D[[Middleware Pipeline]]:::security
D -->|Validated| E[Plugin Manager]:::core
E --> F[DI Container]:::core
F --> G[Plugin Runner]:::plugin
G --> H[Target Plugin]:::plugin
H -->|JSON Response| A
sequenceDiagram
autonumber
participant C as Client
participant K as Kernel
participant P as Pipeline
participant M as Plugin Manager
participant PL as Plugin
C->>+K: Send Request (Headers + Body)
K->>+P: Run Security Middleware
Note over P: API Key, Timestamp, Nonce, Signature
P-->>-K: Success: Context Validated
K->>+M: Resolve Plugin (Lazy Loading)
M->>+PL: Instantiate & handle()
PL-->>-M: Logic Result
M-->>-K: Return Array
K-->>-C: 200 OK JSON Response
flowchart TD
classDef decision fill:#fffbeb,color:#92400e,stroke:#f59e0b;
classDef error fill:#fff1f2,color:#991b1b,stroke:#ef4444;
classDef success fill:#f0fdf4,color:#166534,stroke:#22c55e;
Start([Incoming Request]) --> Auth{API Key Valid?}:::decision
Auth -- No --> Fail401[Abort 401]:::error
Auth -- Yes --> Time{Timestamp Valid?}:::decision
Time -- No --> Fail403[Abort 403]:::error
Time -- Yes --> Nonce{Nonce Used?}:::decision
Nonce -- Yes --> Fail403
Nonce -- No --> Sign{Signature Match?}:::decision
Sign -- No --> Fail403
Sign -- Yes --> Run[Execute Plugin]:::success
graph LR
classDef container fill:#f1f5f9,stroke:#475569;
classDef service fill:#e0f2fe,stroke:#0ea5e9,color:#0369a1;
classDef plugin fill:#6366f1,color:#fff;
subgraph DI [Container]
C1[Reflection]:::container
C2[Registry]:::container
end
subgraph Deps [Services]
S1[MathService]:::service
S2[Logger]:::service
end
P[Plugin Instance]:::plugin
C1 --> P
C2 --> S1 & S2
S1 & S2 -->|Injected| P
flowchart LR
classDef step fill:#f8fafc,stroke:#64748b;
classDef result fill:#ecfdf5,stroke:#10b981,color:#065f46;
P[Payload]:::step --> K[ksort keys]:::step
K --> J[json_encode]:::step
J --> C[Concatenate with<br/>Timestamp + Nonce]:::step
C --> H[HMAC SHA256]:::step
S[Shared Secret] --> H
H --> R[X-Signature]:::result
graph LR
classDef bus fill:#1e293b,color:#fff;
classDef module fill:#eef2ff,stroke:#6366f1;
MB((MethodBus)):::bus -->|math:*| M1[Math Module]:::module
MB -->|user:*| M2[User Module]:::module
MB -->|system:*| M3[System Module]:::module
M1 --> V1[add:v1]
M1 --> V2[sub:v1]