Belajar PHP - Microservices & Monolith Modular
Series/Belajar PHP/Episode 21
Episode 21 of 23

Belajar PHP - Microservices & Monolith Modular

Merancang arsitektur aplikasi PHP yang tumbuh: membandingkan modular monolith dan microservices, memahami peran API Gateway dan message queues untuk event-driven, lalu menerapkan DTO, service layer, dan dependency injection container agar kode tetap terstruktur dan mudah diuji.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Sejauh ini kita membangun satu aplikasi. Episode 21 membahas pertanyaan yang muncul saat aplikasi tumbuh besar dan melibatkan banyak tim: bagaimana menata arsitekturnya? Dua ekstrem yang sering dipertentangkan adalah modular monolith dan microservices — dan jawaban yang benar sering kali bukan keduanya secara mentah, melainkan kombinasi yang disengaja.

Mengapa penting? Arsitektur menentukan kemampuan tim untuk bergerak cepat bertahun-tahun ke depan. Microservices yang dipaksakan pada tim kecil justru memperlambat (banyak infra, banyak deployment), sementara monolith tanpa batas modul menjadi sulit dipelihara. Episode ini juga memperkenalkan praktik kode yang menjaga perbatasan arsitektur tetap sehat: DTO, service layer, dan dependency injection.

Modular Monolith vs Microservices

AspekModular MonolithMicroservices
Unit deploySatu aplikasi, modul internalBanyak service, deploy terpisah
KomunikasiLangsung via kode/fungsiVia HTTP/gRPC/message queue
Batas batasModule dalam satu codebaseService terpisah, independen
KelebihanSederhana, murah, mudah debugSkala & tim independen, isolasi kegagalan
KekuranganBatas bisa luntur; scaling seragamKompleksitas besar; network failure; debugging sulit

Aturan praktis industri:

  1. Mulai sebagai modular monolith — bahkan dengan niat microservices.
  2. Pecah hanya saat ada pemicu nyata — tim berbeda, beban tidak merata, kebutuhan deploy terpisah.
  3. Jaga batas modul keras sejak awal — inilah yang membuat monolith "modular".
Struktur modular monolith
app/Modules/
  Auth/
    Controllers/  Services/  Repositories/  DTOs/
  Order/
    Controllers/  Services/  Repositories/  DTOs/
  Payment/
    Controllers/  Services/  Repositories/  DTOs/

Setiap modul punya public API (controller + DTO yang boleh dipakai modul lain) dan internal (yang tidak boleh diakses di luar). Aturan ini dijaga lewat review kode dan tooling.

API Gateway dan Message Queues

API Gateway

Saat service sudah terpecah, client butuh satu pintu masuk. API Gateway merutekan, mengautentikasi, membatasi rate, dan mengagregasi response:

100%

Implementasi populer di ekosistem PHP: Laravel API Gateway (package/cloud), atau pasangan Nginx + rate limiting untuk pemisahan yang lebih ringan. Poin kunci: gateway adalah satu titik otentikasi dan satu bentuk kontrak API untuk client.

Message Queues untuk Event-Driven

Saat service berkomunikasi asinkron, message queue menjadi tulang punggung:

ToolKarakteristikKapan dipakai
Redis StreamsCepat, sederhana, dalam ekosistem LaravelEvent internal, workload sedang
RabbitMQRouting/queue canggih, broker klasikEnterprise, pola kompleks
Apache KafkaLog berdurasi panjang, throughput raksasaStreaming/event-sourcing skala besar
Event + listener (Laravel)
namespace App\Events;
 
use App\Models\Order;
 
class OrderPaid
{
    public function __construct(public Order $order) {}
}
Event dipublish & diproses asinkron
event(new OrderPaid($order));        // publish
 
// didengarkan oleh listener yang menangani
// notifikasi, email, stok, dsb. — kadang lewat queue (episode 20)
class KirimNotifikasiOrder implements ShouldQueue
{
    public function handle(OrderPaid $event): void { /* ... */ }
}

Pola event-driven memisahkan modul: modul Order tidak tahu menahu soal notifikasi atau email — ia hanya mem-publish event. Ini menjaga coupling antar modul tetap rendah.

Tip

Kencangkan event sebagai kontrak antar modul: nama event dan bentuk payload-nya adalah public API. Bila dua modul mulai membaca properti internal satu sama lain, batas modul sudah bocor — refactor sebelum terlambat.

DTO: Data Transfer Object

DTO adalah object immutable yang membawa data antar lapisan — pengganti array mentah yang tidak bertipe:

DTO dengan readonly (PHP 8.2+)
namespace App\Modules\Order\DTOs;
 
final readonly class CreateOrderDTO
{
    public function __construct(
        public int $userId,
        public int $productId,
        public int $qty,
        public float $total,
    ) {}
 
    public static function fromRequest(array $input): self
    {
        return new self(
            userId: (int) $input["user_id"],
            productId: (int) $input["product_id"],
            qty: max(1, (int) $input["qty"]),
            total: (float) $input["total"],
        );
    }
}

Kelebihan DTO: type-safe (bukan array yang bisa berisi apa pun), immutable (readonly), self-documenting, dan mudah ditest. Ini pilar praktik "Poka-Yoke" kode — kesalahan terdeteksi saat kompilasi/analisis statis, bukan saat produksi.

Service Layer

Service layer memegang logika bisnis; controller hanya menerima request dan mengembalikan response:

Service layer
namespace App\Modules\Order\Services;
 
use App\Modules\Order\DTOs\CreateOrderDTO;
 
final class OrderService
{
    public function __construct(
        private OrderRepository $repo,
        private PaymentGateway $payment,
    ) {}
 
    public function create(CreateOrderDTO $dto): Order
    {
        if ($dto->qty <= 0) {
            throw new InvalidQuantityException();
        }
 
        return $this->payment->process($dto)
            ? $this->repo->create($dto)
            : throw new PaymentFailedException();
    }
}

Controller memanggil service — sekali lagi kita melihat pola dari episode 13 dan 8 dipadukan. Service yang tipis dan fokus membuat unit test langsung menargetkan logika bisnis tanpa perlu HTTP (episode 10).

Dependency Injection Container

DI Container adalah mesin yang menciptakan dan menyuntikkan dependensi secara otomatis:

Dependency injection (konstruktor)
$orderService = new OrderService(
    new OrderRepository($pdo),
    new PaymentGateway(),
);
DI container (Laravel / Laravel container)
// Laravel: otomatis me-resolve constructor dependency
$service = app(OrderService::class);
 
// Symfony: definisi eksplisit di services.yaml
// services:
//   App\Modules\Order\Services\OrderService: ~

Manfaat DI container:

  • Otomatisasi wiring — container tahu cara membuat OrderService karena tahu cara membuat semua dependensinya.
  • Mudah diganti — untuk test, inject fake/stub; untuk produksi, inject implementasi nyata (interface sebagai tipe).
  • Lifecycle terkelola — singleton vs per-request bisa diatur.

Danger

Jangan membuat Service Locator (mengambil dependensi dari container di dalam service) — itu menyembunyikan dependensi dan merusak keterujian. Pola benar: constructor injection — semua dependensi terlihat jelas di tanda tangan konstruktor.

Common Pitfalls

  • Microservices sebelum waktunya — kompleksitas infra menelan tim kecil; mulai modular monolith.
  • Batas modul bocor — modul saling akses internal; definisikan public API + DTO.
  • Array mentah di seluruh lapisan — ganti dengan DTO readonly untuk type safety.
  • Logika bisnis di controller — pindahkan ke service layer.
  • Service Locator — injeksi via constructor, bukan mengambil dari container.

Penutup

Inti yang harus dibawa pulang:

  • Mulai modular monolith, pecah ke microservices hanya saat ada pemicu nyata.
  • API Gateway = satu pintu masuk; message queue (Redis/RabbitMQ/Kafka) = komunikasi asinkron antar modul.
  • DTO readonly = data terketik yang aman melewati batas lapisan.
  • Service layer = rumah logika bisnis; controller tetap tipis.
  • DI container dengan constructor injection menjaga wiring otomatis dan keterujian.

Di episode 22 — episode terakhir series ini — kita menarik kesimpulan besar: Ekosistem, Alternatif & Refleksi Akhir — membandingkan PHP dengan Node.js, Python, dan Java, kapan memilih yang mana, lalu rekap lengkap episode 0-21 dan checklist production untuk aplikasi PHP kalian.

Belajar PHP - Microservices & Monolith Modular | Belajar PHP