โ† Semua pembelajaran / Laravel Nol โ†’ Enterprise
Fase 10 ยท Enterprise & Capstone

Modularisasi kode

Aplikasi yang tumbuh selalu sampai pada pertanyaan yang sama: bagaimana supaya folder app/ yang berisi tiga ratus berkas tetap bisa dinavigasi. Jawabannya jarang berupa layanan terpisah.

Sumber asli laravel.com Resmi Rangkuman ~7 menit baca

Intisari

  • Modular monolith: satu aplikasi, satu deploy, tapi batas modul yang jelas dan ditegakkan.
  • Modul dipisahkan berdasarkan domain bisnis, bukan berdasarkan jenis kelas.
  • Batasnya ditegakkan otomatis lewat uji arsitektur Pest โ€” bukan lewat kesepakatan lisan.
  • Package lokal (path repository di Composer) memberi batas yang paling tegas.
  • Microservice memindahkan kompleksitas dari kode ke jaringan. Bayar hanya kalau kamu memang mendapat sesuatu.

Dua cara memecah kode

BERDASARKAN JENIS (default Laravel)      BERDASARKAN DOMAIN
app/                                     app/
โ”œโ”€โ”€ Models/                              โ”œโ”€โ”€ Redaksi/
โ”‚   โ”œโ”€โ”€ Artikel.php                      โ”‚   โ”œโ”€โ”€ Models/Artikel.php
โ”‚   โ”œโ”€โ”€ Iklan.php                        โ”‚   โ”œโ”€โ”€ Http/ArtikelController.php
โ”‚   โ””โ”€โ”€ Pengguna.php                     โ”‚   โ”œโ”€โ”€ Actions/TerbitkanArtikel.php
โ”œโ”€โ”€ Http/Controllers/                    โ”‚   โ””โ”€โ”€ Policies/ArtikelPolicy.php
โ”‚   โ”œโ”€โ”€ ArtikelController.php            โ”œโ”€โ”€ Iklan/
โ”‚   โ”œโ”€โ”€ IklanController.php              โ”‚   โ””โ”€โ”€ โ€ฆ
โ”‚   โ””โ”€โ”€ PenggunaController.php           โ””โ”€โ”€ Akun/
โ””โ”€โ”€ Policies/                                โ””โ”€โ”€ โ€ฆ

Kapan pemecahan berdasarkan jenis mulai gagal. Selama aplikasimu punya sepuluh model, folder Models/ lebih mudah dinavigasi. Setelah lima puluh model dari lima area bisnis yang berbeda, kamu harus membuka empat folder untuk memahami satu fitur โ€” dan tidak ada yang bisa menjawab "kalau saya mengubah ini, apa lagi yang terpengaruh". Pemecahan berdasarkan domain menjawab pertanyaan itu dengan struktur folder.

Isi sebuah modul

app/Redaksi/
โ”œโ”€โ”€ Models/                 Artikel, Kategori, Revisi
โ”œโ”€โ”€ Http/
โ”‚   โ”œโ”€โ”€ Controllers/
โ”‚   โ”œโ”€โ”€ Requests/
โ”‚   โ””โ”€โ”€ Resources/
โ”œโ”€โ”€ Actions/                TerbitkanArtikel, ArsipkanArtikel
โ”œโ”€โ”€ Events/                 ArtikelTerbit
โ”œโ”€โ”€ Jobs/                   BuatRingkasan
โ”œโ”€โ”€ Policies/
โ”œโ”€โ”€ RedaksiServiceProvider.php
โ””โ”€โ”€ routes.php
// app/Redaksi/RedaksiServiceProvider.php
class RedaksiServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Route::middleware('web')->group(__DIR__.'/routes.php');

        $this->loadViewsFrom(__DIR__.'/resources/views', 'redaksi');
        $this->loadMigrationsFrom(__DIR__.'/database/migrations');

        Gate::policy(Artikel::class, ArtikelPolicy::class);
    }
}

Menegakkan batas

// tests/Feature/ArsitekturTest.php

arch('modul Iklan tidak boleh menyentuh internal Redaksi')
    ->expect('App\Iklan')
    ->not->toUse('App\Redaksi\Actions');

arch('modul hanya berkomunikasi lewat event dan kontrak publik')
    ->expect('App\Redaksi\Http')
    ->not->toUse(['App\Akun\Models', 'App\Iklan\Models']);

arch('controller tidak menyentuh database langsung')
    ->expect('App\Redaksi\Http\Controllers')
    ->not->toUse('Illuminate\Support\Facades\DB');

Inilah yang membedakan modularisasi sungguhan dari sekadar memindahkan berkas. Tanpa penegakan, batas modul akan luntur dalam hitungan bulan โ€” seseorang akan memanggil kelas dari modul lain karena sedang buru-buru, dan tidak ada yang menyadarinya saat review. Uji arsitektur mengubah kesepakatan jadi gerbang yang tidak bisa dilewati diam-diam.

Komunikasi antar modul

CaraKoplingKapan
EventPaling longgar"Sesuatu terjadi" โ€” modul lain boleh bereaksi
Kontrak publik (interface)SedangModul A butuh jawaban dari modul B
Memanggil kelas internalTerlalu eratJangan โ€” inilah yang dicegah uji arsitektur
Berbagi tabel databasePaling eratKadang tidak terhindarkan; sadari harganya
// Modul Redaksi mengumumkan; ia tidak tahu siapa yang mendengarkan
ArtikelTerbit::dispatch($artikel);

// Modul Iklan mendengarkan; Redaksi tidak perlu tahu Iklan itu ada
class SegarkanSlotIklan
{
    public function handle(ArtikelTerbit $event): void { /* ... */ }
}

Package lokal: batas yang paling tegas

// composer.json
"repositories": [
    { "type": "path", "url": "modules/*" }
],
"require": {
    "portal/redaksi": "*"
}

Dengan package lokal, modul punya composer.json-nya sendiri โ€” dan dependensinya jadi eksplisit. Modul yang tidak mendeklarasikan ketergantungan pada modul lain tidak bisa memakainya, karena autoload-nya memang tidak menemukan kelasnya. Ini batas yang ditegakkan oleh alat, bukan oleh disiplin.

Harganya: setiap modul jadi punya siklus hidupnya sendiri. Menambah dependensi berarti mengedit dua composer.json; menjalankan tes berarti memikirkan modul mana. Untuk tim besar dengan kepemilikan yang jelas per modul, itu sepadan. Untuk tim lima orang, folder plus uji arsitektur biasanya sudah cukup dan jauh lebih ringan.

Kenapa bukan microservice

Yang hilang saat memecah jadi layanan terpisah
Transaksi database yang mencakup beberapa domain
Join sederhana antar tabel yang tadinya bertetangga
Refactor lintas modul dalam satu commit
Satu deploy, satu rollback
Debugging dengan satu stack trace
Panggilan yang tidak bisa gagal karena jaringan

Microservice memindahkan kompleksitas, bukan menghapusnya. Ia memindahkannya dari struktur kode ke jaringan โ€” tempat kegagalan bersifat parsial, latensi bertambah, dan konsistensi jadi masalah yang harus kamu rancang sendiri. Alasan yang sah untuk membayar itu ada: tim yang benar-benar terpisah dan perlu merilis sendiri-sendiri, atau satu bagian yang kebutuhan penskalaannya sangat berbeda. "Supaya rapi" bukan salah satunya. Monolith modular memberi hampir seluruh manfaat organisasinya tanpa satu pun biaya jaringannya.

Jalur yang masuk akal

  1. Mulai dengan struktur Laravel biasa. Jangan membangun modul untuk aplikasi yang belum punya pengguna.
  2. Saat satu area sudah punya lebih dari sekitar sepuluh model dan pemiliknya jelas, pindahkan ke modul.
  3. Tegakkan batasnya dengan uji arsitektur pada hari yang sama.
  4. Komunikasi antar modul lewat event dan kontrak, tidak pernah lewat kelas internal.
  5. Baru pertimbangkan layanan terpisah kalau ada alasan operasional yang nyata โ€” bukan alasan estetika.

Latihan: pindahkan satu area aplikasimu ke app/<Domain>/ dengan service provider dan berkas rutenya sendiri. Lalu tulis satu arch() yang melarang modul lain memakai folder Actions-nya, dan buktikan tesnya gagal saat kamu sengaja melanggarnya.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.